public getClassByFQSEN ( |
||
$fqsen | The FQSEN of a class to get | |
return | A class with the given FQSEN |
public function testMethodInCodeBase() { $context = $this->contextForCode("\n namespace A;\n Class B {\n public function c() {\n return 42;\n }\n }\n "); $class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\A\\b'); self::assertTrue($this->code_base->hasClassWithFQSEN($class_fqsen), "Class with FQSEN {$class_fqsen} not found"); $clazz = $this->code_base->getClassByFQSEN($class_fqsen); self::assertTrue($clazz->hasMethodWithName($this->code_base, 'c'), "Method with FQSEN not found"); }
/** * @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 ''; }
/** * 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()); } }
/** * 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; }
/** * 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()) { Issue::emit(Issue::RedefineClassInternal, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class); // Otherwise, print the coordinates of the original // definition } else { Issue::emit(Issue::RedefineClass, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class, $original_class->getContext()->getFile(), $original_class->getContext()->getLineNumberStart()); } return; }
/** * 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(); }
/** * @return Clazz * The class that defined this element * * @throws CodeBaseException * An exception may be thrown if we can't find the * class */ public function getDefiningClass(CodeBase $code_base) : Clazz { $class_fqsen = $this->getDefiningClassFQSEN(); if (!$code_base->hasClassWithFQSEN($class_fqsen)) { throw new CodeBaseException($class_fqsen, "Defining class {$class_fqsen} for {$this->getFQSEN()} not found"); } return $code_base->getClassByFQSEN($class_fqsen); }
public function visitNew(Node $node) : bool { if (!$this->classExists()) { return $this->classExistsOrIsNative($node); } $clazz = $this->code_base->getClassByFQSEN($this->class_fqsen); if ($clazz->isAbstract()) { if (!$this->context->hasClassFQSEN() || $clazz->getFQSEN() != $this->context->getClassFQSEN()) { Log::err(Log::ETYPE, "Cannot instantiate abstract class {$this->class_name}", $this->context->getFile(), $node->lineno); return false; } return true; } if ($clazz->isInterface()) { if (!UnionType::fromStringInContext($this->class_name, $this->context)->isNativeType()) { Log::err(Log::ETYPE, "Cannot instantiate interface {$this->class_name}", $this->context->getFile(), $node->lineno); return false; } } return true; }
public function visitStaticProp(Node $node) : bool { if (!$this->classExists()) { return $this->classExistsOrIsNative($node); } $clazz = $this->code_base->getClassByFQSEN($this->class_fqsen); if (is_string($node->children['prop']) && !$clazz->hasPropertyWithName($this->code_base, $node->children['prop'])) { Log::err(Log::ETYPE, "Access to undeclared static property {$node->children['prop']} on {$this->class_name}", $this->context->getFile(), $node->lineno); return false; } return true; }
/** * @return Clazz[] * A list of classes associated with the given node * * @throws CodeBaseException * An exception is thrown if we can't find a class for * the given type */ private function classListFromNode(Node $node) { // Get the types associated with the node $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node); // Iterate over each viable class type to see if any // have the constant we're looking for foreach ($union_type->nonNativeTypes()->getTypeList() as $class_type) { // Get the class FQSEN $class_fqsen = $class_type->asFQSEN(); // See if the class exists if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) { throw new CodeBaseException($class_fqsen, "reference to undeclared class {$class_fqsen}"); } (yield $this->code_base->getClassByFQSEN($class_fqsen)); } }
/** * @return Clazz[] * A list of classes associated with the given node * * @throws IssueException * An exception is thrown if we can't find a class for * the given type */ private function classListFromNode(Node $node) { // Get the types associated with the node $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node); // Iterate over each viable class type to see if any // have the constant we're looking for foreach ($union_type->nonNativeTypes()->getTypeSet() as $class_type) { // Get the class FQSEN $class_fqsen = $class_type->asFQSEN(); // See if the class exists if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) { throw new IssueException(Issue::fromType(Issue::UndeclaredClassReference)($this->context->getFile(), $node->lineno ?? 0, [(string) $class_fqsen])); } (yield $this->code_base->getClassByFQSEN($class_fqsen)); } }
/** * @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 function getClass(bool $validate_class_name = true) : Clazz { // Figure out the name of the class $class_name = $this->getClassName($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($this->node, 'Could not find 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)) { throw new CodeBaseException($class_fqsen, "Can't find class {$class_fqsen}"); } $class = $this->code_base->getClassByFQSEN($class_fqsen); return $class; }
/** * @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()); }
/** * @return Clazz[] * The set of all alternates to this class */ public function alternateGenerator(CodeBase $code_base) : \Generator { $alternate_id = 0; $fqsen = $this->getFQSEN(); while ($code_base->hasClassWithFQSEN($fqsen)) { (yield $code_base->getClassByFQSEN($fqsen)); $fqsen = $fqsen->withAlternateId(++$alternate_id); } }
/** * @return Clazz[] * A list of classes representing the non-native types * associated with this UnionType * * @throws CodeBaseException * An exception is thrown if a non-native type does not have * an associated class */ public function asClassList(CodeBase $code_base) { // Iterate over each viable class type to see if any // have the constant we're looking for foreach ($this->nonNativeTypes()->getTypeList() as $class_type) { // Get the class FQSEN $class_fqsen = $class_type->asFQSEN(); // See if the class exists if (!$code_base->hasClassWithFQSEN($class_fqsen)) { throw new CodeBaseException($class_fqsen, "Cannot find class {$class_fqsen}"); } (yield $code_base->getClassByFQSEN($class_fqsen)); } }
public function importParentClass(CodeBase $code_base) { $this->memoize(__METHOD__, function () use($code_base) { if (!$this->hasParentClassFQSEN()) { return; } // Let the parent class finder worry about this if (!$code_base->hasClassWithFQSEN($this->getParentClassFQSEN())) { return; } assert($code_base->hasClassWithFQSEN($this->getParentClassFQSEN()), "Clazz {$this->getParentClassFQSEN()} should already have been proven to exist."); // Get the parent class $parent = $code_base->getClassByFQSEN($this->getParentClassFQSEN()); // Tell the parent to import its own parents first $parent->importAncestorClasses($code_base); // Import elements from the parent $this->importAncestorClass($code_base, $parent); }); }
/** * @param CodeBase $code_base * The entire code base from which we'll find ancestor * details * * @param FullyQualifiedClassName[] * A list of class FQSENs to turn into a list of * Clazz objects * * @return Clazz[] */ private function getClassListFromFQSENList(CodeBase $code_base, array $fqsen_list) : array { $class_list = []; foreach ($fqsen_list as $fqsen) { if ($code_base->hasClassWithFQSEN($fqsen)) { $class_list[] = $code_base->getClassByFQSEN($fqsen); } } return $class_list; }
public function isSubclassOf(CodeBase $code_base, Type $parent) { $fqsen = $this->asFQSEN(); assert($fqsen instanceof FullyQualifiedClassName); $this_clazz = $code_base->getClassByFQSEN($fqsen); $parent_fqsen = $parent->asFQSEN(); assert($parent_fqsen instanceof FullyQualifiedClassName); $parent_clazz = $code_base->getClassByFQSEN($parent_fqsen); return $this_clazz->isSubclassOf($code_base, $parent_clazz); }
/** * @param CodeBase $code_base * The global code base holding all state * * @return Clazz * Get the class in this scope, or fail real hard * * @throws CodeBaseException * Thrown if we can't find the class in scope within the * given codebase. */ public function getClassInScope(CodeBase $code_base) : Clazz { assert($this->isInClassScope(), "Must be in class scope to get class"); if (!$code_base->hasClassWithFQSEN($this->getClassFQSEN())) { throw new CodeBaseException($this->getClassFQSEN(), "Cannot find class with FQSEN {$this->getClassFQSEN()} in context {$this}"); } return $code_base->getClassByFQSEN($this->getClassFQSEN()); }
public function isSubclassOf(CodeBase $code_base, Type $parent) { $this_clazz = $code_base->getClassByFQSEN($this->asFQSEN()); $parent_clazz = $code_base->getClassByFQSEN($parent->asFQSEN()); return $this_clazz->isSubclassOf($code_base, $parent_clazz); }
/** * @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 ''; }
/** * @param CodeBase * The code base to use in order to find super classes, etc. * * @param $recursion_depth * This thing has a tendency to run-away on me. This tracks * how bad I messed up by seeing how far the expanded types * go * * @return UnionType * Expands class types to all inherited classes returning * a superset of this type. */ public function asExpandedTypes(CodeBase $code_base, int $recursion_depth = 0) : UnionType { return $this->memoize(__METHOD__, function () use($code_base, $recursion_depth) : UnionType { // We're going to assume that if the type hierarchy // is taller than some value we probably messed up // and should bail out. assert($recursion_depth < 20, "Recursion has gotten out of hand for type {$this}"); if ($this->isNativeType()) { return $this->asUnionType(); } $union_type = $this->asUnionType(); $class_fqsen = $this->isGenericArray() ? $this->genericArrayElementType()->asFQSEN() : $this->asFQSEN(); if (!$code_base->hasClassWithFQSEN($class_fqsen)) { return $union_type; } $clazz = $code_base->getClassByFQSEN($class_fqsen); $union_type->addUnionType($this->isGenericArray() ? $clazz->getUnionType()->asGenericArrayTypes() : $clazz->getUnionType()); // Resurse up the tree to include all types $recursive_union_type = new UnionType(); foreach ($union_type->getTypeSet() as $clazz_type) { if ((string) $clazz_type != (string) $this) { $recursive_union_type->addUnionType($clazz_type->asExpandedTypes($code_base, $recursion_depth + 1)); } else { $recursive_union_type->addType($clazz_type); } } return $recursive_union_type; }); }
/** * @param CodeBase * The code base to use in order to find super classes, etc. * * @param $recursion_depth * This thing has a tendency to run-away on me. This tracks * how bad I messed up by seeing how far the expanded types * go * * @return UnionType * Expands class types to all inherited classes returning * a superset of this type. */ public function asExpandedTypes(CodeBase $code_base, int $recursion_depth = 0) : UnionType { return $this->memoize(__METHOD__, function () use($code_base, $recursion_depth) : UnionType { assert($recursion_depth < 10, "Recursion has gotten out of hand for type {$this}"); if ($this->isNativeType()) { return $this->asUnionType(); } $union_type = $this->asUnionType(); $class_fqsen = $this->isGenericArray() ? $this->genericArrayElementType()->asFQSEN() : $this->asFQSEN(); if (!$code_base->hasClassWithFQSEN($class_fqsen)) { return $union_type; } $clazz = $code_base->getClassByFQSEN($class_fqsen); $union_type->addUnionType($this->isGenericArray() ? $clazz->getUnionType()->asGenericArrayTypes() : $clazz->getUnionType()); // Resurse up the tree to include all types $recursive_union_type = new UnionType(); foreach ($union_type->getTypeList() as $clazz_type) { if ((string) $clazz_type != (string) $this) { $recursive_union_type->addUnionType($clazz_type->asExpandedTypes($code_base, $recursion_depth + 1)); } else { $recursive_union_type->addType($clazz_type); } } return $recursive_union_type; }); }