public function getReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $this->report = array();
     $tokens = $pf->getTokens();
     $count = count($tokens);
     for ($i = 0; $i < $count; ++$i) {
         $t = $tokens[$i];
         // warnings:
         //      if ($foo = $bar) ;
         //      if ($bar = null) ;
         //      if ($baz = 25 && $qux == 30) ;
         // ok:
         //      if ($fh = fopen($file, "w")) ;
         //      if ($a = $foo->next() ) ;
         if ($t->kind == T_IF || $t->kind == T_ELSEIF) {
             $n = $t->nextNS();
             if ($n->text != '(') {
                 throw new XRef_ParseException($n);
             }
             $last_index = $pf->getIndexOfPairedBracket($n->index);
             while ($n->index < $last_index) {
                 $n = $n->nextNS();
                 // skip function/method calls inside 'if' statements
                 //      if (someFunc($paramName = value)) ...
                 if ($n->text == '(') {
                     $p = $n->prevNS();
                     if ($p->kind == T_STRING) {
                         $n = $pf->getTokenAt($pf->getIndexOfPairedBracket($n->index));
                         continue;
                     }
                 }
                 if ($n->text == '=') {
                     $n = $n->nextNS();
                     $nn = $n->nextNS();
                     if ($nn->text == ')' || $nn->kind == T_BOOLEAN_AND || $nn->kind == T_BOOLEAN_OR) {
                         $this->addDefect($n, self::E_ASSIGNMENT_IN_CONDITION);
                     }
                 }
             }
             $i = $last_index;
             continue;
         }
     }
     return $this->report;
 }
 public function createFileSlice(XRef_IParsedFile $pf, $is_library_file = false)
 {
     // names of classes that has constructors and don't call parent class constructors
     // array of array(class_name, line_number);
     $slice = array();
     foreach ($pf->getClasses() as $class) {
         foreach ($class->methods as $method) {
             if (strtolower($method->name) == '__construct' && $method->bodyStarts > 0) {
                 // ok, this class has constructor
                 $does_call_parent_constructor = false;
                 $t = $pf->getTokenAt($method->bodyStarts);
                 while ($t->index < $method->bodyEnds) {
                     if ($t->text == 'parent') {
                         $t = $t->nextNS();
                         if ($t->kind == T_DOUBLE_COLON) {
                             $t = $t->nextNS();
                             if ($t->text == '__construct') {
                                 $t = $t->nextNS();
                                 if ($t->text == '(') {
                                     // ok, this is a call for the parent class constructor
                                     $does_call_parent_constructor = true;
                                     break;
                                 }
                             }
                         }
                     }
                     $t = $t->nextNS();
                 }
                 if (!$does_call_parent_constructor) {
                     // this is suspicious and,
                     // if there is a parent class with constructor, error-prone
                     $slice[] = array($class->name, $class->lineNumber);
                 }
                 break;
             }
         }
     }
     return $slice;
 }
 /**
  * Function to summarize content of parsed file.
  * The result will be added to database by addFileSlice() method
  * and can be serialized/stored meanwhile.
  *
  * @param XRef_ParsedFile $pf
  * @param bool $is_library_file
  * @return array file summary,
  */
 public function createFileSlice(XRef_IParsedFile $pf, $is_library_file = false)
 {
     // TODO: add constants
     // filter functions that are not methods and not closures
     $functions = array();
     foreach ($pf->getMethods() as $m) {
         if (!$m->className && $m->name) {
             $functions[] = $m;
         }
     }
     // check if function/method calls
     // - call_user_func()/call_user_func_array()
     //  (then can't reliable say if child class calls constructor of its base class)
     // - func_get_args()/func_num_args()
     //  (then can't tell the number of arguments)
     foreach ($pf->getMethods() as $m) {
         if ($m->bodyStarts > 0) {
             $t = $pf->getTokenAt($m->bodyStarts);
             // TODO: remove all token iteration; use a real parser that returns AST
             while ($t->index < $m->bodyEnds) {
                 if ($t->kind == T_STRING) {
                     $text = $t->text;
                     $t = $t->nextNS();
                     if ($t->text == '(') {
                         if ($text == 'call_user_func' || $text == 'call_user_func_array') {
                             $m->flags |= self::FLAG_CALLS_USER_FUNC;
                         } elseif ($text == 'func_get_args' || $text == 'func_get_arg' || $text == 'func_num_args') {
                             $m->flags |= self::FLAG_CALLS_GET_ARGS;
                         }
                     }
                 }
                 $t = $t->nextNS();
             }
         }
     }
     $slice = array("classes" => $pf->getClasses(), "functions" => $functions);
     return $slice;
 }
Exemple #4
0
 public function getReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $this->report = array();
     // special case: if file has no declarations of function/classes, it can be included into
     // body of some instance method, then use of $this is legit.
     // see also: joomla code.
     $allow_this_in_global_scope = false;
     if (count($pf->getClasses()) == 0 && count($pf->getMethods()) == 0) {
         $allow_this_in_global_scope = true;
     }
     // TOKEN:
     $tokens = $pf->getTokens();
     $tokens_count = count($tokens);
     for ($i = 0; $i < $tokens_count; ++$i) {
         $t = $tokens[$i];
         // ignore bodies of non-static functions declared inside classes
         //      TODO: should we allow $this inside function arguments, e.g.
         //      class Foo { public function bar($this, $baz = $this) {} } ??
         if ($t->kind == T_FUNCTION && $t->prevNS()->kind != T_STATIC && $pf->getClassAt($t->index) != null) {
             $n = $t->nextNS();
             while ($n->text != "(") {
                 $n = $n->nextNS();
             }
             // fast-forward to closing ")" of the function arguments
             $n = $pf->getTokenAt($pf->getIndexOfPairedBracket($n->index));
             $n = $n->nextNS();
             if ($n->kind == T_USE) {
                 $n = $n->nextNS();
                 if ($n->text != '(') {
                     throw new XRef_ParseException($n);
                 }
                 $n = $pf->getTokenAt($pf->getIndexOfPairedBracket($n->index));
                 $n = $n->nextNS();
             }
             if ($n->text == ';') {
                 // declaration only or abstract function: function foo();
                 // do nothing, skip main loop to the next token
                 $i = $n->index;
             } elseif ($n->text == '{') {
                 // fast-forward main loop to the end of the function body
                 $i = $pf->getIndexOfPairedBracket($n->index);
             } else {
                 throw new XRef_ParseException($n, "'{' or ';'");
             }
             continue;
         }
         // if we found $this anywhere else, this is an error
         if ($t->text == '$this') {
             if ($allow_this_in_global_scope) {
                 $this->addDefect($t, self::E_THIS_IN_GLOBAL_SCOPE);
             } else {
                 $this->addDefect($t, self::E_THIS_OUTSIDE_OF_METHOD);
             }
         }
         // similar error: self is used outside of class declaration
         // however, self:: is allowed in static methods
         // also, allow $object->parent
         if (($t->text == 'self' || $t->text == 'parent') && !$pf->getClassAt($t->index)) {
             $n = $t->nextNS();
             $p = $t->prevNS();
             if ($n->text == '::' || $p->kind == T_NEW || $p->kind == T_INSTANCEOF) {
                 if ($allow_this_in_global_scope) {
                     $this->addDefect($t, self::E_CLASS_CONSTRUCT_IN_GLOBAL_SCOPE);
                 } else {
                     $this->addDefect($t, self::E_CLASS_CONSTRUCT_OUTSIDE_OF_METHOD);
                 }
             }
         }
     }
     // end of TOKEN loop
     return $this->report;
 }
 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;
 }