Inheritance: extends FullyQualifiedGlobalStructuralElement, implements Phan\Language\FQSEN\FullyQualifiedFunctionLikeName
Example #1
0
 /**
  * @return Func[]
  * One or more (alternate) methods begotten from
  * reflection info and internal method data
  */
 public static function functionListFromSignature(CodeBase $code_base, FullyQualifiedFunctionName $fqsen, array $signature) : array
 {
     $context = new Context();
     $return_type = UnionType::fromStringInContext(array_shift($signature), $context);
     $func = new Func($context, $fqsen->getName(), $return_type, 0, $fqsen);
     return self::functionListFromFunction($func, $code_base);
 }
Example #2
0
 public function testFullyQualifiedFunctionName()
 {
     $this->assertFQSENEqual(FullyQualifiedFunctionName::make('\\Name\\Space', 'g'), '\\Name\\Space\\g');
     $this->assertFQSENEqual(FullyQualifiedFunctionName::make('', 'g'), '\\g');
     $this->assertFQSENEqual(FullyQualifiedGlobalConstantName::make('', 'g'), '\\g');
     $this->assertFQSENEqual(FullyQualifiedFunctionName::fromFullyQualifiedString('\\g'), '\\g');
     $this->assertFQSENEqual(FullyQualifiedFunctionName::fromStringInContext('g', $this->context), '\\g');
 }
Example #3
0
 public static function createSchema() : Schema
 {
     $schema = new Schema('File', [new Column('file_path', Column::TYPE_STRING, true), new Column('modification_time', Column::TYPE_INT)]);
     $schema->addAssociation(new ListAssociation('FileClassFQSEN', Column::TYPE_STRING, function (File $file, array $class_fqsen_string_list) {
         $file->getFile()->setClassFQSENList(array_map(function (string $fqsen_string) {
             return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
         }, $class_fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FullyQualifiedClassName $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getClassFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FileMethodFQSEN', Column::TYPE_STRING, function (File $file, array $method_fqsen_string_list) {
         $file->getFile()->setMethodFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedMethodName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $method_fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getMethodFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FilePropertyFQSEN', Column::TYPE_STRING, function (File $file, array $fqsen_string_list) {
         $file->getFile()->setPropertyFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedPropertyName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getPropertyFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FileConstantFQSEN', Column::TYPE_STRING, function (File $file, array $fqsen_string_list) {
         $file->getFile()->setConstantFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedConstantName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getConstantFQSENList());
     }));
     return $schema;
 }
Example #4
0
 /**
  * Add any functions from the FunctionSignatureMap that aren't
  * defined in this version of PHP to the code base
  *
  * @return void
  */
 private function addUndefinedFunctionSignatures()
 {
     $function_signature_map = UnionType::internalFunctionSignatureMap();
     foreach ($function_signature_map as $function_name => $signature) {
         $fqsen = FullyQualifiedFunctionName::make('\\', $function_name);
         // If we already loaded the function, skip it
         if ($this->hasMethod($fqsen)) {
             continue;
         }
         // Add each method returned for the signature
         foreach (Method::methodListFromSignature($this, $fqsen, $signature) as $method) {
             $this->addMethod($method);
         }
     }
 }
Example #5
0
 /**
  * @param FullyQualifiedFunctionName
  * The FQSEN of a function we'd like to look up
  *
  * @return bool
  * If the FQSEN represents an internal function that
  * hasn't been loaded yet, true is returned.
  */
 private function hasInternalFunctionWithFQSEN(FullyQualifiedFunctionName $fqsen) : bool
 {
     // Only root namespaced functions will be found in
     // the internal function map.
     if ($fqsen->getNamespace() != '\\') {
         return false;
     }
     // For elements in the root namespace, check to see if
     // there's a static method signature for something that
     // hasn't been loaded into memory yet and create a
     // method out of it as its requested
     $function_signature_map = UnionType::internalFunctionSignatureMap();
     if (!empty($function_signature_map[$fqsen->getNameWithAlternateId()])) {
         $signature = $function_signature_map[$fqsen->getNameWithAlternateId()];
         // Add each method returned for the signature
         foreach (FunctionFactory::functionListFromSignature($this, $fqsen, $signature) as $i => $function) {
             $this->addFunction($function);
         }
         return true;
     }
     return false;
 }
Example #6
0
 /**
  * @return FullyQualifiedGlobalStructuralElement
  * The namespace mapped name for the given flags and name
  */
 public function getNamespaceMapFor(int $flags, string $name) : FullyQualifiedGlobalStructuralElement
 {
     $name = strtolower($name);
     // Look for the mapping on the part before a
     // slash
     $name_parts = explode('\\', $name, 2);
     $suffix = '';
     if (count($name_parts) > 1) {
         $name = $name_parts[0];
         $suffix = $name_parts[1];
     }
     assert(!empty($this->namespace_map[$flags][$name]), "No namespace defined for name");
     assert($this->namespace_map[$flags][$name] instanceof FQSEN, "Namespace map was not an FQSEN");
     $fqsen = $this->namespace_map[$flags][$name];
     if (!$suffix) {
         return $fqsen;
     }
     switch ($flags) {
         case \ast\flags\USE_NORMAL:
             return FullyQualifiedClassName::fromFullyQualifiedString((string) $fqsen . '\\' . $suffix);
         case \ast\flags\USE_FUNCTION:
             return FullyQualifiedFunctionName::fromFullyQualifiedString((string) $fqsen . '\\' . $suffix);
     }
     assert(false, "Unknown flag {$flags}");
     return $fqsen;
 }
Example #7
0
 public function unserialize($serialized)
 {
     list($file_ref, $serialized) = explode('^', $serialized);
     parent::unserialize($file_ref);
     list($namespace, $is_conditional, $class_fqsen, $method_fqsen, $closure_fqsen) = explode('|', $serialized);
     $this->namespace = $namespace;
     $this->is_conditional = (bool) $is_conditional;
     $this->class_fqsen = $class_fqsen ? FullyQualifiedClassName::fromFullyQualifiedString($class_fqsen) : null;
     $this->method_fqsen = $method_fqsen ? FullyQualifiedMethodName::fromFullyQualifiedString($method_fqsen) : null;
     $this->closure_fqsen = $closure_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($closure_fqsen) : null;
 }
Example #8
0
 /**
  * @return Method[]
  * One or more (alternate) methods begotten from
  * reflection info and internal method data
  */
 public static function methodListFromReflectionFunction(CodeBase $code_base, \ReflectionFunction $reflection_function) : array
 {
     $number_of_required_parameters = $reflection_function->getNumberOfRequiredParameters();
     $number_of_optional_parameters = $reflection_function->getNumberOfParameters() - $number_of_required_parameters;
     $context = new Context();
     $parts = explode('\\', $reflection_function->getName());
     $method_name = array_pop($parts);
     $namespace = '\\' . implode('\\', $parts);
     $fqsen = FullyQualifiedFunctionName::make($namespace, $method_name);
     $method = new Method($context, $fqsen->getName(), new UnionType(), 0, $number_of_required_parameters, $number_of_optional_parameters);
     $method->setFQSEN($fqsen);
     return self::methodListFromMethod($method, $code_base);
 }
Example #9
0
 /**
  * Visit a node with kind `\ast\AST_CLOSURE`
  *
  * @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 visitClosure(Decl $node) : UnionType
 {
     // The type of a closure is the fqsen pointing
     // at its definition
     $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context);
     $type = CallableType::instanceWithClosureFQSEN($closure_fqsen)->asUnionType();
     return $type;
 }
Example #10
0
 /**
  * @return array
  * A map from alias to target
  */
 private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array
 {
     assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes');
     $map = [];
     foreach ($node->children ?? [] as $child_node) {
         $target = $child_node->children['name'];
         if (empty($child_node->children['alias'])) {
             if (($pos = strrpos($target, '\\')) !== false) {
                 $alias = substr($target, $pos + 1);
             } else {
                 $alias = $target;
             }
         } else {
             $alias = $child_node->children['alias'];
         }
         // if AST_USE does not have any flags set, then its AST_USE_ELEM
         // children will (this will be for AST_GROUP_USE)
         if ($node->flags !== 0) {
             $target_node = $node;
         } else {
             $target_node = $child_node;
         }
         if ($target_node->flags == T_FUNCTION) {
             $parts = explode('\\', $target);
             $function_name = array_pop($parts);
             $target = FullyQualifiedFunctionName::make($prefix . '\\' . implode('\\', $parts), $function_name);
         } else {
             if ($target_node->flags == T_CONST) {
                 $parts = explode('\\', $target);
                 $name = array_pop($parts);
                 $target = FullyQualifiedGlobalConstantName::make($prefix . '\\' . implode('\\', $parts), $name);
             } else {
                 $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target);
             }
         }
         $map[$alias] = [$target_node->flags, $target];
     }
     return $map;
 }
Example #11
0
 /**
  * Visit a node with kind `\ast\AST_FUNC_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitFuncDecl(Decl $node) : Context
 {
     $function_name = (string) $node->name;
     // Hunt for an un-taken alternate ID
     $alternate_id = 0;
     $function_fqsen = null;
     do {
         $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context)->withNamespace($this->context->getNamespace())->withAlternateId($alternate_id++);
     } while ($this->code_base->hasFunctionWithFQSEN($function_fqsen));
     $func = Func::fromNode($this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? 0), $this->code_base, $node, $function_fqsen);
     $this->code_base->addFunction($func);
     // Send the context into the function and reset the scope
     $context = $this->context->withScope($func->getInternalScope());
     return $context;
 }
Example #12
0
 /**
  * Visit a node with kind `\ast\AST_CLOSURE`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitClosure(Decl $node) : Context
 {
     $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context->withLineNumberStart($node->lineno ?? 0));
     $func = Func::fromNode($this->context, $this->code_base, $node, $closure_fqsen);
     // If we have a 'this' variable in our current scope,
     // pass it down into the closure
     if ($this->context->getScope()->hasVariableWithName('this')) {
         $func->getInternalScope()->addVariable($this->context->getScope()->getVariableByName('this'));
     }
     // Make the closure reachable by FQSEN from anywhere
     $this->code_base->addFunction($func);
     if (!empty($node->children['uses']) && $node->children['uses']->kind == \ast\AST_CLOSURE_USES) {
         $uses = $node->children['uses'];
         foreach ($uses->children as $use) {
             if ($use->kind != \ast\AST_CLOSURE_VAR) {
                 $this->emitIssue(Issue::VariableUseClause, $node->lineno ?? 0);
                 continue;
             }
             $variable_name = (new ContextNode($this->code_base, $this->context, $use->children['name']))->getVariableName();
             if (empty($variable_name)) {
                 continue;
             }
             $variable = null;
             // Check to see if the variable exists in this scope
             if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
                 // If this is not pass-by-reference variable we
                 // have a problem
                 if (!($use->flags & \ast\flags\PARAM_REF)) {
                     $this->emitIssue(Issue::UndeclaredVariable, $node->lineno ?? 0, $variable_name);
                     continue;
                 } else {
                     // If the variable doesn't exist, but its
                     // a pass-by-reference variable, we can
                     // just create it
                     $variable = Variable::fromNodeInContext($use, $this->context, $this->code_base, false);
                 }
             } else {
                 $variable = $this->context->getScope()->getVariableByName($variable_name);
                 // If this isn't a pass-by-reference variable, we
                 // clone the variable so state within this scope
                 // doesn't update the outer scope
                 if (!($use->flags & \ast\flags\PARAM_REF)) {
                     $variable = clone $variable;
                 }
             }
             // Pass the variable into a new scope
             $func->getInternalScope()->addVariable($variable);
         }
     }
     // Add all parameters to the scope
     if (!empty($node->children['params']) && $node->children['params']->kind == \ast\AST_PARAM_LIST) {
         $params = $node->children['params'];
         foreach ($params->children as $param) {
             // Read the parameter
             $parameter = Parameter::fromNode($this->context, $this->code_base, $param);
             // Add it to the scope
             $func->getInternalScope()->addVariable($parameter);
         }
     }
     if ($this->analyzeFunctionLikeIsGenerator($node)) {
         $this->setReturnTypeOfGenerator($func, $node);
     }
     return $this->context->withScope($func->getInternalScope());
 }
Example #13
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()->getVariableByName($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->hasFunctionWithFQSEN($closure_fqsen)) {
                     // Get the closure
                     $function = $this->code_base->getFunctionByFQSEN($closure_fqsen);
                     // Check the call for paraemter and argument types
                     $this->analyzeCallToMethod($this->code_base, $function, $node);
                 }
             }
         }
     } elseif ($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) {
             Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
             return $this->context;
         }
         // Check the call for paraemter and argument types
         $this->analyzeCallToMethod($this->code_base, $method, $node);
     } elseif ($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 #14
0
 /**
  * @return Method[]
  * One or more (alternate) methods begotten from
  * reflection info and internal method data
  */
 public static function methodListFromSignature(CodeBase $code_base, FullyQualifiedFunctionName $fqsen, array $signature) : array
 {
     $context = new Context();
     $return_type = UnionType::fromStringInContext(array_shift($signature), $context);
     $method = new Method($context, $fqsen->getName(), $return_type, 0);
     $method->setFQSEN($fqsen);
     return self::methodListFromMethod($method, $code_base);
 }
Example #15
0
 /**
  * @param array
  * A map from column name to value
  *
  * @return Model
  * An instance of the model derived from row data
  */
 public static function fromRow(array $row) : Method
 {
     list($scope, $name) = explode('|', $row['scope_name']);
     $method_element = new MethodElement(unserialize(base64_decode($row['context'])), $row['name'], UnionType::fromFullyQualifiedString($row['type']), (int) $row['flags']);
     $method_element->setNumberOfRequiredParameters($row['number_of_required_parameters']);
     $method_element->setNumberOfOptionalParameters($row['number_of_optional_parameters']);
     $method = new Method($method_element, $scope, $name);
     if (false !== strpos($row['fqsen'], '::')) {
         $fqsen = FullyQualifiedMethodName::fromFullyQualifiedString($row['fqsen']);
     } else {
         $fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($row['fqsen']);
     }
     $method->getMethod()->setFQSEN($fqsen);
     return $method;
 }
Example #16
0
 /**
  * @return Method
  */
 public function getClosure() : Func
 {
     $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context);
     if (!$this->code_base->hasFunctionWithFQSEN($closure_fqsen)) {
         throw new CodeBaseException($closure_fqsen, "Could not find closure {$closure_fqsen}");
     }
     return $this->code_base->getFunctionByFQSEN($closure_fqsen);
 }
Example #17
0
 /**
  * A list of types for parameters associated with the
  * given builtin function with the given name
  *
  * @param FullyQualifiedMethodName|FullyQualifiedFunctionName $function_fqsen
  *
  * @see internal_varargs_check
  * Formerly `function internal_varargs_check`
  */
 public static function internalFunctionSignatureMapForFQSEN($function_fqsen) : array
 {
     $context = new Context();
     $map = self::internalFunctionSignatureMap();
     if ($function_fqsen instanceof FullyQualifiedMethodName) {
         $class_fqsen = $function_fqsen->getFullyQualifiedClassName();
         $class_name = $class_fqsen->getName();
         $function_name = $class_name . '::' . $function_fqsen->getName();
     } else {
         $function_name = $function_fqsen->getName();
     }
     $function_name_original = $function_name;
     $alternate_id = 0;
     $configurations = [];
     while (isset($map[$function_name])) {
         // Get some static data about the function
         $type_name_struct = $map[$function_name];
         if (empty($type_name_struct)) {
             continue;
         }
         // Figure out the return type
         $return_type_name = array_shift($type_name_struct);
         $return_type = $return_type_name ? UnionType::fromStringInContext($return_type_name, $context) : null;
         $name_type_name_map = $type_name_struct;
         $property_name_type_map = [];
         foreach ($name_type_name_map as $name => $type_name) {
             $property_name_type_map[$name] = empty($type_name) ? new UnionType() : UnionType::fromStringInContext($type_name, $context);
         }
         $configurations[] = ['return_type' => $return_type, 'property_name_type_map' => $property_name_type_map];
         $function_name = $function_name_original . '\'' . ++$alternate_id;
     }
     return $configurations;
 }
Example #18
0
 /**
  * @return FullyQualifiedFunctionName|FullyQualifiedMethodName
  */
 public function getFQSEN() : FQSEN
 {
     // Allow overrides
     if ($this->fqsen) {
         return $this->fqsen;
     }
     if ($this->getContext()->isInClassScope()) {
         return FullyQualifiedMethodName::fromStringInContext($this->getName(), $this->getContext());
     }
     return FullyQualifiedFunctionName::fromStringInContext($this->getName(), $this->getContext());
 }
Example #19
0
 /**
  * Visit a node with kind `\ast\AST_CLOSURE`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitClosure(Node $node) : Context
 {
     $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context);
     $method = Method::fromNode($this->context, $this->code_base, $node);
     // Override the FQSEN with the found alternate ID
     $method->setFQSEN($closure_fqsen);
     // Make the closure reachable by FQSEN from anywhere
     $this->code_base->addMethod($method);
     // If we have a 'this' variable in our current scope,
     // pass it down into the closure
     $context = $this->context->withScope(new Scope());
     if ($context->getScope()->hasVariableWithName('this')) {
         $context = $context->addScopeVariable($this->context->getScope()->getVariableWithName('this'));
     }
     if (!empty($node->children['uses']) && $node->children['uses']->kind == \ast\AST_CLOSURE_USES) {
         $uses = $node->children['uses'];
         foreach ($uses->children as $use) {
             if ($use->kind != \ast\AST_CLOSURE_VAR) {
                 Log::err(Log::EVAR, "You can only have variables in a closure use() clause", $this->context->getFile(), $node->lineno);
                 continue;
             }
             $variable_name = AST::variableName($use->children['name']);
             if (empty($variable_name)) {
                 continue;
             }
             $variable = null;
             // Check to see if the variable exists in this scope
             if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
                 // If this is not pass-by-reference variable we
                 // have a problem
                 if (!($use->flags & \ast\flags\PARAM_REF)) {
                     Log::err(Log::EVAR, "Variable \${$variable_name} is not defined", $this->context->getFile(), $node->lineno);
                     continue;
                 } else {
                     // If the variable doesn't exist, but its
                     // a pass-by-reference variable, we can
                     // just create it
                     $variable = Variable::fromNodeInContext($use, $this->context, $this->code_base, false);
                 }
             } else {
                 $variable = $this->context->getScope()->getVariableWithName($variable_name);
                 // If this isn't a pass-by-reference variable, we
                 // clone the variable so state within this scope
                 // doesn't update the outer scope
                 if (!($use->flags & \ast\flags\PARAM_REF)) {
                     $variable = clone $variable;
                 }
             }
             // Pass the variable into a new scope
             $context = $context->withScopeVariable($variable);
         }
     }
     // Add all parameters to the scope
     if (!empty($node->children['params']) && $node->children['params']->kind == \ast\AST_PARAM_LIST) {
         $params = $node->children['params'];
         foreach ($params->children as $param) {
             // Read the parameter
             $parameter = Parameter::fromNode($this->context, $this->code_base, $param);
             // Add it to the scope
             $context = $context->withScopeVariable($parameter);
         }
     }
     return $context->withClosureFQSEN($closure_fqsen);
 }
Example #20
0
 /**
  * Visit a node with kind `\ast\AST_CALL`
  *
  * @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 visitCall(Node $node) : UnionType
 {
     if ($node->children['expr']->kind !== \ast\AST_NAME) {
         // Things like `$func()`
         return new UnionType();
     }
     $function_name = $node->children['expr']->children['name'];
     $function_fqsen = null;
     // If its not fully qualified
     if ($node->children['expr']->flags & \ast\flags\NAME_NOT_FQ) {
         // Check to see if we have a mapped name
         if ($this->context->hasNamespaceMapFor(T_FUNCTION, $function_name)) {
             $function_fqsen = $this->context->getNamespaceMapFor(T_FUNCTION, $function_name);
         } else {
             $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context);
         }
         // If the name is fully qualified
     } else {
         $function_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($function_name);
     }
     // If the function doesn't exist, check to see if its
     // a call to a builtin method
     if (!$this->code_base->hasMethod($function_fqsen)) {
         $function_fqsen = FullyQualifiedFunctionName::make('', $function_name);
     }
     if (!$this->code_base->hasMethod($function_fqsen)) {
         // Missing internal (bulitin) method.
         return new UnionType();
     }
     $function = $this->code_base->getMethod($function_fqsen);
     // If this is an internal function, see if we can get
     // its types from the static dataset.
     if ($function->getContext()->isInternal() && $function->getUnionType()->isEmpty()) {
         $map = UnionType::internalFunctionSignatureMapForFQSEN($function_fqsen);
         return $map[$function_name] ?? new UnionType();
     }
     return $function->getUnionType();
 }
Example #21
0
 /**
  * @param FunctionInterface $method
  * Any method
  *
  * @param FullyQualifiedFunctionName $fqsen
  * The FQSEN for the method
  *
  * @return null
  */
 private function addMethodWithFunctionFQSEN(FunctionInterface $method, FullyQualifiedFunctionName $fqsen)
 {
     $this->addMethodWithScopeAndName($method, $fqsen->getNamespace(), $fqsen->getNameWithAlternateId());
 }
Example #22
0
 /**
  * @param string $function_name
  * The name of the function we'd like to look up
  *
  * @param Context $context
  * The context in which we found the reference to the
  * given function name
  *
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @param bool $is_function_declaration
  * This must be set to true if we're getting a function
  * that is being declared and false if we're getting a
  * function being called.
  *
  * @return Method
  * A method with the given name in the given context
  *
  * @throws CodeBaseExtension
  * An exception is thrown if we can't find the given
  * function
  */
 public static function functionFromNameInContext(string $function_name, Context $context, CodeBase $code_base, bool $is_function_declaration = false) : Method
 {
     if ($is_function_declaration) {
         $function_fqsen = FullyQualifiedFunctionName::make($context->getNamespace(), $function_name);
     } else {
         $function_fqsen = FullyQualifiedFunctionName::make($context->getNamespace(), $function_name);
         // If it doesn't exist in the local namespace, try it
         // in the global namespace
         if (!$code_base->hasMethod($function_fqsen)) {
             $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $context);
         }
     }
     // Make sure the method we're calling actually exists
     if (!$code_base->hasMethod($function_fqsen)) {
         throw new CodeBaseException("call to undefined function {$function_fqsen}()");
     }
     $method = $code_base->getMethod($function_fqsen);
     return $method;
 }
Example #23
0
 /**
  * @return array
  * A map from alias to target
  */
 private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array
 {
     assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes');
     $map = [];
     foreach ($node->children ?? [] as $child_node) {
         $target = $child_node->children['name'];
         if (empty($child_node->children['alias'])) {
             if (($pos = strrpos($target, '\\')) !== false) {
                 $alias = substr($target, $pos + 1);
             } else {
                 $alias = $target;
             }
         } else {
             $alias = $child_node->children['alias'];
         }
         if ($node->flags == T_FUNCTION) {
             $parts = explode('\\', $target);
             $function_name = array_pop($parts);
             $target = FullyQualifiedFunctionName::make(implode('\\', $parts), $function_name);
         } else {
             $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target);
         }
         $map[$alias] = [$child_node->flags, $target];
     }
     return $map;
 }
Example #24
0
 /**
  * @return void
  */
 public function unserialize($serialized)
 {
     list($file_ref, $serialized) = explode('^', $serialized);
     parent::unserialize($file_ref);
     list($namespace, $class_fqsen, $method_fqsen, $closure_fqsen) = explode('|', $serialized);
     $this->namespace = $namespace;
     $this->class_fqsen = $class_fqsen ? FullyQualifiedClassName::fromFullyQualifiedString($class_fqsen) : null;
     // Determine if we have a method or a function
     if (false === strpos($method_fqsen, '::')) {
         $this->method_fqsen = $method_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($method_fqsen) : null;
     } else {
         $this->method_fqsen = $method_fqsen ? FullyQualifiedMethodName::fromFullyQualifiedString($method_fqsen) : null;
     }
     $this->closure_fqsen = $closure_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($closure_fqsen) : null;
 }
Example #25
0
 /**
  * Visit a node with kind `\ast\AST_FUNC_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitFuncDecl(Decl $node) : Context
 {
     $function_name = (string) $node->name;
     // Hunt for an un-taken alternate ID
     $alternate_id = 0;
     $function_fqsen = null;
     do {
         $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context)->withNamespace($this->context->getNamespace())->withAlternateId($alternate_id++);
     } while ($this->code_base->hasFunctionWithFQSEN($function_fqsen));
     $func = Func::fromNode($this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? 0), $this->code_base, $node);
     $func->setFQSEN($function_fqsen);
     $this->code_base->addFunction($func);
     // Send the context into the function and reset the scope
     $context = $this->context->withMethodFQSEN($function_fqsen)->withScope(new Scope());
     // Add each method parameter to the scope. We clone it
     // so that changes to the variable don't alter the
     // parameter definition
     foreach ($func->getParameterList() as $parameter) {
         $context->addScopeVariable(clone $parameter);
     }
     return $context;
 }
Example #26
0
 /**
  * @param array
  * A map from column name to value
  *
  * @return Model
  * An instance of the model derived from row data
  */
 public static function fromRow(array $row) : Parameter
 {
     if (false !== strpos($row['method_fqsen'], '::')) {
         $method_fqsen = FullyQualifiedMethodName::fromFullyQualifiedString($row['method_fqsen']);
     } else {
         $method_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($row['method_fqsen']);
     }
     $primary_key_value = $row['id'] ?? null;
     $parameter = new Parameter(new ParameterElement(unserialize(base64_decode($row['context'])), $row['name'], UnionType::fromFullyQualifiedString($row['type']), (int) $row['flags']), $method_fqsen, $primary_key_value);
     return $parameter;
 }
Example #27
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 #28
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitClosure(Node $node) : Context
 {
     $this->analyzeNoOp($node, "no-op closure");
     return $this->context->withClosureFQSEN(FullyQualifiedFunctionName::fromClosureInContext($this->context));
 }