/**
  * @return Method[]
  */
 public static function methodListFromReflectionClassAndMethod(Context $context, CodeBase $code_base, \ReflectionClass $class, \ReflectionMethod $reflection_method) : array
 {
     $reflection_method = new \ReflectionMethod($class->getName(), $reflection_method->name);
     $method = new Method($context, $reflection_method->name, new UnionType(), $reflection_method->getModifiers());
     $method->setNumberOfRequiredParameters($reflection_method->getNumberOfRequiredParameters());
     $method->setNumberOfOptionalParameters($reflection_method->getNumberOfParameters() - $reflection_method->getNumberOfRequiredParameters());
     $method->setFQSEN(FullyQualifiedMethodName::fromStringInContext($method->getName(), $context));
     return self::functionListFromFunction($method, $code_base);
 }
Exemple #2
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());
     }
 }
Exemple #3
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());
     }
 }
Exemple #4
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());
         }
     }
 }
Exemple #5
0
 /**
  * @return null
  */
 public function addMethod(CodeBase $code_base, Method $method)
 {
     $method_fqsen = FullyQualifiedMethodName::make($this->getFQSEN(), $method->getName());
     // Don't overwrite overridden methods with
     // parent methods
     if ($code_base->hasMethod($method_fqsen)) {
         return;
     }
     $code_base->addMethodInScope($method, $this->getFQSEN());
 }
Exemple #6
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;
     }
 }
Exemple #7
0
 /**
  * @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) : 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, (string) $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());
     $method->setSuppressIssueList($comment->getSuppressIssueList());
     if ($method->getName() == '__call') {
         $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->hasClassFQSEN()) {
                 // 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;
 }
Exemple #8
0
 /**
  * @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()];
 }
Exemple #9
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;
     }
 }
Exemple #10
0
 /**
  * Add a method to this class
  *
  * @param CodeBase $code_base
  * A reference to the code base in which the ancestor exists
  *
  * @param Method $method
  * The method to copy onto this class
  *
  * @param Option<Type>|None $type_option
  * A possibly defined type used to define template
  * parameter types when importing the method
  *
  * @return null
  */
 public function addMethod(CodeBase $code_base, Method $method, $type_option)
 {
     $method_fqsen = FullyQualifiedMethodName::make($this->getFQSEN(), $method->getName(), $method->getFQSEN()->getAlternateId());
     // Don't overwrite overridden methods with
     // parent methods
     if ($code_base->hasMethodWithFQSEN($method_fqsen)) {
         // Note that we're overriding something
         $existing_method = $code_base->getMethodByFQSEN($method_fqsen);
         $existing_method->setIsOverride(true);
         // Don't add the method
         return;
     }
     if ($method->getFQSEN() !== $method_fqsen) {
         $method = clone $method;
         $method->setDefiningFQSEN($method->getFQSEN());
         $method->setFQSEN($method_fqsen);
         // If we have a parent type defined, map the method's
         // return type and parameter types through it
         if ($type_option->isDefined()) {
             // Map the method's return type
             if ($method->getUnionType()->hasTemplateType()) {
                 $method->setUnionType($method->getUnionType()->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base)));
             }
             // Map each method parameter
             $method->setParameterList(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;
             }, $method->getParameterList()));
         }
     }
     if ($method->getHasYield()) {
         // There's no phpdoc standard for template types of Generators at the moment.
         $newType = UnionType::fromFullyQualifiedString('\\Generator');
         $oldType = $method->getUnionType();
         if (!$newType->canCastToUnionType($method->getUnionType())) {
             $method->setUnionType($newType);
         }
     }
     $code_base->addMethod($method);
 }
Exemple #11
0
 /**
  * @return null
  */
 public function addMethod(CodeBase $code_base, Method $method)
 {
     $method_fqsen = FullyQualifiedMethodName::make($this->getFQSEN(), $method->getName());
     // Don't overwrite overridden methods with
     // parent methods
     if ($code_base->hasMethodWithFQSEN($method_fqsen)) {
         // Note that we're overriding something
         $existing_method = $code_base->getMethodByFQSEN($method_fqsen);
         $existing_method->setIsOverride(true);
         // Don't add the method
         return;
     }
     if ($method->getFQSEN() !== $method_fqsen) {
         $method = clone $method;
         $method->setFQSEN($method_fqsen);
     }
     $code_base->addMethod($method);
 }
Exemple #12
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)
 {
     // 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`");
     }
 }
Exemple #13
0
 /**
  * Add a method to this class
  *
  * @param CodeBase $code_base
  * A reference to the code base in which the ancestor exists
  *
  * @param Method $method
  * The method to copy onto this class
  *
  * @param Option<Type> $type_option
  * A possibly defined type used to define template
  * parameter types when importing the method
  *
  * @return null
  */
 public function addMethod(CodeBase $code_base, Method $method, $type_option)
 {
     $method_fqsen = FullyQualifiedMethodName::make($this->getFQSEN(), $method->getName());
     // Don't overwrite overridden methods with
     // parent methods
     if ($code_base->hasMethodWithFQSEN($method_fqsen)) {
         // Note that we're overriding something
         $existing_method = $code_base->getMethodByFQSEN($method_fqsen);
         $existing_method->setIsOverride(true);
         // Don't add the method
         return;
     }
     if ($method->getFQSEN() !== $method_fqsen) {
         $method = clone $method;
         $method->setDefiningFQSEN($method->getFQSEN());
         $method->setFQSEN($method_fqsen);
         // If we have a parent type defined, map the method's
         // return type and parameter types through it
         if ($type_option->isDefined()) {
             // Map the method's return type
             if ($method->getUnionType()->hasTemplateType()) {
                 $method->setUnionType($method->getUnionType()->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base)));
             }
             // Map each method parameter
             $method->setParameterList(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;
             }, $method->getParameterList()));
         }
     }
     $code_base->addMethod($method);
 }