/** * @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); }
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'); }
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; }
/** * 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); } } }
/** * @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; }
/** * @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; }
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; }
/** * @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); }
/** * 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; }
/** * @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; }
/** * 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; }
/** * 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()); }
/** * @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; }
/** * @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); }
/** * @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; }
/** * @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); }
/** * 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; }
/** * @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()); }
/** * 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); }
/** * 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(); }
/** * @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()); }
/** * @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; }
/** * @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; }
/** * @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; }
/** * 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; }
/** * @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; }
/** * @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 visitClosure(Node $node) : Context { $this->analyzeNoOp($node, "no-op closure"); return $this->context->withClosureFQSEN(FullyQualifiedFunctionName::fromClosureInContext($this->context)); }