作者:ets
项目:pha
/**
* @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
return (new BlockAnalysisVisitor($code_base, clone $context))($this->getNode());
}
作者:actan
项目:pha
/**
* Check to see if the given Clazz is a duplicate
*
* @return null
*/
public static function analyzeParentConstructorCalled(CodeBase $code_base, Clazz $clazz)
{
// Only look at classes configured to require a call
// to its parent constructor
if (!in_array($clazz->getName(), Config::get()->parent_constructor_required)) {
return;
}
// Don't worry about internal classes
if ($clazz->isInternal()) {
return;
}
// Don't worry if there's no parent class
if (!$clazz->hasParentClassFQSEN()) {
return;
}
if (!$code_base->hasClassWithFQSEN($clazz->getParentClassFQSEN())) {
// This is an error, but its caught elsewhere. We'll
// just roll through looking for other errors
return;
}
$parent_clazz = $code_base->getClassByFQSEN($clazz->getParentClassFQSEN());
if (!$parent_clazz->isAbstract() && !$clazz->getIsParentConstructorCalled()) {
Issue::emit(Issue::TypeParentConstructorCalled, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz->getFQSEN(), (string) $parent_clazz->getFQSEN());
}
}
作者:ablyle
项目:pha
public function testClassContext()
{
$code = "<?php\n class C {\n private function f() {\n return 42;\n }\n }";
$stmt_list_node = \ast\parse_code($code, Config::get()->ast_version);
$class_node = $stmt_list_node->children[0];
$context = new Context();
$context = (new ParseVisitor($this->code_base, $context))($class_node);
$stmt_list_node = $class_node->children['stmts'];
$method_node = $stmt_list_node->children[0];
$context = (new ParseVisitor($this->code_base, $context))($method_node);
}
作者:nagyistg
项目:pha
/**
* @return string
* The path of the file relative to the project
* root directory
*/
public function getProjectRelativePath() : string
{
$cwd_relative_path = $this->file;
// Get a path relative to the project root
$path = str_replace(Config::get()->getProjectRootDirectory(), '', realpath($cwd_relative_path) ?: $cwd_relative_path);
// Strip any beginning directory separators
if (0 === ($pos = strpos($path, DIRECTORY_SEPARATOR))) {
$path = substr($path, $pos + 1);
}
return $path;
}
作者:kangko
项目:pha
/**
* Take a look at all globally accessible elements and see if
* we can find any dead code that is never referenced
*
* @return void
*/
public static function analyzeReferenceCounts(CodeBase $code_base)
{
// Check to see if dead code detection is enabled. Keep
// in mind that the results here are just a guess and
// we can't tell with certainty that anything is
// definitely unreferenced.
if (!Config::get()->dead_code_detection) {
return;
}
// Get the count of all known elements
$total_count = count($code_base->getMethodMap(), COUNT_RECURSIVE) + count($code_base->getPropertyMap(), COUNT_RECURSIVE) + count($code_base->getConstantMap(), COUNT_RECURSIVE) + count($code_base->getClassMap(), COUNT_RECURSIVE);
$i = 0;
$analyze_list = function ($list) use($code_base, &$i, $total_count) {
foreach ($list as $name => $element) {
CLI::progress('dead code', ++$i / $total_count);
self::analyzeElementReferenceCounts($code_base, $element);
}
};
$analyze_map = function ($map) use($code_base, &$i, $total_count) {
foreach ($map as $fqsen_string => $list) {
foreach ($list as $name => $element) {
CLI::progress('dead code', ++$i / $total_count);
// Don't worry about internal elements
if ($element->getContext()->isInternal()) {
continue;
}
$element_fqsen = $element->getFQSEN();
if ($element_fqsen instanceof FullyQualifiedClassElement) {
$class_fqsen = $element->getDefiningClassFQSEN();
// Don't analyze elements defined in a parent
// class
if ((string) $class_fqsen !== $fqsen_string) {
continue;
}
$defining_class = $element->getDefiningClass($code_base);
// Don't analyze elements on interfaces or on
// abstract classes, as they're uncallable.
if ($defining_class->isInterface() || $defining_class->isAbstract() || $defining_class->isTrait()) {
continue;
}
// Ignore magic methods
if ($element instanceof Method && $element->getIsMagic()) {
continue;
}
}
self::analyzeElementReferenceCounts($code_base, $element);
}
}
};
$analyze_map($code_base->getMethodMap());
$analyze_map($code_base->getPropertyMap());
$analyze_map($code_base->getConstantMap());
$analyze_list($code_base->getClassMap());
}
作者:ablyle
项目:pha
/**
* Scan a list of files, applying the given closure to every
* AST node
*
* @param string[] $file_list
* A list of files to scan
*
* @param \Closure $visit_node
* A closure that is to be applied to every AST node
*
* @return void
*/
public static function scanFileList(array $file_list, \Closure $visit_node)
{
foreach ($file_list as $file_path) {
// Convert the file to an Abstract Syntax Tree
// before passing it on to the recursive version
// of this method
$node = \ast\parse_file($file_path, Config::get()->ast_version);
// Skip empty files
if (!$node) {
continue;
}
self::scanNodeInFile($node, $file_path, $visit_node);
}
}
作者:hslatma
项目:pha
/**
* @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;
}
// 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 = (new Phan())->analyzeNodeInContext($this->getNode(), clone $context, $code_base);
return $context;
}
作者:tpun
项目:pha
/**
* Take a look at all globally accessible elements and see if
* we can find any dead code that is never referenced
*
* @return void
*/
public static function analyzeReferenceCounts(CodeBase $code_base)
{
// Check to see if dead code detection is enabled. Keep
// in mind that the results here are just a guess and
// we can't tell with certainty that anything is
// definitely unreferenced.
if (!Config::get()->dead_code_detection) {
return;
}
// Get the count of all known elements
$total_count = $code_base->totalElementCount();
$i = 0;
// Functions
self::analyzeElementListReferenceCounts($code_base, $code_base->getFunctionMap(), Issue::UnreferencedMethod, $total_count, $i);
// Constants
self::analyzeElementListReferenceCounts($code_base, $code_base->getGlobalConstantMap(), Issue::UnreferencedConstant, $total_count, $i);
// Classes
self::analyzeElementListReferenceCounts($code_base, $code_base->getClassMap(), Issue::UnreferencedClass, $total_count, $i);
// Class Maps
foreach ($code_base->getClassMapMap() as $class_map) {
self::analyzeClassMapReferenceCounts($code_base, $class_map, $total_count, $i);
}
}
作者:hslatma
项目:pha
public static function err(int $etype, string $msg, string $file = '', int $lineno = 0)
{
$log = self::getInstance();
if ($etype == self::EFATAL) {
self::display();
// Something went wrong - abort
if ($file) {
throw new \Exception("{$file}:{$lineno} {$msg}");
} else {
throw new \Exception($msg);
}
}
// 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];
}
}
作者:black-silenc
项目:pha
private function addMethodWithScopeAndName(FunctionInterface $method, string $scope, string $name)
{
$this->method_map[$scope][$name] = $method;
// If we're doing dead code detection, map the name
// directly to the method so we can quickly look up
// all methods with that name to add a possible
// reference
if (Config::get()->dead_code_detection) {
$this->method_name_map[strtolower($name)][] = $method;
}
// Associate the element with the file it was found in
$this->getFileByPath($method->getFileRef()->getFile())->addMethodFQSEN($method->getFQSEN());
}
作者:Jvbzephi
项目:pha
/**
* 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));
}
作者:tpun
项目:pha
/**
* Visit a node with kind `\ast\AST_RETURN`
*
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitReturn(Node $node) : Context
{
if (Config::get()->backward_compatibility_checks) {
(new ContextNode($this->code_base, $this->context, $node))->analyzeBackwardCompatibility();
}
// Make sure we're actually returning from a method.
if (!$this->context->isInFunctionLikeScope()) {
return $this->context;
}
// Get the method/function/closure we're in
$method = $this->context->getFunctionLikeInScope($this->code_base);
assert(!empty($method), "We're supposed to be in either method or closure scope.");
// Mark the method as returning something
$method->setHasReturn(($node->children['expr'] ?? null) !== null);
return $this->context;
}
作者:nagyistg
项目:pha
/**
* @return string
* The relative path appended to the project root directory.
*
* @suppress PhanUnreferencedMethod
*/
public static function projectPath(string $relative_path)
{
return implode(DIRECTORY_SEPARATOR, [Config::get()->getProjectRootDirectory(), $relative_path]);
}
作者:ets
项目:pha
/**
* Make sure signatures line up between methods and the
* methods they override
*
* @see https://en.wikipedia.org/wiki/Liskov_substitution_principle
*/
private static function analyzeOverrideSignature(CodeBase $code_base, Method $method)
{
if (!Config::get()->analyze_signature_compatibility) {
return;
}
// Hydrate the class this method is coming from in
// order to understand if its an override or not
$class = $method->getClass($code_base);
$class->hydrate($code_base);
// Check to see if the method is an override
// $method->analyzeOverride($code_base);
// Make sure we're actually overriding something
if (!$method->getIsOverride()) {
return;
}
// Dont' worry about signatures lining up on
// constructors. We just want to make sure that
// calling a method on a subclass won't cause
// a runtime error. We usually know what we're
// constructing at instantiation time, so there
// is less of a risk.
if ($method->getName() == '__construct') {
return;
}
// Get the method that is being overridden
$o_method = $method->getOverriddenMethod($code_base);
// Get the class that the overridden method lives on
$o_class = $o_method->getClass($code_base);
// PHP doesn't complain about signature mismatches
// with traits, so neither shall we
if ($o_class->isTrait()) {
return;
}
// Get the parameters for that method
$o_parameter_list = $o_method->getParameterList();
// If we have a parent type defined, map the method's
// return type and parameter types through it
$type_option = $class->getParentTypeOption();
// Map overridden method parameter types through any
// template type parameters we may have
if ($type_option->isDefined()) {
$o_parameter_list = array_map(function (Parameter $parameter) use($type_option, $code_base) : Parameter {
if (!$parameter->getUnionType()->hasTemplateType()) {
return $parameter;
}
$mapped_parameter = clone $parameter;
$mapped_parameter->setUnionType($mapped_parameter->getUnionType()->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base)));
return $mapped_parameter;
}, $o_parameter_list);
}
// Map overridden method return type through any template
// type parameters we may have
$o_return_union_type = $o_method->getUnionType();
if ($type_option->isDefined() && $o_return_union_type->hasTemplateType()) {
$o_return_union_type = $o_return_union_type->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base));
}
// Determine if the signatures match up
$signatures_match = true;
// Make sure the count of parameters matches
if ($method->getNumberOfRequiredParameters() > $o_method->getNumberOfRequiredParameters()) {
$signatures_match = false;
} else {
if ($method->getNumberOfParameters() < $o_method->getNumberOfParameters()) {
$signatures_match = false;
// If parameter counts match, check their types
} else {
foreach ($method->getParameterList() as $i => $parameter) {
if (!isset($o_parameter_list[$i])) {
continue;
}
$o_parameter = $o_parameter_list[$i];
// Changing pass by reference is not ok
// @see https://3v4l.org/Utuo8
if ($parameter->isPassByReference() != $o_parameter->isPassByReference()) {
$signatures_match = false;
break;
}
// A stricter type on an overriding method is cool
if ($o_parameter->getUnionType()->isEmpty() || $o_parameter->getUnionType()->isType(MixedType::instance())) {
continue;
}
// Its not OK to have a more relaxed type on an
// overriding method
//
// https://3v4l.org/XTm3P
if ($parameter->getUnionType()->isEmpty()) {
$signatures_match = false;
break;
}
// If we have types, make sure they line up
//
// TODO: should we be expanding the types on $o_parameter
// via ->asExpandedTypes($code_base)?
//
//.........这里部分代码省略.........
作者:Jvbzephi
项目:pha
/**
* Once we know what the universe looks like we
* can scan for more complicated issues.
*
* @param CodeBase $code_base
* The global code base holding all state
*
* @param string $file_path
* A list of files to scan
*
* @return Context
*/
public static function analyzeFile(CodeBase $code_base, string $file_path) : Context
{
// Convert the file to an Abstract Syntax Tree
// before passing it on to the recursive version
// of this method
$node = \ast\parse_file($file_path, Config::get()->ast_version);
// Set the file on the context
$context = (new Context())->withFile($file_path);
// Ensure we have some content
if (empty($node)) {
Issue::emit(Issue::EmptyFile, $file_path, 0, $file_path);
return $context;
}
// Start recursively analyzing the tree
return self::analyzeNodeInContext($code_base, $context, $node);
}
作者:ets
项目:pha
/**
* @return bool
* True if this Type can be cast to the given Type
* cleanly
*/
public function canCastToType(Type $type) : bool
{
if ($this === $type) {
return true;
}
$s = strtolower((string) $this);
$d = strtolower((string) $type);
if ($s[0] == '\\') {
$s = substr($s, 1);
}
if ($d[0] == '\\') {
$d = substr($d, 1);
}
if ($s === $d) {
return true;
}
if (Config::get()->scalar_implicit_cast) {
if ($type->isScalar() && $this->isScalar()) {
return true;
}
}
if ($s === 'int' && $d === 'float') {
return true;
// int->float is ok
}
if (($s === 'array' || $s === 'string' || strpos($s, '[]') !== false || $s === 'closure') && $d === 'callable') {
return true;
}
if ($s === 'object' && !$type->isScalar() && $d !== 'array') {
return true;
}
if ($d === 'object' && !$this->isScalar() && $s !== 'array') {
return true;
}
if (strpos($s, '[]') !== false && ($d == 'array' || $d == '\\ArrayAccess')) {
return true;
}
if (strpos($d, '[]') !== false && $s === 'array') {
return true;
}
if ($s === 'callable' && $d === 'closure') {
return true;
}
if (($pos = strrpos($d, '\\')) !== false) {
if ('\\' !== $this->getNamespace()) {
if (trim($this->getNamespace() . '\\' . $s, '\\') == $d) {
return true;
}
} else {
if (substr($d, $pos + 1) === $s) {
return true;
// Lazy hack, but...
}
}
}
if (($pos = strrpos($s, '\\')) !== false) {
if ('\\' !== $type->getNamespace()) {
if (trim($type->getNamespace() . '\\' . $d, '\\') == $s) {
return true;
}
} else {
if (substr($s, $pos + 1) === $d) {
return true;
// Lazy hack, but...
}
}
}
return false;
}
作者:black-silenc
项目:pha
/**
* Emit all collected issues
*
* @return void
*/
private static function display()
{
$collector = self::$issueCollector;
if (Config::get()->progress_bar) {
fwrite(STDERR, "\n");
}
$printer = self::$printer;
foreach ($collector->getCollectedIssues() as $issue) {
$printer->print($issue);
}
if ($printer instanceof BufferedPrinterInterface) {
$printer->flush();
}
}
作者:tpun
项目:pha
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);
作者:ets
项目:pha
/**
* @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");
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) {
Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
return $this->context;
}
if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
$this->emitIssue(Issue::TypeMismatchProperty, $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
return $this->context;
} else {
// If we're assigning to an array element then we don't
// know what the constitutation of the parameter is
// outside of the scope of this assignment, so we add to
// its union type rather than replace it.
if ($this->is_dim_assignment) {
$property->getUnionType()->addUnionType($this->right_type);
}
}
// After having checked it, add this type to it
$property->getUnionType()->addUnionType($this->right_type);
return $this->context;
}
$std_class_fqsen = FullyQualifiedClassName::getStdClassFQSEN();
if (Config::get()->allow_missing_properties || !empty($class_list) && $class_list[0]->getFQSEN() == $std_class_fqsen) {
try {
// Create the property
$property = (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
$property->getUnionType()->addUnionType($this->right_type);
} catch (\Exception $exception) {
// swallow it
}
} elseif (!empty($class_list)) {
$this->emitIssue(Issue::UndeclaredProperty, $node->lineno ?? 0, "{$class_list[0]->getFQSEN()}->{$property_name}");
} else {
// If we hit this part, we couldn't figure out
// the class, so we ignore the issue
}
return $this->context;
}
作者:tpun
项目:pha
/**
* @return Comment
* A comment built by parsing the given doc block
* string.
*/
public static function fromStringInContext(string $comment, Context $context) : Comment
{
if (!Config::get()->read_type_annotations) {
return new Comment(false, [], [], [], new None(), new UnionType(), []);
}
$is_deprecated = false;
$variable_list = [];
$parameter_list = [];
$template_type_list = [];
$inherited_type = new None();
$return_union_type = new UnionType();
$suppress_issue_list = [];
$lines = explode("\n", $comment);
foreach ($lines as $line) {
if (strpos($line, '@param') !== false) {
$parameter_list[] = self::parameterFromCommentLine($context, $line);
} elseif (stripos($line, '@var') !== false) {
$variable_list[] = self::parameterFromCommentLine($context, $line);
} elseif (stripos($line, '@template') !== false) {
// Make sure support for generic types is enabled
if (Config::get()->generic_types_enabled) {
if ($template_type = self::templateTypeFromCommentLine($context, $line)) {
$template_type_list[] = $template_type;
}
}
} elseif (stripos($line, '@inherits') !== false) {
// Make sure support for generic types is enabled
if (Config::get()->generic_types_enabled) {
$inherited_type = self::inheritsFromCommentLine($context, $line);
}
} elseif (stripos($line, '@return') !== false) {
$return_union_type = self::returnTypeFromCommentLine($context, $line);
} elseif (stripos($line, '@suppress') !== false) {
$suppress_issue_list[] = self::suppressIssueFromCommentLine($line);
}
if (($pos = stripos($line, '@deprecated')) !== false) {
if (preg_match('/@deprecated\\b/', $line, $match)) {
$is_deprecated = true;
}
}
}
return new Comment($is_deprecated, $variable_list, $parameter_list, $template_type_list, $inherited_type, $return_union_type, $suppress_issue_list);
}