Example #1
0
 public function __construct()
 {
     // don't read config file, if any
     XRef::setConfigFileName("default");
     XRef::setConfigValue("git.repository-dir", ".");
     $this->xref = new XRef();
 }
Example #2
0
 public function __construct()
 {
     // don't read config file, if any
     XRef::setConfigFileName("default");
     XRef::setConfigValue("xref.data-dir", "tmp");
     $this->xref = new XRef();
 }
Example #3
0
 /** optimized method - may use caching etc */
 public function getReport(XRef_IFileProvider $file_provider)
 {
     $this->loadFilesMap($file_provider);
     $files = $this->xref->filterFiles($file_provider->getFiles());
     $this->stats["total_files"] = count($files);
     $count = 1;
     foreach ($files as $filename) {
         if ($this->showProgressBar) {
             XRef::progressBar($count, count($files), $filename);
         }
         $this->report[$filename] = $this->getFileReportCached($file_provider, $filename);
         $this->slices[$filename] = $this->getFileSlicesCached($file_provider, $filename);
         $count++;
     }
     $this->releaseParsedFile();
     $this->saveFilesMap($file_provider);
     return $this->collectReport();
 }
Example #4
0
 protected function getParsedFile($file_id, $filename, $file_content)
 {
     if (!$this->pf || $this->pfFileId != $file_id) {
         // release the old file, if any
         if ($this->pf) {
             $this->pf->release();
             $this->pf = null;
         }
         try {
             $pf = $this->xref->getParsedFile($filename, $file_content);
             $this->pf = $pf;
             $this->pfFileId = $file_id;
             $this->parseException = null;
             $this->stats["parsed_files"]++;
         } catch (XRef_ParseException $e) {
             // catch XRef_ParseException here and allow to process the next file
             // however, all other exceptions are propagated
             $this->pf = false;
             $this->parseException = $e;
         }
     }
     return $this->pf;
 }
Example #5
0
<?php

/**
 * lib/web-scripts/xref-ci.php
 *
 * This script compares 2 arbitrary git commits
 * and finds/reports new errors in changed files.
 *
 * @author Igor Gariev <*****@*****.**>
 * @copyright Copyright (c) 2013 Igor Gariev
 * @licence http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */
$includeDir = "@php_dir@" == "@" . "php_dir@" ? dirname(__FILE__) . "/.." : "@php_dir@/XRef";
require_once "{$includeDir}/XRef.class.php";
$xref = new XRef();
$xref->loadPluginGroup("lint");
$scm = $xref->getSourceCodeManager();
$rev1 = isset($_REQUEST["rev1"]) ? preg_replace('#[^\\w\\-/}{ @]#', '', $_REQUEST["rev1"]) : '';
$rev2 = isset($_REQUEST["rev2"]) ? preg_replace('#[^\\w\\-/}{ @]#', '', $_REQUEST["rev2"]) : '';
if ($rev1 && $rev2) {
    $fileErrors = array();
    $modified_files = $scm->getListOfModifiedFiles($rev1, $rev2);
    $file_provider1 = $scm->getFileProvider($rev1);
    $file_provider2 = $scm->getFileProvider($rev2);
    $lint_engine = XRef::getConfigValue("xref.project-check", true) ? new XRef_LintEngine_ProjectCheck($xref) : new XRef_LintEngine_Simple($xref);
    $errors = $lint_engine->getIncrementalReport($file_provider1, $file_provider2, $modified_files);
}
echo $xref->fillTemplate("ci-web.tmpl", array('rev1' => $rev1, 'rev2' => $rev2, 'fileErrors' => $errors));
Example #6
0
 *      2. mark line numbers
 *      3. highlight syntax
 *
 * @author Igor Gariev <*****@*****.**>
 * @copyright Copyright (c) 2013 Igor Gariev
 * @licence http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */
$includeDir = "@php_dir@" == "@" . "php_dir@" ? dirname(__FILE__) . "/.." : "@php_dir@/XRef";
require_once "{$includeDir}/XRef.class.php";
$filename = $_REQUEST["filename"];
$revision = $_REQUEST["revision"];
if (preg_match('#[^\\w\\.\\/\\-]#', $filename) || preg_match('#\\.\\.#', $filename) || preg_match('#[^\\w\\-/}{@ ]#', $revision)) {
    echo "Invalid filename or revision";
    exit(1);
}
$xref = new XRef();
$xref->loadPluginGroup("lint");
$sourcePlugin = $xref->getPluginById("files");
if ($sourcePlugin) {
    $css = $sourcePlugin->getDefaultCSS();
    $scm = $xref->getSourceCodeManager();
    $source = $scm->getFileContent($revision, $filename);
    $parsedFile = $xref->getParsedFile("unknown.php", $source);
    $formattedText = $sourcePlugin->getFormattedText($parsedFile, "");
    echo "<html><head><style>{$css}</style></head><body>";
    echo "<pre>{$formattedText}</pre>";
    echo "</body></html>";
} else {
    echo "Can't display formatted text: 'files' plugin not found";
}
// vim: tabstop=4 expandtab
Example #7
0
if (isset($options['no-cache']) && $options['no-cache']) {
    $use_cache = false;
}
//
// color: on/off/auto, for text output to console only
//
$color = XRef::getConfigValue("lint.color", 'auto');
if ($color === "auto") {
    $color = function_exists('posix_isatty') && posix_isatty(STDOUT);
}
$colorMap = array("fatal" => "", "error" => "", "warning" => "", "notice" => "", "_off" => "");
$filesWithDefects = 0;
$numberOfNotices = 0;
$numberOfWarnings = 0;
$numberOfErrors = 0;
$xref = new XRef();
$xref->loadPluginGroup("lint");
$total_report = array();
$exclude_paths = XRef::getConfigValue("project.exclude-path", array());
$lint_engine = XRef::getConfigValue("xref.project-check", true) ? new XRef_LintEngine_ProjectCheck($xref, $use_cache) : new XRef_LintEngine_Simple($xref, $use_cache);
if (isset($options['git']) && $options['git']) {
    $old_rev = null;
    $new_rev = null;
    if (isset($options['git-rev']) && $options['git-rev']) {
        // find errors in specific revision or compare two revisions
        $r = preg_split('#:#', $options['git-rev']);
        if (count($r) == 2) {
            // --git-rev=<from>:<to>
            list($old_rev, $new_rev) = $r;
        } elseif (count($r) == 1) {
            // --git-rev=<revision>
Example #8
0
my_echo("PHP-P uncompress time", $b->timeIt($f), "sec");
$f = function () use($gzencoded) {
    $data = function_exists('gzdecode') ? gzdecode($gzencoded) : gzinflate(substr($gzencoded, 10, -8));
    if (!$data) {
        throw new Exception();
    }
};
my_echo("PHP-P gzdecode time", $b->timeIt($f), "sec");
$stmts_array = array_cast_recursive($stmts);
$serialized = serialize($stmts_array);
my_echo("PHP-P array serialized", strlen($serialized), "bytes");
$f = function () use($serialized) {
    $stmts = unserialize($serialized);
};
my_echo("PHP-P array unserialize time", $b->timeIt($f), "sec");
$xref = new XRef();
// init autoload etc
$parser = new XRef_Parser_PHP();
$f = function () use($parser, $code, $filename) {
    $pf = $parser->parse($code, $filename);
    $pf->release();
};
my_echo("XRef internal parser", $b->timeIt($f, 30), "sec");
$slices = array();
$pf = $parser->parse($code, $filename);
$project_database = new XRef_ProjectDatabase();
foreach ($xref->getPlugins("XRef_IProjectLintPlugin") as $id => $plugin) {
    $slices[$id] = $plugin->createFileSlice($pf);
}
$slices['_db'] = $project_database->createFileSlice($pf);
$pf->release();
Example #9
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);
 }
Example #10
0
    public function testVariablesAssignedByFunctions()
    {
        // function that can assign values to variables passed by reference:
        // list of known functions both that can and cannot set variable's value
        //
        //  strict mode:
        //      known_function_that_assign_variable($unknown_var);          // ok
        //      known_function_that_doesnt_assign_variable($unknown_var);   // error
        //      unknown_function($unknown_var);                             // warning
        //      unknown_function($unknown_var_in_expression*2);             // error
        // relaxed mode:
        //      known_function_that_assign_variable($unknown_var);          // ok
        //      known_function_that_doesnt_assign_variable($unknown_var);   // warning
        //      unknown_function($unknown_var);                             // warning
        //      unknown_function($unknown_var_in_expression*2);             // warning
        //
        // list-of-known-function =
        //      explicit list of functions +
        //      config file defined funcions +
        //      result of get_defined_functions() +     // don't overwrite functions from above
        //      parsing of current file                 // overwrite or not?
        //
        $testPhpCode = '<?php

            function foo () {
                // internal functions
                preg_match("#pattern#", "string-to-be-mateched", $matches);     // ok
                preg_match("#pattern#", "string-to-be-mateched", null);         // error (non-var pass by ref)
                preg_grep("pattern", $input);                                   // error
                sort($array);                                                   // error
                sort( array(1,2,3) );                                           // error (non-var pass by ref)

                // locally-defined functions
                local_function_with_pass_by_reference_argument2(1, $var2);      // ok
                local_function_with_pass_by_reference_argument2($var3, $var4);  // error in $var3 only

                // unknown functions
                unknown_function($unknown_var);                                 // warning
                unknown_function($unknown_var_in_expression*2);                 // error
            }

            function bar ($args) {
                extract($args);                                                 // relaxed mode from here

                // internal functions
                preg_match("#pattern#", "string-to-be-mateched", $matches);     // ok
                preg_match("#pattern#", "string-to-be-mateched", null);         // error (non-var pass by ref)
                preg_grep("pattern", $input);                                   // warning
                sort($array);                                                   // warning
                sort( array(1,2,3) );                                           // error (non-var pass by ref)

                // locally-defined functions
                local_function_with_pass_by_reference_argument2(1, $var2);      // ok
                local_function_with_pass_by_reference_argument2($var3, $var4);  // warning in $var3 only

                // unknown functions
                unknown_function($unknown_var);                                 // warning
                unknown_function($unknown_var_in_expression*2);                 // warning
            }

            function local_function_with_pass_by_reference_argument2($arg1, &$arg2) {
                $arg2 = $arg1;
            }

            sort( array(1,2,3) );                                               // error (non-var pass by ref)
            sort( Foo::$bar );                                                  // ok

        ';
        $expectedDefects = array(array('null', 6, XRef::ERROR), array('$input', 7, XRef::ERROR), array('$array', 8, XRef::ERROR), array('array', 9, XRef::ERROR), array('$var3', 13, XRef::ERROR), array('$unknown_var', 16, XRef::WARNING), array('$unknown_var_in_expression', 17, XRef::ERROR), array('null', 25, XRef::ERROR), array('$input', 26, XRef::WARNING), array('$array', 27, XRef::WARNING), array('array', 28, XRef::ERROR), array('$var3', 32, XRef::WARNING), array('$unknown_var', 35, XRef::WARNING), array('$unknown_var_in_expression', 36, XRef::WARNING), array('array', 43, XRef::ERROR));
        $this->checkPhpCode($testPhpCode, $expectedDefects);
        $testPhpCode = '<?php

            class Foo {
                public function preg_match() {}
                public function sort(&$x)    {}

                public function bar() {
                    $this->preg_match("", "", $x);      // error: method preg_match doesnt initialize vars
                    Foo::preg_match("", "", $y);        // error
                    self::preg_match("", "", $z);       // error
                    preg_match("", "", $ok);            // ok, this is internal preg_match

                    $this->sort($a);                    // ok
                    Foo::sort($b);                      // ok
                    self::sort($c);                     // ok
                    sort($d);                           // error - internal sort doesnt intialize vars
                }
            }

            function test () {
                Foo::preg_match("", "", $i);                // error
                preg_match("", "", $j);                     // ok
                $foo = new SomeClass();
                $foo->preg_match("", "", $k);               // warning: unknown preg_match (?)

                Foo::sort($l);                              // ok
                sort($m);                                   // error, internal sort
                $foo->sort($n);                             // warning, unknown $foo sort
            }

            Foo::preg_match("", "", $i);                // warning (global relaxed scope, otherwise - error)
            preg_match("", "", $j);                     // ok
            $foo = new SomeClass();
            $foo->preg_match("", "", $k);               // warning: this is unknown preg_match

            Foo::sort($l);                              // ok
            sort($m);                                   // warning, internal sort
            $foo->sort($n);                             // warning, unknown sort
        ';
        $expectedDefects = array(array('$x', 8, XRef::ERROR), array('$y', 9, XRef::ERROR), array('$z', 10, XRef::ERROR), array('$d', 16, XRef::ERROR), array('$i', 21, XRef::ERROR), array('$k', 24, XRef::WARNING), array('$m', 27, XRef::ERROR), array('$n', 28, XRef::WARNING), array('$i', 31, XRef::WARNING), array('$k', 34, XRef::WARNING), array('$m', 37, XRef::WARNING), array('$n', 38, XRef::WARNING));
        $this->checkPhpCode($testPhpCode, $expectedDefects);
        //
        $testPhpCode = '<?php

            $arraysort = array();
            array_multisort($arraysort, SORT_ASC);  // ok, no error on second arg
            echo $expectedError;
        ';
        $expectedDefects = array(array('$expectedError', 5, XRef::WARNING));
        $this->checkPhpCode($testPhpCode, $expectedDefects);
        //
        XRef::setConfigValue('lint.add-function-signature', array('foo(&$x)', 'Foo::bar($a, &$b)', 'Bar::baz(&$a, &$b)', '?::qux(&$a, &$b)'));
        // re-create the lint plugins with new config settings
        $this->resetPlugins();
        $testPhpCode = '<?php

            function t() {
                foo($x);                    // ok
                foo($y, $z);                // error on $z

                Foo::bar(1, $a);            // ok
                Foo::bar($b, $c);           // error on $b

                $i = 0;
                $x->baz($i, $j);            // warning on $j
                $l = $m = 10;
                $x->foo->baz($k, $l, $m);   // warning on $k

                $x->qux($p, $q);            // ok, unknown class of $x, matches "?::qux"
                $p->foo->bar->qux($r);      // ok
                $x->qux($s, $t, $u);        // error on $u

            }
        ';
        $expectedDefects = array(array('$z', 5, XRef::ERROR), array('$b', 8, XRef::ERROR), array('$j', 11, XRef::WARNING), array('$k', 13, XRef::WARNING), array('$u', 17, XRef::ERROR));
        $this->checkPhpCode($testPhpCode, $expectedDefects);
        $testPhpCode = '<?php

            class Foo {
                public $bar = array();
                public static $baz = array();
            }

            function passed_by_ref_arg(Array &$a) {
                echo array_pop($a);
            }

            $a = array();
            $foo = new Foo;

            passed_by_ref_arg($a);                  // ok
            passed_by_ref_arg($foo->bar);           // ok
            passed_by_ref_arg(Foo::$baz);           // ok
            passed_by_ref_arg(\\External\\Code::$x);  // ok
            passed_by_ref_arg(Some\\Other::$z);      // ok
            passed_by_ref_arg( array(1, 2, 3) );    // error
        ';
        $expectedDefects = array(array('array', 20, XRef::ERROR));
        $this->checkPhpCode($testPhpCode, $expectedDefects);
    }
Example #11
0
<?php

require_once 'PEAR/PackageFileManager2.php';
require_once dirname(__FILE__) . '/../XRef.class.php';
$version = XRef::version();
PEAR::setErrorHandling(PEAR_ERROR_DIE);
$pfm = new PEAR_PackageFileManager2();
$pfm->setOptions(array('baseinstalldir' => 'XRef', 'packagedirectory' => '.', 'filelistgenerator' => 'file', 'ignore' => array('Makefile', 'dev/', '.idea', '.xref', 'tmp'), 'dir_roles' => array('bin' => 'script', 'config' => 'data', 'tests' => 'test', 'examples' => 'doc', 'templates' => 'data', 'scripts' => 'php', 'web-scripts' => 'php', 'plugins' => 'php'), 'exceptions' => array('README.md' => 'doc')));
$pfm->setPackage('XRef');
$pfm->setSummary('XRef - php source file toolkit');
$pfm->setDescription('XRef Lint - php lint and crossref doc tool');
$pfm->setChannel('pear.xref-lint.net');
$pfm->setAPIVersion($version);
$pfm->setReleaseVersion($version);
$pfm->setReleaseStability('stable');
$pfm->setAPIStability('stable');
$pfm->setNotes("v1.0.0 release candidate #1");
$pfm->setPackageType('php');
// dependencies
//$pfm->addDependency("Console_Getopt");
// windows-release
$pfm->addRelease();
// set up a release section
$pfm->setOSInstallCondition('windows');
$pfm->addIgnoreToRelease('bin/xref-lint');
$pfm->addIgnoreToRelease('bin/xref-doc');
$pfm->addIgnoreToRelease('bin/xref-ci');
$pfm->addIgnoreToRelease('bin/git-xref-lint');
$pfm->addInstallAs('bin/xref-lint.bat', 'xref-lint.bat');
$pfm->addInstallAs('bin/xref-doc.bat', 'xref-doc.bat');
// other platforms
Example #12
0
    public function testConfigConstants()
    {
        XRef::setConfigValue('lint.add-constant', array('foo', 'FooBar', 'BAZ'));
        $testPhpCode = '<?php

            class Foo {
                const BAZ = 10;
            }

            echo foo;       //ok
            echo FooBar;    // ok
            echo Foo::BAZ;  // ok
            echo BAZ;       // ok, global BAZ
            echo unknown;   // warning
        ';
        $exceptedDefects = array(array('unknown', 11, XRef::WARNING));
        $this->checkPhpCode($testPhpCode, $exceptedDefects);
        XRef::setConfigValue('lint.add-constant', array());
    }
Example #13
0
 public function resetPlugins()
 {
     $this->xref->resetPlugins();
     $this->xref->loadPluginGroup('lint');
     XRef::setConfigValue("lint.check-global-scope", true);
 }
Example #14
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;
 }
Example #15
0
 private function checkAccessError(XRef_IProjectDatabase $db, $class_name, $key, $name, $from_class, $is_static, $check_parent_only)
 {
     /** @var $lr XRef_LookupResult */
     $lr = null;
     switch ($key) {
         case 'property':
             $lr = $db->lookupProperty($class_name, $name, $check_parent_only);
             break;
         case 'method':
             $lr = $db->lookupMethod($class_name, $name, $check_parent_only);
             break;
         case 'constant':
             $lr = $db->lookupConstant($class_name, $name, $check_parent_only);
             break;
     }
     if (!$lr || $lr->code == XRef_LookupResult::NOT_FOUND) {
         // definition not found
         if ($key == 'property') {
             $lr_magic = $db->lookupMethod($class_name, '__get', $check_parent_only);
             if ($lr_magic->code == XRef_LookupResult::FOUND) {
                 // can't validate reference to (missing) property,
                 // because it or it's base class has method '__get'
                 return array(self::E_MAGIC_GETTER, $class_name, array($class_name));
             }
         }
         if ($key == 'method' && $name == '__construct') {
             // ok, php creates a default constructor
         } else {
             switch ($key) {
                 case 'method':
                     $error_code = self::E_ACCESS_TO_UNDEFINED_METHOD;
                     break;
                 case 'constant':
                     $error_code = self::E_ACCESS_TO_UNDEFINED_CONSTANT;
                     break;
                 case 'property':
                     $error_code = self::E_ACCESS_TO_UNDEFINED_PROPERTY;
                     break;
                 default:
                     throw new Exception($key);
             }
             $uniq = "{$from_class}/{$class_name}/{$key}/{$name}";
             return array($error_code, $uniq, array($name, $class_name));
         }
     } elseif ($lr->code == XRef_LookupResult::CLASS_MISSING) {
         // definition not found because definition of either class or its base class is missing
         $missing_class_name = $lr->missingClassName;
         if (!isset($this->ignore_missing_classes[strtolower($missing_class_name)])) {
             if (strtolower($missing_class_name) == strtolower($class_name)) {
                 // class definition is missing
                 return array(self::E_MISSING_CLASS, $class_name, array($class_name));
             } else {
                 // base class definition is missing
                 return array(self::E_MISSING_BASE_CLASS, $class_name, array($class_name, $missing_class_name));
             }
         }
     } else {
         // got definition, check access
         $attributes = $lr->elements[0]->attributes;
         $found_in_class = $lr->elements[0]->className;
         // 1. static vs. instance
         if ($key != 'constant' && !($key == 'method' && $name == '__construct')) {
             if ($is_static) {
                 if (!XRef::isStatic($attributes)) {
                     // reference to instance method or property as if they were static
                     if ($key == 'method' || $key == 'property') {
                         return array(self::E_ACCESS_INSTANCE_AS_STATIC, "{$from_class}/{$class_name}/{$key}/{$name}", array($name, $found_in_class));
                     } else {
                         throw new Exception($key);
                     }
                 }
             } else {
                 if ($key == 'property' && XRef::isStatic($attributes)) {
                     // reference to static property as if it were instance
                     return array(self::E_ACCESS_STATIC_AS_INSTANCE, "{$from_class}/{$class_name}/{$name}", array($name, $found_in_class));
                 }
             }
         }
         // 2. public, private, protected
         if (XRef::isPublic($attributes)) {
             // ok
         } elseif (XRef::isPrivate($attributes)) {
             if (!$from_class || strtolower($found_in_class) != strtolower($from_class)) {
                 // attempt to access a private member (method or property) of class $found_in_class
                 // from $class_name
                 return array(self::E_PRIVATE_MEMBER, "{$from_class}/{$class_name}/{$name}", array($name, $found_in_class));
             }
         } elseif (XRef::isProtected($attributes)) {
             if (!$from_class || !$this->isSubclassOf($from_class, $found_in_class)) {
                 return array(self::E_PROTECTED_MEMBER, "{$from_class}/{$class_name}/{$name}", array($name, $found_in_class));
             }
         } else {
             // shouldn't be here
             throw new Exception("Should be public? {$attributes}");
         }
         // 3. check that the called method is defined, not only declared.
         // however, allow to call declared methods from abstract classes and traits
         if ($key == 'method' && is_null($lr->elements[0]->bodyStarts)) {
             $lc = $db->lookupClass($from_class);
             if ($lc && $lc->code == XRef_LookupResult::FOUND && ($lc->elements[0]->isAbstract || $lc->elements[0]->kind == T_TRAIT)) {
                 // ok, allow to call abstract method
             } else {
                 $found_in_class = $lr->elements[0]->className;
                 return array(self::E_ACCESS_TO_UNDEFINED_METHOD, "{$from_class}/{$found_in_class}/{$name}", array($name, $found_in_class));
             }
         }
     }
     return;
 }
Example #16
0
 public function testConfigValue()
 {
     // reset and cache to empty (default) values
     list($options, $arguments) = XRef::getCmdOptions(array());
     // get config by value, save current settings
     $old_config_value = XRef::getConfig();
     $config = XRef::getConfig(true);
     $this->assertTrue(is_array($config));
     $this->assertTrue(!isset($config["foo.bar"]));
     $args = array("test.php", "-d", "foo.bar=true");
     list($options, $arguments) = XRef::getCmdOptions($args);
     $config = XRef::getConfig(true);
     $this->assertTrue(is_array($config));
     $this->assertTrue(isset($config["foo.bar"]));
     $this->assertTrue($config["foo.bar"]);
     $args = array("test.php", "-d", "f.b=true", "--define", "p=false", "-d", "x=y");
     list($options, $arguments) = XRef::getCmdOptions($args);
     $config = XRef::getConfig(true);
     $this->assertTrue(is_array($config));
     $this->assertTrue(!isset($config["foo.bar"]));
     $this->assertTrue(isset($config["f.b"]));
     $this->assertTrue(isset($config["p"]));
     $this->assertTrue(isset($config["x"]));
     $this->assertTrue($config["f.b"] === true);
     $this->assertTrue($config["p"] === false);
     $this->assertTrue($config["x"] === "y");
     // reset command-line options, restore value of the config
     list($options, $arguments) = XRef::getCmdOptions(array());
     $config_ref =& XRef::getConfig();
     $config_ref = $old_config_value;
 }
Example #17
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"));
     }
 }
Example #18
0
<?php

/**
 * lib/bin-scripts/xref-lint.php
 *
 * This is a lint (a tool to find potential bugs in source code) for PHP sources.
 * This is a web version
 *
 * @author Igor Gariev <*****@*****.**>
 * @copyright Copyright (c) 2013 Igor Gariev
 * @licence http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */
$includeDir = "@php_dir@" == "@" . "php_dir@" ? dirname(__FILE__) . "/.." : "@php_dir@/XRef";
require_once "{$includeDir}/XRef.class.php";
// web-server lint script
$xref = new XRef();
$xref->loadPluginGroup("lint");
$sourcePlugin = $xref->getPluginById("files");
$css = $sourcePlugin->getDefaultCSS();
$textareaContent = '// put source code here';
$report = null;
$exceptionMessage = null;
$formattedText = null;
if (isset($_REQUEST["source"]) && $_REQUEST["source"]) {
    $source = get_magic_quotes_gpc() ? stripslashes($_REQUEST["source"]) : $_REQUEST["source"];
    try {
        $parsed_file = $xref->getParsedFile("unknown.php", $source);
        if (count($parsed_file->getTokens()) > 1) {
            $lint_engine = XRef::getConfigValue("xref.project-check", true) ? new XRef_LintEngine_ProjectCheck($xref) : new XRef_LintEngine_Simple($xref);
            $lint_engine->addParsedFile($parsed_file);
            $report = $lint_engine->collectReport();
Example #19
0
 public function __construct()
 {
     $xref = new XRef();
     $xref->loadPluginGroup('lint');
     $this->xref = $xref;
 }
Example #20
0
    public function testCI()
    {
        $old_rev = "377d2edc1da549a80b5b44286e7dcaf59cee300a";
        $current_rev = "190fc6a9fddcae0313decbbbce92e4a83bf47ab9";
        XRef::setConfigValue('mail.reply-to', '*****@*****.**');
        XRef::setConfigValue('mail.from', '*****@*****.**');
        XRef::setConfigValue('mail.to', array('*****@*****.**', '{%ae}', '{%an}@xref-lint.net'));
        XRef::setConfigValue('project.name', 'unit-test');
        XRef::setConfigValue('project.source-url', 'https://github.com/gariev/xref/blob/{%revision}/{%fileName}#L{%lineNumber}');
        XRef::setConfigValue('xref.smarty-class', self::SMARTY_CLASS_PATH);
        XRef::setConfigValue('xref.data-dir', 'tmp');
        $scm = $this->xref->getSourceCodeManager();
        $file_provider_old = $scm->getFileProvider($old_rev);
        $file_provider_new = $scm->getFileProvider($current_rev);
        $modified_files = $scm->getListOfModifiedFiles($old_rev, $current_rev);
        $lint_engine = new XRef_LintEngine_ProjectCheck($this->xref, false);
        $errors = $lint_engine->getIncrementalReport($file_provider_old, $file_provider_new, $modified_files);
        list($recipients, $subject, $body, $headers) = $this->xref->getNotificationEmail($errors, 'tests-git', $old_rev, $current_rev);
        //print_r(array($recipients, $subject, $body, $headers));
        // assert that our comparison function works
        $this->assertTrue($this->strSmartSpaces("\t hello,\r \n world  ") == $this->strSmartSpaces("hello, world"));
        $this->assertTrue($this->strSmartSpaces("\t hello,\r \n world  ") != $this->strSmartSpaces("hello , world"));
        $this->assertTrue(count($recipients) == 3);
        $this->assertTrue($recipients[0] == '*****@*****.**');
        $this->assertTrue($recipients[1] == '*****@*****.**');
        $this->assertTrue($recipients[2] == '*****@*****.**');
        $this->assertTrue($this->strSmartSpaces($subject) == "XRef CI unit-test: tests-git/190fc6a");
        $expected_body = <<<END
            <html><body>
            Hi, you've got this e-mail as the author (gariev) of commit 190fc6a to branch tests-git.
            It looks like there are problems in file(s) modified since previous revision 377d2ed:
                <ul>
                    <li>broken.php</li>
                    <ul>
                        <li>
                            <span class='error'>error</span>
                            (<a href="https://github.com/gariev/xref/blob/master/README.md#xr010">xr010</a>): Use of unknown variable (\$error)
                             at <a href="https://github.com/gariev/xref/blob/190fc6a9fddcae0313decbbbce92e4a83bf47ab9/broken.php#L3">line 3</a>
                        </li>
                    </ul>
                </ul>
            <p>If the problems above are real, you can fix them.
            If these warnings are from code merged from other branch and not result of merge conflict, you can ignore them or find the author of the original commit.
            If the report is wrong, you can help <a href='mailto:gariev@hotmail.com?subject=xref'>improve XRef CI</a> itself and/or ignore this e-mail.
            </p>
            <p><small>
              Generated by XRef CI version <<VERSION>>. About XRef:
                <a href="https://github.com/gariev/xref/blob/master/README.md">documentation</a>,
                <a href="https://github.com/gariev/xref/">source code</a>,
                <a href="http://xref-lint.net/bin/xref-lint.php">online tool</a>.
            </small></p>
            </body></html>
END;
        $expected_body = str_replace('<<VERSION>>', XRef::version(), $expected_body);
        $this->assertTrue($this->strSmartSpaces($body) == $this->strSmartSpaces($expected_body));
        $expected_headers = <<<END
            MIME-Version: 1.0
            Content-type: text/html
            Reply-to: no-reply@xref-lint.net
            From: ci-server@xref-lint.net
END;
        $this->assertTrue($this->strSmartSpaces($headers) == $this->strSmartSpaces($expected_headers));
    }
Example #21
0
$includeDir = "@php_dir@" == "@" . "php_dir@" ? dirname(__FILE__) . "/.." : "@php_dir@/XRef";
require_once "{$includeDir}/XRef.class.php";
// command-line arguments
try {
    list($options, $arguments) = XRef::getCmdOptions();
} catch (Exception $e) {
    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);
Example #22
0
 */
$includeDir = "@php_dir@" == "@" . "php_dir@" ? dirname(__FILE__) . "/.." : "@php_dir@/XRef";
require_once "{$includeDir}/XRef.class.php";
try {
    list($options, $arguments) = XRef::getCmdOptions();
} catch (Exception $e) {
    error_log($e->getMessage());
    error_log("See 'xref-ci --help'");
    exit(1);
}
if (XRef::needHelp() || count($arguments)) {
    XRef::showHelpScreen("xref-ci - continuous integration server");
    exit(1);
}
$incremental = XRef::getConfigValue("ci.incremental", false);
$xref = new XRef();
$xref->loadPluginGroup("lint");
$scm = $xref->getSourceCodeManager();
$storage = $xref->getStorageManager();
$exclude_paths = XRef::getConfigValue("project.exclude-path", array());
//
// Normal run:
// Find modified files from the last run and find new errors in these files
//
if (!$storage->getLock("ci")) {
    error_log("Can't obtain lock - already running?");
    exit(0);
}
$db = $storage->restoreData("ci", "database");
if (!$db) {
    // initialize the database
Example #23
0
 /**
  * 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);
 }
Example #24
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;
 }
Example #25
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);
 }