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;
 }
Exemple #2
0
     if (XRef::verbose()) {
         error_log("Branch {$branch_name} was not modified");
     }
     continue;
 }
 if (XRef::verbose()) {
     error_log("Processing branch {$branch_name}");
 }
 // $errors: array(file name => XRef_CodeDefect[])
 $errors = array();
 $file_provider_old = $scm->getFileProvider($old_rev);
 $file_provider_new = $scm->getFileProvider($current_rev);
 $file_provider_new->excludePaths($exclude_paths);
 $file_provider_new->excludePaths($exclude_paths);
 $modified_files = $scm->getListOfModifiedFiles($old_rev, $current_rev);
 $lint_engine = XRef::getConfigValue("xref.project-check", true) ? new XRef_LintEngine_ProjectCheck($xref) : new XRef_LintEngine_Simple($xref);
 if ($incremental) {
     // incremental mode - find only the errors that are new from the old version of the same file
     $errors = $lint_engine->getIncrementalReport($file_provider_old, $file_provider_new, $modified_files);
 } else {
     // normal mode - report about every error in file
     $errors = $lint_engine->getReport($file_provider_new);
 }
 if (count($errors)) {
     if (XRef::verbose()) {
         error_log(count($errors) . " file(s) with errors found");
     }
     list($recipients, $subject, $body, $headers) = $xref->getNotificationEmail($errors, $branch_name, $old_rev, $current_rev);
     foreach ($recipients as $to) {
         mail($to, $subject, $body, $headers);
     }
Exemple #3
0
 public function setXRef(XRef $xref)
 {
     parent::setXRef($xref);
     $this->lintEngine = XRef::getConfigValue("xref.project-check", true) ? new XRef_LintEngine_ProjectCheck($xref) : new XRef_LintEngine_Simple($xref);
 }
 /**
  * returns array with config-defined functions
  * (see config key "lint.add-function-signature" in README)
  */
 private function getConfigSlice()
 {
     // config-defined functions & class methods
     $functions = array();
     $classes = array();
     foreach (XRef::getConfigValue("lint.add-function-signature", array()) as $str) {
         $function = self::getFunctionFromString($str);
         if ($function->className) {
             $cl_name = strtolower($function->className);
             if (isset($classes[$cl_name])) {
                 $c = $classes[$cl_name];
             } else {
                 $c = new XRef_Class();
                 $c->index = -1;
                 $c->nameIndex = -1;
                 $c->bodyStarts = -1;
                 $c->bodyEnds = -1;
                 $c->kind = T_CLASS;
                 $c->name = $function->className;
                 $classes[$cl_name] = $c;
             }
             $c->methods[] = $function;
         } else {
             $functions[] = $function;
         }
     }
     return array('classes' => array_values($classes), 'functions' => $functions);
 }
Exemple #5
0
        $file_provider_new->excludePaths($exclude_paths);
        $modified_files = $scm->getListOfModifiedFiles($old_rev, $new_rev);
        $total_report = $lint_engine->getIncrementalReport($file_provider_old, $file_provider_new, $modified_files);
    } else {
        // regular mode - find all errors in revision <$old_rev>
        $file_provider = $scm->getFileProvider($old_rev);
        $file_provider->excludePaths($exclude_paths);
        $total_report = $lint_engine->getReport($file_provider);
    }
} else {
    // main loop over all files
    // list of files to check (in order):
    //  1) command-line arguments, 2) config-defined project.source-code-dir 3) current dir
    $paths = $arguments;
    if (!$paths) {
        $paths = XRef::getConfigValue("project.source-code-dir", array());
    }
    if (!$paths) {
        $paths = array(".");
    }
    $file_provider = new XRef_FileProvider_FileSystem($paths);
    $file_provider->excludePaths($exclude_paths);
    $total_report = $lint_engine->getReport($file_provider);
}
// calculate some stats
foreach ($total_report as $file_name => $report) {
    $filesWithDefects++;
    foreach ($report as $code_defect) {
        if ($code_defect->severity == XRef::NOTICE) {
            $numberOfNotices++;
        } elseif ($code_defect->severity == XRef::WARNING) {
Exemple #6
0
    error_log($e->getMessage());
    error_log("See 'xref-doc --help'");
    exit(1);
}
//help
if (XRef::needHelp() || count($arguments)) {
    XRef::showHelpScreen("xref-doc - tool to create cross-reference source code reports");
    exit(1);
}
$xref = new XRef();
$xref->setOutputDir(XRef::getConfigValue("doc.output-dir"));
$xref->loadPluginGroup("doc");
$plugins = $xref->getPlugins("XRef_IDocumentationPlugin");
$path = XRef::getConfigValue("project.source-code-dir");
$file_provider = new XRef_FileProvider_FileSystem($path);
$exclude_paths = XRef::getConfigValue("project.exclude-path", array());
$file_provider->excludePaths($exclude_paths);
$numberOfFiles = 0;
$numberOfCodeLines = 0;
// 1. Call each plugin once for each input file
$files = $xref->filterFiles($file_provider->getFiles());
foreach ($files as $filename) {
    try {
        $file_content = $file_provider->getFileContent($filename);
        $pf = $xref->getParsedFile($filename, $file_content);
        foreach ($plugins as $pluginId => $plugin) {
            $plugin->generateFileReport($pf);
        }
        $numberOfFiles++;
        $numberOfCodeLines += $pf->getNumberOfLines();
        $pf->release();
 public function __construct()
 {
     parent::__construct("project-check", "Cross-reference integrity check");
     $ignore_missing_classes = XRef::getConfigValue("lint.ignore-missing-class", array());
     foreach ($ignore_missing_classes as $class_name) {
         $this->ignore_missing_classes[strtolower($class_name)] = true;
     }
 }
Exemple #8
0
 /**
  * Internal wrapper to run git executable, returns git output
  * Warning: extra spaces/newlines at command output are trimmed
  *
  * @param string[] $arguments
  * @param boolean $wantarray
  * @param boolean $failOnError
  * @return string[]|string
  */
 private static function git($arguments, $wantarray = false, $failOnError = true)
 {
     $gitDir = XRef::getConfigValue("git.repository-dir") . "/.git";
     $arguments = array_merge(array("git", "--git-dir={$gitDir}"), $arguments);
     $cmd = implode($arguments, " ");
     // hate php:
     //  - shell_exec can't tell failed run from empty output
     //  - exec returns output in array (even if I need a scalar)
     //  - system returns the last line only
     //  - proc_open/proc_close is good but overcomplicated
     exec($cmd, $output, $return_var);
     if ($return_var != 0) {
         if ($failOnError) {
             die("Can't execute {$cmd}");
         } else {
             $output = array();
         }
     }
     if ($wantarray) {
         return $output;
     } else {
         return rtrim(implode($output, "\n"));
     }
 }
 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;
 }
Exemple #10
0
 public function getNotificationEmail($report, $branch_name, $old_rev, $current_rev)
 {
     $this->addSourceCodeLinks($report, $current_rev);
     $reply_to = XRef::getConfigValue('mail.reply-to');
     $from = XRef::getConfigValue('mail.from');
     $mail_to = XRef::getConfigValue('mail.to');
     $project_name = XRef::getConfigValue('project.name', '');
     // this works for git, will it work for other scms?
     $old_rev_short = strlen($old_rev) > 7 ? substr($old_rev, 0, 7) : $old_rev;
     $current_rev_short = strlen($current_rev) > 7 ? substr($current_rev, 0, 7) : $current_rev;
     // $commitInfo: array('an'=>'igariev', 'ae'=>'igariev@9e1ac877-.', ...)
     $commitInfo = $this->getSourceCodeManager()->getRevisionInfo($current_rev);
     $subject = "XRef CI {$project_name}: {$branch_name}/{$current_rev_short}";
     $headers = "MIME-Version: 1.0\n" . "Content-type: text/html\n" . "Reply-to: {$reply_to}\n" . "From: {$from}\n";
     $body = $this->fillTemplate("ci-email.tmpl", array('branchName' => $branch_name, 'oldRev' => $old_rev, 'oldRevShort' => $old_rev_short, 'currentRev' => $current_rev, 'currentRevShort' => $current_rev_short, 'fileErrors' => $report, 'commitInfo' => $commitInfo));
     $recipients = array();
     foreach ($mail_to as $to) {
         $to = preg_replace('#\\{%(\\w+)\\}#e', '$commitInfo["$1"]', $to);
         $recipients[] = $to;
     }
     return array($recipients, $subject, $body, $headers);
 }