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; }
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); }
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); }
$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) {
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; } }
/** * 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; }
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); }