/** * @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 = AST::classFromNodeInContext($node, $this->context, $this->code_base); } 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 AST::getOrCreatePropertyFromNodeInContext($property_name, $node, $this->context, $this->code_base); } 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; }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitStaticCall(Node $node) : Context { // Get the name of the method being called $method_name = $node->children['method']; // Give up on things like Class::$var if (!is_string($method_name)) { return $this->context; } // Get the name of the static class being referenced $static_class = ''; if ($node->children['class']->kind == \ast\AST_NAME) { $static_class = $node->children['class']->children['name']; } // Short circuit on a constructor being called statically // on something other than 'parent' if ($method_name === '__construct') { if ($static_class !== 'parent') { Log::err(Log::EUNDEF, "static call to undeclared method {$static_class}::{$method_name}()", $this->context->getFile(), $node->lineno); } return $this->context; } try { // Get a reference to the method being called $method = AST::classMethodFromNodeInContext($node, $this->context, $this->code_base, $method_name, true); // If the method isn't static and we're not calling // it on 'parent', we're in a bad spot. if (!$method->isStatic() && 'parent' !== $static_class) { $clazz = AST::classFromNodeInContext($node, $this->context, $this->code_base); Log::err(Log::ESTATIC, "static call to non-static method {$clazz->getFQSEN()}::{$method_name}()" . " defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()}", $this->context->getFile(), $node->lineno); } // Make sure the parameters look good $this->analyzeCallToMethod($this->code_base, $method, $node); } catch (CodeBaseException $exception) { Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno); return $this->context; } catch (NodeException $exception) { // If we can't figure out what kind of a call // this is, don't worry about it return $this->context; } return $this->context; }
/** * Visit a node with kind `\ast\AST_CLASS_CONST` * * @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 visitClassConst(Node $node) : UnionType { $constant_name = $node->children['const']; if ($constant_name == 'class') { return StringType::instance()->asUnionType(); // class name fetch } try { $defining_clazz = AST::classFromNodeInContext($node, $this->context, $this->code_base, false); } catch (CodeBaseException $exception) { $class_name = $node->children['class']->children['name'] ?? ''; Log::err(Log::EUNDEF, "Can't access constant {$constant_name} from undeclared class {$class_name}", $this->context->getFile(), $node->lineno); return new UnionType(); } catch (NodeException $exception) { $class_name = $node->children['class']->children['name'] ?? ''; Log::err(Log::EUNDEF, "Can't access constant {$constant_name} from undeclared class {$class_name}", $this->context->getFile(), $node->lineno); // If we can't figure out what kind of a call // this is, don't worry about it return new UnionType(); } if (!$defining_clazz->hasConstantWithName($this->code_base, $constant_name)) { Log::err(Log::EUNDEF, "Can't access undeclared constant {$defining_clazz->getName()}::{$constant_name}", $this->context->getFile(), $node->lineno); return new UnionType(); } return $defining_clazz->getConstantWithName($this->code_base, $constant_name)->getUnionType(); }