/** * @param Node $node * An AST_VAR node * * @param Context $context * The context in which the variable is found * * @param CodeBase $code_base * * @return Variable * A variable begotten from a node */ public static function fromNodeInContext(Node $node, Context $context, CodeBase $code_base, bool $should_check_type = true) : Variable { $variable_name = AST::variableName($node); // Get the type of the assignment $union_type = $should_check_type ? UnionType::fromNode($context, $code_base, $node) : new UnionType(); $variable = new Variable($context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? 0), $variable_name, $union_type, $node->flags); return $variable; }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitCall(Node $node) : Context { $expression = $node->children['expr']; if (Config::get()->backward_compatibility_checks) { AST::backwardCompatibilityCheck($this->context, $node); foreach ($node->children['args']->children as $arg_node) { if ($arg_node instanceof Node) { AST::backwardCompatibilityCheck($this->context, $arg_node); } } } if ($expression->kind == \ast\AST_NAME) { try { $method = AST::functionFromNameInContext($expression->children['name'], $this->context, $this->code_base); } catch (CodeBaseException $exception) { Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno); return $this->context; } // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } else { if ($expression->kind == \ast\AST_VAR) { $variable_name = AST::variableName($expression); if (empty($variable_name)) { return $this->context; } // $var() - hopefully a closure, otherwise we don't know if ($this->context->getScope()->hasVariableWithName($variable_name)) { $variable = $this->context->getScope()->getVariableWithName($variable_name); $union_type = $variable->getUnionType(); if ($union_type->isEmpty()) { return $this->context; } $type = $union_type->head(); if (!$type instanceof CallableType) { return $this->context; } $closure_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString((string) $type->asFQSEN()); if ($this->code_base->hasMethod($closure_fqsen)) { // Get the closure $method = $this->code_base->getMethod($closure_fqsen); // Check the call for paraemter and argument types $this->analyzeCallToMethod($this->code_base, $method, $node); } } } } 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 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; }
/** * @param CodeBase $code_base * The global code base * * @param Method $method * The method we're analyzing arguments for * * @param Node $node * The node holding the method call we're looking at * * @param Context $context * The context in which we see the call * * @return null * * @see \Phan\Deprecated\Pass2::arglist_type_check * Formerly `function arglist_type_check` */ private static function analyzeParameterList(CodeBase $code_base, Method $method, Node $node, Context $context) { foreach ($node->children ?? [] as $i => $argument) { // Get the parameter associated with this argument $parameter = $method->getParameterList()[$i] ?? null; // This issue should be caught elsewhere if (!$parameter) { continue; } // If this is a pass-by-reference parameter, make sure // we're passing an allowable argument if ($parameter->isPassByReference()) { if (!$argument instanceof \ast\Node || $argument->kind != \ast\AST_VAR && $argument->kind != \ast\AST_DIM && $argument->kind != \ast\AST_PROP && $argument->kind != \ast\AST_STATIC_PROP) { Log::err(Log::ETYPE, "Only variables can be passed by reference at arg#" . ($i + 1) . " of {$method->getFQSEN()}()", $context->getFile(), $node->lineno); } else { $variable_name = AST::variableName($argument); if ($argument->kind == \ast\AST_STATIC_PROP) { if (in_array($variable_name, ['self', 'static', 'parent'])) { Log::err(Log::ESTATIC, "Using {$variable_name}:: when not in object context", $context->getFile(), $argument->lineno); } } } } // Get the type of the argument. We'll check it against // the parameter in a moment $argument_type = UnionType::fromNode($context, $code_base, $argument); // Expand it to include all parent types up the chain $argument_type_expanded = $argument_type->asExpandedTypes($code_base); /* TODO see issue #42 If argument is an object and it has a String union type, then we need to ignore that in strict_types=1 mode. if ($argument instanceof \ast\Node) { if(!empty($argument->children['class'])) { // arg is an object if ($method->getContext()->getStrictTypes()) { ... } } } or maybe UnionType::fromNode should check strict_types and not return the string union type or we shouldn't add the string type at all when a class has a __toString() and instead set a flag and check that instead */ // Check the method to see if it has the correct // parameter types. If not, keep hunting through // alternates of the method until we find one that // takes the correct types $alternate_parameter = null; $alternate_found = false; foreach ($method->alternateGenerator($code_base) as $alternate_id => $alternate_method) { if (empty($alternate_method->getParameterList()[$i])) { continue; } // Get the parameter associated with this argument $alternate_parameter = $alternate_method->getParameterList()[$i] ?? null; // Expand the types to find all parents and traits $alternate_parameter_type_expanded = $alternate_parameter->getUnionType()->asExpandedTypes($code_base); // See if the argument can be cast to the // parameter if ($argument_type_expanded->canCastToUnionType($alternate_parameter_type_expanded)) { $alternate_found = true; break; } } if (!$alternate_found) { $parameter_name = $alternate_parameter ? $alternate_parameter->getName() : 'unknown'; $parameter_type = $alternate_parameter ? $alternate_parameter->getUnionType() : 'unknown'; if ($method->getContext()->isInternal()) { Log::err(Log::ETYPE, "arg#" . ($i + 1) . "({$parameter_name}) is " . "{$argument_type_expanded} but {$method->getFQSEN()}() " . "takes {$parameter_type}", $context->getFile(), $node->lineno); } else { Log::err(Log::ETYPE, "arg#" . ($i + 1) . "({$parameter_name}) is " . "{$argument_type_expanded} but {$method->getFQSEN()}() " . "takes {$parameter_type} " . "defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()}", $context->getFile(), $node->lineno); } } } }
/** * @param Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitVar(Node $node) : Context { $variable_name = AST::variableName($node); // Check to see if the variable already exists if ($this->context->getScope()->hasVariableWithName($variable_name)) { $variable = $this->context->getScope()->getVariableWithName($variable_name); // 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) { $variable->getUnionType()->addUnionType($this->right_type); } else { // If the variable isn't a pass-by-reference paramter // we clone it so as to not disturb its previous types // as we replace it. if (!($variable instanceof Parameter && $variable->isPassByReference())) { $variable = clone $variable; } if ($this->context->getIsConditional()) { // If we're within a conditional, we shouldn't // replace the type since the other side of // the branch may have another type $variable->getUnionType()->addUnionType($this->right_type); } else { $variable->setUnionType($this->right_type); } } $this->context->addScopeVariable($variable); return $this->context; } $variable = Variable::fromNodeInContext($this->assignment_node, $this->context, $this->code_base); // Set that type on the variable $variable->getUnionType()->addUnionType($this->right_type); // Note that we're not creating a new scope, just // adding variables to the existing scope $this->context->addScopeVariable($variable); 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 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 Node $node * A node to parse * * @return Context * A new or an unchanged context resulting from * parsing the node */ public function visitVar(Node $node) : Context { $variable_name = AST::variableName($node); // Check to see if the variable already exists if ($this->context->getScope()->hasVariableWithName($variable_name)) { $variable = $this->context->getScope()->getVariableWithName($variable_name); $variable->setUnionType($this->right_type); $this->context->addScopeVariable($variable); return $this->context; } $variable = Variable::fromNodeInContext($this->assignment_node, $this->context, $this->code_base); // Set that type on the variable $variable->getUnionType()->addUnionType($this->right_type); // Note that we're not creating a new scope, just // adding variables to the existing scope $this->context->addScopeVariable($variable); return $this->context; }