public function createFileSlice(XRef_IParsedFile $pf, $is_library_file = false) { $this->slice = array(); $this->already_seen = array(); $tokens = $pf->getTokens(); for ($i = 0; $i < count($tokens); ++$i) { $t = $tokens[$i]; // new Foo(); // new \Bar\Baz(); // new $class // new self() if ($t->kind == T_NEW) { $n = $t->nextNS(); $class_name = ''; while ($n->kind == T_NS_SEPARATOR || $n->kind == T_STRING) { $class_name = $class_name . $n->text; $n = $n->nextNS(); } if ($class_name) { $class_name = $pf->qualifyName($class_name, $t->index); if ($class_name == 'self' || $class_name == 'parent' || $class_name == 'static') { $class = $pf->getClassAt($t->index); if (!$class) { continue; } $class_name = $class->name; } $this->addUsedConstruct($class_name, 'method', '__construct', $t->lineNumber, $class_name, false, false); } continue; } // $this->foo(); // $this->bar; if ($t->kind == T_VARIABLE && $t->text == '$this') { $n = $t->nextNS(); if ($n->text == '->') { $n = $n->nextNS(); if ($n->kind == T_STRING) { $name = $n->text; $n = $n->nextNS(); $class = $pf->getClassAt($t->index); if (!$class) { continue; } $class_name = $class->name; $key = $n->text == '(' ? 'method' : 'property'; $this->addUsedConstruct($class_name, $key, $name, $t->lineNumber, $class_name, false, false); } } continue; } // Foo::bar(); // \Foo::bar(); // Foo\Bar::BAZ if ($t->kind == T_DOUBLE_COLON) { $class_name = ''; $p = $t->prevNS(); while ($p->kind == T_NS_SEPARATOR || $p->kind == T_STRING) { $class_name = $p->text . $class_name; $p = $p->prevNS(); } if ($class_name) { $class_name = $pf->qualifyName($class_name, $t->index); $check_parent_only = $class_name == 'parent'; $from_class = $pf->getClassAt($t->index); $from_class_name = $from_class ? $from_class->name : ''; if ($class_name == 'self' || $class_name == 'static' || $class_name == 'parent') { $check_parent_only = $class_name == 'parent'; $class_name = $from_class_name; $from_method = $pf->getMethodAt($t->index); $is_static_context = !$from_class || !$from_method || XRef::isStatic($from_method->attributes); } else { $is_static_context = true; } $n = $t->nextNS(); if ($n->kind == T_STRING) { $nn = $n->nextNS(); if ($nn->text == '(') { // Foo::bar() // self::bar() - this can be either static or instance access, depends on context $this->addUsedConstruct($class_name, 'method', $n->text, $t->lineNumber, $from_class_name, $is_static_context, $check_parent_only); } else { // Foo::BAR - constant $const_name = $n->text; $this->addUsedConstruct($class_name, 'constant', $n->text, $t->lineNumber, $from_class_name, true, $check_parent_only); } } elseif ($n->kind == T_VARIABLE) { // Foo::$bar $property_name = substr($n->text, 1); // skip '$' sign $this->addUsedConstruct($class_name, 'property', $property_name, $t->lineNumber, $from_class_name, true, $check_parent_only); } else { // e.g. self::$$keyName //error_log($n); } continue; } } } return $this->slice; }
public function __construct(XRef_IParsedFile $pf, $startIndex, $endIndex = 0) { $this->fileName = $pf->getFileName(); $this->lineNumber = $pf->getLineNumberAt($startIndex); $this->startIndex = $startIndex; $this->endIndex = $endIndex ? $endIndex : $startIndex; $class = $pf->getClassAt($startIndex); $method = $pf->getMethodAt($startIndex); $this->inClass = $class ? $class->name : null; $this->inMethod = $method ? $method->name : null; }
public function getReport(XRef_IParsedFile $pf) { if ($pf->getFileType() != $this->supportedFileType) { return; } $tokens = $pf->getTokens(); $tokens_count = count($tokens); // initialization/clean-up after previous parsed file, if any $this->listOfScopes = array(); $this->listOfCommands = array(); $this->report = array(); // create a global scope ... $global_scope = new XRef_Lint_UninitializedVars_Scope(0, $tokens_count); $global_scope->mode = self::MODE_RELAXED; $global_scope->isGlobal = true; $this->listOfScopes[] = $global_scope; $this->currentScope = $global_scope; foreach (self::$knownGlobals as $var_name) { $var = $global_scope->getOrCreateVarByName($var_name); $var->state = self::VAR_ASSIGNED; } // and create a separate scope for each function/method/closure foreach ($pf->getMethods() as $method) { // if the method/function has a body, create a scope if ($method->bodyStarts) { $scope = new XRef_Lint_UninitializedVars_Scope($method->bodyStarts, $method->bodyEnds); $this->listOfScopes[] = $scope; // copy function parameters to list of known variables of this scope foreach ($method->parameters as $param) { $var = $scope->getOrCreateVarByName($param->name); $var->status = self::VAR_ASSIGNED; } foreach ($method->usedVariables as $param) { $var = $scope->getOrCreateVarByName($param->name); $var->status = self::VAR_ASSIGNED; } // commands to switch the scope $this->addCommand(array($method->bodyStarts + 1, self::CMD_CHANGE_CURRENT_SCOPE)); $this->addCommand(array($method->bodyEnds + 1, self::CMD_CHANGE_CURRENT_SCOPE)); // special command to check used variables of a closure // right before T_FUNCTION token of the closure starts if ($method->usedVariables) { $this->addCommand(array($method->index - 1, self::CMD_CHECK_USED_VARIABLES, $method)); } } // for all method declaration/definitions, skip the part // "function" <name> (argument list...) from checks $skip_to_index = $method->bodyStarts ? $method->bodyStarts : $method->bodyEnds; $this->addCommand(array($method->index, self::CMD_SKIP_TO_TOKEN, $skip_to_index)); } // add functions of the current file - we'll use their signatures to find functions/methods // that can initialize passed-by-reference variables $this->functionsOfCurrentFile = array(); foreach ($pf->getMethods() as $function) { if ($function->name) { $full_name = $function->className ? $function->className . '::' . $function->name : $function->name; $this->functionsOfCurrentFile[strtolower($full_name)] = $function; } } // skip the content of class body from checks; check method bodies only // add commands: // skip from class start to start of body of first method // skip from end of body of first method to start of body of second method // ... // skip from end of the body of the last method till the end of the class foreach ($pf->getClasses() as $class) { // hm, there are no class declaration without a definition in PHP, right? if ($class->bodyStarts) { $start_skip_from = $class->index; foreach ($class->methods as $method) { if ($method->bodyStarts) { $this->addCommand(array($start_skip_from, self::CMD_SKIP_TO_TOKEN, $method->bodyStarts)); $start_skip_from = $method->bodyEnds; } } $this->addCommand(array($start_skip_from, self::CMD_SKIP_TO_TOKEN, $class->bodyEnds)); } } // to check or not to check variables in the global scope // variables in local scope (inside functions) will always be checked $checkGlobalScope = XRef::getConfigValue("lint.check-global-scope", true); for ($i = 0; $i < $tokens_count; ++$i) { $t = $tokens[$i]; // skip whitespaces but not doc comments (we'll process them later) // TODO: move doc comments parse code into XRef_ParsedFile (?) if ($t->kind == T_WHITESPACE || $t->kind == T_COMMENT) { continue; } // process the command(s) if ($this->listOfCommands && $this->listOfCommands[0][0] <= $i) { $cmd = array_shift($this->listOfCommands); switch ($cmd[1]) { case self::CMD_CHANGE_CURRENT_SCOPE: // there can be nested scopes, find the deepest one foreach ($this->listOfScopes as $scope) { if ($scope->start > $i) { break; } if ($scope->end > $i) { $this->currentScope = $scope; } } break; case self::CMD_SKIP_TO_TOKEN: if ($cmd[2] > $i) { $i = $cmd[2]; } break; case self::CMD_CHECK_USED_VARIABLES: $closure = $cmd[2]; foreach ($closure->usedVariables as $arg) { // check that this variable exists at outer scope // however, if it's passed by reference, it can be created by the closure: // $foo = function () using (&$x) { $x = 1; }; // $foo(); // now we have $x here $token = $pf->getTokenAt($arg->index); if ($arg->isPassedByReference) { $var = $this->currentScope->getOrCreateVar($token); $var->status = self::VAR_ASSIGNED; // not quite true - } else { $this->checkVarAndAddDefectIfMissing($token); } } break; case self::CMD_START_LOOP: $this->currentScope->isLoopMode = true; break; case self::CMD_END_LOOP: $this->currentScope->isLoopMode = false; foreach ($this->currentScope->loopVars as $variable_name => $v) { list($token, $error_code) = $v; $this->checkVarAndAddDefectIfMissing($token, $error_code); } $this->currentScope->loopVars = array(); break; case self::CMD_SWITCH_TO_RELAXED_MODE: $this->currentScope->mode = self::MODE_RELAXED; break; default: // shouldn't be here throw new XRef_ParseException($t); } // executing a command can change the current pointer, redo the loop // and maybe execute another command // however, don't increment $i --$i; continue; } // // Switch from strict mode to relaxed? // // use of extract() or $$foo notation // trick is: the mode should be switched AFTER the statement, e.g. // function foo() { extract($foo); echo $bar; } // $foo must be declared (still in strict scope); $bar - not (in relaxed mode) if ($this->currentScope->mode == self::MODE_STRICT) { // use of extract() if ($t->kind == T_STRING && $t->text == 'extract') { $n = $t->nextNS(); // next non-space token if ($n->text == '(') { $this->addCommand(array($pf->getIndexOfPairedBracket($n->index), self::CMD_SWITCH_TO_RELAXED_MODE, $t)); $this->addDefect($t, self::E_LOSS_OF_STRICT_MODE); continue; } } // use of eval(); if ($t->kind == T_EVAL) { $n = self::skipTillText($t, ';'); $this->addCommand(array($n->index, self::CMD_SWITCH_TO_RELAXED_MODE, $t)); $this->addDefect($t, self::E_LOSS_OF_STRICT_MODE); continue; } // $$var notation in assignment. // Non-assignment (read) operations doesn't cause mode switch // $$foo = // $$bar["baz"] = // TODO: other forms of assignment? $$foo++; ? if ($t->text == '$') { $n = $t->nextNS(); // next non-space token if ($n->kind == T_VARIABLE) { $nn = $n->nextNS(); while ($nn->text == '[') { // quick forward to closing ']' $nn = $pf->getTokenAt($pf->getIndexOfPairedBracket($nn->index)); $nn = $nn->nextNS(); } if ($nn->text == '=') { $s = self::skipTillText($n, ';'); // find the end of the statement $this->addCommand(array($s->index, self::CMD_SWITCH_TO_RELAXED_MODE, $n)); $this->addDefect($t, self::E_LOSS_OF_STRICT_MODE); } } } // include/require statements // if you use them inside functions, well, it's impossible to make any assertions about your code. if ($t->kind == T_INCLUDE || $t->kind == T_REQUIRE || $t->kind == T_INCLUDE_ONCE || $t->kind == T_REQUIRE_ONCE) { $s = self::skipTillText($t, ';'); // find the end of the statement if ($s) { $this->addCommand(array($s->index, self::CMD_SWITCH_TO_RELAXED_MODE, $t)); $this->addDefect($t, self::E_LOSS_OF_STRICT_MODE); } } } // loops: // in loop, a variable can be tested first, and then assigned and this in not an error: // foreach // on parsing this line, $this->loop_starts_at will be set // ($items as $item) // { // here $this->loop_ends_at will be set as marker // // that we are inside of the loop // if (isset($total)) { // $total += $item->cost; // } else { // $total = $item->cost; // } // } // here the loop scope will be dropped and all // // missing vars will be added to report // if (!$this->currentScope->isLoopMode) { // start a new loop scope? // for, foreach, while, do ... while if ($t->kind == T_FOR || $t->kind == T_FOREACH || $t->kind == T_DO || $t->kind == T_WHILE) { $n = $t->nextNS(); // skip condition, may be missing if token is T_DO if ($n->text == '(') { $n = $pf->getTokenAt($pf->getIndexOfPairedBracket($n->index)); $n = $n->nextNS(); } // is there a block or a single statement as loop's body? if ($n->text == '{') { $this->addCommand(array($n->index, self::CMD_START_LOOP)); $this->addCommand(array($pf->getIndexOfPairedBracket($n->index), self::CMD_END_LOOP)); } } } // // Part 1. // // Find "known" (declared or assigned) variables. // Variable is "known" in following cases: // 1. value is assigned to the variable: $foo = expr // 2. loop var: foreach (array() as $foo) // 3. parameter of a function: function bar($foo) // 4. catch(Exception $err) // 5. Array autovivification: $foo['index'] // 6. Scalar autovivification: $count++, $text .= // 7. superglobals // 8. list($foo) = array(); // 9. globals: global $foo; // 10. functions that modify arguments: // int preg_match ( string $pattern , string $subject [, array &$matches ...]) // 11. test for existence of var in "relaxed" mode: isset($foo), empty($bar) // 12. variables declared via annotations: @var ClassName $varName (relaxed mode only) // $foo = // $foo[...] = // $foo[...][...] = // $foo++ // $foo .= // exclude class variables: public $foo = 1; // special case: allow declarations of variables with undefined value: $foo; if ($t->kind == T_VARIABLE) { // skip variables declaration in classes // public $foo; // private static $bar; // var $baz; if ($pf->getClassAt($t->index) != null && $pf->getMethodAt($t->index) == null) { // shouldn't be here // remove this once it passes the tests throw new XRef_ParseException($t); } $n = $t->nextNS(); // next non-space token $p = $t->prevNS(); // prev non-space token // skip static class variables: // Foo::$bar, self::$foo if ($p->kind == T_DOUBLE_COLON) { continue; } $is_array = false; while ($n->text == '[') { // quick forward to closing ']' $n = $pf->getTokenAt($pf->getIndexOfPairedBracket($n->index)); $n = $n->nextNS(); $is_array = true; } if ($n->text == '=') { if ($is_array && !$this->currentScope->checkVar($t)) { // array autovivification? $this->checkVarAndAddDefectIfMissing($t, self::E_ARRAY_AUTOVIVIFICATION); } else { $var = $this->currentScope->getOrCreateVar($t); $var->status = self::VAR_ASSIGNED; } continue; } if ($n->kind == T_INC || $n->kind == T_DEC || $p->kind == T_INC || $p->kind == T_DEC || $n->kind == T_CONCAT_EQUAL || $n->kind == T_PLUS_EQUAL) { $error_code = $is_array ? self::E_ARRAY_AUTOVIVIFICATION : self::E_SCALAR_AUTOVIVIFICATION; $this->checkVarAndAddDefectIfMissing($t, $error_code); continue; } if ($n->text == ';' && !$is_array) { if ($p && ($p->text == ';' || $p->text == '{')) { $this->addDefect($t, self::E_EMPTY_STATEMENT); $var = $this->currentScope->getOrCreateVar($t); $var->status = self::VAR_ASSIGNED; continue; } } } // foreach (expr as $foo) // foreach (expr as $foo => & $var) if ($t->kind == T_FOREACH) { $n = $t->nextNS(); while ($n->kind != T_AS) { $n = $n->nextNS(); } $nn = $n->nextNS(); if ($nn->text == '&') { $nn = $nn->nextNS(); } $var = $this->currentScope->getOrCreateVar($nn); $var->status = self::VAR_ASSIGNED; $n = $nn->nextNS(); if ($n->kind == T_DOUBLE_ARROW) { $nn = $n->nextNS(); if ($nn->text == '&') { $nn = $nn->nextNS(); } $var = $this->currentScope->getOrCreateVar($nn); $var->status = self::VAR_ASSIGNED; $n = $nn->nextNS(); } if ($n->text == ")") { // ok } else { // PHP code generated by smarty: // foreach ($_from as $this->_tpl_vars['event']): } // TODO: can't skip to ")" of foreach(expr as ...), because expr will be unparsed // TODO: loop vars will be scanned again and counted as used even if they are not continue; } // function &asdf($foo, $bar = array()) // function asdf(&$foo); // function asdf(Foo $foo); // function ($x) use ($y) ... // here a new scope frame is created if ($t->kind == T_FUNCTION) { // shouldn't be here // function declaration parst should be skipped by CMD_SKIP_TO throw new XRef_ParseException($t); } // catch (Exception $foo) if ($t->kind == T_CATCH) { $n = $t->nextNS(); if ($n->text != '(') { throw new Exception("{$n} found instead of '('"); } $n = $n->nextNS(); list($type, $n) = self::parseType($n); if (!$type) { throw new Exception("No exception type found ({$n})"); } if ($n->kind == T_VARIABLE) { $var = $this->currentScope->getOrCreateVar($n); $var->isCatchVar = true; } else { throw new Exception("{$n} found instead of variable"); } $n = $n->nextNS(); // if ($n->text != ')') { throw new Exception("{$n} found instead of ')'"); } $i = $n->index; continue; } // list($a, $b) = ... // TODO: check that the list is used in the left side of the assinment operator // TODO: example from PHP documentation: list($a, list($b, $c)) = array(1, array(2, 3)); if ($t->kind == T_LIST) { $n = $t->nextNS(); if (!$n->text == "(") { throw new Exception("Invalid list declaration found: {$t}"); } $closingBraketIndex = $pf->getIndexOfPairedBracket($n->index); while ($n->index != $closingBraketIndex) { if ($n->kind == T_VARIABLE) { $this->currentScope->getOrCreateVar($n); } $n = $n->nextNS(); } $i = $n->index; continue; } // globals: // global $foo; // makes the variable $foo known // global $$bar; // uh-oh, the var $bar must be known and relaxed mode afterwards // // TODO: check that the variable does exist at global level // TODO: link this var to the var at global level if ($t->kind == T_GLOBAL) { $n = $t->nextNS(); while (true) { if ($n->kind == T_VARIABLE) { $var = $this->currentScope->getOrCreateVar($n); $var->isGlobal = true; $var->status = self::VAR_ASSIGNED; $n = $n->nextNS(); } elseif ($n->text == '$') { $n = $n->nextNS(); if ($n->kind == T_VARIABLE) { // check that this var is declared $this->checkVarAndAddDefectIfMissing($n); // turn the relaxed mode on beginning of the next statement $s = self::skipTillText($n, ';'); $token_caused_mode_switch = $n; $switch_to_relaxed_scope_at = $s->index; } else { throw new Exception("Invalid 'global' decalaraion found: {$nn}"); } $n = $n->nextNS(); } else { throw new Exception("Invalid 'global' decalaraion found: {$n}"); } if ($n->text == ',') { $n = $n->nextNS(); continue; // next variable in list } elseif ($n->text == ';') { break; // end of list } else { throw new Exception("Invalid 'global' declaration found: {$n}"); } } $i = $n->index; continue; } // static function variables // function foo() { // static $foo; // <-- well, strictly speaking this variable is not intilialized, // static $bar = 10, $baz; // but it's declared so let's hope that author knows what's going on // } // other usage of "static" keyword: // $foo = new static(); // $foo = new static; // $foo instanceof staic // $foo = static::methodName(); if ($t->kind == T_STATIC && $pf->getMethodAt($t->index) != null) { $n = $t->nextNS(); $p = $t->prevNS(); if ($n->kind != T_DOUBLE_COLON && $p->kind != T_NEW && $p->kind != T_INSTANCEOF) { $list = $pf->extractList($n, ',', ';'); foreach ($list as $n) { if ($n->kind == T_VARIABLE) { $var = $this->currentScope->getOrCreateVar($n); $var->status = self::VAR_ASSIGNED; $i = $n->index; } else { // oops? throw new Exception("Invalid 'static' decalaraion found: {$n}"); } } continue; } } // // Functions that can return values into passed-by-reference arguments, // e.g. preg_match, preg_match_all etc. // // foo($x); // Foo::foo($x); // $foo->foo($x); // // Checks for known functions: // 1. if function doesn't accept parameters by reference ( not function foo(&$x) ) // and can't therefore initialize a passed variable, check that the variable exists, // otherwise, report an error // 2. if function does accept &$vars, check that variable, not an expression is // actually passed // 3. if function does accept params-by-reference, but does not intialize them // (e.g. sort()), check that variable exists // // Unknown (user-defined) functions can accept vars by reference too, // but we don't know about them, so just produce a warning // // Summary: // known_function_that_assign_variable($unknown_var); // ok (processed here) // known_function_that_doesnt_assign_variable($unknown_var); // error/warning (processed later) // unknown_function($unknown_var); // warning (here) // unknown_function($unknown_var_in_expression*2); // error/warning (later) // if ($t->kind == T_STRING) { $n = $t->nextNS(); if ($n->text == '(') { $function = $this->getFunctionAtToken($pf, $t); $arguments = $pf->extractList($n->nextNS()); if ($function) { // For known functions: // - mark variables that are used as passed-by-reference return arguments as known // - do nothing with variables that are not returned by function - they will be checked later $function_name = $function->className ? $function->className . '::' . $function->name : $function->name; foreach ($function->parameters as $pos => $param) { if ($pos < count($arguments) && $param->isPassedByReference) { $n = $arguments[$pos]; if ($n->text == '&') { $n = $n->nextNS(); } if ($n->kind == T_VARIABLE) { if (isset(self::$internalFunctionsThatDoesntInitializePassedByReferenceParams[$function_name])) { // if the function takes parameters by reference, but they must be defined prior to that // (e.g. sort), than check that this var exists $this->checkVarAndAddDefectIfMissing($n); } else { // otherwise, just note that this var will be initialized by this method call $var = $this->currentScope->getOrCreateVar($n); $var->status = self::VAR_ASSIGNED; } } else { // warn about non-variable being passed by reference // allow static class variable: Foo::$bar, \Foo\Bar::$baz $is_class_variable = false; if ($n->kind == T_STRING || $n->kind == T_NS_SEPARATOR) { list($type, $t) = self::parseType($n); if ($t->kind == T_DOUBLE_COLON && $t->nextNS()->kind == T_VARIABLE) { $is_class_variable = true; } } if (!$is_class_variable) { $this->addDefect($n, self::E_NON_VAR_PASSED_BY_REF); } } } } } else { // For unknown functions: // If argument look like a single variable (not a part of a complex expression), // it too can be passed/returned/initialized by function. // Issue a warning if this variable is not known foreach ($arguments as $n) { if ($n->text == '&') { $n = $n->nextNS(); } if ($n->kind == T_VARIABLE) { $nn = $n->nextNS(); if ($nn->text == ',' || $nn->text == ')') { // single variable - check that it exists but if not issue a warning only, // cause it can be initialized by this unknown functiton $this->checkVarAndAddDefectIfMissing($n, self::E_UNKNOWN_VAR_ARGUMENT); } } } } } continue; } // test for variable in relaxed mode only: // if (isset($variable)) ... // this makes $variable "known" in relaxed mode // if (!empty($variable)) ... // No expressions as function argument: // isset( $foo["bar"] ); // doesn't make $foo "declared", it must exist or this is an error if ($t->kind == T_ISSET || $t->kind == T_EMPTY) { $n = $t->nextNS(); if ($n && $n->text == '(') { $nn = $n->nextNS(); if ($nn && $nn->kind == T_VARIABLE) { $nnn = $nn->nextNS(); if ($nnn && $nnn->text == ')') { // ok, this is a simple expression with a variable inside function call if ($this->currentScope->mode == self::MODE_RELAXED) { // mark this variable as "known" in relaxed mode $var = $this->currentScope->getOrCreateVar($nn); $var->status = self::VAR_ASSIGNED; } else { // skip till the end of statement in strict mode $i = $nnn->index; continue; } } } } continue; } // doc comment (/** */) annotations // 1. Type info about variables (/** @var Foo $bar */) // 2. Variable declaration in relaxed mode (/** @var $foo */) if ($t->kind == T_DOC_COMMENT) { $variables_list = self::parseDocComment($t->text); $is_relaxed_mode = $this->currentScope->mode == self::MODE_RELAXED; foreach ($variables_list as $var_name => $var_type) { if ($var_type) { $this->currentScope->setVarType($var_name, $var_type); } if ($is_relaxed_mode) { $this->currentScope->getOrCreateVarByName($var_name); } } continue; } // eval: a common pattern: // eval('$foo = ...'); // eval "\$foo = ...'; if ($t->kind == T_EVAL) { $n = $t->nextNS(); if ($n->text == '(') { $n = $n->nextNS(); } if ($n->kind == T_CONSTANT_ENCAPSED_STRING) { if (preg_match('#^\'\\s*(\\$\\w+)\\s*=#', $n->text, $matches) || preg_match('#^"\\s*\\\\(\\$\\w+)\\s*=#', $n->text, $matches)) { $this->currentScope->getOrCreateVarByName($matches[1]); } } continue; } // Part 2. // Check if a variable is defined // if ($t->kind == T_VARIABLE) { $skipVariable = false; // skip class static variables: // Foo::$foo // TODO: check that this class variable is really declared $p = $t->prevNS(); if ($p->kind == T_DOUBLE_COLON) { $skipVariable = true; } // skip variables in the global scope, because it's often polluted by vars // included from included/required files if ($checkGlobalScope == false && $this->currentScope->isGlobal) { $skipVariable = true; } if (!$skipVariable) { $this->checkVarAndAddDefectIfMissing($t); } } } // end of "for each token" loop return $this->report; }