상속: extends Phan\Language\Element\TypedStructuralElement, use trait Phan\Language\Element\Addressable, use trait Phan\Analyze\Analyzable
예제 #1
0
 /**
  * @param CodeBase $code_base
  * The code base in which the method exists
  *
  * @param Method $method
  * A method being analyzed
  *
  * @return void
  */
 public function analyzeMethod(CodeBase $code_base, Method $method)
 {
     // Ignore methods inherited by subclasses
     if ($method->getFQSEN() !== $method->getDefiningFQSEN()) {
         return;
     }
     $this->analyzeTypedElement($code_base, $method);
 }
예제 #2
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeParameterTypes(CodeBase $code_base, Method $method)
 {
     // Look at each method parameter
     foreach ($method->getParameterList() as $parameter) {
         $union_type = $parameter->getUnionType();
         // Look at each type in the parameter's Union Type
         foreach ($union_type->getTypeList() as $type) {
             // If its a native type or a reference to
             // self, its OK
             if ($type->isNativeType() || $type->isSelfType()) {
                 continue;
             }
             // Otherwise, make sure the class exists
             $type_fqsen = $type->asFQSEN();
             if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
                 Log::err(Log::EUNDEF, "parameter of undeclared type {$type_fqsen}", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart());
             }
         }
     }
 }
예제 #3
0
 /**
  * @return Method[]
  */
 public static function methodListFromReflectionClassAndMethod(Context $context, CodeBase $code_base, \ReflectionClass $class, \ReflectionMethod $reflection_method) : array
 {
     $method_fqsen = FullyQualifiedMethodName::fromStringInContext($reflection_method->getName(), $context);
     $reflection_method = new \ReflectionMethod($class->getName(), $reflection_method->name);
     $method = new Method($context, $reflection_method->name, new UnionType(), $reflection_method->getModifiers(), $method_fqsen);
     $method->setNumberOfRequiredParameters($reflection_method->getNumberOfRequiredParameters());
     $method->setNumberOfOptionalParameters($reflection_method->getNumberOfParameters() - $reflection_method->getNumberOfRequiredParameters());
     if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
         $method->setNumberOfOptionalParameters(999);
         $method->setNumberOfRequiredParameters(0);
     }
     return self::functionListFromFunction($method, $code_base);
 }
예제 #4
0
파일: Method.php 프로젝트: hslatman/phan
 /**
  * @return array
  * Get a map from column name to row values for
  * this instance
  */
 public function toRow() : array
 {
     return ['scope_name' => $this->primaryKeyValue(), 'fqsen' => (string) $this->method->getFQSEN(), 'name' => (string) $this->method->getName(), 'type' => (string) $this->method->getUnionType(), 'flags' => $this->method->getFlags(), 'context' => base64_encode(serialize($this->method->getContext())), 'is_deprecated' => $this->method->isDeprecated(), 'number_of_required_parameters' => $this->method->getNumberOfRequiredParameters(), 'number_of_optional_parameters' => $this->method->getNumberOfOptionalParameters(), 'is_dynamic' => $this->method->isDynamic()];
 }
예제 #5
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param Method $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @param CodeBase $code_base
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arg_check
  * Formerly `function arg_check`
  */
 private static function analyzeInternalArgumentType(Method $method, Node $node, Context $context, CodeBase $code_base)
 {
     $arglist = $node->children['args'];
     $argcount = count($arglist->children);
     switch ($method->getName()) {
         case 'join':
         case 'implode':
             // (string glue, array pieces),
             // (array pieces, string glue) or
             // (array pieces)
             if ($argcount == 1) {
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(pieces) is %s but {$method->getFQSEN()}() takes array when passed only 1 arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'pieces', (string) $method->getFQSEN(), 'string', 'array']);
                 });
                 return;
             } else {
                 if ($argcount == 2) {
                     $arg1_type = UnionType::fromNode($context, $code_base, $arglist->children[0]);
                     $arg2_type = UnionType::fromNode($context, $code_base, $arglist->children[1]);
                     if ((string) $arg1_type == 'array') {
                         if (!$arg1_type->canCastToUnionType(StringType::instance()->asUnionType())) {
                             Issue::emit(Issue::ParamSpecial1, $context->getFile(), $context->getLineNumberStart(), 2, 'glue', (string) $arg2_type, (string) $method->getFQSEN(), 'string', 1, 'array');
                         }
                     } else {
                         if ((string) $arg1_type == 'string') {
                             if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                                 Issue::emit(Issue::ParamSpecial1, $context->getFile(), $context->getLineNumberStart(), 2, 'pieces', (string) $arg2_type, (string) $method->getFQSEN(), 'array', 1, 'string');
                             }
                         }
                     }
                     return;
                 }
             }
             // Any other arg counts we will let the regular
             // checks handle
             break;
         case 'array_udiff':
         case 'array_diff_uassoc':
         case 'array_uintersect_assoc':
         case 'array_intersect_ukey':
             if ($argcount < 3) {
                 Issue::emit(Issue::ParamTooFewInternal, $context->getFile(), $context->getLineNumberStart(), $argcount, (string) $method->getFQSEN(), $method->getNumberOfRequiredParameters());
                 return;
             }
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial3)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             for ($i = 0; $i < $argcount - 1; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method, $i) {
                     // "arg#".($i+1)." is %s but {$method->getFQSEN()}() takes array"
                     return Issue::fromType(Issue::ParamTypeMismatch)($context->getFile(), $context->getLineNumberStart(), [$i + 1, (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 });
             }
             return;
         case 'array_diff_uassoc':
         case 'array_uintersect_uassoc':
             if ($argcount < 4) {
                 Issue::emit(Issue::ParamTooFewInternal, $context->getFile(), $context->getLineNumberStart(), $argcount, (string) $method->getFQSEN(), $method->getNumberOfRequiredParameters());
                 return;
             }
             // The last 2 arguments must be a callable and there
             // can be a variable number of arrays before it
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial3)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 2], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The second last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial4)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             for ($i = 0; $i < $argcount - 2; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method, $i) {
                     // "arg#".($i+1)." is %s but {$method->getFQSEN()}() takes array"
                     return Issue::fromType(Issue::ParamTypeMismatch)($context->getFile(), $context->getLineNumberStart(), [$i + 1, (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 });
             }
             return;
         case 'strtok':
             // (string str, string token) or (string token)
             if ($argcount == 1) {
                 // If we have just one arg it must be a string token
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(token) is %s but {$method->getFQSEN()}() takes string when passed only one arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'token', (string) $node_type, (string) $method->getFQSEN(), 'string']);
                 });
             }
             // The arginfo check will handle the other case
             break;
         case 'min':
         case 'max':
             if ($argcount == 1) {
                 // If we have just one arg it must be an array
                 if (!self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(values) is %s but {$method->getFQSEN()}() takes array when passed only one arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'values', (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 })) {
                     return;
                 }
             }
             // The arginfo check will handle the other case
             break;
         default:
             break;
     }
 }
 /**
  * Analyze the parameters and arguments for a call
  * to the given method or function
  *
  * @param CodeBase $code_base
  * @param Method $method
  * @param Node $node
  *
  * @return null
  */
 private function analyzeCallToMethod(CodeBase $code_base, Method $method, Node $node)
 {
     $method->addReference($this->context);
     // Create variables for any pass-by-reference
     // parameters
     $argument_list = $node->children['args'];
     foreach ($argument_list->children as $i => $argument) {
         $parameter = $method->getParameterList()[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If pass-by-reference, make sure the variable exists
         // or create it if it doesn't.
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 // We don't do anything with it; just create it
                 // if it doesn't exist
                 $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
             } else {
                 if ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                     $property_name = $argument->children['prop'];
                     if (is_string($property_name)) {
                         // We don't do anything with it; just create it
                         // if it doesn't exist
                         try {
                             $property = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop']);
                         } catch (CodeBaseException $exception) {
                             Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                         } catch (\Exception $exception) {
                             // If we can't figure out what kind of a call
                             // this is, don't worry about it
                         }
                     } else {
                         // This is stuff like `Class->$foo`. I'm ignoring
                         // it.
                     }
                 }
             }
         }
     }
     // Confirm the argument types are clean
     ArgumentType::analyze($method, $node, $this->context, $this->code_base);
     // Take another pass over pass-by-reference parameters
     // and assign types to passed in variables
     foreach ($argument_list->children as $i => $argument) {
         $parameter = $method->getParameterList()[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If the parameter is pass-by-reference and we're
         // passing a variable in, see if we should pass
         // the parameter and variable types to eachother
         $variable = null;
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
             } else {
                 if ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                     $property_name = $argument->children['prop'];
                     if (is_string($property_name)) {
                         // We don't do anything with it; just create it
                         // if it doesn't exist
                         try {
                             $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop']);
                         } catch (CodeBaseException $exception) {
                             Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                         } catch (\Exception $exception) {
                             // If we can't figure out what kind of a call
                             // this is, don't worry about it
                         }
                     } else {
                         // This is stuff like `Class->$foo`. I'm ignoring
                         // it.
                     }
                 }
             }
             if ($variable) {
                 $variable->getUnionType()->addUnionType($parameter->getUnionType());
             }
         }
     }
     // If we're in quick mode, don't retest methods based on
     // parameter types passed in
     if (Config::get()->quick_mode) {
         return;
     }
     // We're going to hunt to see if any of the arguments
     // have a mismatch with the parameters. If so, we'll
     // re-check the method to see how the parameters impact
     // its return type
     $has_argument_parameter_mismatch = false;
     // Now that we've made sure the arguments are sufficient
     // for definitions on the method, we iterate over the
     // arguments again and add their types to the parameter
     // types so we can test the method again
     $argument_list = $node->children['args'];
     // We create a copy of the parameter list so we can switch
     // back to it after
     $original_parameter_list = $method->getParameterList();
     // Create a backup of the method's scope so that we can
     // reset it after f*****g with it below
     $original_method_scope = $method->getContext()->getScope();
     foreach ($argument_list->children as $i => $argument) {
         $parameter = $method->getParameterList()[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If the parameter has no type, pass the
         // argument's type to it
         if ($parameter->getUnionType()->isEmpty()) {
             $has_argument_parameter_mismatch = true;
             $argument_type = UnionType::fromNode($this->context, $this->code_base, $argument);
             // If this isn't an internal function or method
             // and it has no type, add the argument's type
             // to it so we can compare it to subsequent
             // calls
             if (!$parameter->getContext()->isInternal()) {
                 // Clone the parameter in the original
                 // parameter list so we can reset it
                 // later
                 $original_parameter_list[$i] = clone $parameter;
                 // Then set the new type on that parameter based
                 // on the argument's type. We'll use this to
                 // retest the method with the passed in types
                 $parameter->getUnionType()->addUnionType($argument_type);
                 // If we're passing by reference, get the variable
                 // we're dealing with wrapped up and shoved into
                 // the scope of the method
                 if ($parameter->isPassByReference()) {
                     if ($argument->kind == \ast\AST_VAR) {
                         // Get the variable
                         $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
                         // Add it to the scope of the function wrapped
                         // in a way that makes it addressable as the
                         // parameter its mimicking
                         $method->getContext()->addScopeVariable(new PassByReferenceVariable($parameter, $variable));
                     }
                 } else {
                     // Overwrite the method's variable representation
                     // of the parameter with the parameter with the
                     // new type
                     $method->getContext()->addScopeVariable($parameter);
                 }
             }
         }
     }
     // Now that we know something about the parameters used
     // to call the method, we can reanalyze the method with
     // the types of the parameter, making sure we don't get
     // into an infinite loop of checking calls to the current
     // method in scope
     if ($has_argument_parameter_mismatch && !$method->getContext()->isInternal() && (!$this->context->isMethodScope() || $method->getFQSEN() !== $this->context->getMethodFQSEN())) {
         $method->analyze($method->getContext(), $code_base);
     }
     // Reset to the original parameter list after having
     // tested the parameters with the types passed in
     $method->setParameterList($original_parameter_list);
     // Reset the scope to its original version before we
     // put new parameters in it
     $method->getContext()->setScope($original_method_scope);
 }
예제 #7
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));
     // If we have a 'this' variable in our current scope,
     // pass it down into the closure
     $context = $this->context->withScope(new Scope());
     if ($this->context->getScope()->hasVariableWithName('this')) {
         $context->addScopeVariable($this->context->getScope()->getVariableWithName('this'));
     }
     $method = Method::fromNode($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 (!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) {
                 Issue::emit(Issue::VariableUseClause, $this->context->getFile(), $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)) {
                     Issue::emit(Issue::UndeclaredVariable, $this->context->getFile(), $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()->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);
 }
예제 #8
0
파일: MethodMap.php 프로젝트: actank/phan
 private function addMethodWithScopeAndName(Method $method, string $scope, string $name)
 {
     $this->method_map[$scope][$name] = $method;
     // If we're doing dead code detection, map the name
     // directly to the method so we can quickly look up
     // all methods with that name to add a possible
     // reference
     if (Config::get()->dead_code_detection) {
         $this->method_name_map[strtolower($name)][] = $method;
     }
     // Associate the element with the file it was found in
     $this->getFileByPath($method->getContext()->getFile())->addMethodFQSEN($method->getFQSEN());
 }
예제 #9
0
파일: Method.php 프로젝트: tpunt/phan
 /**
  * @param Context $context
  * The context in which the node appears
  *
  * @param CodeBase $code_base
  *
  * @param Node $node
  * An AST node representing a method
  *
  * @return Method
  * A Method representing the AST node in the
  * given context
  */
 public static function fromNode(Context $context, CodeBase $code_base, Decl $node, FullyQualifiedMethodName $fqsen) : Method
 {
     // Create the skeleton method object from what
     // we know so far
     $method = new Method($context, (string) $node->name, new UnionType(), $node->flags ?? 0, $fqsen);
     // Parse the comment above the method to get
     // extra meta information about the method.
     $comment = Comment::fromStringInContext($node->docComment ?? '', $context);
     // @var Parameter[]
     // The list of parameters specified on the
     // method
     $parameter_list = Parameter::listFromNode($context, $code_base, $node->children['params']);
     // Add each parameter to the scope of the function
     foreach ($parameter_list as $parameter) {
         $method->getInternalScope()->addVariable($parameter);
     }
     // If the method is Analyzable, set the node so that
     // we can come back to it whenever we like and
     // rescan it
     $method->setNode($node);
     // Set the parameter list on the method
     $method->setParameterList($parameter_list);
     $method->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isRequired() ? 1 : 0);
     }, 0));
     $method->setNumberOfOptionalParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isOptional() ? 1 : 0);
     }, 0));
     // Check to see if the comment specifies that the
     // method is deprecated
     $method->setIsDeprecated($comment->isDeprecated());
     $method->setSuppressIssueList($comment->getSuppressIssueList());
     if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
         $method->setNumberOfOptionalParameters(999);
         $method->setNumberOfRequiredParameters(0);
     }
     // Take a look at method return types
     if ($node->children['returnType'] !== null) {
         // Get the type of the parameter
         $union_type = UnionType::fromNode($context, $code_base, $node->children['returnType']);
         $method->getUnionType()->addUnionType($union_type);
     }
     if ($comment->hasReturnUnionType()) {
         // See if we have a return type specified in the comment
         $union_type = $comment->getReturnType();
         if ($union_type->hasSelfType()) {
             // We can't actually figure out 'static' at this
             // point, but fill it in regardless. It will be partially
             // correct
             if ($context->isInClassScope()) {
                 // n.b.: We're leaving the reference to self, static
                 //       or $this in the type because I'm guessing
                 //       it doesn't really matter. Apologies if it
                 //       ends up being an issue.
                 $union_type->addUnionType($context->getClassFQSEN()->asUnionType());
             }
         }
         $method->getUnionType()->addUnionType($union_type);
     }
     // Add params to local scope for user functions
     if (!$method->isInternal()) {
         $parameter_offset = 0;
         foreach ($method->getParameterList() as $i => $parameter) {
             if ($parameter->getUnionType()->isEmpty()) {
                 // If there is no type specified in PHP, check
                 // for a docComment with @param declarations. We
                 // assume order in the docComment matches the
                 // parameter order in the code
                 if ($comment->hasParameterWithNameOrOffset($parameter->getName(), $parameter_offset)) {
                     $comment_type = $comment->getParameterWithNameOrOffset($parameter->getName(), $parameter_offset)->getUnionType();
                     $parameter->getUnionType()->addUnionType($comment_type);
                 }
             }
             // If there's a default value on the parameter, check to
             // see if the type of the default is cool with the
             // specified type.
             if ($parameter->hasDefaultValue()) {
                 $default_type = $parameter->getDefaultValueType();
                 if (!$default_type->isEqualTo(NullType::instance()->asUnionType())) {
                     if (!$default_type->isEqualTo(NullType::instance()->asUnionType()) && !$default_type->canCastToUnionType($parameter->getUnionType())) {
                         Issue::maybeEmit($code_base, $context, Issue::TypeMismatchDefault, $node->lineno ?? 0, (string) $parameter->getUnionType(), $parameter->getName(), (string) $default_type);
                     }
                     $parameter->getUnionType()->addUnionType($default_type);
                 }
                 // If we have no other type info about a parameter,
                 // just because it has a default value of null
                 // doesn't mean that is its type. Any type can default
                 // to null
                 if ((string) $default_type === 'null' && !$parameter->getUnionType()->isEmpty()) {
                     $parameter->getUnionType()->addType(NullType::instance());
                 }
             }
             ++$parameter_offset;
         }
     }
     return $method;
 }
예제 #10
0
파일: Clazz.php 프로젝트: hslatman/phan
 /**
  * @return Method
  * The method with the given name
  */
 public function getMethodByNameInContext(CodeBase $code_base, string $name, Context $context) : Method
 {
     $method_fqsen = FullyQualifiedMethodName::make($this->getFQSEN(), $name);
     if (!$code_base->hasMethod($method_fqsen)) {
         if ('__construct' === $name) {
             // Create a default constructor if its requested
             // but doesn't exist yet
             $default_constructor = Method::defaultConstructorForClassInContext($this, $this->getContext()->withClassFQSEN($this->getFQSEN()));
             $this->addMethod($code_base, $default_constructor);
             return $default_constructor;
         }
         throw new CodeBaseException("Method with name {$name} does not exist for class {$this->getFQSEN()}.");
     }
     return $code_base->getMethod($method_fqsen);
 }
예제 #11
0
파일: ClassMap.php 프로젝트: nagyistge/phan
 /**
  * @return void
  */
 public function addMethod(Method $method)
 {
     $this->method_map[strtolower($method->getFQSEN()->getNameWithAlternateId())] = $method;
 }
예제 #12
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;
 }
예제 #13
0
파일: DemoPlugin.php 프로젝트: tpunt/phan
 /**
  * @param CodeBase $code_base
  * The code base in which the method exists
  *
  * @param Method $method
  * A method being analyzed
  *
  * @return void
  */
 public function analyzeMethod(CodeBase $code_base, Method $method)
 {
     // As an example, we test to see if the name of the
     // method is `function`, and emit an issue if it is.
     if ($method->getName() == 'function') {
         $this->emitIssue($code_base, $method->getContext(), 'DemoPluginMethodName', "Method {$method->getFQSEN()} cannot be called `function`");
     }
 }
예제 #14
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param Method $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @param CodeBase $code_base
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arg_check
  * Formerly `function arg_check`
  */
 private static function analyzeInternalArgumentType(Method $method, Node $node, Context $context, CodeBase $code_base)
 {
     $arglist = $node->children['args'];
     $argcount = count($arglist->children);
     switch ($method->getName()) {
         case 'join':
         case 'implode':
             // (string glue, array pieces),
             // (array pieces, string glue) or
             // (array pieces)
             if ($argcount == 1) {
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(pieces) is %s but {$method->getFQSEN()}() takes array when passed only 1 arg");
                 return;
             } else {
                 if ($argcount == 2) {
                     $arg1_type = UnionType::fromNode($context, $code_base, $arglist->children[0]);
                     $arg2_type = UnionType::fromNode($context, $code_base, $arglist->children[1]);
                     if ((string) $arg1_type == 'array') {
                         if (!$arg1_type->canCastToUnionType(StringType::instance()->asUnionType())) {
                             Log::err(Log::EPARAM, "arg#2(glue) is {$arg2_type} but {$method->getFQSEN()}() takes string when arg#1 is array", $context->getFile(), $context->getLineNumberStart());
                         }
                     } else {
                         if ((string) $arg1_type == 'string') {
                             if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                                 Log::err(Log::EPARAM, "arg#2(pieces) is {$arg2_type} but {$method->getFQSEN()}() takes array when arg#1 is string", $context->getFile(), $context->getLineNumberStart());
                             }
                         }
                     }
                     return;
                 }
             }
             // Any other arg counts we will let the regular
             // checks handle
             break;
         case 'array_udiff':
         case 'array_diff_uassoc':
         case 'array_uintersect_assoc':
         case 'array_intersect_ukey':
             if ($argcount < 3) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 return;
             }
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), "The last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 1; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, CallableType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes array");
             }
             return;
         case 'array_diff_uassoc':
         case 'array_uintersect_uassoc':
             if ($argcount < 4) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 return;
             }
             // The last 2 arguments must be a callable and there
             // can be a variable number of arrays before it
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), "The last argument to {$method->getFQSEN()} must be a callable");
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 2], $context, $code_base, CallableType::instance()->asUnionType(), "The second last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 2; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes array");
             }
             return;
         case 'strtok':
             // (string str, string token) or (string token)
             if ($argcount == 1) {
                 // If we have just one arg it must be a string token
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(token) is %s but {$method->getFQSEN()}() takes string when passed only one arg");
             }
             // The arginfo check will handle the other case
             break;
         case 'min':
         case 'max':
             if ($argcount == 1) {
                 // If we have just one arg it must be an array
                 if (!self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(values) is %s but {$method->getFQSEN()}() takes array when passed only one arg")) {
                     return;
                 }
             }
             // The arginfo check will handle the other case
             break;
         default:
             break;
     }
 }
예제 #15
0
 private function addMethodWithScopeAndName(Method $method, string $scope, string $name)
 {
     $this->method_map[$scope][$name] = $method;
     // For elements that aren't internal PHP classes
     // if (!$method->getContext()->isInternal()) {
     // Associate the element with the file it was found in
     $this->getFileByPath($method->getContext()->getFile())->addMethodFQSEN($method->getFQSEN());
     // }
 }
예제 #16
0
파일: ParseVisitor.php 프로젝트: tpunt/phan
 /**
  * Visit a node with kind `\ast\AST_METHOD`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitMethod(Decl $node) : Context
 {
     // Bomb out if we're not in a class context
     $class = $this->getContextClass();
     $method_name = (string) $node->name;
     $method_fqsen = FullyQualifiedMethodName::fromStringInContext($method_name, $this->context);
     // Hunt for an available alternate ID if necessary
     $alternate_id = 0;
     while ($this->code_base->hasMethodWithFQSEN($method_fqsen)) {
         $method_fqsen = $method_fqsen->withAlternateId(++$alternate_id);
     }
     $method = Method::fromNode(clone $this->context, $this->code_base, $node, $method_fqsen);
     $class->addMethod($this->code_base, $method, new None());
     if ('__construct' === $method_name) {
         $class->setIsParentConstructorCalled(false);
         if ($class->isGeneric()) {
             // Get the set of template type identifiers defined on
             // the class
             $template_type_identifiers = array_keys($class->getTemplateTypeMap());
             // Get the set of template type identifiers defined
             // across all parameter types
             $parameter_template_type_identifiers = [];
             foreach ($method->getParameterList() as $parameter) {
                 foreach ($parameter->getUnionType()->getTypeSet() as $type) {
                     if ($type instanceof TemplateType) {
                         $parameter_template_type_identifiers[] = $type->getName();
                     }
                 }
             }
             $missing_template_type_identifiers = array_diff($template_type_identifiers, $parameter_template_type_identifiers);
             if ($missing_template_type_identifiers) {
                 $this->emitIssue(Issue::GenericConstructorTypes, $node->lineno ?? 0, implode(',', $missing_template_type_identifiers), (string) $class->getFQSEN());
             }
         }
     } elseif ('__invoke' === $method_name) {
         $class->getUnionType()->addType(CallableType::instance());
     } elseif ('__toString' === $method_name && !$this->context->getIsStrictTypes()) {
         $class->getUnionType()->addType(StringType::instance());
     }
     // Create a new context with a new scope
     return $this->context->withScope($method->getInternalScope());
 }
예제 #17
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateFunction(CodeBase $code_base, Method $method)
 {
     $fqsen = $method->getFQSEN();
     if (!$fqsen->isAlternate()) {
         return;
     }
     $original_fqsen = $fqsen->getCanonicalFQSEN();
     if (!$code_base->hasMethod($original_fqsen)) {
         return;
     }
     $original_method = $code_base->getMethod($original_fqsen);
     $method_name = $method->getName();
     if ('internal' === $original_method->getContext()->getFile()) {
         // If its in an conditional and the original is an
         // internal method, presume its all OK.
         if ($method->getContext()->getIsConditional()) {
             return;
         }
         Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined internally", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart());
     } else {
         Log::err(Log::EREDEF, "Function {$method_name} defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()} was previously defined at {$original_method->getContext()->getFile()}:{$original_method->getContext()->getLineNumberStart()}", $method->getContext()->getFile(), $method->getContext()->getLineNumberStart());
     }
 }
예제 #18
0
 /**
  * Make sure signatures line up between methods and the
  * methods they override
  *
  * @see https://en.wikipedia.org/wiki/Liskov_substitution_principle
  */
 private static function analyzeOverrideSignature(CodeBase $code_base, Method $method)
 {
     if (!Config::get()->analyze_signature_compatibility) {
         return;
     }
     // Hydrate the class this method is coming from in
     // order to understand if its an override or not
     $class = $method->getClass($code_base);
     $class->hydrate($code_base);
     // Check to see if the method is an override
     // $method->analyzeOverride($code_base);
     // Make sure we're actually overriding something
     if (!$method->getIsOverride()) {
         return;
     }
     // Dont' worry about signatures lining up on
     // constructors. We just want to make sure that
     // calling a method on a subclass won't cause
     // a runtime error. We usually know what we're
     // constructing at instantiation time, so there
     // is less of a risk.
     if ($method->getName() == '__construct') {
         return;
     }
     // Get the method that is being overridden
     $o_method = $method->getOverriddenMethod($code_base);
     // Get the class that the overridden method lives on
     $o_class = $o_method->getClass($code_base);
     // PHP doesn't complain about signature mismatches
     // with traits, so neither shall we
     if ($o_class->isTrait()) {
         return;
     }
     // Get the parameters for that method
     $o_parameter_list = $o_method->getParameterList();
     // If we have a parent type defined, map the method's
     // return type and parameter types through it
     $type_option = $class->getParentTypeOption();
     // Map overridden method parameter types through any
     // template type parameters we may have
     if ($type_option->isDefined()) {
         $o_parameter_list = array_map(function (Parameter $parameter) use($type_option, $code_base) : Parameter {
             if (!$parameter->getUnionType()->hasTemplateType()) {
                 return $parameter;
             }
             $mapped_parameter = clone $parameter;
             $mapped_parameter->setUnionType($mapped_parameter->getUnionType()->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base)));
             return $mapped_parameter;
         }, $o_parameter_list);
     }
     // Map overridden method return type through any template
     // type parameters we may have
     $o_return_union_type = $o_method->getUnionType();
     if ($type_option->isDefined() && $o_return_union_type->hasTemplateType()) {
         $o_return_union_type = $o_return_union_type->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base));
     }
     // Determine if the signatures match up
     $signatures_match = true;
     // Make sure the count of parameters matches
     if ($method->getNumberOfRequiredParameters() > $o_method->getNumberOfRequiredParameters()) {
         $signatures_match = false;
     } else {
         if ($method->getNumberOfParameters() < $o_method->getNumberOfParameters()) {
             $signatures_match = false;
             // If parameter counts match, check their types
         } else {
             foreach ($method->getParameterList() as $i => $parameter) {
                 if (!isset($o_parameter_list[$i])) {
                     continue;
                 }
                 $o_parameter = $o_parameter_list[$i];
                 // Changing pass by reference is not ok
                 // @see https://3v4l.org/Utuo8
                 if ($parameter->isPassByReference() != $o_parameter->isPassByReference()) {
                     $signatures_match = false;
                     break;
                 }
                 // A stricter type on an overriding method is cool
                 if ($o_parameter->getUnionType()->isEmpty() || $o_parameter->getUnionType()->isType(MixedType::instance())) {
                     continue;
                 }
                 // Its not OK to have a more relaxed type on an
                 // overriding method
                 //
                 // https://3v4l.org/XTm3P
                 if ($parameter->getUnionType()->isEmpty()) {
                     $signatures_match = false;
                     break;
                 }
                 // If we have types, make sure they line up
                 //
                 // TODO: should we be expanding the types on $o_parameter
                 //       via ->asExpandedTypes($code_base)?
                 //
                 //       @see https://3v4l.org/ke3kp
                 if (!$o_parameter->getUnionType()->canCastToUnionType($parameter->getUnionType())) {
                     $signatures_match = false;
                     break;
                 }
             }
         }
     }
     // Return types should be mappable
     if (!$o_return_union_type->isEmpty()) {
         if (!$method->getUnionType()->asExpandedTypes($code_base)->canCastToUnionType($o_return_union_type)) {
             $signatures_match = false;
         }
     }
     // Static or non-static should match
     if ($method->isStatic() != $o_method->isStatic()) {
         if ($o_method->isStatic()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessStaticToNonStatic, $method->getFileRef()->getLineNumberStart(), $o_method->getFQSEN());
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessNonStaticToStatic, $method->getFileRef()->getLineNumberStart(), $o_method->getFQSEN());
         }
     }
     if ($o_method->returnsRef() && !$method->returnsRef()) {
         $signatures_match = false;
     }
     if (!$signatures_match) {
         if ($o_method->isInternal()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::ParamSignatureMismatchInternal, $method->getFileRef()->getLineNumberStart(), $method, $o_method);
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::ParamSignatureMismatch, $method->getFileRef()->getLineNumberStart(), $method, $o_method, $o_method->getFileRef()->getFile(), $o_method->getFileRef()->getLineNumberStart());
         }
     }
     // Access must be compatible
     if ($o_method->isProtected() && $method->isPrivate() || $o_method->isPublic() && !$method->isPublic()) {
         if ($o_method->isInternal()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessSignatureMismatchInternal, $method->getFileRef()->getLineNumberStart(), $method, $o_method);
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::AccessSignatureMismatch, $method->getFileRef()->getLineNumberStart(), $method, $o_method, $o_method->getFileRef()->getFile(), $o_method->getFileRef()->getLineNumberStart());
         }
     }
 }
예제 #19
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateFunction(CodeBase $code_base, Method $method)
 {
     $fqsen = $method->getFQSEN();
     if (!$fqsen->isAlternate()) {
         return;
     }
     $original_fqsen = $fqsen->getCanonicalFQSEN();
     if (!$code_base->hasMethod($original_fqsen)) {
         return;
     }
     $original_method = $code_base->getMethod($original_fqsen);
     $method_name = $method->getName();
     if ('internal' === $original_method->getContext()->getFile()) {
         Issue::emit(Issue::RedefineFunctionInternal, $method->getContext()->getFile(), $method->getContext()->getLineNumberStart(), $method_name, $method->getContext()->getFile(), $method->getContext()->getLineNumberStart());
     } else {
         Issue::emit(Issue::RedefineFunction, $method->getContext()->getFile(), $method->getContext()->getLineNumberStart(), $method_name, $method->getContext()->getFile(), $method->getContext()->getLineNumberStart(), $original_method->getContext()->getFile(), $original_method->getContext()->getLineNumberStart());
     }
 }
예제 #20
0
 /**
  * Visit a node with kind `\ast\AST_METHOD`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitMethod(Decl $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     $method_name = (string) $node->name;
     $method_fqsen = FullyQualifiedMethodName::fromStringInContext($method_name, $this->context);
     // Hunt for an available alternate ID if necessary
     $alternate_id = 0;
     while ($this->code_base->hasMethodWithFQSEN($method_fqsen)) {
         $method_fqsen = $method_fqsen->withAlternateId(++$alternate_id);
     }
     // Create a new context with a new scope
     $context = $this->context->withScope(new Scope());
     $method = Method::fromNode($context, $this->code_base, $node);
     // Override the FQSEN with the found alternate ID
     $method->setFQSEN($method_fqsen);
     $clazz->addMethod($this->code_base, $method);
     if ('__construct' === $method_name) {
         $clazz->setIsParentConstructorCalled(false);
     } elseif ('__invoke' === $method_name) {
         $clazz->getUnionType()->addType(CallableType::instance());
     } elseif ('__toString' === $method_name && !$this->context->getIsStrictTypes()) {
         $clazz->getUnionType()->addType(StringType::instance());
     }
     // Send the context into the method and reset the scope
     $context = $this->context->withMethodFQSEN($method->getFQSEN());
     return $context;
 }
예제 #21
0
파일: Method.php 프로젝트: hslatman/phan
 /**
  * @param Context $context
  * The context in which the node appears
  *
  * @param CodeBase $code_base
  *
  * @param Node $node
  * An AST node representing a method
  *
  * @return Method
  * A Method representing the AST node in the
  * given context
  *
  *
  * @see \Phan\Deprecated\Pass1::node_func
  * Formerly 'function node_func'
  */
 public static function fromNode(Context $context, CodeBase $code_base, Node $node) : Method
 {
     // Parse the comment above the method to get
     // extra meta information about the method.
     $comment = Comment::fromStringInContext($node->docComment ?? '', $context);
     // @var Parameter[]
     // The list of parameters specified on the
     // method
     $parameter_list = Parameter::listFromNode($context, $code_base, $node->children['params']);
     // Add each parameter to the scope of the function
     foreach ($parameter_list as $parameter) {
         $context = $context->withScopeVariable($parameter);
     }
     // Create the skeleton method object from what
     // we know so far
     $method = new Method($context, $node->name, new UnionType(), $node->flags ?? 0);
     // If the method is Analyzable, set the node so that
     // we can come back to it whenever we like and
     // rescan it
     $method->setNode($node);
     // Set the parameter list on the method
     $method->setParameterList($parameter_list);
     $method->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isRequired() ? 1 : 0);
     }, 0));
     $method->setNumberOfOptionalParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isOptional() ? 1 : 0);
     }, 0));
     // Check to see if the comment specifies that the
     // method is deprecated
     $method->setIsDeprecated($comment->isDeprecated());
     // Take a look at method return types
     if ($node->children['returnType'] !== null) {
         $union_type = UnionType::fromSimpleNode($context, $node->children['returnType']);
         $method->getUnionType()->addUnionType($union_type);
     } else {
         if ($comment->hasReturnUnionType()) {
             // See if we have a return type specified in the comment
             $union_type = $comment->getReturnType();
             if ($union_type->hasSelfType()) {
                 // We can't actually figure out 'static' at this
                 // point, but fill it in regardless. It will be partially
                 // correct
                 if ($context->hasClassFQSEN()) {
                     $union_type = $union_type->addUnionType($context->getClassFQSEN()->asUnionType());
                 }
             }
             $method->getUnionType()->addUnionType($union_type);
         }
     }
     // Add params to local scope for user functions
     if ($context->getFile() != 'internal') {
         $parameter_offset = 0;
         foreach ($method->parameter_list as $i => $parameter) {
             if ($parameter->getUnionType()->isEmpty()) {
                 // If there is no type specified in PHP, check
                 // for a docComment with @param declarations. We
                 // assume order in the docComment matches the
                 // parameter order in the code
                 if ($comment->hasParameterAtOffset($parameter_offset)) {
                     $comment_type = $comment->getParameterAtOffset($parameter_offset)->getUnionType();
                     $parameter->getUnionType()->addUnionType($comment_type);
                 }
             }
             // If there's a default value on the parameter, check to
             // see if the type of the default is cool with the
             // specified type.
             if ($parameter->hasDefaultValue()) {
                 $default_type = $parameter->getDefaultValueType();
                 if (!$default_type->canCastToUnionType($parameter->getUnionType())) {
                     Log::err(Log::ETYPE, "Default value for {$parameter->getUnionType()} \${$parameter->getName()} can't be {$default_type}", $context->getFile(), $node->lineno);
                 }
                 // If we have no other type info about a parameter,
                 // just because it has a default value of null
                 // doesn't mean that is its type. Any type can default
                 // to null
                 if ((string) $default_type === 'null' && !$parameter->getUnionType()->isEmpty()) {
                     $parameter->getUnionType()->addType(NullType::instance());
                 }
             }
             ++$parameter_offset;
         }
     }
     return $method;
 }