Skip to content

Commit 185138c

Browse files
author
Yuriy Nasretdinov
committed
Adding canIncludeFile to PHP_CodeCoverage_Util that provides function for basic checks of included file
1 parent b23c4b2 commit 185138c

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

PHP/CodeCoverage/Util.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,158 @@ public static function getLinesToBeIgnored($filename, $cacheTokens = TRUE)
238238
return self::$ignoredLines[$filename];
239239
}
240240

241+
/**
242+
* Checks whether or not it is safe to include file. It ensures that file contains only class/function definitions.
243+
* It also checks that function/class definitions do not exist prior to including.
244+
*
245+
* @param $filename string Filename to be checked
246+
* @param $toplevel_funcs array List of function calls at top level that are allowed (e.g. array('define'))
247+
* @param $classes array Classes that are present in file with respect of namespaces
248+
*
249+
* @return bool
250+
*/
251+
public static function canIncludeFile($filename, array $toplevel_funcs, &$classes, &$errmsg)
252+
{
253+
if (!is_readable($filename) || !is_file($filename)) return false;
254+
$source = file_get_contents($filename);
255+
if ($source === false) return false;
256+
$tokens = token_get_all($source);
257+
if ($tokens === false) return false;
258+
259+
$toplevel_funcs = array_flip($toplevel_funcs);
260+
$state = "default";
261+
$depth = 0; // depth of "(" or "{"
262+
$line = 1;
263+
$namespace = "";
264+
265+
foreach ($tokens as $row) {
266+
// printf("%-12s ", $state);
267+
if (is_array($row)) {
268+
list($token, $text, $line) = $row;
269+
$text = str_replace("\n", '\\n', $text);
270+
// echo token_name($token) . " '$text' on line $line\n";
271+
if ($token === T_WHITESPACE || $token === T_COMMENT || $token === T_DOC_COMMENT) continue;
272+
273+
switch ($state) {
274+
case "default":
275+
if ($token !== T_OPEN_TAG) {
276+
$errmsg = "Have something before <?php tag on line $line";
277+
return false;
278+
}
279+
$state = "root";
280+
break;
281+
case "root":
282+
if ($token === T_STRING) { // function call
283+
if (!isset($toplevel_funcs[$text])) {
284+
$errmsg = "Forbidden top level function call: $text(...) on line $line";
285+
return false;
286+
}
287+
$state = "funccall";
288+
$depth = 0;
289+
} else if ($token === T_ABSTRACT || $token === T_FINAL) {
290+
continue;
291+
} else if ($token === T_CLASS) {
292+
$state = "classdef";
293+
} else if ($token === T_CLOSE_TAG) {
294+
$state = "default";
295+
} else if ($token === T_NAMESPACE) {
296+
$state = "namespace";
297+
$namespace = "";
298+
} else if ($token === T_USE) {
299+
$state = "use";
300+
} else if ($token === T_FUNCTION) {
301+
$state = "funcdef";
302+
} else {
303+
$errmsg = "Disallowed top level token " . token_name($token) . " ($text) on line $line";
304+
return false;
305+
}
306+
break;
307+
case "namespace":
308+
$namespace .= $text;
309+
break;
310+
case "classdef":
311+
if ($token === T_EXTENDS) {
312+
$state = "extends";
313+
} else if ($token === T_IMPLEMENTS) {
314+
$state = "implements";
315+
} else if ($token === T_STRING) {
316+
$classname = $namespace . "\\" . $text;
317+
if (class_exists($classname)) {
318+
$errmsg = "Class '$classname' already exists on line $line";
319+
return false;
320+
} else {
321+
$classes[] = $classname;
322+
}
323+
} else {
324+
$errmsg = "Unexpected token " . token_name($token) . " ($text) on line $line";
325+
return false;
326+
}
327+
break;
328+
case "funccall_end":
329+
$errmsg = "Unexpected terminator for function call: " . token_name($token) . " ($text) on line $line";
330+
return false;
331+
}
332+
333+
} else {
334+
// echo "$row\n";
335+
switch ($state) {
336+
case "funccall":
337+
if ($row === "(") $depth++;
338+
else if ($row === ")") $depth--;
339+
if ($depth == 0) $state = "funccall_end";
340+
break;
341+
case "funccall_end":
342+
if ($row !== ";") {
343+
$errmsg = "Unexpected terminator for function call: '$row' on line $line";
344+
return false;
345+
}
346+
$state = "root";
347+
break;
348+
case "namespace":
349+
if ($row === ";") {
350+
$state = "root";
351+
} else {
352+
$errmsg = "Unexpected token '$row' on line $line (expected ';')";
353+
return false;
354+
}
355+
break;
356+
case "use":
357+
if ($row === ";") $state = "root";
358+
break;
359+
case "classdef":
360+
case "extends":
361+
case "implements":
362+
if ($row === "{") {
363+
$state = "class";
364+
$depth = 1;
365+
} else {
366+
$errmsg = "Unexpected token '$row' on line $line (expected '{')";
367+
return false;
368+
}
369+
break;
370+
case "funcdef":
371+
if ($row === "{") {
372+
$state = "function";
373+
$depth = 1;
374+
}
375+
break;
376+
case "function":
377+
if ($row === "{") $depth++;
378+
else if ($row === "}") $depth--;
379+
if ($depth == 0) $state = "root";
380+
break;
381+
case "class":
382+
if ($row === "{") $depth++;
383+
else if ($row === "}") $depth--;
384+
if ($depth == 0) $state = "root";
385+
break;
386+
}
387+
}
388+
}
389+
390+
return true;
391+
}
392+
241393
/**
242394
* @param float $a
243395
* @param float $b

0 commit comments

Comments
 (0)