Exemplo n.º 1
0
 /**
  * Get a Context after parsing the given
  * bit of code.
  */
 private function contextForCode(string $code_stub) : Context
 {
     return Analysis::parseNodeInContext($this->code_base, new Context(), \ast\parse_code('<?php ' . $code_stub, Config::get()->ast_version));
 }
Exemplo n.º 2
0
 /**
  * Analyze the given set of files and emit any issues
  * found to STDOUT.
  *
  * @param CodeBase $code_base
  * A code base needs to be passed in because we require
  * it to be initialized before any classes or files are
  * loaded.
  *
  * @param string[] $file_path_list
  * A list of files to scan
  *
  * @return null
  * We emit messages to STDOUT. Nothing is returned.
  *
  * @see \Phan\CodeBase
  */
 public static function analyzeFileList(CodeBase $code_base, array $file_path_list)
 {
     $file_count = count($file_path_list);
     // We'll construct a set of files that we'll
     // want to run an analysis on
     $analyze_file_path_list = [];
     // This first pass parses code and populates the
     // global state we'll need for doing a second
     // analysis after.
     foreach ($file_path_list as $i => $file_path) {
         CLI::progress('parse', ($i + 1) / $file_count);
         // Kick out anything we read from the former version
         // of this file
         $code_base->flushDependenciesForFile($file_path);
         // If the file is gone, no need to continue
         if (!file_exists($file_path)) {
             continue;
         }
         try {
             // Parse the file
             Analysis::parseFile($code_base, $file_path);
             // Update the timestamp on when it was last
             // parsed
             $code_base->setParseUpToDateForFile($file_path);
             // Save this to the set of files to analyze
             $analyze_file_path_list[] = $file_path;
         } catch (\Throwable $throwable) {
             error_log($file_path . ' ' . $throwable->getMessage() . "\n");
         }
     }
     // Don't continue on to analysis if the user has
     // chosen to just dump the AST
     if (Config::get()->dump_ast) {
         exit;
     }
     // Take a pass over all classes verifying various
     // states now that we have the whole state in
     // memory
     Analysis::analyzeClasses($code_base);
     // Take a pass over all functions verifying
     // various states now that we have the whole
     // state in memory
     Analysis::analyzeFunctions($code_base);
     // We can only save classes, methods, properties and
     // constants after we've merged parent classes in.
     $code_base->store();
     // Once we know what the universe looks like we
     // can scan for more complicated issues.
     $file_count = count($analyze_file_path_list);
     foreach ($analyze_file_path_list as $i => $file_path) {
         CLI::progress('analyze', ($i + 1) / $file_count);
         // We skip anything defined as 3rd party code
         // to save a lil' time
         if (self::isExcludedAnalysisFile($file_path)) {
             continue;
         }
         // Analyze the file
         Analysis::analyzeFile($code_base, $file_path);
     }
     // Scan through all globally accessible elements
     // in the code base and emit errors for dead
     // code.
     Analysis::analyzeDeadCode($code_base);
     // Emit all log messages
     self::display();
 }
Exemplo n.º 3
0
Arquivo: Phan.php Projeto: tpunt/phan
 /**
  * Analyze the given set of files and emit any issues
  * found to STDOUT.
  *
  * @param CodeBase $code_base
  * A code base needs to be passed in because we require
  * it to be initialized before any classes or files are
  * loaded.
  *
  * @param string[] $file_path_list
  * A list of files to scan
  *
  * @return bool
  * We emit messages to the configured printer and return
  * true if issues were found.
  *
  * @see \Phan\CodeBase
  */
 public static function analyzeFileList(CodeBase $code_base, array $file_path_list) : bool
 {
     $file_count = count($file_path_list);
     // We'll construct a set of files that we'll
     // want to run an analysis on
     $analyze_file_path_list = [];
     // This first pass parses code and populates the
     // global state we'll need for doing a second
     // analysis after.
     foreach ($file_path_list as $i => $file_path) {
         CLI::progress('parse', ($i + 1) / $file_count);
         // Kick out anything we read from the former version
         // of this file
         $code_base->flushDependenciesForFile($file_path);
         // If the file is gone, no need to continue
         if (($real = realpath($file_path)) === false || !file_exists($real)) {
             continue;
         }
         try {
             // Parse the file
             Analysis::parseFile($code_base, $file_path);
             // Save this to the set of files to analyze
             $analyze_file_path_list[] = $file_path;
         } catch (\Throwable $throwable) {
             error_log($file_path . ' ' . $throwable->getMessage() . "\n");
         }
     }
     // Don't continue on to analysis if the user has
     // chosen to just dump the AST
     if (Config::get()->dump_ast) {
         exit(EXIT_SUCCESS);
     }
     // With parsing complete, we need to tell the code base to
     // start hydrating any requested elements on their way out.
     // Hydration expands class types, imports parent methods,
     // properties, etc., and does stuff like that.
     //
     // This is an optimization that saves us a significant
     // amount of time on very large code bases. Instead of
     // hydrating all classes, we only hydrate the things we
     // actually need. When running as multiple processes this
     // lets us only need to do hydrate a subset of classes.
     $code_base->setShouldHydrateRequestedElements(true);
     // We used to scan all classes here, but now we do it
     // on demand after hydration.
     // Take a pass over all functions verifying
     // various states now that we have the whole
     // state in memory
     Analysis::analyzeFunctions($code_base);
     // Filter out any files that are to be excluded from
     // analysis
     $analyze_file_path_list = array_filter($analyze_file_path_list, function ($file_path) {
         return !self::isExcludedAnalysisFile($file_path);
     });
     // Get the count of all files we're going to analyze
     $file_count = count($analyze_file_path_list);
     // Prevent an ugly failure if we have no files to
     // analyze.
     if (0 == $file_count) {
         return false;
     }
     // Get a map from process_id to the set of files that
     // the given process should analyze in a stable order
     $process_file_list_map = (new Ordering($code_base))->orderForProcessCount(Config::get()->processes, $analyze_file_path_list);
     // This worker takes a file and analyzes it
     $analysis_worker = function ($i, $file_path) use($file_count, $code_base) {
         CLI::progress('analyze', ($i + 1) / $file_count);
         Analysis::analyzeFile($code_base, $file_path);
     };
     // Determine how many processes we're running on. This may be
     // less than the provided number if the files are bunched up
     // excessively.
     $process_count = count($process_file_list_map);
     assert($process_count > 0 && $process_count <= Config::get()->processes, "The process count must be between 1 and the given number of processes. After mapping files to cores, {$process_count} process were set to be used.");
     // Check to see if we're running as multiple processes
     // or not
     if ($process_count > 1) {
         // Run analysis one file at a time, splitting the set of
         // files up among a given number of child processes.
         $pool = new ForkPool($process_file_list_map, function () {
             // Remove any issues that were collected prior to forking
             // to prevent duplicate issues in the output.
             self::getIssueCollector()->reset();
         }, $analysis_worker, function () {
             // Return the collected issues to be serialized.
             return self::getIssueCollector()->getCollectedIssues();
         });
         // Wait for all tasks to complete and collect the results.
         self::collectSerializedResults($pool->wait());
     } else {
         // Get the task data from the 0th processor
         $analyze_file_path_list = array_values($process_file_list_map)[0];
         // If we're not running as multiple processes, just iterate
         // over the file list and analyze them
         foreach ($analyze_file_path_list as $i => $file_path) {
             $analysis_worker($i, $file_path);
         }
         // Scan through all globally accessible elements
         // in the code base and emit errors for dead
         // code.
         Analysis::analyzeDeadCode($code_base);
     }
     // Get a count of the number of issues that were found
     $is_issue_found = 0 !== count(self::$issueCollector->getCollectedIssues());
     // Collect all issues, blocking
     self::display();
     return $is_issue_found;
 }
Exemplo n.º 4
0
 /**
  * For 'closed context' items (classes, methods, functions,
  * closures), we analyze children in the parent context, but
  * then return the parent context itself unmodified by the
  * children.
  *
  *           │
  *           ▼
  *        ┌──●────┐
  *        │       │
  *        ●──●──● │
  *           ┌────┘
  *           ●
  *           │
  *           ▼
  *
  * @param Node $node
  * An AST node we'd like to determine the UnionType
  * for
  *
  * @return Context
  * The updated context after visiting the node
  */
 public function visitClosedContext(Node $node) : Context
 {
     // Make a copy of the internal context so that we don't
     // leak any changes within the closed context to the
     // outer scope
     $context = clone $this->context->withLineNumberStart($node->lineno ?? 0);
     // Visit the given node populating the code base
     // with anything we learn and get a new context
     // indicating the state of the world within the
     // given node
     $context = (new PreOrderAnalysisVisitor($this->code_base, $context))($node);
     assert(!empty($context), 'Context cannot be null');
     // We collect all child context so that the
     // PostOrderAnalysisVisitor can optionally operate on
     // them
     $child_context_list = [];
     $child_context = $context;
     // With a context that is inside of the node passed
     // to this method, we analyze all children of the
     // node.
     foreach ($node->children ?? [] as $child_node) {
         // Skip any non Node children.
         if (!$child_node instanceof Node) {
             continue;
         }
         if (!Analysis::shouldVisit($child_node)) {
             $child_context->withLineNumberStart($child_node->lineno ?? 0);
             continue;
         }
         // Step into each child node and get an
         // updated context for the node
         $child_context = (new BlockAnalysisVisitor($this->code_base, $child_context, $node, $this->depth + 1))($child_node);
         $child_context_list[] = $child_context;
     }
     // For if statements, we need to merge the contexts
     // of all child context into a single scope based
     // on any possible branching structure
     $context = (new ContextMergeVisitor($this->code_base, $context, $child_context_list))($node);
     // Now that we know all about our context (like what
     // 'self' means), we can analyze statements like
     // assignments and method calls.
     $context = (new PostOrderAnalysisVisitor($this->code_base, $context->withLineNumberStart($node->lineno ?? 0), $this->parent_node))($node);
     // Return the initial context as we exit
     return $this->context;
 }
Exemplo n.º 5
0
 /**
  * @return null
  * Analyze the node associated with this object
  * in the given context
  */
 public function analyze(Context $context, CodeBase $code_base) : Context
 {
     // Don't do anything if we care about being
     // fast
     if (Config::get()->quick_mode) {
         return $context;
     }
     if (!$this->hasNode()) {
         return $context;
     }
     // Closures depend on the context surrounding them such
     // as for getting `use(...)` variables. Since we don't
     // have them, we can't re-analyze them until we change
     // that.
     //
     // TODO: Store the parent context on Analyzable objects
     if ($this->getNode()->kind === \ast\AST_CLOSURE) {
         return $context;
     }
     // Don't go deeper than one level in
     if ($this->recursion_depth++ > 2) {
         return $context;
     }
     // Analyze the node in a cloned context so that we
     // don't overwrite anything
     $context = Analysis::analyzeNodeInContext($code_base, clone $context, $this->getNode());
     return $context;
 }
Exemplo n.º 6
0
 /**
  * Analyze the given set of files and emit any issues
  * found to STDOUT.
  *
  * @param CodeBase $code_base
  * A code base needs to be passed in because we require
  * it to be initialized before any classes or files are
  * loaded.
  *
  * @param string[] $file_path_list
  * A list of files to scan
  *
  * @return null
  * We emit messages to STDOUT. Nothing is returned.
  *
  * @see \Phan\CodeBase
  */
 public static function analyzeFileList(CodeBase $code_base, array $file_path_list)
 {
     $file_count = count($file_path_list);
     // We'll construct a set of files that we'll
     // want to run an analysis on
     $analyze_file_path_list = [];
     // This first pass parses code and populates the
     // global state we'll need for doing a second
     // analysis after.
     foreach ($file_path_list as $i => $file_path) {
         CLI::progress('parse', ($i + 1) / $file_count);
         // Kick out anything we read from the former version
         // of this file
         $code_base->flushDependenciesForFile($file_path);
         // If the file is gone, no need to continue
         if (!file_exists($file_path)) {
             continue;
         }
         try {
             // Parse the file
             Analysis::parseFile($code_base, $file_path);
             // Save this to the set of files to analyze
             $analyze_file_path_list[] = $file_path;
         } catch (\Throwable $throwable) {
             error_log($file_path . ' ' . $throwable->getMessage() . "\n");
         }
     }
     // Don't continue on to analysis if the user has
     // chosen to just dump the AST
     if (Config::get()->dump_ast) {
         exit(EXIT_SUCCESS);
     }
     // With parsing complete, we need to tell the code base to
     // start hydrating any requested elements on their way out.
     // Hydration expands class types, imports parent methods,
     // properties, etc., and does stuff like that.
     //
     // This is an optimization that saves us a significant
     // amount of time on very large code bases. Instead of
     // hydrating all classes, we only hydrate the things we
     // actually need. When running as multiple processes this
     // lets us only need to do hydrate a subset of classes.
     $code_base->setShouldHydrateRequestedElements(true);
     // Take a pass over all classes verifying various
     // states now that we have the whole state in
     // memory
     Analysis::analyzeClasses($code_base);
     // Take a pass over all functions verifying
     // various states now that we have the whole
     // state in memory
     Analysis::analyzeFunctions($code_base);
     // We can only save classes, methods, properties and
     // constants after we've merged parent classes in.
     // TODO: Reinstate this
     // $code_base->store();
     // Filter out any files that are to be excluded from
     // analysis
     $analyze_file_path_list = array_filter($analyze_file_path_list, function ($file_path) {
         return !self::isExcludedAnalysisFile($file_path);
     });
     // Get the count of all files we're going to analyze
     $file_count = count($analyze_file_path_list);
     // This worker takes a file and analyzes it
     $analysis_worker = function ($i, $file_path) use($file_count, $code_base) {
         CLI::progress('analyze', ($i + 1) / $file_count);
         Analysis::analyzeFile($code_base, $file_path);
     };
     // Check to see if we're running as multiple processes
     // or not
     if (Config::get()->processes > 1) {
         // Collect all issues, blocking
         self::display();
         // Run analysis one file at a time, splitting the set of
         // files up among a given number of child processes.
         $pool = new ForkPool(Config::get()->processes, $analyze_file_path_list, function () {
         }, $analysis_worker, function () {
             self::display();
         });
         // Wait for all tasks to complete
         $pool->wait();
     } else {
         // If we're not running as multiple processes, just iterate
         // over the file list and analyze them
         foreach ($analyze_file_path_list as $i => $file_path) {
             $analysis_worker($i, $file_path);
         }
         // Scan through all globally accessible elements
         // in the code base and emit errors for dead
         // code.
         Analysis::analyzeDeadCode($code_base);
         // Collect all issues, blocking
         self::display();
     }
 }