public static getStdClassFQSEN ( ) : |
||
return | The FQSEN for \stdClass. |
/** * @param string $name * The name of the property * * @param Context $context * The context of the caller requesting the property * * @return Property * A property with the given name * * @throws IssueException * An exception may be thrown if the caller does not * have access to the given property from the given * context */ public function getPropertyByNameInContext(CodeBase $code_base, string $name, Context $context) : Property { // Get the FQSEN of the property we're looking for $property_fqsen = FullyQualifiedPropertyName::make($this->getFQSEN(), $name); $property = null; // Figure out if we have the property $has_property = $code_base->hasPropertyWithFQSEN($property_fqsen); // Figure out if the property is accessible $is_property_accessible = false; if ($has_property) { $property = $code_base->getPropertyByFQSEN($property_fqsen); $is_remote_access = !$context->isInClassScope() || !$context->getClassInScope($code_base)->getUnionType()->canCastToExpandedUnionType($this->getUnionType(), $code_base); $is_property_accessible = !$is_remote_access || $property->isPublic(); } // If the property exists and is accessible, return it if ($is_property_accessible) { return $property; } // Check to see if we can use a __get magic method if ($this->hasMethodWithName($code_base, '__get')) { $method = $this->getMethodByName($code_base, '__get'); // Make sure the magic method is accessible if ($method->isPrivate()) { throw new IssueException(Issue::fromType(Issue::AccessPropertyPrivate)($context->getFile(), $context->getLineNumberStart(), [(string) $property_fqsen])); } else { if ($method->isProtected()) { throw new IssueException(Issue::fromType(Issue::AccessPropertyProtected)($context->getFile(), $context->getLineNumberStart(), [(string) $property_fqsen])); } } $property = new Property($context, $name, $method->getUnionType(), 0, $property_fqsen); $this->addProperty($code_base, $property, new None()); return $property; } else { if ($has_property) { // If we have a property, but its inaccessible, emit // an issue if ($property->isPrivate()) { throw new IssueException(Issue::fromType(Issue::AccessPropertyPrivate)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"])); } if ($property->isProtected()) { throw new IssueException(Issue::fromType(Issue::AccessPropertyProtected)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"])); } } } // Check to see if missing properties are allowed // or we're stdclass if (Config::get()->allow_missing_properties || $this->getFQSEN() == FullyQualifiedClassName::getStdClassFQSEN()) { $property = new Property($context, $name, new UnionType(), 0, $property_fqsen); $this->addProperty($code_base, $property, new None()); return $property; } throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$name}}"])); }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitProp(Node $node) : Context { $property_name = $node->children['prop']; // Things like $foo->$bar if (!is_string($property_name)) { return $this->context; } assert(is_string($property_name), "Property must be string"); try { $class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList(); } catch (CodeBaseException $exception) { // This really shouldn't happen since the code // parsed cleanly. This should fatal. // throw $exception; return $this->context; } catch (\Exception $exception) { // If we can't figure out what kind of a class // this is, don't worry about it return $this->context; } foreach ($class_list as $clazz) { // Check to see if this class has the property or // a setter if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) { if (!$clazz->hasMethodWithName($this->code_base, '__set')) { continue; } } try { $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context); } catch (IssueException $exception) { Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance()); return $this->context; } if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) { $this->emitIssue(Issue::TypeMismatchProperty, $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType()); return $this->context; } else { // If we're assigning to an array element then we don't // know what the constitutation of the parameter is // outside of the scope of this assignment, so we add to // its union type rather than replace it. if ($this->is_dim_assignment) { $property->getUnionType()->addUnionType($this->right_type); } } // After having checked it, add this type to it $property->getUnionType()->addUnionType($this->right_type); return $this->context; } $std_class_fqsen = FullyQualifiedClassName::getStdClassFQSEN(); if (Config::get()->allow_missing_properties || !empty($class_list) && $class_list[0]->getFQSEN() == $std_class_fqsen) { try { // Create the property $property = (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name); $property->getUnionType()->addUnionType($this->right_type); } catch (\Exception $exception) { // swallow it } } elseif (!empty($class_list)) { $this->emitIssue(Issue::UndeclaredProperty, $node->lineno ?? 0, "{$class_list[0]->getFQSEN()}->{$property_name}"); } else { // If we hit this part, we couldn't figure out // the class, so we ignore the issue } return $this->context; }
/** * @param string|Node $property_name * The name of the property we're looking up * * @return Property * A variable in scope or a new variable * * @throws NodeException * An exception is thrown if we can't understand the node * * @throws IssueException * An exception is thrown if we can't find the given * class or if we don't have access to the property (its * private or protected). * * @throws TypeException * An exception may be thrown if the only viable candidate * is a non-class type. * * @throws UnanalyzableException * An exception is thrown if we hit a construct in which * we can't determine if the property exists or not */ public function getProperty($property_name) : Property { $property_name = $this->node->children['prop']; // Give up for things like C::$prop_name if (!is_string($property_name)) { throw new NodeException($this->node, "Cannot figure out non-string property name"); } $class_fqsen = null; try { $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList(true); } catch (CodeBaseException $exception) { throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($this->context->getFile(), $this->node->lineno ?? 0, ["{$exception->getFQSEN()}->{$property_name}"])); } foreach ($class_list as $i => $class) { $class_fqsen = $class->getFQSEN(); // Keep hunting if this class doesn't have the given // property if (!$class->hasPropertyWithName($this->code_base, $property_name)) { // If there's a getter on properties then all // bets are off. if ($class->hasGetMethod($this->code_base)) { throw new UnanalyzableException($this->node, "Can't determine if property {$property_name} exists in class {$class->getFQSEN()} with __get defined"); } continue; } $property = $class->getPropertyByNameInContext($this->code_base, $property_name, $this->context); if ($property->isDeprecated()) { throw new IssueException(Issue::fromType(Issue::DeprecatedProperty)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $property->getFQSEN(), $property->getFileRef()->getFile(), $property->getFileRef()->getLineNumberStart()])); } return $property; } $std_class_fqsen = FullyQualifiedClassName::getStdClassFQSEN(); // If missing properties are cool, create it on // the first class we found if ($class_fqsen && $class_fqsen === $std_class_fqsen || Config::get()->allow_missing_properties) { if (count($class_list) > 0) { $class = $class_list[0]; return $class->getPropertyByNameInContext($this->code_base, $property_name, $this->context); } } // If the class isn't found, we'll get the message elsewhere if ($class_fqsen) { throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($this->context->getFile(), $this->node->lineno ?? 0, ["{$class_fqsen}->{$property_name}"])); } throw new NodeException($this->node, "Cannot figure out property from {$this->context}"); }