Exemplo n.º 1
0
 public function generateFileReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $this->lintEngine->addParsedFile($pf);
 }
Exemplo n.º 2
0
 public function getReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $this->report = array();
     $tokens = $pf->getTokens();
     if (!$tokens) {
         return;
         // skip empty php files
     }
     $skip_closing_tag_checks = false;
     // do we have text before the opening tag?
     $t = $tokens[0];
     if ($t->kind != T_OPEN_TAG) {
         if ($t->kind != T_INLINE_HTML) {
             throw new XRef_ParseException($t);
         }
         // is there are spaces only or meaningful html?
         // regexp = optional byte order mark in the beginning + (spaces or nbsp)+
         $regexp = "#^(\\xEF\\xBB\\xBF|\\xFE\\xFF|\\xFF\\xFE|\\x00\\x00\\xFE\\xFF|\\xFF\\xFE\\x00\\x00)?[\\s\\xa0]*\$#";
         if (preg_match($regexp, $t->text)) {
             $this->addDefect($t, self::E_EXTRA_SPACES);
         } else {
             $skip_closing_tag_checks = true;
         }
     }
     if (!$skip_closing_tag_checks) {
         $count = count($tokens);
         for ($i = 0; $i < $count; ++$i) {
             $t = $tokens[$i];
             // if there is only one closing tag, and there is nothing after it
             // (except maybe a space-only T_INLINE_HTML), report an error
             if ($t->kind == T_CLOSE_TAG) {
                 if ($i == $count - 1) {
                     $this->addDefect($t, self::E_CLOSING_TAG);
                 } elseif ($i == $count - 2) {
                     $n = $tokens[$count - 1];
                     if (preg_match('#^[\\s+\\xa0]*$#', $n->text)) {
                         $this->addDefect($t, self::E_CLOSING_TAG);
                     }
                 }
                 break;
             }
         }
     }
     return $this->report;
 }
Exemplo n.º 3
0
 public function generateFileReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $tokens = $pf->getTokens();
     // Property declared:
     // (public|protected|private)  $foo;
     for ($i = 0; $i < count($tokens); ++$i) {
         $t = $tokens[$i];
         if ($t->kind == T_PUBLIC || $t->kind == T_PROTECTED || $t->kind == T_PRIVATE) {
             $n = $t->nextNS();
             if ($n->kind == T_VARIABLE) {
                 $name = $n->text;
                 if (substr($name, 0, 1) == '$') {
                     $name = substr($name, 1);
                 } else {
                     error_log("Strange property name: {$name}");
                 }
                 $p = $this->getOrCreate($name);
                 $filePos = new XRef_FilePosition($pf, $n->index);
                 $p->declaredAt[] = $filePos;
                 $this->xref->addSourceFileLink($filePos, $this->reportId, $name);
             }
         }
     }
     // property used:
     // $foo->bar->baz
     for ($i = 0; $i < count($tokens); ++$i) {
         $t = $tokens[$i];
         if ($t->kind == T_OBJECT_OPERATOR) {
             $t = $t->nextNS();
             $n = $t->nextNS();
             if ($n->kind == XRef::T_ONE_CHAR && $n->text == '(') {
                 // method call: $foo->bar()
                 continue;
             }
             $name = $t->text;
             $p = $this->getOrCreate($name);
             $filePos = new XRef_FilePosition($pf, $t->index);
             $p->usedAt[] = $filePos;
             // link from source file HTML page to report page "reportId/objectId"
             $this->xref->addSourceFileLink($filePos, $this->reportId, $name);
             continue;
         }
     }
 }
Exemplo n.º 4
0
 public function generateFileReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     // collect all declared methods
     $pf_methods = $pf->getMethods();
     foreach ($pf_methods as $pfm) {
         if ($pfm->name) {
             $m = $this->getOrCreate($pfm->name);
             $definedAt = new XRef_FilePosition($pf, $pfm->nameIndex);
             $m->definedAt[] = $definedAt;
             // link from source file HTML page to report page "reportId/objectId"
             $this->xref->addSourceFileLink($definedAt, $this->reportId, $m->id, true);
         }
     }
     // collect all method calls
     $tokens = $pf->getTokens();
     for ($i = 0; $i < count($tokens); ++$i) {
         $t = $tokens[$i];
         // find something like:
         //      <name> "("
         // and not like
         //      "new" <name> "("
         //      "function" <name> "("
         //      "function" ("get"|"set") <name> "(" // AS3 declaration of getter/setter function
         if ($t->kind == T_STRING) {
             $n = $t->nextNS();
             if ($n != null && $n->kind == XRef::T_ONE_CHAR && $n->text == "(") {
                 $p = $t->prevNS();
                 if ($p != null && $p->kind == XRef::T_ONE_CHAR && $p->text == '&') {
                     // PHP - function &foo(
                     $p = $p->prevNS();
                 }
                 if ($p->kind != T_NEW && $p->kind != T_FUNCTION && $p->kind != XRef::T_GET && $p->kind != XRef::T_SET || $p == null) {
                     $m = $this->getOrCreate($t->text);
                     $calledFrom = new XRef_FilePosition($pf, $t->index);
                     $m->calledFrom[] = $calledFrom;
                     // link from source file HTML page to report page "reportId/objectId"
                     $this->xref->addSourceFileLink($calledFrom, $this->reportId, $m->id);
                 }
             }
         }
     }
 }
Exemplo n.º 5
0
 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;
 }
Exemplo n.º 6
0
 /**
  * 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;
 }
Exemplo n.º 7
0
 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;
 }
Exemplo n.º 8
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;
 }
Exemplo n.º 9
0
 public function getReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $this->report = array();
     // config_constants are initialized here and not in constructor only for
     // unittest. TODO: make unittest reload plugins after config changes
     $this->config_constants = array();
     foreach (XRef::getConfigValue("lint.add-constant", array()) as $const_name) {
         $this->config_constants[$const_name] = true;
     }
     // lower/mixed-case string literals:
     // for (foo=0; $foo<10; )
     // $bar[x]  - is it $bar['x'] or $bar[$x] ?
     $seen_strings = array();
     // report every literal once per file, don't be noisy
     $global_constants = array();
     // list of all constants defined in global space in this file
     $class_constants = array();
     // list of all class constants names defined in this file
     // list of token indexes that contains allowed string literals
     $ignore_tokens = array();
     foreach ($pf->getConstants() as $c) {
         if ($c->className) {
             $class_constants[$c->name] = 1;
         } else {
             $global_constants[$c->name] = 1;
             // TODO: this is a namespaced name
         }
         $ignore_tokens[$c->index] = 1;
         // don't parse/analyze this token
     }
     $tokens = $pf->getTokens();
     $tokens_count = count($tokens);
     $is_inside_interpolated_string = false;
     for ($i = 0; $i < $tokens_count; ++$i) {
         $t = $tokens[$i];
         if (isset($ignore_tokens[$i])) {
             continue;
         }
         // skip namespaces declarations and imports:
         // namespace foo\bar;
         // use foo\bar as foobar;
         if ($t->kind == T_NAMESPACE || $t->kind == T_USE) {
             do {
                 $t = $t->nextNS();
             } while ($t->text != ';' && $t->text != '{');
             $i = $t->index;
             continue;
         }
         // skip class names completely
         // class Foo extends Bar implements Baz, Qux
         if ($t->kind == T_CLASS || $t->kind == T_INTERFACE || $t->kind == T_TRAIT) {
             do {
                 $t = $t->nextNS();
             } while ($t->text != ';' && $t->text != '{');
             $i = $t->index;
             continue;
         }
         if ($t->kind == T_INSTANCEOF || $t->kind == T_NEW) {
             // ok, class name:
             // $foo instanceof Foo
             // $foo = new Foo;
             do {
                 $t = $t->nextNS();
             } while ($t->kind == T_STRING || $t->kind == T_NS_SEPARATOR);
             $i = $t->index;
             continue;
         }
         if ($t->kind == XRef::T_ONE_CHAR && $t->text == '"') {
             $is_inside_interpolated_string = !$is_inside_interpolated_string;
         }
         if ($t->kind == T_STRING) {
             if (isset($seen_strings[$t->text])) {
                 // report each string only once
                 continue;
             }
             // skip know constants defined in this file (const foo=1),
             // system constants (SORT_DESC) and config-defined constants
             if (isset($global_constants[$t->text]) || isset($this->system_constants[$t->text]) || isset($this->config_constants[$t->text])) {
                 continue;
             }
             // PHP predefined case-insensitive constants?
             $str_upper = strtoupper($t->text);
             if ($str_upper == "TRUE" || $str_upper == "FALSE" || $str_upper == "NULL") {
                 continue;
             }
             // skip all fully-qualified names (names with namespaces), if any
             // Foo\Bar\Baz
             $n = $t->nextNS();
             while ($n->kind == T_STRING || $n->kind == T_NS_SEPARATOR) {
                 $n = $n->nextNS();
             }
             // add constants defined in this file to list of known strings and don't report them
             //  define('foo', <expression>)
             if ($t->text == 'define') {
                 if ($n->text == '(') {
                     $nn = $n->nextNS();
                     if ($nn->kind == T_CONSTANT_ENCAPSED_STRING) {
                         $string = $nn->text;
                         $nn = $nn->nextNS();
                         if ($nn->text == ',' && strlen($string) > 2) {
                             // remove the first and the last quotes
                             $string = substr($string, 1, strlen($string) - 2);
                             $global_constants[$string] = 1;
                             continue;
                         }
                     }
                 }
             }
             if ($n->text == '(') {
                 // ok, function call: Foo(...)
                 $i = $n->index;
                 continue;
             }
             if ($n->kind == T_DOUBLE_COLON) {
                 // ok, class name: foo::$something
                 $i = $n->index;
                 continue;
             }
             if ($n->text == ':') {
                 // ok, label (e.g. goto foo; foo: ...);
                 $i = $n->index;
                 continue;
             }
             // some kind of variable declared with class?
             // catch (Foo $x);
             // function bar(Foo\Bar &$x)
             if ($n->text == '&') {
                 $n = $n->nextNS();
             }
             if ($n->kind == T_VARIABLE) {
                 $i = $n->index;
                 continue;
             }
             $p = $t->prevNS();
             if ($p->kind == T_DOUBLE_COLON || $p->kind == T_OBJECT_OPERATOR) {
                 // ok, static or instance member: $bar->foo, Bar::foo
                 continue;
             }
             if ($p->kind == T_GOTO) {
                 // ok, label for goto
                 continue;
             }
             // declare(ticks=1) ?
             if ($t->text == 'ticks' || $t->text == 'encoding') {
                 if ($p->text == '(') {
                     $pp = $p->prevNS();
                     if (strtolower($pp->text) == 'declare') {
                         // ok, skip this
                         continue;
                     }
                 }
             }
             if ($is_inside_interpolated_string) {
                 // exception: it's ok to index arrays inside interpolated (double-quoted) strings
                 // with bare words:
                 //      "Foo is $data[foo]";        // ok
                 //      "Foo is {$data['foo']}";    // ok
                 //      "Foo is {$data[foo]}";      // NOT ok
                 if ($p->text == '[') {
                     $pp = $p->prev();
                     if ($pp && $pp->kind == T_VARIABLE) {
                         $pp = $pp->prev();
                         if ($pp && $pp->kind != T_CURLY_OPEN) {
                             // ok, allow "$data[something]"
                             continue;
                         }
                     }
                 }
             }
             // is it some known class constant used without prefix?
             //  class Foo { const BAR = 1; }
             //  echo BAR; // should be Foo::BAR (or self::BAR inside the class)
             if (isset($class_constants[$t->text])) {
                 $this->addDefect($t, self::E_UNPREFIXED_CLASS_CONSTANT);
                 $seen_strings[$t->text] = 1;
                 continue;
             }
             if ($t->text == $str_upper) {
                 // ok, all-uppercase, SOME_CONSTANT, I hope
                 continue;
             }
             $this->addDefect($t, self::E_LOWER_CASE_STRING_LITERAL);
             $seen_strings[$t->text] = 1;
         }
     }
     return $this->report;
 }
Exemplo n.º 10
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;
 }
Exemplo n.º 11
0
 public function getFormattedText(XRef_IParsedFile $pf, $root)
 {
     $filename = $pf->getFileName();
     $links = $this->xref->getSourceFileLinks($filename);
     $tokens = $pf->getTokens();
     $lineNumber = 1;
     $ret = '';
     $ret .= sprintf(self::LINE_NUMBER_FORMAT, $lineNumber, $lineNumber);
     for ($i = 0; $i < count($tokens); ++$i) {
         $token = $tokens[$i];
         if ($links && array_key_exists($i, $links)) {
             // TODO: ugly, redo
             if (is_array($links[$i])) {
                 list($reportName, $objectId) = $links[$i];
                 $link = $this->xref->getHtmlLinkFor($reportName, $objectId, $root);
                 $ret .= "<a href='{$link}'>";
             } else {
                 $ret .= "</a>";
             }
         }
         $text = htmlspecialchars($token->text);
         if ($token->kind == XRef::T_ONE_CHAR) {
             $ret .= $text;
         } else {
             $span_class = array_key_exists($token->kind, XRef::$tokenNames) ? XRef::$tokenNames[$token->kind] : token_name($token->kind);
             if ($span_class == 'UNKNOWN') {
                 $span_class = $token->kind;
             }
             // special classes for php predefined constants
             if ($token->kind == T_STRING) {
                 $str_upper = strtoupper($token->text);
                 switch (strtoupper($token->text)) {
                     case "NULL":
                         $span_class == 'T_NULL';
                         break;
                     case "TRUE":
                         $span_class == 'T_TRUE';
                         break;
                     case "FALSE":
                         $span_class == 'T_FALSE';
                         break;
                 }
             }
             if (strpos($text, "\n") === false) {
                 // no new lines, simple
                 $ret .= "<span class='{$span_class}'>{$text}</span>";
             } else {
                 // ugly code that does the following:
                 // input:
                 //      some \n multiline text \n startig at line 10
                 // output:
                 //      <span class="text">some</span>
                 //      11 <span class="text">multiline text </span>
                 //      12  <span class="text"> starting at line 10 </span>
                 $parts = explode("\n", $text);
                 for ($j = 0; $j < count($parts); $j++) {
                     if ($j != 0) {
                         $lineNumber++;
                         $ret .= sprintf(self::LINE_NUMBER_FORMAT, $lineNumber, $lineNumber);
                     }
                     $ret .= "<span class='{$span_class}'>{$parts[$j]}</span>";
                     if ($j != count($parts) - 1) {
                         $ret .= "\n";
                     }
                 }
             }
         }
     }
     return $ret;
 }
Exemplo n.º 12
0
 /** methods to use when parsed files are already available */
 public function addParsedFile(XRef_IParsedFile $pf)
 {
     $this->report[$pf->getFileName()] = $this->getFileReport($pf);
 }
Exemplo n.º 13
0
 public function generateFileReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     // collect all declared classes
     foreach ($pf->getClasses() as $pfc) {
         $c = $this->getOrCreate($pfc->name);
         $c->isInterface = $pfc->kind == T_INTERFACE;
         $definedAt = new XRef_FilePosition($pf, $pfc->nameIndex);
         $c->definedAt[] = $definedAt;
         // link from source file HTML page to report page "reportId/objectId"
         $this->xref->addSourceFileLink($definedAt, $this->reportId, $c->id);
         // extended classes/interfaces
         foreach ($pfc->extends as $ext_name) {
             $c->extends[] = $ext_name;
             $ext_class = $this->getOrCreate($ext_name);
             $ext_class->inheritedBy[] = $pfc->name;
             $extendsIndex = $pfc->extendsIndex[$ext_name];
             $filePos = new XRef_FilePosition($pf, $extendsIndex[0], $extendsIndex[1]);
             $ext_class->usedAt[] = $filePos;
             // link from source file HTML page to report page "reportId/objectId"
             $this->xref->addSourceFileLink($filePos, $this->reportId, $ext_class->id);
         }
         // extended classes/interfaces
         foreach ($pfc->implements as $imp_name) {
             $c->implements[] = $imp_name;
             $imp_class = $this->getOrCreate($imp_name);
             $imp_class->inheritedBy[] = $pfc->name;
             $extendsIndex = $pfc->implementsIndex[$imp_name];
             $filePos = new XRef_FilePosition($pf, $extendsIndex[0], $extendsIndex[1]);
             $imp_class->usedAt[] = $filePos;
             // link from source file HTML page to report page "reportId/objectId"
             $this->xref->addSourceFileLink($filePos, $this->reportId, $imp_class->id);
         }
     }
     // foreach declared class
     // collect all places where this class is instantiated or mentioned:
     $tokens = $pf->getTokens();
     $tokens_count = count($tokens);
     for ($i = 0; $i < $tokens_count; ++$i) {
         $t = $tokens[$i];
         // "new" <name>
         // "new" $className
         // "new" "(" <callable-name> ")"  // AS3
         // "new" "<" <int> ">"            // AS3
         if ($t->kind == T_NEW) {
             $t = $t->nextNS();
             if ($t->kind != T_STRING) {
                 continue;
             }
             // TODO: scan for namespaced name, e.g. new \Foo\Bar()
             $name = $pf->qualifyName($t->text, $t->index);
             $new = $this->getOrCreate($name);
             $filePos = new XRef_FilePosition($pf, $t->index);
             $new->instantiatedAt[] = $filePos;
             // link from source file HTML page to report page "reportId/objectId"
             $this->xref->addSourceFileLink($filePos, $this->reportId, $new->id);
             continue;
         }
         // T_NEW
         //
         // Class :: $foo
         // Class :: foo()
         // but not Class :: CONST
         if ($t->kind == T_DOUBLE_COLON) {
             $p = $t->prevNS();
             if ($p->kind == T_STRING) {
                 $n = $t->nextNS();
                 if ($n->kind == T_VARIABLE) {
                     // ok, static field
                 } elseif ($n->kind == T_STRING) {
                     // method or constant
                     $nn = $n->nextNS();
                     if ($nn->text != '(') {
                         continue;
                     }
                 } elseif ($n->text == '$') {
                     // rare case: Class::$$static_field_name
                     continue;
                 } else {
                     throw new XRef_ParseException($n);
                 }
                 $className = $pf->qualifyName($p->text, $p->index);
                 if ($pf->getFileType() == XRef::FILETYPE_PHP) {
                     if ($className == 'self') {
                         $class = $pf->getClassAt($p->index);
                         if (!$class) {
                             error_log("Reference to self:: class not inside a class method at " . $pf->getFileName() . ":{$t->lineNumber}");
                             continue;
                         }
                         $className = $class->name;
                     }
                     // TODO: super:: class resolution needs 2-pass parser
                 }
                 $c = $this->getOrCreate($className);
                 $filePos = new XRef_FilePosition($pf, $p->index);
                 $c->usedAt[] = $filePos;
                 $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id);
             } else {
                 error_log("Unexpected token {$t->text} at " . $pf->getFileName() . ":{$t->lineNumber}");
             }
         }
         // $foo isinstanceof Bar
         // $foo isinstanceof $bar
         if ($t->kind == T_INSTANCEOF) {
             $n = $t->nextNS();
             if ($n->kind == T_STRING) {
                 $className = $pf->qualifyName($n->text, $n->index);
                 $c = $this->getOrCreate($className);
                 $filePos = new XRef_FilePosition($pf, $n->index);
                 $c->usedAt[] = $filePos;
                 $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id);
             }
         }
         // is_a($foo, "Bar")
         if ($t->kind == T_STRING && $t->text == "is_a") {
             $n = $t->nextNS();
             if ($n->text != '(') {
                 continue;
             }
             // TODO: add more checks below
             $n = $n->nextNS();
             // var name. Actually, should skip any expression, e.g.: is_a($user->world, "World")
             $n = $n->nextNS();
             // comma
             $n = $n->nextNS();
             // class name literal?
             $className = $pf->qualifyName(preg_replace("#[\\'\"]#", '', $n->text), $n->index);
             $c = $this->getOrCreate($className);
             $filePos = new XRef_FilePosition($pf, $n->index);
             $c->usedAt[] = $filePos;
             $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id);
         }
     }
     // foreach $token
 }
Exemplo n.º 14
0
 public function generateFileReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $tokens = $pf->getTokens();
     for ($i = 0; $i < count($tokens); ++$i) {
         $t = $tokens[$i];
         // Const declared:
         // const FOO = 10;
         if ($t->kind == T_CONST) {
             $n = $t->nextNS();
             $class = $pf->getClassAt($n->index);
             if ($class) {
                 $name = $class->name . "::" . $n->text;
             } else {
                 $name = $n->text;
             }
             $c = $this->getOrCreate($name);
             $filePos = new XRef_FilePosition($pf, $n->index);
             $c->declaredAt[] = $filePos;
             // link from source file HTML page to report page "reportId/objectId"
             $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id);
         }
         // Const used:
         // foo::bar, but not foo::bar() or foo::$bar\
         if ($t->kind == T_DOUBLE_COLON) {
             $p = $t->prevNS();
             if ($p->kind == T_STRING) {
                 $n = $t->nextNS();
                 if ($n->kind == T_VARIABLE) {
                     // static field
                     continue;
                 } elseif ($n->kind == T_STRING) {
                     $nn = $n->nextNS();
                     if ($nn->kind == XRef::T_ONE_CHAR && $nn->text == '(') {
                         // method call
                         continue;
                     }
                 } elseif ($n->text == '$') {
                     // rare case: Class::$$static_field_name
                     continue;
                 } else {
                     error_log("What's this: {$n->text}");
                     continue;
                 }
                 $className = $p->text;
                 if ($className == 'self') {
                     $class = $pf->getClassAt($p->index);
                     if (!$class) {
                         error_log("Reference to self:: class not inside a class method at " . $pf->getFileName() . ":{$p->lineNumber}");
                         continue;
                     }
                     $className = $class->name;
                 }
                 $name = $className . "::" . $n->text;
                 $c = $this->getOrCreate($name);
                 $filePos = new XRef_FilePosition($pf, $n->index);
                 $c->usedAt[] = $filePos;
                 // link from source file HTML page to report page "reportId/objectId"
                 $this->xref->addSourceFileLink($filePos, $this->reportId, $c->id);
             }
         }
     }
     // foreach token
 }
Exemplo n.º 15
0
 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;
 }
Exemplo n.º 16
0
 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;
 }
Exemplo n.º 17
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;
 }