Polished up and enabled the undefined variable scan by default Mantis: 2691

git-svn-id: file:///srv/svn/scanner/trunk@20 a0501263-5b7a-4423-a8ba-1edf086583e7
This commit is contained in:
Correl Roush 2008-05-23 21:13:36 +00:00
parent 08bc427e4a
commit 26d9f61672
4 changed files with 55 additions and 10 deletions

View file

@ -9,25 +9,40 @@ class VariableModule extends ScannerModule {
function parserCallback( $object ) { function parserCallback( $object ) {
$pattern = '/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/'; $pattern = '/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/';
$matches = array(); $matches = array();
$variable = preg_match($pattern, $object['name'], $matches) > 0 ? $matches[0] : false;
$scope = "{$object['in_class']}::{$object['in_function']}"; $scope = "{$object['in_class']}::{$object['in_function']}";
if (!isset($this->assigned_variables[$scope] ) ) if (!isset($this->assigned_variables[$scope] ) )
$this->assigned_variables[$scope] = array(); $this->assigned_variables[$scope] = array();
if ($object['type'] == PHPPARSER_ASSIGNMENT) { if ($object['type'] == PHPPARSER_ASSIGNMENT) {
list($var, $value) = explode('=', $object['name']);
if ($variable == $var) {
// Regular variable assignment
//$this->fault($object, 0, "Assignment: {$object['name']}"); //$this->fault($object, 0, "Assignment: {$object['name']}");
list($variable, $value) = explode('=', $object['name']); $this->assigned_variables[$scope][] = $var;
} else {
// Array index assignment
//$this->fault($object, 0, "Array index assignment: [{$variable}] {$object['name']}");
if (
!in_array($variable, $this->assigned_variables[$scope])
&& (empty($object['in_class']) && $variable == '$this')
) {
$this->fault($object, FAULT_MINOR, "Array key assignment on previously undefined variable: $var");
}
$this->assigned_variables[$scope][] = $variable; $this->assigned_variables[$scope][] = $variable;
} }
}
if ( if (
$object['type'] == PHPPARSER_VARIABLE $object['type'] == PHPPARSER_VARIABLE
// Cannot yet accurately scan the global scope, so functions only // Cannot yet accurately scan the global scope, so functions only
&& !empty($object['in_function']) && !empty($object['in_function'])
&& !in_array($object['name'], $this->assigned_variables[$scope]) && !in_array($variable, $this->assigned_variables[$scope])
&& !in_array($object['name'], array( && !in_array($variable, array(
// Superglobals are exempt, obviously // Superglobals are exempt, obviously
'$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV' '$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV'
)) ))
&& (empty($object['in_class']) && $variable == '$this')
) { ) {
$this->fault($object, FAULT_MEDIUM, "Undefined Variable: {$object['name']}"); $this->fault($object, FAULT_MEDIUM, "Undefined Variable: $variable");
} }
} }
} }

View file

@ -89,14 +89,16 @@ class PHPParser {
$function = $functionname = null; $function = $functionname = null;
$string = $last_token = null; $string = $last_token = null;
$switch = array(); $switch = array();
$expression = ''; $expressions = array('');
$expression = &$expressions[0];
$assignment = false; $assignment = false;
$line_text = ''; $line_text = '';
$internal_functions = get_defined_functions(); $internal_functions = $internal_functions['internal']; $internal_functions = get_defined_functions(); $internal_functions = $internal_functions['internal'];
$local_functions = array(); $local_functions = array();
$local_classes = array(); $local_classes = array();
$variables = array(); $expression_variables = array(array());
$variables = &$expression_variables[0];
$open_blocks = array( 0 ); $open_blocks = array( 0 );
$in_string = false; $in_string = false;
$buffer = array(T_INLINE_HTML => ''); $buffer = array(T_INLINE_HTML => '');
@ -168,6 +170,25 @@ class PHPParser {
$in_foreach_params = false; $in_foreach_params = false;
$in_list = false; $in_list = false;
break; break;
case '[':
$expressions[] = '';
$expression_variables[] = array();
$variables = &$expression_variables[count($expression_variables) - 1];
break;
case ']':
/*
TODO: Subexpression reporting is NOT quite as accurate as it should be...
See the unit test failure for an example. The assigned variable is reported fine, which is good,
but the value it is assigned will not be. Since this isn't awfully important, and the fact that
everything else does is, I'm leaving this as-is for now.
*/
$e = array_pop($expressions);
$expression = &$expressions[count($expressions) - 1];
$expression .= trim($e);
array_pop($expression_variables);
$variables = &$expression_variables[count($expression_variables) - 1];
$variables[count($variables) - 1] = end($variables) . '[' . trim($e) . ']';
break;
default: default:
/* Should be able to add a hook here later on to catch the use of defines, /* Should be able to add a hook here later on to catch the use of defines,
which are basically just T_STRINGs that PHP can't find anything else to which are basically just T_STRINGs that PHP can't find anything else to
@ -236,6 +257,8 @@ class PHPParser {
) ); ) );
} }
} else { $expression .= $token; } } else { $expression .= $token; }
if ($expression !== end($expressions))
$expression = &$expressions[count($expressions) - 1];
if( !empty($buffer[T_INLINE_HTML]) && (bool)($this->fetch_mode & PHPPARSER_FETCH_INLINE_HTML) ) { if( !empty($buffer[T_INLINE_HTML]) && (bool)($this->fetch_mode & PHPPARSER_FETCH_INLINE_HTML) ) {
$this->foundObject( array( $this->foundObject( array(
'type' => PHPPARSER_INLINE_HTML, 'type' => PHPPARSER_INLINE_HTML,
@ -336,6 +359,13 @@ class PHPParser {
break; break;
case T_VARIABLE: case T_VARIABLE:
if ($in_list) { if ($in_list) {
/*
TODO: Proper fix for this.
Has to do with the less-than-perfect sub-expressions. If an entry in a list() is a keyed value of an array (i.e. $var[$key]),
everything sort of falls apart.
*/
if (!is_array($variables[count($variables) - 1]) )
$variables[count($variables) - 1] = array(end($variables));
$variables[count($variables) - 1][] = $text; $variables[count($variables) - 1][] = $text;
} else { } else {
$variables[] = $text; $variables[] = $text;

View file

@ -266,7 +266,7 @@ for( $i = 1; $i < $argc; $i++ ) {
} }
} }
} }
if (empty($config['modules'])) $config['modules'] = array('lint', 'functions', 'pattern'); if (empty($config['modules'])) $config['modules'] = array('lint', 'functions', 'variables', 'pattern');
if( count( $files ) == 0 ) { if( count( $files ) == 0 ) {
if( count( $revisions ) > 0 ) { if( count( $revisions ) > 0 ) {

View file

@ -116,7 +116,7 @@ class ParserTests extends PHPUnit_Framework_TestCase {
'$hash["answer"]=42', '$hash["answer"]=42',
'$data[$index[0]]="complex"', '$data[$index[0]]="complex"',
'$index=0', '$index=0',
'$data[$index = 0]="nested"', '$data[0]="nested"',
); );
$this->assertEquals($expected, $assignments); $this->assertEquals($expected, $assignments);
} }