getScope() public method

public getScope ( ) : Scope
return Scope An object describing the contents of the current scope.
Example #1
0
 /**
  * @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 visitVar(Node $node) : string
 {
     // $$var->method()
     if ($node->children['name'] instanceof Node) {
         return '';
     }
     // $this->method()
     if ($node->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['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);
         }
     }
     // We land here if we have a variable
     // with a native type or no known type.
     return '';
 }
Example #2
0
 /**
  * @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'];
     (new ContextNode($this->code_base, $this->context, $node))->analyzeBackwardCompatibility();
     foreach ($node->children['args']->children ?? [] as $arg_node) {
         if ($arg_node instanceof Node) {
             (new ContextNode($this->code_base, $this->context, $arg_node))->analyzeBackwardCompatibility();
         }
     }
     if ($expression->kind == \ast\AST_VAR) {
         $variable_name = (new ContextNode($this->code_base, $this->context, $expression))->getVariableName();
         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;
             }
             foreach ($union_type->getTypeSet() as $type) {
                 if (!$type instanceof CallableType) {
                     continue;
                 }
                 $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);
                 }
             }
         }
     } else {
         if ($expression->kind == \ast\AST_NAME) {
             try {
                 $method = (new ContextNode($this->code_base, $this->context, $expression))->getFunction($expression->children['name'] ?? $expression->children['method']);
             } catch (IssueException $exception) {
                 $exception->getIssueInstance()();
                 return $this->context;
             }
             // Check the call for paraemter and argument types
             $this->analyzeCallToMethod($this->code_base, $method, $node);
         } else {
             if ($expression->kind == \ast\AST_CALL || $expression->kind == \ast\AST_STATIC_CALL || $expression->kind == \ast\AST_NEW || $expression->kind == \ast\AST_METHOD_CALL) {
                 $class_list = (new ContextNode($this->code_base, $this->context, $expression))->getClassList();
                 foreach ($class_list as $class) {
                     if (!$class->hasMethodWithName($this->code_base, '__invoke')) {
                         continue;
                     }
                     $method = $class->getMethodByNameInContext($this->code_base, '__invoke', $this->context);
                     // Check the call for paraemter and argument types
                     $this->analyzeCallToMethod($this->code_base, $method, $node);
                 }
             }
         }
     }
     return $this->context;
 }
Example #3
0
 /**
  * @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;
             }
             $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;
 }
Example #4
0
 /**
  * @return Variable
  * A variable in scope or a new variable
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws IssueException
  * A IssueException is thrown if the variable doesn't
  * exist
  */
 public function getVariable() : Variable
 {
     // Get the name of the variable
     $variable_name = $this->getVariableName();
     if (empty($variable_name)) {
         throw new NodeException($this->node, "Variable name not found");
     }
     // Check to see if the variable exists in this scope
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredVariable)($this->context->getFile(), $this->node->lineno ?? 0, [$variable_name]));
     }
     return $this->context->getScope()->getVariableWithName($variable_name);
 }
Example #5
0
 /**
  * @return Variable
  * A variable in scope or a new variable
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws CodeBaseException
  * A CodeBaseException is thrown if the variable doesn't
  * exist
  */
 public function getVariable() : Variable
 {
     // Get the name of the variable
     $variable_name = $this->getVariableName();
     if (empty($variable_name)) {
         throw new NodeException($this->node, "Variable name not found");
     }
     // Check to see if the variable exists in this scope
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         throw new CodeBaseException(null, "Variable with name {$variable_name} doesn't exist in {$this->context}");
     }
     return $this->context->getScope()->getVariableWithName($variable_name);
 }
Example #6
0
 /**
  * @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;
 }
Example #7
0
 /**
  * @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;
 }
Example #8
0
 /**
  * @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 visitVar(Node $node) : string
 {
     // $$var->method()
     if ($node->children['name'] instanceof Node) {
         return '';
     }
     // $this->method()
     if ($node->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['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);
     $union_type = $variable->getUnionType()->nonNativeTypes()->nonGenericArrayTypes();
     // If there are no candidate classes, we'll emit whatever
     // we have so that we can differentiate between
     // no-known-type and a shitty type
     if ($union_type->isEmpty()) {
         if (!$variable->getUnionType()->isEmpty() && !$variable->getUnionType()->hasType(MixedType::instance()) && !$variable->getUnionType()->hasType(ArrayType::instance()) && !$variable->getUnionType()->hasType(ObjectType::instance())) {
             $type = (string) $variable->getUnionType();
             throw new TypeException("Calling method on non-class type {$type}");
         }
         // No viable class types for the variable.
         return '';
     }
     $class_fqsen = $this->chooseSingleFQSEN(array_map(function (Type $type) {
         return $type->asFQSEN();
     }, $union_type->getTypeList()));
     if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
         return (string) $class_fqsen;
     }
     // We couldn't find any viable classes
     return '';
 }
Example #9
0
 /**
  * Visit a node with kind `\ast\AST_VAR`
  *
  * @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 visitVar(Node $node) : UnionType
 {
     // $$var or ${...} (whose idea was that anyway?)
     if ($node->children['name'] instanceof Node && ($node->children['name']->kind == \ast\AST_VAR || $node->children['name']->kind == \ast\AST_BINARY_OP)) {
         return MixedType::instance()->asUnionType();
     }
     // This is nonsense. Give up.
     if ($node->children['name'] instanceof Node) {
         return new UnionType();
     }
     $variable_name = $node->children['name'];
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         if (!Variable::isSuperglobalVariableWithName($variable_name)) {
             Log::err(Log::EVAR, "Variable \${$variable_name} is not defined", $this->context->getFile(), $node->lineno ?? 0);
         }
     } else {
         $variable = $this->context->getScope()->getVariableWithName($variable_name);
         return $variable->getUnionType();
     }
     return new UnionType();
 }
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitIf(Node $node) : Context
 {
     // Get the list of scopes for each branch of the
     // conditional
     $scope_list = array_map(function (Context $context) {
         return $context->getScope();
     }, $this->child_context_list);
     $has_else = array_reduce($node->children ?? [], function (bool $carry, $child_node) {
         return $carry || $child_node instanceof Node && empty($child_node->children['cond']);
     }, false);
     // If we're not guaranteed to hit at least one
     // branch, mark the incoming scope as a possibility
     if (!$has_else) {
         $scope_list[] = $this->context->getScope();
     }
     // If there weren't multiple branches, continue on
     // as if the conditional never happened
     if (count($scope_list) < 2) {
         return array_values($this->child_context_list)[0];
     }
     // Get a list of all variables in all scopes
     $variable_map = [];
     foreach ($scope_list as $scope) {
         foreach ($scope->getVariableMap() as $name => $variable) {
             $variable_map[$name] = $variable;
         }
     }
     // A function that determins if a variable is defined on
     // every branch
     $is_defined_on_all_branches = function (string $variable_name) use($scope_list) {
         return array_reduce($scope_list, function (bool $has_variable, Scope $scope) use($variable_name) {
             return $has_variable && $scope->hasVariableWithName($variable_name);
         }, true);
     };
     // Get the intersection of all types for all versions of
     // the variable from every side of the branch
     $common_union_type = function (string $variable_name) use($scope_list) {
         // Get a list of all variables with the given name from
         // each scope
         $variable_list = array_filter(array_map(function (Scope $scope) use($variable_name) {
             if (!$scope->hasVariableWithName($variable_name)) {
                 return null;
             }
             return $scope->getVariableWithName($variable_name);
         }, $scope_list));
         // Get the list of types for each version of the variable
         $type_set_list = array_map(function (Variable $variable) : Set {
             return $variable->getUnionType()->getTypeSet();
         }, $variable_list);
         if (count($type_set_list) < 2) {
             return new UnionType($type_set_list[0] ?? []);
         }
         return new UnionType(Set::intersectAll($type_set_list));
     };
     $scope = new Scope();
     foreach ($variable_map as $name => $variable) {
         // Skip variables that are only partially defined
         if (!$is_defined_on_all_branches($name)) {
             continue;
         }
         // Limit the type of the variable to the subset
         // of types that are common to all branches
         $variable = clone $variable;
         $variable->setUnionType($common_union_type($name));
         // Add the variable to the outgoing scope
         $scope->addVariable($variable);
     }
     // print '<'.implode("\t", $scope_list) . "\n";
     // print '>'.$scope."\n";
     // Set the new scope with only the variables and types
     // that are common to all branches
     return $this->context->withScope($scope);
 }
Example #11
0
 /**
  * @param Node $node
  * A node that has a reference to a variable
  *
  * @param Context $context
  * The context in which we found the reference
  *
  * @param CodeBase $code_base
  *
  * @return Variable
  * A variable in scope or a new variable
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  */
 public static function getOrCreateVariableFromNodeInContext(Node $node, Context $context, CodeBase $code_base) : Variable
 {
     // Get the name of the variable
     $variable_name = self::variableName($node);
     if (empty($variable_name)) {
         throw new NodeException($node, "Variable name not found");
     }
     // Check to see if the variable exists in this scope
     if ($context->getScope()->hasVariableWithName($variable_name)) {
         return $context->getScope()->getVariableWithName($variable_name);
     }
     // Create a new variable
     $variable = Variable::fromNodeInContext($node, $context, $code_base, false);
     $context->addScopeVariable($variable);
     return $variable;
 }
Example #12
0
 /**
  * @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 '';
 }
Example #13
0
 /**
  * @param string $type_string
  * A '|' delimited string representing a type in the form
  * 'int|string|null|ClassName'.
  *
  * @param Context $context
  * The context in which the type string was
  * found
  *
  * @return UnionType
  */
 public static function fromStringInContext(string $type_string, Context $context) : UnionType
 {
     if (empty($type_string)) {
         return new UnionType();
     }
     // If our scope has a generic type identifier defined on it
     // that matches the type string, return that UnionType.
     if ($context->getScope()->hasTemplateType($type_string)) {
         return $context->getScope()->getTemplateType($type_string)->asUnionType();
     }
     return new UnionType(array_map(function (string $type_name) use($context, $type_string) {
         assert($type_name !== '', "Type cannot be empty.");
         return Type::fromStringInContext($type_name, $context);
     }, array_filter(array_map(function (string $type_name) {
         return trim($type_name);
     }, explode('|', $type_string)))));
 }