Пример #1
0
 /**
  * @param XRef_ParsedFile $pf
  * @param bool $is_library_file
  * @return * any data
  */
 public function createFileSlice(XRef_IParsedFile $pf, $is_library_file = false)
 {
     /** array of arrays with info about called functions and methods */
     $called_functions = array();
     /** map: (function call info => true), to report any call once only */
     $uniqs = array();
     /** map of function names, checked for existence by function_exists() */
     $checked_for_functions = array();
     $tokens = $pf->getTokens();
     for ($i = 0; $i < count($tokens); ++$i) {
         $t = $tokens[$i];
         // $this->foo();
         // Foo\Barr::foo();
         if ($t->text == '(') {
             $p = $t->prevNS();
             if ($p->kind != T_STRING) {
                 continue;
             }
             // array with parts of the name (T_NS_SEPARATOR && T_STRING)
             $function_name_parts = array($p->text);
             $class_name = '';
             $pp = $p->prevNS();
             if ($pp->kind == T_NS_SEPARATOR) {
                 // Foo\bar();
                 // \bar();
                 while ($pp->kind == T_NS_SEPARATOR || $pp->kind == T_STRING) {
                     array_unshift($function_name_parts, $pp->text);
                     $pp = $pp->prevNS();
                 }
             } elseif ($pp->kind == T_OBJECT_OPERATOR) {
                 // $var->bar()
                 // $this->bar();
                 $pp = $pp->prevNS();
                 if ($pp->kind == T_VARIABLE && $pp->text == '$this') {
                     $class_name = 'self';
                     // will be resolved later
                 } else {
                     // TODO
                     continue;
                 }
             } elseif ($pp->kind == T_DOUBLE_COLON) {
                 // Foo::bar();
                 // Foo\Bar::bar();
                 // self::foo();
                 // static::bar();
                 $pp = $pp->prevNS();
                 while ($pp->kind == T_NS_SEPARATOR || $pp->kind == T_STRING || $pp->kind == T_STATIC) {
                     $class_name = $pp->text . $class_name;
                     $pp = $pp->prevNS();
                 }
             }
             if ($pp->text == '&') {
                 $pp = $pp->prevNS();
             }
             if ($pp->kind == T_FUNCTION) {
                 // skip function declarations
                 continue;
             }
             $arguments = $pf->extractList($t->nextNS());
             $num_of_arguments = count($arguments);
             $from_class = $pf->getClassAt($t->index);
             $from_class_name = $from_class ? $from_class->name : '';
             if ($pp->kind == T_NEW) {
                 // new Something();
                 // new ns\Another\Something();
                 // new \Foo();
                 $class_name = implode('', $function_name_parts);
                 if ($class_name == 'static' || $class_name == 'self') {
                     $class_name = $from_class_name;
                 } elseif ($class_name == 'parent') {
                     $class_name = $from_class && $from_class->extends ? $from_class->extends[0] : null;
                 } else {
                     $class_name = $pf->qualifyName($class_name, $t->index);
                 }
                 if ($class_name) {
                     $uniq = "new##{$class_name}#{$num_of_arguments}";
                     if (!isset($uniqs[$uniq])) {
                         $uniqs[$uniq] = true;
                         $called_functions[] = array(self::F_CLASS_CONSTRUCTOR, null, $class_name, $t->lineNumber, $num_of_arguments);
                     }
                 }
             } elseif ($class_name) {
                 // method call:
                 //  $this->foo();
                 //  self::foo();
                 //  Foo::bar();
                 //  \bar\Baz::foo();
                 if ($class_name == 'static' || $class_name == 'self') {
                     $class_name = $from_class_name;
                 } elseif ($class_name == 'parent') {
                     $class_name = $from_class && $from_class->extends ? $from_class->extends[0] : null;
                 } else {
                     $class_name = $pf->qualifyName($class_name, $t->index);
                 }
                 if ($class_name) {
                     if (count($function_name_parts) > 1) {
                         throw new Exception("{$class_name}::" . implode('', $function_name_parts));
                     }
                     $function_name = $function_name_parts[0];
                     if ($function_name == '__construct') {
                         $uniq = "new##{$class_name}#{$num_of_arguments}";
                         if (!isset($uniqs[$uniq])) {
                             $uniqs[$uniq] = true;
                             $called_functions[] = array(self::F_CLASS_CONSTRUCTOR, null, $class_name, $t->lineNumber, $num_of_arguments);
                         }
                     } else {
                         $uniq = "{$function_name}##{$class_name}#{$num_of_arguments}";
                         if (!isset($uniqs[$uniq])) {
                             $uniqs[$uniq] = true;
                             $called_functions[] = array(self::F_CLASS_METHOD, $function_name, $class_name, $t->lineNumber, $num_of_arguments);
                         }
                     }
                 }
             } else {
                 // function call: qualified or unqualified
                 // foo();
                 // foo\bar();
                 // \foo\bar\baz();
                 if (count($function_name_parts) > 1) {
                     $function_name = $pf->qualifyName(implode('', $function_name_parts), $t->index);
                     $uniq = "{$function_name}##{$num_of_arguments}";
                     if (!isset($uniqs[$uniq])) {
                         $uniqs[$uniq] = true;
                         $called_functions[] = array(self::F_FULLY_QUALIFIED_FUNC, $function_name, null, $t->lineNumber, $num_of_arguments);
                     }
                 } else {
                     $function_name = $function_name_parts[0];
                     $namespace = $pf->getNamespaceAt($t->index);
                     $namespace_name = $namespace ? $namespace->name : '';
                     $uniq = "{$function_name}##{$namespace_name}#{$num_of_arguments}";
                     if (!isset($uniqs[$uniq])) {
                         $uniqs[$uniq] = true;
                         $called_functions[] = array(self::F_NOT_QUALIFIED_FUNC, $function_name, $namespace_name, $t->lineNumber, $num_of_arguments, $from_class_name);
                     }
                 }
                 if ($function_name == 'function_exists') {
                     $n = $t->nextNS();
                     if ($n->kind == T_CONSTANT_ENCAPSED_STRING) {
                         $checked_for_function_name = trim($n->text, '\'"');
                         $checked_for_functions[$checked_for_function_name] = true;
                     }
                 }
             }
         }
     }
     $file_slice = array("called" => $called_functions, "checked" => $checked_for_functions);
     return $file_slice;
 }
Пример #2
0
 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;
 }