예제 #1
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateClass(CodeBase $code_base, Clazz $clazz)
 {
     // Determine if its a duplicate by looking to see if
     // the FQSEN is suffixed with an alternate ID.
     if (!$clazz->getFQSEN()->isAlternate()) {
         return;
     }
     $original_fqsen = $clazz->getFQSEN()->getCanonicalFQSEN();
     if (!$code_base->hasClassWithFQSEN($original_fqsen)) {
         // If there's a missing class we'll catch that
         // elsewhere
         return;
     }
     // Get the original class
     $original_class = $code_base->getClassByFQSEN($original_fqsen);
     // Check to see if the original definition was from
     // an internal class
     if ($original_class->isInternal()) {
         Log::err(Log::EREDEF, "{$clazz} defined at " . "{$clazz->getContext()->getFile()}:{$clazz->getContext()->getLineNumberStart()} " . "was previously defined as {$original_class} internally", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart());
         // Otherwise, print the coordinates of the original
         // definition
     } else {
         Log::err(Log::EREDEF, "{$clazz} defined at " . "{$clazz->getContext()->getFile()}:{$clazz->getContext()->getLineNumberStart()} " . "was previously defined as {$original_class} at " . "{$original_class->getContext()->getFile()}:{$original_class->getContext()->getLineNumberStart()}", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart());
     }
     return;
 }
예제 #2
0
 /**
  * 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()) {
         Log::err(Log::ETYPE, "{$clazz->getFQSEN()} extends {$parent_clazz->getFQSEN()} but doesn't call parent::__construct()", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart());
     }
 }
예제 #3
0
 /**
  * @return bool
  * True if the FQSEN exists. If not, a log line is emitted
  */
 private static function fqsenExistsForClass(FQSEN $fqsen, CodeBase $code_base, Clazz $clazz, string $message_template) : bool
 {
     if (!$code_base->hasClassWithFQSEN($fqsen)) {
         Log::err(Log::EUNDEF, sprintf($message_template, $fqsen), $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart());
         return false;
     }
     return true;
 }
예제 #4
0
 /**
  * @return bool
  * True if the FQSEN exists. If not, a log line is emitted
  */
 private static function fqsenExistsForClass(FQSEN $fqsen, CodeBase $code_base, Clazz $clazz) : bool
 {
     if (!$code_base->hasClassWithFQSEN($fqsen)) {
         Log::err(Log::EUNDEF, "Trying to inherit from unknown class {$fqsen}", $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart());
         return false;
     }
     return true;
 }
예제 #5
0
 /**
  * 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) {
         $union_type = $property->getUnionType();
         // Look at each type in the parameter's Union Type
         foreach ($union_type->getTypeList() 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)) {
                 Log::err(Log::EUNDEF, "property of undeclared type {$type_fqsen}", $property->getContext()->getFile(), $property->getContext()->getLineNumberStart());
             }
         }
     }
 }
예제 #6
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateFunction(CodeBase $code_base, Method $method)
 {
     $fqsen = $method->getFQSEN();
     if (!$fqsen->isAlternate()) {
         return;
     }
     $original_fqsen = $fqsen->getCanonicalFQSEN();
     if (!$code_base->hasMethod($original_fqsen)) {
         return;
     }
     $original_method = $code_base->getMethod($original_fqsen);
     $method_name = $method->getName();
     if ('internal' === $original_method->getContext()->getFile()) {
         // If its in an conditional and the original is an
         // internal method, presume its all OK.
         if ($method->getContext()->getIsConditional()) {
             return;
         }
         Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined internally", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart());
     } else {
         Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined at {$original_method->getContext()->getFile()}:{$original_method->getContext()->getLineNumberStart()}", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart());
     }
 }
예제 #7
0
 /**
  * @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 string
  * The class name represented by the given call
  */
 public function visitNew(Node $node) : string
 {
     // Things of the form `new $class_name();`
     if ($node->children['class']->kind == \ast\AST_VAR) {
         return '';
     }
     // Things of the form `new $method->name()`
     if ($node->children['class']->kind !== \ast\AST_NAME) {
         return '';
     }
     $class_name = $node->children['class']->children['name'];
     if (!in_array($class_name, ['self', 'static', 'parent'])) {
         return AST::qualifiedName($this->context, $node->children['class']);
     }
     if (!$this->context->isInClassScope()) {
         Log::err(Log::ESTATIC, "Cannot access {$class_name}:: when no class scope is active", $this->context->getFile(), $node->lineno);
         return '';
     }
     if ($class_name == 'static') {
         return (string) $this->context->getClassFQSEN();
     }
     if ($class_name == 'self') {
         if ($this->context->isGlobalScope()) {
             assert(false, "Unimplemented branch is required for {$this->context}");
         } else {
             return (string) $this->context->getClassFQSEN();
         }
     }
     if ($class_name == 'parent') {
         $clazz = $this->context->getClassInScope($this->code_base);
         if (!$clazz->hasParentClassFQSEN()) {
             return '';
         }
         return (string) $clazz->getParentClassFQSEN();
     }
     return '';
 }
예제 #8
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeElementReferenceCounts(CodeBase $code_base, TypedStructuralElement $element)
 {
     // Don't worry about internal elements
     if ($element->getContext()->isInternal()) {
         return;
     }
     if ($element->getReferenceCount($code_base) < 1) {
         if ($element instanceof Addressable) {
             Log::err(Log::ENOOP, "{$element->getFQSEN()} may have zero references", $element->getContext()->getFile(), $element->getContext()->getLineNumberStart());
         } else {
             Log::err(Log::ENOOP, "{$element} may have zero references", $element->getContext()->getFile(), $element->getContext()->getLineNumberStart());
         }
     }
 }
예제 #9
0
 /**
  * @return bool
  * False if the class name doesn't point to a known class
  */
 private function classExistsOrIsNative(Node $node) : bool
 {
     if ($this->classExists()) {
         return true;
     }
     $type = UnionType::fromStringInContext($this->class_name, $this->context);
     if ($type->isNativeType()) {
         return true;
     }
     Log::err(Log::EUNDEF, "reference to undeclared class {$this->class_fqsen}", $this->context->getFile(), $node->lineno);
     return false;
 }
예제 #10
0
파일: CLI.php 프로젝트: Jvbzephir/phan
 /**
  * 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:aeqbrpid:s:3:y:l:xh::", ['fileset:', 'output-mode:', 'output:', 'parent-constructor-required:', 'expanded-dependency-list', 'dump-ast', 'quick', 'backward-compatibility-checks', 'reanalyze-file-list', 'progress-bar', 'ignore-undeclared', 'project-root-directory:', 'state-file:', 'exclude-directory-list:', 'minimum-severity:', 'directory:', 'dead-code-detection', 'help']);
     // 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();
     foreach ($opts ?? [] as $key => $value) {
         switch ($key) {
             case 'h':
             case 'help':
                 $this->usage();
                 break;
             case 'f':
             case 'fileset':
                 $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':
                 $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, ['text', 'codeclimate'])) {
                     $this->usage("Unknown output mode: {$value}");
                 }
                 Log::setOutputMode($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 'expanded-dependency-list':
                 Config::get()->expanded_dependency_list = true;
                 break;
             case 'o':
             case 'output':
                 Log::setFilename($value);
                 break;
             case 'i':
             case 'ignore-undeclared':
                 Log::setOutputMask(Log::getOutputMask() ^ 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 'r':
             case 'reanalyze-file-list':
                 Config::get()->reanalyze_file_list = true;
                 break;
             case 'y':
             case 'minimum-severity':
                 Config::get()->minimum_severity = $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;
         }
     }
     $pruneargv = array();
     foreach ($opts ?? [] as $opt => $value) {
         foreach ($argv as $key => $chunk) {
             $regex = '/^' . (isset($opt[1]) ? '--' : '-') . $opt . '/';
             if ($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}'");
         }
     }
     // 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));
     }
 }
예제 #11
0
 /**
  * @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 string
  * The class name represented by the given call
  */
 public function visitNew(Node $node) : string
 {
     // Things of the form `new $class_name();`
     if ($node->children['class']->kind == \ast\AST_VAR) {
         return '';
     }
     // Anonymous class
     // $v = new class { ... }
     if ($node->children['class']->kind == \ast\AST_CLASS && $node->children['class']->flags & \ast\flags\CLASS_ANONYMOUS) {
         return (new ContextNode($this->code_base, $this->context, $node->children['class']))->getUnqualifiedNameForAnonymousClass();
     }
     // Things of the form `new $method->name()`
     if ($node->children['class']->kind !== \ast\AST_NAME) {
         return '';
     }
     $class_name = $node->children['class']->children['name'];
     if (!in_array($class_name, ['self', 'static', 'parent'])) {
         return (string) UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $node->children['class']);
     }
     if (!$this->context->isInClassScope()) {
         Log::err(Log::ESTATIC, "Cannot access {$class_name}:: when no class scope is active", $this->context->getFile(), $node->lineno);
         return '';
     }
     if ($class_name == 'static') {
         return (string) $this->context->getClassFQSEN();
     }
     if ($class_name == 'self') {
         if ($this->context->isGlobalScope()) {
             assert(false, "Unimplemented branch is required for {$this->context}");
         } else {
             return (string) $this->context->getClassFQSEN();
         }
     }
     if ($class_name == 'parent') {
         $clazz = $this->context->getClassInScope($this->code_base);
         if (!$clazz->hasParentClassFQSEN()) {
             return '';
         }
         return (string) $clazz->getParentClassFQSEN();
     }
     return '';
 }
예제 #12
0
 private function visitClassNode(Node $node) : UnionType
 {
     // Things of the form `new $class_name();`
     if ($node->kind == \ast\AST_VAR) {
         return new UnionType();
     }
     // Anonymous class of form `new class { ... }`
     if ($node->kind == \ast\AST_CLASS && $node->flags & \ast\flags\CLASS_ANONYMOUS) {
         // Generate a stable name for the anonymous class
         $anonymous_class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
         // Turn that into a fully qualified name
         $fqsen = FullyQualifiedClassName::fromStringInContext($anonymous_class_name, $this->context);
         // Turn that into a union type
         return Type::fromFullyQualifiedString((string) $fqsen)->asUnionType();
     }
     // Things of the form `new $method->name()`
     if ($node->kind !== \ast\AST_NAME) {
         return new UnionType();
     }
     // Get the name of the class
     $class_name = $node->children['name'];
     // If this is a straight-forward class name, recurse into the
     // class node and get its type
     if (!Type::isSelfTypeString($class_name)) {
         // TODO: does anyone else call this method?
         return self::unionTypeFromClassNode($this->code_base, $this->context, $node);
     }
     // This is a self-referential node
     if (!$this->context->isInClassScope()) {
         Log::err(Log::ESTATIC, "Cannot access {$class_name} when not in a class scope", $this->context->getFile(), $node->lineno);
         return new UnionType();
     }
     // Reference to a parent class
     if ($class_name === 'parent') {
         $class = $this->context->getClassInScope($this->code_base);
         if (!$class->hasParentClassFQSEN()) {
             Log::err(Log::ESTATIC, "Reference to parent of parentless class {$class->getFQSEN()}", $this->context->getFile(), $node->lineno);
             return new UnionType();
         }
         return Type::fromFullyQualifiedString((string) $class->getParentClassFQSEN())->asUnionType();
     }
     return Type::fromFullyQualifiedString((string) $this->context->getClassFQSEN())->asUnionType();
 }
예제 #13
0
 /**
  * @param Node $node
  * A node to check types on
  *
  * @return UnionType
  * The resulting type(s) of the binary operation
  */
 public function visitBinaryAdd(Node $node) : UnionType
 {
     $left = UnionType::fromNode($this->context, $this->code_base, $node->children['left']);
     $right = UnionType::fromNode($this->context, $this->code_base, $node->children['right']);
     // fast-track common cases
     if ($left->isType(IntType::instance()) && $right->isType(IntType::instance())) {
         return IntType::instance()->asUnionType();
     }
     if (($left->isType(IntType::instance()) || $left->isType(FloatType::instance())) && ($right->isType(IntType::instance()) || $right->isType(FloatType::instance()))) {
         return FloatType::instance()->asUnionType();
     }
     $left_is_array = !empty($left->genericArrayElementTypes()) && empty($left->nonGenericArrayTypes());
     $right_is_array = !empty($right->genericArrayElementTypes()) && empty($right->nonGenericArrayTypes());
     if ($left_is_array && !$right->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $this->context->getFile(), $node->lineno);
         return new UnionType();
     } else {
         if ($right_is_array && !$left->canCastToUnionType(ArrayType::instance()->asUnionType())) {
             Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $this->context->getFile(), $node->lineno);
             return new UnionType();
         } else {
             if ($left_is_array || $right_is_array) {
                 // If it is a '+' and we know one side is an array
                 // and the other is unknown, assume array
                 return ArrayType::instance()->asUnionType();
             }
         }
     }
     return new UnionType([IntType::instance(), FloatType::instance()]);
 }
예제 #14
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param Method $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @param CodeBase $code_base
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arg_check
  * Formerly `function arg_check`
  */
 private static function analyzeInternalArgumentType(Method $method, Node $node, Context $context, CodeBase $code_base)
 {
     $arglist = $node->children['args'];
     $argcount = count($arglist->children);
     switch ($method->getName()) {
         case 'join':
         case 'implode':
             // (string glue, array pieces),
             // (array pieces, string glue) or
             // (array pieces)
             if ($argcount == 1) {
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(pieces) is %s but {$method->getFQSEN()}() takes array when passed only 1 arg");
                 return;
             } else {
                 if ($argcount == 2) {
                     $arg1_type = UnionType::fromNode($context, $code_base, $arglist->children[0]);
                     $arg2_type = UnionType::fromNode($context, $code_base, $arglist->children[1]);
                     if ((string) $arg1_type == 'array') {
                         if (!$arg1_type->canCastToUnionType(StringType::instance()->asUnionType())) {
                             Log::err(Log::EPARAM, "arg#2(glue) is {$arg2_type} but {$method->getFQSEN()}() takes string when arg#1 is array", $context->getFile(), $context->getLineNumberStart());
                         }
                     } else {
                         if ((string) $arg1_type == 'string') {
                             if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                                 Log::err(Log::EPARAM, "arg#2(pieces) is {$arg2_type} but {$method->getFQSEN()}() takes array when arg#1 is string", $context->getFile(), $context->getLineNumberStart());
                             }
                         }
                     }
                     return;
                 }
             }
             // Any other arg counts we will let the regular
             // checks handle
             break;
         case 'array_udiff':
         case 'array_diff_uassoc':
         case 'array_uintersect_assoc':
         case 'array_intersect_ukey':
             if ($argcount < 3) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 return;
             }
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), "The last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 1; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, CallableType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes array");
             }
             return;
         case 'array_diff_uassoc':
         case 'array_uintersect_uassoc':
             if ($argcount < 4) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 return;
             }
             // The last 2 arguments must be a callable and there
             // can be a variable number of arrays before it
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), "The last argument to {$method->getFQSEN()} must be a callable");
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 2], $context, $code_base, CallableType::instance()->asUnionType(), "The second last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 2; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes array");
             }
             return;
         case 'strtok':
             // (string str, string token) or (string token)
             if ($argcount == 1) {
                 // If we have just one arg it must be a string token
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(token) is %s but {$method->getFQSEN()}() takes string when passed only one arg");
             }
             // The arginfo check will handle the other case
             break;
         case 'min':
         case 'max':
             if ($argcount == 1) {
                 // If we have just one arg it must be an array
                 if (!self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(values) is %s but {$method->getFQSEN()}() takes array when passed only one arg")) {
                     return;
                 }
             }
             // The arginfo check will handle the other case
             break;
         default:
             break;
     }
 }
예제 #15
0
 /**
  * @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 string
  * The class name represented by the given call
  */
 public function visitProp(Node $node) : string
 {
     if (!($node->children['expr']->kind == \ast\AST_VAR && !$node->children['expr']->children['name'] instanceof Node)) {
         return '';
     }
     // $var->prop->method()
     $var = $node->children['expr'];
     $class = null;
     if ($var->children['name'] == 'this') {
         // If we're not in a class scope, 'this' won't work
         if (!$this->context->isInClassScope()) {
             Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
             return '';
         }
         // $this->$node->method()
         if ($node->children['prop'] instanceof Node) {
             // Too hard. Giving up.
             return '';
         }
         $class = $this->context->getClassInScope($this->code_base);
     } else {
         // Get the list of viable class types for the
         // variable
         $union_type = AST::varUnionType($this->context, $var)->nonNativeTypes()->nonGenericArrayTypes();
         if ($union_type->isEmpty()) {
             return '';
         }
         $class_fqsen = $union_type->head()->asFQSEN();
         if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
             return '';
         }
         $class = $this->code_base->getClassByFQSEN($class_fqsen);
     }
     $property_name = $node->children['prop'];
     if (!$class->hasPropertyWithName($this->code_base, $property_name)) {
         // If we can't find the property, there's
         // no type. Thie issue should be caught
         // elsewhere.
         return '';
     }
     try {
         $property = $class->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
     } catch (AccessException $exception) {
         Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
         return '';
     }
     $union_type = $property->getUnionType()->nonNativeTypes();
     if ($union_type->isEmpty()) {
         // If we don't have a type on the property we
         // can't figure out the class type.
         return '';
     } else {
         // Return the first type on the property
         // that could be a reference to a class
         return (string) $union_type->head()->asFQSEN();
     }
     // No such property was found, or none were classes
     // that could be found
     return '';
 }
예제 #16
0
파일: CLI.php 프로젝트: VladaPetrovic/phan
 /**
  * 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
     $opts = getopt("f:m:o:c:haqbrpid:s:3:t::");
     // 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();
     foreach ($opts ?? [] as $key => $value) {
         switch ($key) {
             case 'h':
                 $this->usage();
                 break;
             case 'f':
                 if (is_file($value) && is_readable($value)) {
                     $this->file_list = file($value, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
                 } else {
                     Log::err(Log::EFATAL, "Unable to open {$value}");
                 }
                 break;
             case 'm':
                 if (!in_array($value, ['text', 'codeclimate'])) {
                     $this->usage("Unknown output mode: {$value}");
                 }
                 Log::setOutputMode($value);
                 break;
             case 'c':
                 Config::get()->parent_constructor_required = explode(',', $value);
                 break;
             case 'q':
                 Config::get()->quick_mode = true;
                 break;
             case 'b':
                 Config::get()->backward_compatibility_checks = true;
                 break;
             case 'p':
                 Config::get()->progress_bar = true;
                 break;
             case 'a':
                 Config::get()->dump_ast = true;
                 break;
             case 'o':
                 Log::setFilename($value);
                 break;
             case 'i':
                 Log::setOutputMask(Log::getOutputMask() ^ Log::EUNDEF);
                 break;
             case 't':
                 Config::get()->emit_trace_id = true;
                 break;
             case '3':
                 Config::get()->exclude_analysis_directory_list = explode(',', $value);
                 break;
             case 's':
                 Config::get()->stored_state_file_path = $value;
                 break;
             case 'r':
                 Config::get()->reanalyze_file_list = true;
                 break;
             case 'd':
                 // We handle this flag before parsing options so
                 // that we can get the project root directory to
                 // base other config flags values on
                 break;
             default:
                 $this->usage("Unknown option '-{$key}'");
                 break;
         }
     }
     $pruneargv = array();
     foreach ($opts ?? [] as $opt => $value) {
         foreach ($argv as $key => $chunk) {
             $regex = '/^' . (isset($opt[1]) ? '--' : '-') . $opt . '/';
             if ($chunk == $value && $argv[$key - 1][0] == '-' || preg_match($regex, $chunk)) {
                 array_push($pruneargv, $key);
             }
         }
     }
     while ($key = array_pop($pruneargv)) {
         unset($argv[$key]);
     }
     if (empty($this->file_list) && count($argv) < 2) {
         // Log::err(Log::EFATAL, "No files to analyze");
     }
     foreach ($argv as $arg) {
         if ($arg[0] == '-') {
             $this->usage("Unknown option '{$arg}'");
         }
     }
     $this->file_list = array_merge($this->file_list, array_slice($argv, 1));
 }
예제 #17
0
파일: AST.php 프로젝트: akrabat/phan
 /**
  * Perform some backwards compatibility checks on a node
  *
  * @param Context $context
  * The context in which the node appears
  *
  * @param Node $node
  * The node we'd like to check
  *
  * @return null
  *
  * @see \Phan\Deprecated::bc_check
  * Formerly `function bc_check`
  */
 public static function backwardCompatibilityCheck(Context $context, Node $node)
 {
     if (!$node->children['expr'] instanceof \node\Node) {
         return;
     }
     if ($node->children['expr']->kind !== \node\node_DIM) {
         return;
     }
     $temp = $node->children['expr']->children['expr'];
     $lnode = $temp;
     if (!($temp->kind == \node\node_PROP || $temp->kind == \node\node_STATIC_PROP)) {
         return;
     }
     while ($temp instanceof \node\Node && ($temp->kind == \node\node_PROP || $temp->kind == \node\node_STATIC_PROP)) {
         $lnode = $temp;
         // Lets just hope the 0th is the expression
         // we want
         $temp = array_values($temp->children)[0];
     }
     if (!$temp instanceof \node\Node) {
         return;
     }
     if ($lnode->children['prop'] instanceof \node\Node && $lnode->children['prop']->kind == \node\node_VAR && ($temp->kind == \node\node_VAR || $temp->kind == \node\node_NAME)) {
         $ftemp = new \SplFileObject($context->getFile());
         $ftemp->seek($node->lineno - 1);
         $line = $ftemp->current();
         unset($ftemp);
         if (strpos($line, '}[') === false || strpos($line, ']}') === false || strpos($line, '>{') === false) {
             Log::err(Log::ECOMPAT, "expression may not be PHP 7 compatible", $context->getFile(), $node->lineno);
         }
     }
 }
예제 #18
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitCatch(Node $node) : Context
 {
     try {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $node->children['class']);
         $class_list = AST::classListFromNodeInContext($this->code_base, $this->context, $node->children['class']);
     } catch (CodeBaseException $exception) {
         Log::err(Log::EUNDEF, "catching undeclared class {$exception->getFQSEN()}", $this->context->getFile(), $node->lineno);
     }
     $variable_name = AST::variableName($node->children['var']);
     if (!empty($variable_name)) {
         $variable = Variable::fromNodeInContext($node->children['var'], $this->context, $this->code_base, false);
         if (!$union_type->isEmpty()) {
             $variable->setUnionType($union_type);
         }
         $this->context->addScopeVariable($variable);
     }
     return $this->context;
 }
예제 #19
0
파일: Parameter.php 프로젝트: jazzdan/phan
 /**
  * @return Parameter[]
  * A list of parameters from an AST node.
  *
  * @see \Phan\Deprecated\Pass1::node_paramlist
  * Formerly `function node_paramlist`
  */
 public static function listFromNode(Context $context, CodeBase $code_base, Node $node) : array
 {
     assert($node instanceof Node, "node was not an \\ast\\Node");
     $parameter_list = [];
     $is_optional_seen = false;
     foreach ($node->children ?? [] as $i => $child_node) {
         $parameter = Parameter::fromNode($context, $code_base, $child_node);
         if (!$parameter->isOptional() && $is_optional_seen) {
             Log::err(Log::EPARAM, "required arg follows optional", $context->getFile(), $node->lineno ?? 0);
         } else {
             if ($parameter->isOptional() && !$is_optional_seen && $parameter->getUnionType()->isEmpty()) {
                 $is_optional_seen = true;
             }
         }
         $parameter_list[] = $parameter;
     }
     return $parameter_list;
 }
예제 #20
0
파일: Phan.php 프로젝트: kangkot/phan
 /**
  * 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_list
  * A list of files to scan
  *
  * @return Context
  */
 public 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)) {
         Log::err(Log::EUNDEF, "Empty or missing file  {$file_path}", $file_path, 0);
         return $context;
     }
     // Start recursively analyzing the tree
     return $this->analyzeNodeInContext($code_base, $context, $node);
 }
예제 #21
0
 /**
  * @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 {
         $clazz = (new ContextNode($this->code_base, $this->context, $node))->getClass();
     } catch (CodeBaseException $exception) {
         Log::err(Log::EFATAL, $exception->getMessage(), $this->context->getFile(), $node->lineno);
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a class
         // this is, don't worry about it
         return $this->context;
     }
     if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
         // Check to see if the class has a __set method
         if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
             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
                 }
             } else {
                 Log::err(Log::EAVAIL, "Missing property with name '{$property_name}'", $this->context->getFile(), $node->lineno);
             }
         }
         return $this->context;
     }
     try {
         $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
     } catch (AccessException $exception) {
         Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
         return $this->context;
     }
     if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
         Log::err(Log::ETYPE, "assigning {$this->right_type} to property but {$clazz->getFQSEN()}::{$property->getName()} is {$property->getUnionType()}", $this->context->getFile(), $node->lineno);
         return $this->context;
     }
     // After having checked it, add this type to it
     $property->getUnionType()->addUnionType($this->right_type);
     return $this->context;
 }
예제 #22
0
 /**
  * Visit a node with kind `\ast\AST_PROP_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitPropDecl(Node $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     // Get a comment on the property declaration
     $comment = Comment::fromStringInContext($node->children[0]->docComment ?? '', $this->context);
     foreach ($node->children ?? [] as $i => $child_node) {
         // Ignore children which are not property elements
         if (!$child_node || $child_node->kind != \ast\AST_PROP_ELEM) {
             continue;
         }
         // @var UnionType
         $type = UnionType::fromNode($this->context, $this->code_base, $child_node->children['default']);
         $property_name = $child_node->children['name'];
         assert(is_string($property_name), 'Property name must be a string. ' . 'Got ' . print_r($property_name, true) . ' at ' . $this->context);
         $property = new Property($this->context->withLineNumberStart($child_node->lineno ?? 0)->withLineNumberEnd($child_node->endLineno ?? -1), is_string($child_node->children['name']) ? $child_node->children['name'] : '_error_', $type, $node->flags ?? 0);
         // Add the property to the class
         $clazz->addProperty($this->code_base, $property);
         // Look for any @var declarations
         if ($variable = $comment->getVariableList()[$i] ?? null) {
             if ((string) $type != 'null' && !$type->canCastToUnionType($variable->getUnionType())) {
                 Log::err(Log::ETYPE, "assigning {$type} to property but {$property->getFQSEN()} is {$variable->getUnionType()}", $this->context->getFile(), $child_node->lineno);
             }
             // Set the declared type to the doc-comment type and add
             // |null if the default value is null
             $property->getUnionType()->addUnionType($variable->getUnionType());
         }
     }
     return $this->context;
 }
예제 #23
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitCatch(Node $node) : Context
 {
     // Get the name of the class
     $class_name = $node->children['class']->children['name'];
     $clazz = null;
     // If we can't figure out the class name (which happens
     // from time to time), then give up
     if (!empty($class_name)) {
         $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $this->context);
         // Check to see if the class actually exists
         if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
             $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
         } else {
             Log::err(Log::EUNDEF, "call to method on undeclared class {$class_name}", $this->context->getFile(), $node->lineno);
         }
     }
     $variable_name = AST::variableName($node->children['var']);
     if (!empty($variable_name)) {
         $variable = Variable::fromNodeInContext($node->children['var'], $this->context, $this->code_base, false);
         if ($clazz) {
             $variable->setUnionType($clazz->getUnionType());
         }
         $this->context->addScopeVariable($variable);
     }
     return $this->context;
 }
예제 #24
0
 /**
  * @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 string
  * The class name represented by the given call
  */
 public function visitMethodCall(Node $node) : string
 {
     if ($node->children['expr']->kind == \ast\AST_VAR) {
         if ($node->children['expr']->children['name'] instanceof Node) {
             return '';
         }
         // $var->method()
         if ($node->children['expr']->children['name'] == 'this') {
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             return (string) $this->context->getClassFQSEN();
         }
         $variable_name = $node->children['expr']->children['name'];
         if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
             // Got lost, couldn't find the variable in the current scope
             // If it really isn't defined, it will be caught by the
             // undefined var error
             return '';
         }
         $variable = $this->context->getScope()->getVariableWithName($variable_name);
         // Hack - loop through the possible types of the var and assume
         // first found class is correct
         foreach ($variable->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
             $child_class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             if ($this->code_base->hasClassWithFQSEN($child_class_fqsen)) {
                 return (string) FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             }
         }
         // Could not find name
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_PROP) {
         $prop = $node->children['expr'];
         if (!($prop->children['expr']->kind == \ast\AST_VAR && !$prop->children['expr']->children['name'] instanceof Node)) {
             return '';
         }
         // $var->prop->method()
         $var = $prop->children['expr'];
         if ($var->children['name'] == 'this') {
             // If we're not in a class scope, 'this' won't work
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             // Get the class in scope
             $clazz = $this->code_base->getClassByFQSEN($this->context->getClassFQSEN());
             if ($prop->children['prop'] instanceof Node) {
                 // $this->$prop->method() - too dynamic, give up
                 return '';
             }
             $property_name = $prop->children['prop'];
             if ($clazz->hasPropertyWithName($this->code_base, $property_name)) {
                 try {
                     $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
                 } catch (AccessException $exception) {
                     Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                     return '';
                 }
                 // Find the first viable property type
                 foreach ($property->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
                     $class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
                     if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
                         return (string) $class_fqsen;
                     }
                 }
             }
             // No such property was found, or none were classes
             // that could be found
             return '';
         }
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_METHOD_CALL) {
         // Get the type returned by the first method
         // call.
         $union_type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
         // Find the subset of types that are viable
         // classes
         $viable_class_types = $union_type->nonNativeTypes()->nonGenericArrayTypes();
         // If there are no non-native types, give up
         if ($viable_class_types->isEmpty()) {
             return '';
         }
         // Return the first non-native type in the
         // list and hope its a class
         return (string) $viable_class_types->head();
     }
     return '';
 }
예제 #25
0
파일: Method.php 프로젝트: themarios/phan
 /**
  * @param Context $context
  * The context in which the node appears
  *
  * @param CodeBase $code_base
  *
  * @param Node $node
  * An AST node representing a method
  *
  * @return Method
  * A Method representing the AST node in the
  * given context
  *
  *
  * @see \Phan\Deprecated\Pass1::node_func
  * Formerly 'function node_func'
  */
 public static function fromNode(Context $context, CodeBase $code_base, Node $node) : Method
 {
     // Parse the comment above the method to get
     // extra meta information about the method.
     $comment = Comment::fromStringInContext($node->docComment ?? '', $context);
     // @var Parameter[]
     // The list of parameters specified on the
     // method
     $parameter_list = Parameter::listFromNode($context, $code_base, $node->children['params']);
     // Add each parameter to the scope of the function
     foreach ($parameter_list as $parameter) {
         $context = $context->withScopeVariable($parameter);
     }
     // Create the skeleton method object from what
     // we know so far
     $method = new Method($context, $node->name, new UnionType(), $node->flags ?? 0);
     // If the method is Analyzable, set the node so that
     // we can come back to it whenever we like and
     // rescan it
     $method->setNode($node);
     // Set the parameter list on the method
     $method->setParameterList($parameter_list);
     $method->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isRequired() ? 1 : 0);
     }, 0));
     $method->setNumberOfOptionalParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isOptional() ? 1 : 0);
     }, 0));
     // Check to see if the comment specifies that the
     // method is deprecated
     $method->setIsDeprecated($comment->isDeprecated());
     // Take a look at method return types
     if ($node->children['returnType'] !== null) {
         $union_type = UnionType::fromSimpleNode($context, $node->children['returnType']);
         $method->getUnionType()->addUnionType($union_type);
     } else {
         if ($comment->hasReturnUnionType()) {
             // See if we have a return type specified in the comment
             $union_type = $comment->getReturnType();
             if ($union_type->hasSelfType()) {
                 // We can't actually figure out 'static' at this
                 // point, but fill it in regardless. It will be partially
                 // correct
                 if ($context->hasClassFQSEN()) {
                     // n.b.: We're leaving the reference to self, static
                     //       or $this in the type because I'm guessing
                     //       it doesn't really matter. Apologies if it
                     //       ends up being an issue.
                     $union_type->addUnionType($context->getClassFQSEN()->asUnionType());
                 }
             }
             $method->getUnionType()->addUnionType($union_type);
         }
     }
     // Add params to local scope for user functions
     if ($context->getFile() != 'internal') {
         $parameter_offset = 0;
         foreach ($method->getParameterList() as $i => $parameter) {
             if ($parameter->getUnionType()->isEmpty()) {
                 // If there is no type specified in PHP, check
                 // for a docComment with @param declarations. We
                 // assume order in the docComment matches the
                 // parameter order in the code
                 if ($comment->hasParameterWithNameOrOffset($parameter->getName(), $parameter_offset)) {
                     $comment_type = $comment->getParameterWithNameOrOffset($parameter->getName(), $parameter_offset)->getUnionType();
                     $parameter->getUnionType()->addUnionType($comment_type);
                 }
             }
             // If there's a default value on the parameter, check to
             // see if the type of the default is cool with the
             // specified type.
             if ($parameter->hasDefaultValue()) {
                 $default_type = $parameter->getDefaultValueType();
                 if (!$default_type->canCastToUnionType($parameter->getUnionType())) {
                     Log::err(Log::ETYPE, "Default value for {$parameter->getUnionType()} \${$parameter->getName()} can't be {$default_type}", $context->getFile(), $node->lineno);
                 }
                 // If we have no other type info about a parameter,
                 // just because it has a default value of null
                 // doesn't mean that is its type. Any type can default
                 // to null
                 if ((string) $default_type === 'null' && !$parameter->getUnionType()->isEmpty()) {
                     $parameter->getUnionType()->addType(NullType::instance());
                 }
             }
             ++$parameter_offset;
         }
     }
     return $method;
 }
예제 #26
0
 /**
  * Visit a node with kind `\ast\AST_METHOD_CALL`
  *
  * @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 visitMethodCall(Node $node) : UnionType
 {
     $class_name = AST::classNameFromNode($this->context, $this->code_base, $node);
     if (empty($class_name)) {
         return new UnionType();
     }
     $class_fqsen = FullyQualifiedClassName::fromstringInContext($class_name, $this->context);
     assert($this->code_base->hasClassWithFQSEN($class_fqsen), "Class {$class_fqsen} must exist");
     $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
     $method_name = $node->children['method'];
     // Give up on any complicated nonsense where the
     // method name is a variable such as in
     // `$variable->$function_name()`.
     if ($method_name instanceof Node) {
         return new UnionType();
     }
     // Method names can some times turn up being
     // other method calls.
     assert(is_string($method_name), "Method name must be a string. Something else given.");
     if (!$clazz->hasMethodWithName($this->code_base, $method_name)) {
         Log::err(Log::EUNDEF, "call to undeclared method {$class_fqsen}->{$method_name}()", $this->context->getFile(), $node->lineno);
         return new UnionType();
     }
     $method = $clazz->getMethodByNameInContext($this->code_base, $method_name, $this->context);
     return $method->getUnionType();
 }
예제 #27
0
 /**
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @return Clazz
  * Get the class in this scope, or fail real hard
  */
 public function getClassInScope(CodeBase $code_base) : Clazz
 {
     assert($this->isInClassScope(), "Must be in class scope to get class");
     if (!$code_base->hasClassWithFQSEN($this->getClassFQSEN())) {
         Log::err(Log::EFATAL, "Cannot find class with FQSEN {$this->getClassFQSEN()} in context {$this}", $this->getFile(), 0);
     }
     return $code_base->getClassByFQSEN($this->getClassFQSEN());
 }
예제 #28
0
파일: AST.php 프로젝트: tmli3b3rm4n/phan
 /**
  * Perform some backwards compatibility checks on a node
  *
  * @param Context $context
  * The context in which the node appears
  *
  * @param Node $node
  * The node we'd like to check
  *
  * @return null
  *
  * @see \Phan\Deprecated::bc_check
  * Formerly `function bc_check`
  */
 public static function backwardCompatibilityCheck(Context $context, Node $node)
 {
     if (empty($node->children['expr'])) {
         return;
     }
     if ($node->kind !== \ast\AST_DIM) {
         if (!$node->children['expr'] instanceof Node) {
             return;
         }
         if ($node->children['expr']->kind !== \ast\AST_DIM) {
             AST::backwardCompatibilityCheck($context, $node->children['expr']);
             return;
         }
         $temp = $node->children['expr']->children['expr'];
         $lnode = $temp;
     } else {
         $temp = $node->children['expr'];
         $lnode = $temp;
     }
     if (!($temp->kind == \ast\AST_PROP || $temp->kind == \ast\AST_STATIC_PROP)) {
         return;
     }
     while ($temp instanceof Node && ($temp->kind == \ast\AST_PROP || $temp->kind == \ast\AST_STATIC_PROP)) {
         $lnode = $temp;
         // Lets just hope the 0th is the expression
         // we want
         $temp = array_values($temp->children)[0];
     }
     if (!$temp instanceof Node) {
         return;
     }
     // Foo::$bar['baz'](); is a problem
     // Foo::$bar['baz'] is not
     if ($lnode->kind === \ast\AST_STATIC_PROP && $node->kind !== \ast\AST_CALL) {
         return;
     }
     if (($lnode->children['prop'] instanceof Node && $lnode->children['prop']->kind == \ast\AST_VAR || !empty($lnode->children['class']) && $lnode->children['class'] instanceof Node && ($lnode->children['class']->kind == \ast\AST_VAR || $lnode->children['class']->kind == \ast\AST_NAME)) && ($temp->kind == \ast\AST_VAR || $temp->kind == \ast\AST_NAME)) {
         $ftemp = new \SplFileObject($context->getFile());
         $ftemp->seek($node->lineno - 1);
         $line = $ftemp->current();
         unset($ftemp);
         if (strpos($line, '}[') === false || strpos($line, ']}') === false || strpos($line, '>{') === false) {
             Log::err(Log::ECOMPAT, "expression may not be PHP 7 compatible", $context->getFile(), $node->lineno ?? 0);
         }
     }
 }
예제 #29
0
 /**
  * @param Node $node
  * A node to check to see if its a no-op
  *
  * @param string $message
  * A message to emit if its a no-op
  *
  * @return null
  */
 private function analyzeNoOp(Node $node, string $message)
 {
     if ($this->parent_node instanceof Node && $this->parent_node->kind == \ast\AST_STMT_LIST) {
         Log::err(Log::ENOOP, $message, $this->context->getFile(), $node->lineno);
     }
 }
예제 #30
0
파일: CLI.php 프로젝트: hslatman/phan
 /**
  * Create and read command line arguments, configuring
  * \Phan\Config as a side effect.
  */
 public function __construct()
 {
     global $argv;
     // Parse command line args
     $opts = getopt("f:m:o:c:haqbrpis:3:t::");
     foreach ($opts ?? [] as $key => $value) {
         switch ($key) {
             case 'h':
                 $this->usage();
                 break;
             case 'f':
                 if (is_file($value) && is_readable($value)) {
                     $this->file_list = file($value, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
                 } else {
                     Log::err(Log::EFATAL, "Unable to open {$value}");
                 }
                 break;
             case 'm':
                 if (!in_array($value, ['verbose', 'short', 'json', 'csv'])) {
                     $this->usage("Unknown output mode: {$value}");
                 }
                 Log::setOutputMode($value);
                 break;
             case 'c':
                 Config::get()->parent_constructor_required = explode(',', $value);
                 break;
             case 'q':
                 Config::get()->quick_mode = true;
                 break;
             case 'b':
                 Config::get()->backward_compatibility_checks = true;
                 break;
             case 'p':
                 Config::get()->progress_bar = true;
                 break;
             case 'a':
                 Config::get()->dump_ast = true;
                 break;
             case 'o':
                 Log::setFilename($value);
                 break;
             case 'i':
                 Log::setOutputMask(Log::getOutputMask() ^ Log::EUNDEF);
                 break;
             case 't':
                 Config::get()->emit_trace_id = true;
                 break;
             case '3':
                 Config::get()->third_party_directory_list = explode(',', $value);
                 break;
             case 's':
                 Config::get()->serialized_code_base_file = $value;
                 break;
             case 'r':
                 Config::get()->reanalyze_file_list = true;
                 break;
             default:
                 $this->usage("Unknown option '-{$key}'");
                 break;
         }
     }
     $pruneargv = array();
     foreach ($opts ?? [] as $opt => $value) {
         foreach ($argv as $key => $chunk) {
             $regex = '/^' . (isset($opt[1]) ? '--' : '-') . $opt . '/';
             if ($chunk == $value && $argv[$key - 1][0] == '-' || preg_match($regex, $chunk)) {
                 array_push($pruneargv, $key);
             }
         }
     }
     while ($key = array_pop($pruneargv)) {
         unset($argv[$key]);
     }
     if (empty($this->file_list) && count($argv) < 2) {
         Log::err(Log::EFATAL, "No files to analyze");
     }
     foreach ($argv as $arg) {
         if ($arg[0] == '-') {
             $this->usage("Unknown option '{$arg}'");
         }
     }
     $this->file_list = array_merge($this->file_list, array_slice($argv, 1));
 }