Inheritance: implements Phan\Output\IgnoredFilesFilterInterface
Exemple #1
0
 /**
  * @param int $category
  * The category of error such as `Issue::CATEGORY_UNDEFINED`
  *
  * @param string $type
  * The error type such as `Issue::UndeclaredMethod`
  *
  * @param int $severity
  * The severity of the issue such as `Issue::SEVERITY_NORMAL`
  *
  * @param string $message
  * The error message
  *
  * @param string $file
  * The name of the file with the issue
  *
  * @param int $lineno
  * The line number where the issue occurs
  */
 public static function err(int $category, string $type, int $severity, string $message, string $file, int $lineno)
 {
     $log = self::getInstance();
     // Don't report anything for excluded files
     if (Phan::isExcludedAnalysisFile($file)) {
         return;
     }
     // Don't report anything below our minimum severity
     // threshold
     if ($severity < Config::get()->minimum_severity) {
         return;
     }
     if ($category & $log->output_mask) {
         // This needs to be a sortable key so that output
         // is in the expected order
         $ukey = implode('|', [$file, str_pad((string) $lineno, 5, '0', STR_PAD_LEFT), $type, $message]);
         $log->msgs[$ukey] = ['file' => $file, 'lineno' => $lineno, 'category' => $category, 'type' => $type, 'severity' => $severity, 'message' => $message];
     }
 }
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzePropertyTypes(CodeBase $code_base, Clazz $clazz)
 {
     foreach ($clazz->getPropertyList($code_base) as $property) {
         try {
             $union_type = $property->getUnionType();
         } catch (IssueException $exception) {
             Phan::getIssueCollector()->collectIssue($exception->getIssueInstance());
             continue;
         }
         // Look at each type in the parameter's Union Type
         foreach ($union_type->getTypeSet() as $type) {
             // If its a native type or a reference to
             // self, its OK
             if ($type->isNativeType() || $type->isSelfType()) {
                 continue;
             }
             // Otherwise, make sure the class exists
             $type_fqsen = $type->asFQSEN();
             if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
                 Issue::emit(Issue::UndeclaredTypeProperty, $property->getFileRef()->getFile(), $property->getFileRef()->getLineNumberStart(), (string) $type_fqsen);
             }
         }
     }
 }
Exemple #3
0
 /**
  * This reads all files in `tests/files/src`, runs
  * the analyzer on each and compares the output
  * to the files's counterpart in
  * `tests/files/expected`
  *
  * @param string[] $test_file_list
  * @param string $expected_file_path
  * @dataProvider getTestFiles
  */
 public function testFiles($test_file_list, $expected_file_path)
 {
     $expected_output = '';
     if (is_file($expected_file_path)) {
         // Read the expected output
         $expected_output = trim(file_get_contents($expected_file_path));
     }
     $stream = new BufferedOutput();
     $printer = new PlainTextPrinter();
     $printer->configureOutput($stream);
     Phan::setPrinter($printer);
     Phan::setIssueCollector(new BufferingCollector());
     Phan::analyzeFileList($this->code_base, $test_file_list);
     $output = $stream->fetch();
     // Uncomment to save the output back to the expected
     // output. This should be done for error message
     // text changes and only if you promise to be careful.
     /*
     $saved_output = $output;
     $test_file_elements= explode('/', $test_file_list[0]);
     $test_file_name = array_pop($test_file_elements);
     $saved_output = preg_replace('/[^ :\n]*\/' . $test_file_name . '/', '%s', $saved_output);
     $saved_output = preg_replace('/closure_[^\(]*\(/', 'closure_%s(', $saved_output);
     if (!empty($saved_output) && strlen($saved_output) > 0) {
         $saved_output .= "\n";
     }
     file_put_contents($expected_file_path, $saved_output);
     $expected_output =
         trim(file_get_contents($expected_file_path));
     */
     $wanted_re = preg_replace('/\\r\\n/', "\n", $expected_output);
     // do preg_quote, but miss out any %r delimited sections
     $temp = "";
     $r = "%r";
     $startOffset = 0;
     $length = strlen($wanted_re);
     while ($startOffset < $length) {
         $start = strpos($wanted_re, $r, $startOffset);
         if ($start !== false) {
             // we have found a start tag
             $end = strpos($wanted_re, $r, $start + 2);
             if ($end === false) {
                 // unbalanced tag, ignore it.
                 $end = $start = $length;
             }
         } else {
             // no more %r sections
             $start = $end = $length;
         }
         // quote a non re portion of the string
         $temp = $temp . preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/');
         // add the re unquoted.
         if ($end > $start) {
             $temp = $temp . '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')';
         }
         $startOffset = $end + 2;
     }
     $wanted_re = $temp;
     $wanted_re = str_replace(['%binary_string_optional%'], 'string', $wanted_re);
     $wanted_re = str_replace(['%unicode_string_optional%'], 'string', $wanted_re);
     $wanted_re = str_replace(['%unicode\\|string%', '%string\\|unicode%'], 'string', $wanted_re);
     $wanted_re = str_replace(['%u\\|b%', '%b\\|u%'], '', $wanted_re);
     // Stick to basics
     $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
     $wanted_re = str_replace('%s', '[^\\r\\n]+', $wanted_re);
     $wanted_re = str_replace('%S', '[^\\r\\n]*', $wanted_re);
     $wanted_re = str_replace('%a', '.+', $wanted_re);
     $wanted_re = str_replace('%A', '.*', $wanted_re);
     $wanted_re = str_replace('%w', '\\s*', $wanted_re);
     $wanted_re = str_replace('%i', '[+-]?\\d+', $wanted_re);
     $wanted_re = str_replace('%d', '\\d+', $wanted_re);
     $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
     $wanted_re = str_replace('%f', '[+-]?\\.?\\d+\\.?\\d*(?:[Ee][+-]?\\d+)?', $wanted_re);
     $wanted_re = str_replace('%c', '.', $wanted_re);
     // %f allows two points "-.0.0" but that is the best *simple* expression
     $this->assertRegExp("/^{$wanted_re}\$/", $output, "Unexpected output in {$test_file_list[0]}");
 }
 /**
  * Visit a node with kind `\ast\AST_PROP`
  *
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return UnionType
  * The set of types that are possibly produced by the
  * given node
  */
 public function visitProp(Node $node) : UnionType
 {
     try {
         $property = (new ContextNode($this->code_base, $this->context, $node))->getProperty($node->children['prop']);
         return $property->getUnionType();
     } catch (IssueException $exception) {
         Phan::getIssueCollector()->collectIssue($exception->getIssueInstance());
     } catch (CodeBaseException $exception) {
         $property_name = $node->children['prop'];
         Issue::emit(Issue::UndeclaredProperty, $this->context->getFile(), $node->lineno ?? 0, "{$exception->getFQSEN()}->{$property_name}");
     } catch (UnanalyzableException $exception) {
         // Swallow it. There are some constructs that we
         // just can't figure out.
     } catch (NodeException $exception) {
         // Swallow it. There are some constructs that we
         // just can't figure out.
     }
     return new UnionType();
 }
 /**
  * This reads all files in `tests/files/src`, runs
  * the analyzer on each and compares the output
  * to the files's counterpart in
  * `tests/files/expected`
  *
  * @param string $test_file_path
  * @param string $expected_file_path
  * @dataProvider getTestFiles
  */
 public function testFiles($test_file_path, $expected_file_path)
 {
     $expected_output = '';
     if (is_file($expected_file_path)) {
         // Read the expected output
         $expected_output = trim(file_get_contents($expected_file_path));
     }
     // Start reading everything sent to STDOUT
     // and compare it to the expected value once
     // the analzyer finishes running
     ob_start();
     try {
         // Run the analyzer
         Phan::analyzeFileList($this->codeBase, [$test_file_path]);
     } catch (\Exception $exception) {
         // TODO: inexplicably bad things happen here
         // print "\n" . $exception->getMessage() . "\n";
     }
     $output = trim(ob_get_clean());
     // Uncomment to save the output back to the expected
     // output. This should be done for error message
     // text changes and only if you promise to be careful.
     /*
     $saved_output = $output;
     $saved_output = preg_replace('/[^ :\n]*\/'.$test_file_name.'/', '%s', $saved_output);
     $saved_output = preg_replace('/closure_[^\(]*\(/', 'closure_%s(', $saved_output);
     if (!empty($saved_output) && strlen($saved_output) > 0) {
         $saved_output .= "\n";
     }
     file_put_contents($expected_file_path, $saved_output);
     $expected_output =
         trim(file_get_contents($expected_file_path));
     */
     $wanted_re = preg_replace('/\\r\\n/', "\n", $expected_output);
     // do preg_quote, but miss out any %r delimited sections
     $temp = "";
     $r = "%r";
     $startOffset = 0;
     $length = strlen($wanted_re);
     while ($startOffset < $length) {
         $start = strpos($wanted_re, $r, $startOffset);
         if ($start !== false) {
             // we have found a start tag
             $end = strpos($wanted_re, $r, $start + 2);
             if ($end === false) {
                 // unbalanced tag, ignore it.
                 $end = $start = $length;
             }
         } else {
             // no more %r sections
             $start = $end = $length;
         }
         // quote a non re portion of the string
         $temp = $temp . preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/');
         // add the re unquoted.
         if ($end > $start) {
             $temp = $temp . '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')';
         }
         $startOffset = $end + 2;
     }
     $wanted_re = $temp;
     $wanted_re = str_replace(['%binary_string_optional%'], 'string', $wanted_re);
     $wanted_re = str_replace(['%unicode_string_optional%'], 'string', $wanted_re);
     $wanted_re = str_replace(['%unicode\\|string%', '%string\\|unicode%'], 'string', $wanted_re);
     $wanted_re = str_replace(['%u\\|b%', '%b\\|u%'], '', $wanted_re);
     // Stick to basics
     $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
     $wanted_re = str_replace('%s', '[^\\r\\n]+', $wanted_re);
     $wanted_re = str_replace('%S', '[^\\r\\n]*', $wanted_re);
     $wanted_re = str_replace('%a', '.+', $wanted_re);
     $wanted_re = str_replace('%A', '.*', $wanted_re);
     $wanted_re = str_replace('%w', '\\s*', $wanted_re);
     $wanted_re = str_replace('%i', '[+-]?\\d+', $wanted_re);
     $wanted_re = str_replace('%d', '\\d+', $wanted_re);
     $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
     $wanted_re = str_replace('%f', '[+-]?\\.?\\d+\\.?\\d*(?:[Ee][+-]?\\d+)?', $wanted_re);
     $wanted_re = str_replace('%c', '.', $wanted_re);
     // %f allows two points "-.0.0" but that is the best *simple* expression
     $this->assertRegExp("/^{$wanted_re}\$/", $output, "Unexpected output in {$test_file_path}");
 }
Exemple #6
0
declare (strict_types=1);
// Phan does a ton of GC and this offers a major speed
// improvment if your system can handle it (which it
// should be able to)
gc_disable();
// Check the environment to make sure Phan can run successfully
require_once __DIR__ . '/requirements.php';
// Build a code base based on PHP internally defined
// functions, methods and classes before loading our
// own
$code_base = (require_once __DIR__ . '/codebase.php');
require_once __DIR__ . '/Phan/Bootstrap.php';
use Phan\CLI;
use Phan\CodeBase;
use Phan\Config;
use Phan\Phan;
// Create our CLI interface and load arguments
$cli = new CLI();
$file_list = $cli->getFileList();
// If requested, expand the file list to a set of
// all files that should be re-analyzed
if (Config::get()->expand_file_list) {
    assert((bool) Config::get()->stored_state_file_path, 'Requesting an expanded dependency list can only ' . ' be done if a state-file is defined');
    // Analyze the file list provided via the CLI
    $file_list = Phan::expandedFileList($code_base, $file_list);
}
// Analyze the file list provided via the CLI
$is_issue_found = Phan::analyzeFileList($code_base, $file_list);
// Provide an exit status code based on if
// issues were found
exit($is_issue_found ? EXIT_ISSUES_FOUND : EXIT_SUCCESS);
Exemple #7
0
 /**
  * @param IssueInstance $issue_instance
  * An issue instance to emit
  *
  * @return void
  */
 public static function emitInstance(IssueInstance $issue_instance)
 {
     Phan::getIssueCollector()->collectIssue($issue_instance);
 }
Exemple #8
0
 /**
  * Create and read command line arguments, configuring
  * \Phan\Config as a side effect.
  */
 public function __construct()
 {
     global $argv;
     // Parse command line args
     // still available: g,n,t,u,v,w
     $opts = getopt("f:m:o:c:k:aeqbr:pid:s:3:y:l:xj:zh::", ['backward-compatibility-checks', 'dead-code-detection', 'directory:', 'dump-ast', 'dump-signatures-file:', 'exclude-directory-list:', 'exclude-file:', 'file-list-only:', 'file-list:', 'help', 'ignore-undeclared', 'minimum-severity:', 'output-mode:', 'output:', 'parent-constructor-required:', 'progress-bar', 'project-root-directory:', 'quick', 'state-file:', 'processes:', 'config-file:', 'signature-compatibility', 'markdown-issue-messages']);
     // Determine the root directory of the project from which
     // we root all relative paths passed in as args
     Config::get()->setProjectRootDirectory($opts['d'] ?? $opts['project-root-directory'] ?? getcwd());
     // Before reading the config, check for an override on
     // the location of the config file path.
     if (isset($opts['k'])) {
         $this->config_file = $opts['k'];
     } else {
         if (isset($opts['config-file'])) {
             $this->config_file = $opts['config-file'];
         }
     }
     // Now that we have a root directory, attempt to read a
     // configuration file `.phan/config.php` if it exists
     $this->maybeReadConfigFile();
     $this->output = new ConsoleOutput();
     $factory = new PrinterFactory();
     $printer_type = 'text';
     $minimum_severity = Config::get()->minimum_severity;
     $mask = -1;
     foreach ($opts ?? [] as $key => $value) {
         switch ($key) {
             case 'h':
             case 'help':
                 $this->usage();
                 break;
             case 'r':
             case 'file-list-only':
                 // Mark it so that we don't load files through
                 // other mechanisms.
                 $this->file_list_only = true;
                 // Empty out the file list
                 $this->file_list = [];
                 // Intentionally fall through to load the
                 // file list
             // Intentionally fall through to load the
             // file list
             case 'f':
             case 'file-list':
                 $file_list = is_array($value) ? $value : [$value];
                 foreach ($file_list as $file_name) {
                     $file_path = Config::projectPath($file_name);
                     if (is_file($file_path) && is_readable($file_path)) {
                         $this->file_list = array_merge($this->file_list, file(Config::projectPath($file_name), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
                     } else {
                         error_log("Unable to read file {$file_path}");
                     }
                 }
                 break;
             case 'l':
             case 'directory':
                 if (!$this->file_list_only) {
                     $directory_list = is_array($value) ? $value : [$value];
                     foreach ($directory_list as $directory_name) {
                         $this->file_list = array_merge($this->file_list, $this->directoryNameToFileList($directory_name));
                     }
                 }
                 break;
             case 'k':
             case 'config-file':
                 break;
             case 'm':
             case 'output-mode':
                 if (!in_array($value, $factory->getTypes(), true)) {
                     $this->usage(sprintf('Unknown output mode "%s". Known values are [%s]', $value, implode(',', $factory->getTypes())));
                 }
                 $printer_type = $value;
                 break;
             case 'c':
             case 'parent-constructor-required':
                 Config::get()->parent_constructor_required = explode(',', $value);
                 break;
             case 'q':
             case 'quick':
                 Config::get()->quick_mode = true;
                 break;
             case 'b':
             case 'backward-compatibility-checks':
                 Config::get()->backward_compatibility_checks = true;
                 break;
             case 'p':
             case 'progress-bar':
                 Config::get()->progress_bar = true;
                 break;
             case 'a':
             case 'dump-ast':
                 Config::get()->dump_ast = true;
                 break;
             case 'dump-signatures-file':
                 Config::get()->dump_signatures_file = $value;
                 break;
             case 'o':
             case 'output':
                 $this->output = new StreamOutput(fopen($value, 'w'));
                 break;
             case 'i':
             case 'ignore-undeclared':
                 $mask ^= Issue::CATEGORY_UNDEFINED;
                 break;
             case '3':
             case 'exclude-directory-list':
                 Config::get()->exclude_analysis_directory_list = explode(',', $value);
                 break;
             case 'exclude-file':
                 Config::get()->exclude_file_list = array_merge(Config::get()->exclude_file_list, is_array($value) ? $value : [$value]);
                 break;
             case 's':
             case 'state-file':
                 // TODO: re-enable eventually
                 // Config::get()->stored_state_file_path = $value;
                 break;
             case 'j':
             case 'processes':
                 Config::get()->processes = (int) $value;
                 break;
             case 'z':
             case 'signature-compatibility':
                 Config::get()->analyze_signature_compatibility = (bool) $value;
                 break;
             case 'y':
             case 'minimum-severity':
                 $minimum_severity = (int) $value;
                 break;
             case 'd':
             case 'project-root-directory':
                 // We handle this flag before parsing options so
                 // that we can get the project root directory to
                 // base other config flags values on
                 break;
             case 'x':
             case 'dead-code-detection':
                 Config::get()->dead_code_detection = true;
                 break;
             case 'markdown-issue-messages':
                 Config::get()->markdown_issue_messages = true;
                 break;
             default:
                 $this->usage("Unknown option '-{$key}'");
                 break;
         }
     }
     $printer = $factory->getPrinter($printer_type, $this->output);
     $filter = new ChainedIssueFilter([new FileIssueFilter(new Phan()), new MinimumSeverityFilter($minimum_severity), new CategoryIssueFilter($mask)]);
     $collector = new BufferingCollector($filter);
     Phan::setPrinter($printer);
     Phan::setIssueCollector($collector);
     $pruneargv = array();
     foreach ($opts ?? [] as $opt => $value) {
         foreach ($argv as $key => $chunk) {
             $regex = '/^' . (isset($opt[1]) ? '--' : '-') . $opt . '/';
             if (($chunk == $value || is_array($value) && in_array($chunk, $value)) && $argv[$key - 1][0] == '-' || preg_match($regex, $chunk)) {
                 array_push($pruneargv, $key);
             }
         }
     }
     while ($key = array_pop($pruneargv)) {
         unset($argv[$key]);
     }
     foreach ($argv as $arg) {
         if ($arg[0] == '-') {
             $this->usage("Unknown option '{$arg}'");
         }
     }
     if (!$this->file_list_only) {
         // Merge in any remaining args on the CLI
         $this->file_list = array_merge($this->file_list, array_slice($argv, 1));
         // Merge in any files given in the config
         $this->file_list = array_merge($this->file_list, Config::get()->file_list);
         // Merge in any directories given in the config
         foreach (Config::get()->directory_list as $directory_name) {
             $this->file_list = array_merge($this->file_list, $this->directoryNameToFileList($directory_name));
         }
         // Don't scan anything twice
         $this->file_list = array_unique($this->file_list);
     }
     // Exclude any files that should be excluded from
     // parsing and analysis (not read at all)
     if (count(Config::get()->exclude_file_list) > 0) {
         $exclude_file_set = [];
         foreach (Config::get()->exclude_file_list as $file) {
             $exclude_file_set[$file] = true;
         }
         $this->file_list = array_filter($this->file_list, function (string $file) use($exclude_file_set) : bool {
             return empty($exclude_file_set[$file]);
         });
     }
     // We can't run dead code detection on multiple cores because
     // we need to update reference lists in a globally accessible
     // way during analysis. With our parallelization mechanism, there
     // is no shared state between processes, making it impossible to
     // have a complete set of reference lists.
     assert(Config::get()->processes === 1 || !Config::get()->dead_code_detection, "We cannot run dead code detection on more than one core.");
 }
Exemple #9
0
 /**
  * @param int $etype
  * The error type such as Log::EUNDEF.
  *
  * @param string $msg
  * The error message
  *
  * @param string $file
  * The name of the file with the issue
  *
  * @param int|null $lineno
  * The line number where the issue occurs
  */
 public static function err(int $etype, string $msg, string $file = null, $lineno = 0)
 {
     $log = self::getInstance();
     $lineno = (int) $lineno;
     if ($etype == self::EFATAL) {
         self::display();
         // Something went wrong - abort
         if ($file) {
             throw new \Exception("{$file}:{$lineno} {$msg}");
         } else {
             throw new \Exception($msg);
         }
     }
     // Don't report anything for excluded files
     if (Phan::isExcludedAnalysisFile($file)) {
         return;
     }
     // If configured to do so, prepend the message
     // with a trace ID which indicates where the issue
     // came from allowing us to group on unique classes
     // of issues
     if (Config::get()->emit_trace_id) {
         $msg = self::traceId(debug_backtrace()[1]) . ' ' . $msg;
     }
     if ($etype & $log->output_mask) {
         $ukey = md5($file . $lineno . $etype . $msg);
         $log->msgs[$ukey] = ['file' => $file, 'lineno' => $lineno, 'etype' => $etype, 'msg' => $msg];
     }
 }
Exemple #10
0
<?php

declare (strict_types=1);
// Check the environment to make sure Phan can run successfully
require_once __DIR__ . '/requirements.php';
// Build a code base based on PHP internally defined
// functions, methods and classes before loading our
// own
$code_base = (require_once __DIR__ . '/codebase.php');
require_once __DIR__ . '/Phan/Bootstrap.php';
use Phan\CLI;
use Phan\CodeBase;
use Phan\Config;
use Phan\Phan;
// Create our CLI interface and load arguments
$cli = new CLI();
$file_list = $cli->getFileList();
// If requested, expand the file list to a set of
// all files that should be re-analyzed
if (Config::get()->expand_file_list) {
    assert((bool) Config::get()->stored_state_file_path, 'Requesting an expanded dependency list can only ' . ' be done if a state-file is defined');
    // Analyze the file list provided via the CLI
    $file_list = Phan::expandedFileList($code_base, $file_list);
}
// Analyze the file list provided via the CLI
Phan::analyzeFileList($code_base, $file_list);
Exemple #11
0
<?php

declare (strict_types=1);
assert(extension_loaded('ast'), "The php-ast extension must be loaded in order for Phan to work. See https://github.com/etsy/phan#getting-it-running for more details.");
assert((int) phpversion()[0] >= 7, "Phan requires PHP version 7 or greater. See https://github.com/etsy/phan#getting-it-running for more details.");
// Grab these before we define our own classes
$internal_class_name_list = get_declared_classes();
$internal_interface_name_list = get_declared_interfaces();
$internal_trait_name_list = get_declared_traits();
$internal_function_name_list = get_defined_functions()['internal'];
require_once __DIR__ . '/Phan/Bootstrap.php';
use Phan\CLI;
use Phan\CodeBase;
use Phan\Config;
use Phan\Phan;
// Create our CLI interface and load arguments
$cli = new CLI();
$code_base = new CodeBase($internal_class_name_list, $internal_interface_name_list, $internal_trait_name_list, $internal_function_name_list);
// If requested, expand the file list to a set of
// all files that should be re-analyzed
if (Config::get()->expanded_dependency_list) {
    assert((bool) Config::get()->stored_state_file_path, 'Requesting an expanded dependency list can only ' . ' be done if a state-file is defined');
    // Analyze the file list provided via the CLI
    $dependency_file_list = Phan::dependencyFileList($code_base, $cli->getFileList());
    // Emit the expanded file list
    print implode("\n", $dependency_file_list) . "\n";
    exit(1);
}
// Analyze the file list provided via the CLI
Phan::analyzeFileList($code_base, $cli->getFileList());
Exemple #12
0
 /**
  * Create and read command line arguments, configuring
  * \Phan\Config as a side effect.
  */
 public function __construct()
 {
     global $argv;
     // file_put_contents('/tmp/file', implode("\n", $argv));
     // Parse command line args
     // still available: g,j,k,n,t,u,v,w,z
     $opts = getopt("f:m:o:c:aeqbr:pid:s:3:y:l:xh::", ['backward-compatibility-checks', 'dead-code-detection', 'directory:', 'dump-ast', 'exclude-directory-list:', 'expand-file-list', 'file-list-only:', 'file-list:', 'help', 'ignore-undeclared', 'minimum-severity:', 'output-mode:', 'output:', 'parent-constructor-required:', 'progress-bar', 'project-root-directory:', 'quick', 'state-file:']);
     // Determine the root directory of the project from which
     // we root all relative paths passed in as args
     Config::get()->setProjectRootDirectory($opts['d'] ?? getcwd());
     // Now that we have a root directory, attempt to read a
     // configuration file `.phan/config.php` if it exists
     $this->maybeReadConfigFile();
     $this->output = new ConsoleOutput();
     $factory = new PrinterFactory();
     $printer_type = 'text';
     $minimum_severity = Config::get()->minimum_severity;
     $mask = -1;
     foreach ($opts ?? [] as $key => $value) {
         switch ($key) {
             case 'h':
             case 'help':
                 $this->usage();
                 break;
             case 'r':
             case 'file-list-only':
                 // Mark it so that we don't load files through
                 // other mechanisms.
                 $this->file_list_only = true;
                 // Empty out the file list
                 $this->file_list = [];
                 // Intentionally fall through to load the
                 // file list
             // Intentionally fall through to load the
             // file list
             case 'f':
             case 'file-list':
                 $file_list = is_array($value) ? $value : [$value];
                 foreach ($file_list as $file_name) {
                     if (is_file($file_name) && is_readable($file_name)) {
                         $this->file_list = array_merge($this->file_list, file($file_name, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
                     } else {
                         error_log("Unable to read file {$file_name}");
                     }
                 }
                 break;
             case 'l':
             case 'directory':
                 if (!$this->file_list_only) {
                     $directory_list = is_array($value) ? $value : [$value];
                     foreach ($directory_list as $directory_name) {
                         $this->file_list = array_merge($this->file_list, $this->directoryNameToFileList($directory_name));
                     }
                 }
                 break;
             case 'm':
             case 'output-mode':
                 if (!in_array($value, $factory->getTypes(), true)) {
                     $this->usage(sprintf('Unknown output mode "%s". Known values are [%s]', $value, implode(',', $factory->getTypes())));
                 }
                 $printer_type = $value;
                 break;
             case 'c':
             case 'parent-constructor-required':
                 Config::get()->parent_constructor_required = explode(',', $value);
                 break;
             case 'q':
             case 'quick':
                 Config::get()->quick_mode = true;
                 break;
             case 'b':
             case 'backward-compatibility-checks':
                 Config::get()->backward_compatibility_checks = true;
                 break;
             case 'p':
             case 'progress-bar':
                 Config::get()->progress_bar = true;
                 break;
             case 'a':
             case 'dump-ast':
                 Config::get()->dump_ast = true;
                 break;
             case 'e':
             case 'expand-file-list':
                 Config::get()->expand_file_list = true;
                 break;
             case 'o':
             case 'output':
                 $this->output = new StreamOutput(fopen($value, 'w'));
                 break;
             case 'i':
             case 'ignore-undeclared':
                 $mask ^= Issue::CATEGORY_UNDEFINED;
                 break;
             case '3':
             case 'exclude-directory-list':
                 Config::get()->exclude_analysis_directory_list = explode(',', $value);
                 break;
             case 's':
             case 'state-file':
                 Config::get()->stored_state_file_path = $value;
                 break;
             case 'y':
             case 'minimum-severity':
                 $minimum_severity = (int) $value;
                 break;
             case 'd':
             case 'project-root-directory':
                 // We handle this flag before parsing options so
                 // that we can get the project root directory to
                 // base other config flags values on
                 break;
             case 'x':
             case 'dead-code-detection':
                 Config::get()->dead_code_detection = true;
                 break;
             default:
                 $this->usage("Unknown option '-{$key}'");
                 break;
         }
     }
     $printer = $factory->getPrinter($printer_type, $this->output);
     $filter = new ChainedIssueFilter([new FileIssueFilter(new Phan()), new MinimumSeverityFilter($minimum_severity), new CategoryIssueFilter($mask)]);
     $collector = new BufferingCollector($filter);
     Phan::setPrinter($printer);
     Phan::setIssueCollector($collector);
     $pruneargv = array();
     foreach ($opts ?? [] as $opt => $value) {
         foreach ($argv as $key => $chunk) {
             $regex = '/^' . (isset($opt[1]) ? '--' : '-') . $opt . '/';
             if (($chunk == $value || is_array($value) && in_array($chunk, $value)) && $argv[$key - 1][0] == '-' || preg_match($regex, $chunk)) {
                 array_push($pruneargv, $key);
             }
         }
     }
     while ($key = array_pop($pruneargv)) {
         unset($argv[$key]);
     }
     foreach ($argv as $arg) {
         if ($arg[0] == '-') {
             $this->usage("Unknown option '{$arg}'");
         }
     }
     if (!$this->file_list_only) {
         // Merge in any remaining args on the CLI
         $this->file_list = array_merge($this->file_list, array_slice($argv, 1));
         // Merge in any files given in the config
         $this->file_list = array_merge($this->file_list, Config::get()->file_list);
         // Merge in any directories given in the config
         foreach (Config::get()->directory_list as $directory_name) {
             $this->file_list = array_merge($this->file_list, $this->directoryNameToFileList($directory_name));
         }
     }
 }
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitProp(Node $node) : Context
 {
     $property_name = $node->children['prop'];
     // Things like $foo->$bar
     if (!is_string($property_name)) {
         return $this->context;
     }
     assert(is_string($property_name), "Property must be string in context {$this->context}");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList();
     } catch (CodeBaseException $exception) {
         // This really shouldn't happen since the code
         // parsed cleanly. This should fatal.
         // throw $exception;
         return $this->context;
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a class
         // this is, don't worry about it
         return $this->context;
     }
     foreach ($class_list as $clazz) {
         // Check to see if this class has the property or
         // a setter
         if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
             if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
                 continue;
             }
         }
         try {
             $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
         } catch (IssueException $exception) {
             Phan::getIssueCollector()->collectIssue($exception->getIssueInstance());
             return $this->context;
         }
         if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
             Issue::emit(Issue::TypeMismatchProperty, $this->context->getFile(), $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
             return $this->context;
         }
         // After having checked it, add this type to it
         $property->getUnionType()->addUnionType($this->right_type);
         return $this->context;
     }
     if (Config::get()->allow_missing_properties) {
         try {
             // Create the property
             (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
         } catch (\Exception $exception) {
             // swallow it
         }
     } elseif (!empty($class_list)) {
         Issue::emit(Issue::UndeclaredProperty, $this->context->getFile(), $node->lineno ?? 0, $property_name);
     } else {
         // If we hit this part, we couldn't figure out
         // the class, so we ignore the issue
     }
     return $this->context;
 }
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitMethodCall(Node $node) : Context
 {
     $method_name = $node->children['method'];
     if (!is_string($method_name)) {
         return $this->context;
     }
     try {
         $method = (new ContextNode($this->code_base, $this->context, $node))->getMethod($method_name, false);
     } catch (IssueException $exception) {
         Phan::getIssueCollector()->collectIssue($exception->getIssueInstance());
         return $this->context;
     } catch (NodeException $exception) {
         // If we can't figure out the class for this method
         // call, cry YOLO and mark every method with that
         // name with a reference.
         if (Config::get()->dead_code_detection && Config::get()->dead_code_detection_prefer_false_negative) {
             foreach ($this->code_base->getMethodListByName($method_name) as $method) {
                 $method->addReference($this->context);
             }
         }
         // Swallow it
         return $this->context;
     }
     // Check the call for paraemter and argument types
     $this->analyzeCallToMethod($this->code_base, $method, $node);
     return $this->context;
 }
Exemple #15
0
 /**
  * Emit a log message if the type of the given
  * node cannot be cast to the given type
  *
  * @param Node|null|string|int $node
  * A node or whatever php-ast feels like returning
  *
  * @return bool
  * True if the cast is possible, else false
  */
 private static function analyzeNodeUnionTypeCast($node, Context $context, CodeBase $code_base, UnionType $cast_type, \Closure $issue_instance) : bool
 {
     // Get the type of the node
     $node_type = UnionType::fromNode($context, $code_base, $node);
     // See if it can be cast to the given type
     $can_cast = $node_type->canCastToUnionType($cast_type);
     // If it can't, emit the log message
     if (!$can_cast) {
         Phan::getIssueCollector()->collectIssue($issue_instance($node_type));
     }
     return $can_cast;
 }