/** * @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 AST::unqualifiedNameForAnonymousClassNode($node->children['class'], $this->context); } // 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 ''; }
/** * @param Node|string $method_name * Either then name of the method or a node that * produces the name of the method. * * @param bool $is_static * Set to true if this is a static method call * * @return Method * A method with the given name on the class referenced * from the given node * * @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 given * method * * @throws TypeException * An exception may be thrown if the only viable candidate * is a non-class type. * * @throws IssueException */ public function getMethod($method_name, bool $is_static) : Method { if ($method_name instanceof Node) { // The method_name turned out to be a variable. // There isn't much we can do to figure out what // it's referring to. throw new NodeException($method_name, "Unexpected method node"); } assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}"); try { $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList(); } catch (CodeBaseException $exception) { throw new IssueException(Issue::fromType(Issue::UndeclaredClassMethod)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $exception->getFQSEN()])); } // If there were no classes on the left-type, figure // out what we were trying to call the method on // and send out an error. if (empty($class_list)) { $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']); if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasAnyType([MixedType::instance(), ObjectType::instance(), StringType::instance()]) && !(Config::get()->null_casts_as_any_type && $union_type->hasType(NullType::instance()))) { throw new IssueException(Issue::fromType(Issue::NonClassMethodCall)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $union_type])); } throw new NodeException($this->node, "Can't figure out method call for {$method_name}"); } // Hunt to see if any of them have the method we're // looking for foreach ($class_list as $i => $class) { if ($class->hasMethodWithName($this->code_base, $method_name)) { return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context); } else { if ($class->hasMethodWithName($this->code_base, '__call')) { return $class->getMethodByNameInContext($this->code_base, '__call', $this->context); } } } // Figure out an FQSEN for the method we couldn't find $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name); if ($is_static) { throw new IssueException(Issue::fromType(Issue::UndeclaredStaticMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen])); } throw new IssueException(Issue::fromType(Issue::UndeclaredMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen])); }
/** * @return Clazz[] * A list of classes representing the non-native types * associated with the given node * * @throws CodeBaseException * An exception is thrown if a non-native type does not have * an associated class */ public function getClassList() { $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node); $class_list = []; foreach ($union_type->asClassList($this->code_base) as $i => $clazz) { $class_list[] = $clazz; } return $class_list; }
/** * @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 = (new ContextNode($this->code_base, $this->context, $node->children['class']))->getClassList(); } catch (CodeBaseException $exception) { Issue::emit(Issue::UndeclaredClassCatch, $this->context->getFile(), $node->lineno ?? 0, (string) $exception->getFQSEN()); } $variable_name = (new ContextNode($this->code_base, $this->context, $node->children['var']))->getVariableName(); 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; }
/** * @param Context $context * @param CodeBase $code_base * @param Node|string|null $node * * @return UnionType * * @see \Phan\Deprecated\Pass2::node_type * Formerly 'function node_type' */ public static function fromNode(Context $context, CodeBase $code_base, $node) : UnionType { return UnionTypeVisitor::unionTypeFromNode($code_base, $context, $node); }
/** * @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; }
/** * @param Context $context * The context of the parser at the node for which we'd * like to determine a type * * @param CodeBase $code_base * The code base within which we're operating * * @param Node|string|null $node * The node for which we'd like to determine its type * * @param bool $should_catch_issue_exception * Set to true to cause loggable issues to be thrown * instead of emitted as issues to the log. * * @return UnionType * * @throws IssueException * If $should_catch_issue_exception is false an IssueException may * be thrown for optional issues. */ public static function fromNode(Context $context, CodeBase $code_base, $node, bool $should_catch_issue_exception = true) : UnionType { return UnionTypeVisitor::unionTypeFromNode($code_base, $context, $node, $should_catch_issue_exception); }
/** * @param Node|string $method_name * Either then name of the method or a node that * produces the name of the method. * * @param bool $is_static * Set to true if this is a static method call * * @return Method * A method with the given name on the class referenced * from the given node * * @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 given * method * * @throws TypeException * An exception may be thrown if the only viable candidate * is a non-class type. */ public function getMethod($method_name, bool $is_static) : Method { if ($method_name instanceof Node) { // The method_name turned out to be a variable. // There isn't much we can do to figure out what // it's referring to. throw new NodeException($method_name, "Unexpected method node"); } assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}"); try { $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList(); } catch (CodeBaseException $exception) { // We can give a more explicit message throw new CodeBaseException($exception->getFQSEN(), "Can't access method {$method_name} from undeclared class {$exception->getFQSEN()}"); } // If there were no classes on the left-type, figure // out what we were trying to call the method on // and send out an error. if (empty($class_list)) { $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']); if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasType(MixedType::instance())) { throw new TypeException("Calling method on non-class type {$union_type}"); } throw new NodeException($this->node, "Can't figure out method call for {$method_name}"); } // Hunt to see if any of them have the method we're // looking for foreach ($class_list as $i => $class) { if ($class->hasMethodWithName($this->code_base, $method_name)) { return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context); } } // Figure out an FQSEN for the method we couldn't find $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name); if ($is_static) { throw new CodeBaseException($method_fqsen, "static call to undeclared method {$method_fqsen}"); } throw new CodeBaseException($method_fqsen, "call to undeclared method {$method_fqsen}"); }
/** * @param CodeBase $code_base * The code base in which we're looking for classes * * @param Context $context * The context in which the node exists * * @param Node $node * The node we want to get a type for and classes from * the types * * @return Clazz[] * A list of classes representing the non-native types * associated with the given node * * @throws CodeBaseException * An exception is thrown if a non-native type does not have * an associated class */ public static function classListFromNodeInContext(CodeBase $code_base, Context $context, $node) { $union_type = UnionTypeVisitor::unionTypeFromClassNode($code_base, $context, $node); $class_list = []; foreach ($union_type->asClassList($code_base) as $i => $clazz) { $class_list[] = $clazz; } return $class_list; }