Example #1
0
 /**
  * @param Context $context
  * The context of the current execution
  *
  * @param CodeBase $code_base
  * The global code base
  *
  * @param string $class_name
  * The name we're trying to validate
  */
 public function __construct(Context $context, CodeBase $code_base, string $class_name)
 {
     $this->context = $context;
     $this->code_base = $code_base;
     $this->class_name = $class_name;
     // Compute the FQSEN based on the current context
     $this->class_fqsen = FullyQualifiedClassName::fromStringInContext($this->class_name, $this->context);
 }
Example #2
0
 public function testFullyQualifiedClassName()
 {
     $this->assertFQSENEqual(FullyQualifiedClassName::make('Name\\Space', 'A'), '\\Name\\Space\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::make('', 'A'), '\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromFullyQualifiedString('A'), '\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromFullyQualifiedString('\\Name\\Space\\A'), '\\Name\\Space\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromFullyQualifiedString('\\Namespace\\A,1'), '\\Namespace\\a,1');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromStringInContext('\\Namespace\\A', $this->context), '\\Namespace\\a');
     $this->assertFQSENEqual(FullyQualifiedClassName::fromStringInContext('A', $this->context), '\\a');
 }
Example #3
0
 /**
  * Visit a node with kind `\ast\AST_CLASS`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitClass(Decl $node) : Context
 {
     if ($node->flags & \ast\flags\CLASS_ANONYMOUS) {
         $class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
     } else {
         $class_name = (string) $node->name;
     }
     assert(!empty($class_name), "Class name cannot be empty");
     $alternate_id = 0;
     // Hunt for the alternate of this class defined
     // in this file
     do {
         $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $this->context)->withAlternateId($alternate_id++);
         if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
             throw new CodeBaseException($class_fqsen, "Can't find class {$class_fqsen} - aborting");
         }
         $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
     } while ($this->context->getProjectRelativePath() != $clazz->getContext()->getProjectRelativePath() || $this->context->getLineNumberStart() != $clazz->getContext()->getLineNumberStart());
     return $clazz->getContext()->withClassFQSEN($class_fqsen);
 }
Example #4
0
 /**
  * Visit a node with kind `\ast\AST_USE_TRAIT`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitUseTrait(Node $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     $trait_fqsen_string_list = (new ContextNode($this->code_base, $this->context, $node->children['traits']))->getQualifiedNameList();
     // Add each trait to the class
     foreach ($trait_fqsen_string_list as $trait_fqsen_string) {
         $trait_fqsen = FullyQualifiedClassName::fromStringInContext($trait_fqsen_string, $this->context);
         $clazz->addTraitFQSEN($trait_fqsen);
     }
     return $this->context;
 }
Example #5
0
 /**
  * @return FQSEN
  */
 public function getFQSEN() : FullyQualifiedClassName
 {
     // Allow overrides
     if ($this->fqsen) {
         return $this->fqsen;
     }
     return FullyQualifiedClassName::fromStringInContext($this->getName(), $this->getContext());
 }
Example #6
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();
 }
 /**
  * @param Context $context
  * The context in which the FQSEN string was found
  *
  * @param $fqsen_string
  * An FQSEN string like '\Namespace\Class::methodName'
  *
  * @return FullyQualifiedMethodName
  */
 public static function fromStringInContext(string $fqsen_string, Context $context)
 {
     // Test to see if we have a class defined
     if (false === strpos($fqsen_string, '::')) {
         assert($context->isInClassScope(), "Cannot reference class element without class name when not in class scope.");
         $fully_qualified_class_name = $context->getClassFQSEN();
     } else {
         assert(false !== strpos($fqsen_string, '::'), "Fully qualified class element lacks '::' delimiter");
         list($class_name_string, $fqsen_string) = explode('::', $fqsen_string);
         $fully_qualified_class_name = FullyQualifiedClassName::fromStringInContext($class_name_string, $context);
     }
     // Split off the alternate ID
     $parts = explode(',', $fqsen_string);
     $name = $parts[0];
     $alternate_id = (int) ($parts[1] ?? 0);
     assert(is_int($alternate_id), "Alternate must be an integer");
     return static::make($fully_qualified_class_name, $name, $alternate_id);
 }
Example #8
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;
 }
Example #9
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 visitVar(Node $node) : string
 {
     // $$var->method()
     if ($node->children['name'] instanceof Node) {
         return '';
     }
     // $this->method()
     if ($node->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['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);
         }
     }
     // We land here if we have a variable
     // with a native type or no known type.
     return '';
 }
Example #10
0
 /**
  * @param Node $node
  * The node that has a reference to a class
  *
  * @param Context $context
  * The context in which we found the node
  *
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @param bool $validate_class_name
  * If true, we'll validate that the name of the class
  * is valid.
  *
  * @return Clazz
  * The class being referenced in the given node in
  * the given context
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws CodeBaseExtension
  * An exception is thrown if we can't find the referenced
  * class
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public static function classFromNodeInContext(Node $node, Context $context, CodeBase $code_base, bool $validate_class_name = true) : Clazz
 {
     // Figure out the name of the class
     $class_name = self::classNameFromNode($context, $code_base, $node, $validate_class_name);
     // If we can't figure out the class name (which happens
     // from time to time), then give up
     if (empty($class_name)) {
         throw new NodeException($node, 'Could not find class name');
     }
     $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $context);
     // Check to see if the class actually exists
     if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
         throw new CodeBaseException("Can't find class {$class_fqsen}");
     }
     $class = $code_base->getClassByFQSEN($class_fqsen);
     return $class;
 }
Example #11
0
 /**
  * Visit a node with kind `\ast\AST_STATIC_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 visitStaticCall(Node $node) : UnionType
 {
     $class_name = AST::classNameFromNode($this->context, $this->code_base, $node);
     // assert(!empty($class_name), 'Class name cannot be empty');
     if (!$class_name) {
         return new UnionType();
     }
     $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.");
     $method_fqsen = FullyQualifiedMethodName::make(FullyQualifiedClassName::fromStringInContext($class_name, $this->context), $method_name);
     if (!$this->code_base->hasMethod($method_fqsen)) {
         return new UnionType();
     }
     $method = $this->code_base->getMethod($method_fqsen);
     return $method->getUnionType();
 }
Example #12
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 '';
 }
Example #13
0
File: Clazz.php Project: etsy/phan
 /**
  * @param CodeBase $code_base
  * A reference to the entire code base in which this
  * context exists
  *
  * @param ReflectionClass $class
  * A reflection class representing a builtin class.
  *
  * @return Clazz
  * A Class structural element representing the given named
  * builtin.
  */
 public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz
 {
     // Build a set of flags based on the constitution
     // of the built-in class
     $flags = 0;
     if ($class->isFinal()) {
         $flags = \ast\flags\CLASS_FINAL;
     } elseif ($class->isInterface()) {
         $flags = \ast\flags\CLASS_INTERFACE;
     } elseif ($class->isTrait()) {
         $flags = \ast\flags\CLASS_TRAIT;
     }
     if ($class->isAbstract()) {
         $flags |= \ast\flags\CLASS_ABSTRACT;
     }
     $context = new Context();
     $class_fqsen = FullyQualifiedClassName::fromStringInContext($class->getName(), $context);
     // Build a base class element
     $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags, $class_fqsen);
     // If this class has a parent class, add it to the
     // class info
     if ($parent_class = $class->getParentClass()) {
         $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName());
         $parent_type = $parent_class_fqsen->asType();
         $clazz->setParentType($parent_type);
     }
     // n.b.: public properties on internal classes don't get
     //       listed via reflection until they're set unless
     //       they have a default value. Therefore, we don't
     //       bother iterating over `$class->getProperties()`
     //       `$class->getStaticProperties()`.
     foreach ($class->getDefaultProperties() as $name => $value) {
         $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $name);
         $property = new Property($property_context, $name, Type::fromObject($value)->asUnionType(), 0, $property_fqsen);
         $clazz->addProperty($code_base, $property, new None());
     }
     foreach (UnionType::internalPropertyMapForClassName($clazz->getName()) as $property_name => $property_type_string) {
         $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $property_type = UnionType::fromStringInContext($property_type_string, new Context());
         $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $property_name);
         $property = new Property($property_context, $property_name, $property_type, 0, $property_fqsen);
         $clazz->addProperty($code_base, $property, new None());
     }
     foreach ($class->getInterfaceNames() as $name) {
         $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getTraitNames() as $name) {
         $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getConstants() as $name => $value) {
         $constant_fqsen = FullyQualifiedClassConstantName::make($clazz->getFQSEN(), $name);
         $constant = new ClassConstant($context, $name, Type::fromObject($value)->asUnionType(), 0, $constant_fqsen);
         $clazz->addConstant($code_base, $constant);
     }
     foreach ($class->getMethods() as $reflection_method) {
         $method_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $method_list = FunctionFactory::methodListFromReflectionClassAndMethod($method_context, $code_base, $class, $reflection_method);
         foreach ($method_list as $method) {
             $clazz->addMethod($code_base, $method, new None());
         }
     }
     return $clazz;
 }
Example #14
0
 /**
  * @param CodeBase $code_base
  * A reference to the entire code base in which this
  * context exists
  *
  * @param ReflectionClass $class
  * A reflection class representing a builtin class.
  *
  * @return Clazz
  * A Class structural element representing the given named
  * builtin.
  */
 public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz
 {
     // Build a set of flags based on the constitution
     // of the built-in class
     $flags = 0;
     if ($class->isFinal()) {
         $flags = \ast\flags\CLASS_FINAL;
     } elseif ($class->isInterface()) {
         $flags = \ast\flags\CLASS_INTERFACE;
     } elseif ($class->isTrait()) {
         $flags = \ast\flags\CLASS_TRAIT;
     }
     if ($class->isAbstract()) {
         $flags |= \ast\flags\CLASS_ABSTRACT;
     }
     $context = new Context();
     // Build a base class element
     $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags);
     $clazz->setFQSEN(FullyQualifiedClassName::fromStringInContext($class->getName(), $context));
     // If this class has a parent class, add it to the
     // class info
     if ($parent_class = $class->getParentClass()) {
         $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName());
         $clazz->setParentClassFQSEN($parent_class_fqsen);
     }
     foreach ($class->getDefaultProperties() as $name => $value) {
         // TODO: whats going on here?
         $reflection_property = new \ReflectionProperty($class->getName(), $name);
         $property_context = $context->withClassFQSEN($clazz->getFQSEN());
         $property = new Property($property_context, $name, Type::fromObject($value)->asUnionType(), 0);
         $property->setFQSEN(FullyQualifiedPropertyName::make($clazz->getFQSEN(), $name));
         $clazz->addProperty($code_base, $property);
     }
     foreach ($class->getInterfaceNames() as $name) {
         $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getTraitNames() as $name) {
         $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getConstants() as $name => $value) {
         $constant = new ClassConstant($context, $name, Type::fromObject($value)->asUnionType(), 0);
         $constant->setFQSEN(FullyQualifiedClassConstantName::make($clazz->getFQSEN(), $name));
         $clazz->addConstant($code_base, $constant);
     }
     foreach ($class->getMethods() as $reflection_method) {
         $method_list = FunctionFactory::methodListFromReflectionClassAndMethod($context->withClassFQSEN($clazz->getFQSEN()), $code_base, $class, $reflection_method);
         foreach ($method_list as $method) {
             $clazz->addMethod($code_base, $method);
         }
     }
     return $clazz;
 }