maybeEmit() public static method

public static maybeEmit ( CodeBase $code_base, Context $context, string $issue_type, integer $lineno, $parameters ) : void
$code_base CodeBase The code base within which we're operating
$context Phan\Language\Context The context in which the node we're going to be looking at exits.
$issue_type string The type of issue to emit such as Issue::ParentlessClass
$lineno integer The line number where the issue was found
return void
Exemplo n.º 1
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateFunction(CodeBase $code_base, FunctionInterface $method)
 {
     $fqsen = $method->getFQSEN();
     if (!$fqsen->isAlternate()) {
         return;
     }
     $original_fqsen = $fqsen->getCanonicalFQSEN();
     if ($original_fqsen instanceof FullyQualifiedFunctionName) {
         if (!$code_base->hasFunctionWithFQSEN($original_fqsen)) {
             return;
         }
         $original_method = $code_base->getFunctionByFQSEN($original_fqsen);
     } else {
         if (!$code_base->hasMethodWithFQSEN($original_fqsen)) {
             return;
         }
         $original_method = $code_base->getMethodByFQSEN($original_fqsen);
     }
     $method_name = $method->getName();
     if (!$method->hasSuppressIssue(Issue::RedefineFunction)) {
         if ($original_method->isInternal()) {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::RedefineFunctionInternal, $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart());
         } else {
             Issue::maybeEmit($code_base, $method->getContext(), Issue::RedefineFunction, $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $original_method->getFileRef()->getFile(), $original_method->getFileRef()->getLineNumberStart());
         }
     }
 }
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeParentConstructorCalled(CodeBase $code_base, Clazz $clazz)
 {
     // Only look at classes configured to require a call
     // to its parent constructor
     if (!in_array($clazz->getName(), Config::get()->parent_constructor_required)) {
         return;
     }
     // Don't worry about internal classes
     if ($clazz->isInternal()) {
         return;
     }
     // Don't worry if there's no parent class
     if (!$clazz->hasParentClassFQSEN()) {
         return;
     }
     if (!$code_base->hasClassWithFQSEN($clazz->getParentClassFQSEN())) {
         // This is an error, but its caught elsewhere. We'll
         // just roll through looking for other errors
         return;
     }
     $parent_clazz = $code_base->getClassByFQSEN($clazz->getParentClassFQSEN());
     if (!$parent_clazz->isAbstract() && !$clazz->getIsParentConstructorCalled()) {
         Issue::maybeEmit($code_base, $clazz->getContext(), Issue::TypeParentConstructorCalled, $clazz->getFileRef()->getLineNumberStart(), (string) $clazz->getFQSEN(), (string) $parent_clazz->getFQSEN());
     }
 }
Exemplo n.º 3
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzePropertyTypes(CodeBase $code_base, Clazz $clazz)
 {
     foreach ($clazz->getPropertyList($code_base) as $property) {
         try {
             $union_type = $property->getUnionType();
         } catch (IssueException $exception) {
             Issue::maybeEmitInstance($code_base, $property->getContext(), $exception->getIssueInstance());
             continue;
         }
         // Look at each type in the parameter's Union Type
         foreach ($union_type->getTypeSet() as $type) {
             // If its a native type or a reference to
             // self, its OK
             if ($type->isNativeType() || $type->isSelfType()) {
                 continue;
             }
             if ($type instanceof TemplateType) {
                 if ($property->isStatic()) {
                     Issue::maybeEmit($code_base, $property->getContext(), Issue::TemplateTypeStaticProperty, $property->getFileRef()->getLineNumberStart(), (string) $property->getFQSEN());
                 }
             } else {
                 // Make sure the class exists
                 $type_fqsen = $type->asFQSEN();
                 if (!$code_base->hasClassWithFQSEN($type_fqsen) && !$type instanceof TemplateType && (!$property->hasDefiningFQSEN() || $property->getDefiningFQSEN() == $property->getFQSEN())) {
                     Issue::maybeEmit($code_base, $property->getContext(), Issue::UndeclaredTypeProperty, $property->getFileRef()->getLineNumberStart(), (string) $property->getFQSEN(), (string) $type_fqsen);
                 }
             }
         }
     }
 }
Exemplo n.º 4
0
 /**
  * @return bool
  * True if the FQSEN exists. If not, a log line is emitted
  */
 private static function fqsenExistsForClass(FQSEN $fqsen, CodeBase $code_base, Clazz $clazz, string $issue_type) : bool
 {
     if (!$code_base->hasClassWithFQSEN($fqsen)) {
         Issue::maybeEmit($code_base, $clazz->getContext(), $issue_type, $clazz->getFileRef()->getLineNumberStart(), (string) $fqsen);
         return false;
     }
     return true;
 }
Exemplo n.º 5
0
 /**
  * @param Node $node
  * A node to check types on
  *
  * @return UnionType
  * The resulting type(s) of the binary operation
  */
 public function visitBinaryAdd(Node $node) : UnionType
 {
     $left = UnionType::fromNode($this->context, $this->code_base, $node->children['left']);
     $right = UnionType::fromNode($this->context, $this->code_base, $node->children['right']);
     // fast-track common cases
     if ($left->isType(IntType::instance()) && $right->isType(IntType::instance())) {
         return IntType::instance()->asUnionType();
     }
     // If both left and right are arrays, then this is array
     // concatenation.
     if ($left->isGenericArray() && $right->isGenericArray()) {
         if ($left->isEqualTo($right)) {
             return $left;
         }
         return ArrayType::instance()->asUnionType();
     }
     if (($left->isType(IntType::instance()) || $left->isType(FloatType::instance())) && ($right->isType(IntType::instance()) || $right->isType(FloatType::instance()))) {
         return FloatType::instance()->asUnionType();
     }
     $left_is_array = !empty($left->genericArrayElementTypes()) && empty($left->nonGenericArrayTypes()) || $left->isType(ArrayType::instance());
     $right_is_array = !empty($right->genericArrayElementTypes()) && empty($right->nonGenericArrayTypes()) || $right->isType(ArrayType::instance());
     if ($left_is_array && !$right->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidRightOperand, $node->lineno ?? 0);
         return new UnionType();
     } elseif ($right_is_array && !$left->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidLeftOperand, $node->lineno ?? 0);
         return new UnionType();
     } elseif ($left_is_array || $right_is_array) {
         // If it is a '+' and we know one side is an array
         // and the other is unknown, assume array
         return ArrayType::instance()->asUnionType();
     }
     return new UnionType([IntType::instance(), FloatType::instance()]);
 }
Exemplo n.º 6
0
 /**
  * Once we know what the universe looks like we
  * can scan for more complicated issues.
  *
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @param string $file_path
  * A list of files to scan
  *
  * @return Context
  */
 public static function analyzeFile(CodeBase $code_base, string $file_path) : Context
 {
     // Set the file on the context
     $context = (new Context())->withFile($file_path);
     // Convert the file to an Abstract Syntax Tree
     // before passing it on to the recursive version
     // of this method
     try {
         $node = \ast\parse_file(Config::projectPath($file_path), Config::get()->ast_version);
     } catch (\ParseError $parse_error) {
         Issue::maybeEmit($code_base, $context, Issue::SyntaxError, $parse_error->getLine(), $parse_error->getMessage());
         return $context;
     }
     // Ensure we have some content
     if (empty($node)) {
         Issue::maybeEmit($code_base, $context, Issue::EmptyFile, 0, $file_path);
         return $context;
     }
     return (new BlockAnalysisVisitor($code_base, $context))($node);
 }
Exemplo n.º 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, 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;
 }
Exemplo n.º 8
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());
         }
     }
 }
Exemplo n.º 9
0
 /**
  * Perform some backwards compatibility checks on a node
  *
  * @return void
  */
 public function analyzeBackwardCompatibility()
 {
     if (!Config::get()->backward_compatibility_checks) {
         return;
     }
     if (empty($this->node->children['expr'])) {
         return;
     }
     if ($this->node->kind === \ast\AST_STATIC_CALL || $this->node->kind === \ast\AST_METHOD_CALL) {
         return;
     }
     $llnode = $this->node;
     if ($this->node->kind !== \ast\AST_DIM) {
         if (!$this->node->children['expr'] instanceof Node) {
             return;
         }
         if ($this->node->children['expr']->kind !== \ast\AST_DIM) {
             (new ContextNode($this->code_base, $this->context, $this->node->children['expr']))->analyzeBackwardCompatibility();
             return;
         }
         $temp = $this->node->children['expr']->children['expr'];
         $llnode = $this->node->children['expr'];
         $lnode = $temp;
     } else {
         $temp = $this->node->children['expr'];
         $lnode = $temp;
     }
     if (!($temp->kind == \ast\AST_PROP || $temp->kind == \ast\AST_STATIC_PROP)) {
         return;
     }
     while ($temp instanceof Node && ($temp->kind == \ast\AST_PROP || $temp->kind == \ast\AST_STATIC_PROP)) {
         $llnode = $lnode;
         $lnode = $temp;
         // Lets just hope the 0th is the expression
         // we want
         $temp = array_values($temp->children)[0];
     }
     if (!$temp instanceof Node) {
         return;
     }
     // Foo::$bar['baz'](); is a problem
     // Foo::$bar['baz'] is not
     if ($lnode->kind === \ast\AST_STATIC_PROP && $this->node->kind !== \ast\AST_CALL) {
         return;
     }
     // $this->$bar['baz']; is a problem
     // $this->bar['baz'] is not
     if ($lnode->kind === \ast\AST_PROP && !$lnode->children['prop'] instanceof Node && !$llnode->children['prop'] instanceof Node) {
         return;
     }
     if (($lnode->children['prop'] instanceof Node && $lnode->children['prop']->kind == \ast\AST_VAR || !empty($lnode->children['class']) && $lnode->children['class'] instanceof Node && ($lnode->children['class']->kind == \ast\AST_VAR || $lnode->children['class']->kind == \ast\AST_NAME) || !empty($lnode->children['expr']) && $lnode->children['expr'] instanceof Node && ($lnode->children['expr']->kind == \ast\AST_VAR || $lnode->children['expr']->kind == \ast\AST_NAME)) && ($temp->kind == \ast\AST_VAR || $temp->kind == \ast\AST_NAME)) {
         $ftemp = new \SplFileObject($this->context->getFile());
         $ftemp->seek($this->node->lineno - 1);
         $line = $ftemp->current();
         unset($ftemp);
         if (strpos($line, '}[') === false || strpos($line, ']}') === false || strpos($line, '>{') === false) {
             Issue::maybeEmit($this->code_base, $this->context, Issue::CompatiblePHP7, $this->node->lineno ?? 0);
         }
     }
 }
Exemplo n.º 10
0
 /**
  * Check to see if signatures match
  *
  * @return void
  */
 public static function analyzeComposition(CodeBase $code_base, Clazz $class)
 {
     // Get the Class's FQSEN
     $fqsen = $class->getFQSEN();
     // Get the list of all inherited classes.
     $inherited_class_list = $class->getInheritedClassList($code_base);
     // No chance of failed composition if we don't inherit from
     // lots of stuff.
     if (count($inherited_class_list) < 2) {
         return;
     }
     // For each property, find out every inherited class that defines it
     // and check to see if the types line up.
     foreach ($class->getPropertyList($code_base) as $property) {
         try {
             $property_union_type = $property->getUnionType();
         } catch (IssueException $exception) {
             $property_union_type = new UnionType();
         }
         // Check for that property on each inherited
         // class/trait/interface
         foreach ($inherited_class_list as $inherited_class) {
             // Skip any classes/traits/interfaces not defining that
             // property
             if (!$inherited_class->hasPropertyWithName($code_base, $property->getName())) {
                 continue;
             }
             // We don't call `getProperty` because that will create
             // them in some circumstances.
             $inherited_property_map = $inherited_class->getPropertyMap($code_base);
             if (!isset($inherited_property_map[$property->getName()])) {
                 continue;
             }
             // Get the inherited property
             $inherited_property = $inherited_property_map[$property->getName()];
             // Figure out if this property type can cast to the
             // inherited definition's type.
             $can_cast = $property_union_type->canCastToExpandedUnionType($inherited_property->getUnionType(), $code_base);
             if ($can_cast) {
                 continue;
             }
             // Don't emit an issue if the property suppresses the issue
             if ($property->hasSuppressIssue(Issue::IncompatibleCompositionProp)) {
                 continue;
             }
             Issue::maybeEmit($code_base, $property->getContext(), Issue::IncompatibleCompositionProp, $property->getFileRef()->getLineNumberStart(), (string) $class->getFQSEN(), (string) $inherited_class->getFQSEN(), $property->getName(), (string) $class->getFQSEN(), $class->getFileRef()->getFile(), $class->getFileRef()->getLineNumberStart());
         }
     }
     // TODO: This has too much overlap with PhanParamSignatureMismatch
     //       and we should figure out how to merge it.
     /*
     $method_map =
         $code_base->getMethodMapByFullyQualifiedClassName($fqsen);
     
     // For each method, find out every inherited class that defines it
     // and check to see if the types line up.
     foreach ($method_map as $i => $method) {
     
         $method_union_type = $method->getUnionType();
     
         // We don't need to analyze constructors for signature
         // compatibility
         if ($method->getName() == '__construct') {
             continue;
         }
     
         // Get the method parameter list
     
         // Check for that method on each inherited
         // class/trait/interface
         foreach ($inherited_class_list as $inherited_class) {
     
             // Skip anything that doesn't define this method
             if (!$inherited_class->hasMethodWithName($code_base, $method->getName())) {
                 continue;
             }
     
             $inherited_method =
                 $inherited_class->getMethodByName($code_base, $method->getName());
     
             if ($method == $inherited_method) {
                 continue;
             }
     
             // Figure out if this method return type can cast to the
             // inherited definition's return type.
             $is_compatible =
                 $method_union_type->canCastToExpandedUnionType(
                     $inherited_method->getUnionType(),
                     $code_base
                 );
     
             $inherited_method_parameter_map =
                 $inherited_method->getParameterList();
     
             // Figure out if all of the parameter types line up
             foreach ($method->getParameterList() as $i => $parameter) {
                 $is_compatible = (
                     $is_compatible
                     && isset($inherited_method_parameter_map[$i])
                     && $parameter->getUnionType()->canCastToExpandedUnionType(
                         ($inherited_method_parameter_map[$i])->getUnionType(),
                         $code_base
                     )
                 );
             }
     
             if ($is_compatible) {
                 continue;
             }
     
             // Don't emit an issue if the method suppresses the issue
             if ($method->hasSuppressIssue(Issue::IncompatibleCompositionMethod)) {
                 continue;
             }
     
             Issue::maybeEmit(
                 $code_base,
                 $method->getContext(),
                 Issue::IncompatibleCompositionMethod,
                 $method->getFileRef()->getLineNumberStart(),
                 (string)$method,
                 (string)$inherited_method,
                 $inherited_method->getFileRef()->getFile(),
                 $inherited_method->getFileRef()->getLineNumberStart()
             );
         }
     }
     */
 }
Exemplo n.º 11
0
 /**
  * @param Context $context
  * The context in which the node appears
  *
  * @param CodeBase $code_base
  *
  * @param Node $node
  * An AST node representing a function
  *
  * @return Func
  * A Func representing the AST node in the
  * given context
  */
 public static function fromNode(Context $context, CodeBase $code_base, Decl $node) : Func
 {
     // Parse the comment above the function to get
     // extra meta information about the function.
     $comment = Comment::fromStringInContext($node->docComment ?? '', $context);
     // @var Parameter[]
     // The list of parameters specified on the
     // function
     $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 function object from what
     // we know so far
     $func = new Func($context, (string) $node->name, new UnionType(), $node->flags ?? 0);
     // If the function is Analyzable, set the node so that
     // we can come back to it whenever we like and
     // rescan it
     $func->setNode($node);
     // Set the parameter list on the function
     $func->setParameterList($parameter_list);
     $func->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isRequired() ? 1 : 0);
     }, 0));
     $func->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
     // function is deprecated
     $func->setIsDeprecated($comment->isDeprecated());
     $func->setSuppressIssueList($comment->getSuppressIssueList());
     // Take a look at function return types
     if ($node->children['returnType'] !== null) {
         // Get the type of the parameter
         $union_type = UnionType::fromNode($context, $code_base, $node->children['returnType']);
         $func->getUnionType()->addUnionType($union_type);
     }
     if ($comment->hasReturnUnionType()) {
         // See if we have a return type specified in the comment
         $union_type = $comment->getReturnType();
         assert(!$union_type->hasSelfType(), "Function referencing self in {$context}");
         $func->getUnionType()->addUnionType($union_type);
     }
     // Add params to local scope for user functions
     if (!$func->isInternal()) {
         $parameter_offset = 0;
         foreach ($func->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 $func;
 }
Exemplo n.º 12
0
 /**
  * @return Parameter[]
  * A list of parameters from an AST node.
  *
  * @see \Phan\Deprecated\Pass1::node_paramlist
  * Formerly `function node_paramlist`
  */
 public static function listFromNode(Context $context, CodeBase $code_base, Node $node) : array
 {
     assert($node instanceof Node, "node was not an \\ast\\Node");
     $parameter_list = [];
     $is_optional_seen = false;
     foreach ($node->children ?? [] as $i => $child_node) {
         $parameter = Parameter::fromNode($context, $code_base, $child_node);
         if (!$parameter->isOptional() && $is_optional_seen) {
             Issue::maybeEmit($code_base, $context, Issue::ParamReqAfterOpt, $node->lineno ?? 0);
         } elseif ($parameter->isOptional() && !$is_optional_seen && $parameter->getVariadicElementUnionType()->isEmpty()) {
             $is_optional_seen = true;
         }
         $parameter_list[] = $parameter;
     }
     return $parameter_list;
 }
Exemplo n.º 13
0
 /**
  * Once we know what the universe looks like we
  * can scan for more complicated issues.
  *
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @param string $file_path
  * A list of files to scan
  *
  * @return Context
  */
 public static function analyzeFile(CodeBase $code_base, string $file_path) : Context
 {
     // Set the file on the context
     $context = (new Context())->withFile($file_path);
     // Convert the file to an Abstract Syntax Tree
     // before passing it on to the recursive version
     // of this method
     try {
         $node = \ast\parse_file($file_path, Config::get()->ast_version);
     } catch (\ParseError $parse_error) {
         Issue::maybeEmit($code_base, $context, Issue::SyntaxError, $parse_error->getLine(), $parse_error->getMessage());
         return $context;
     }
     // Ensure we have some content
     if (empty($node)) {
         Issue::maybeEmit($code_base, $context, Issue::EmptyFile, 0, $file_path);
         return $context;
     }
     // Whenever we enter a file, we copy all global scope
     // variables to the local scope
     $context->getScope()->copyGlobalToLocal();
     // Start recursively analyzing the tree
     return self::analyzeNodeInContext($code_base, $context, $node);
 }
Exemplo n.º 14
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param FunctionInterface $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(FunctionInterface $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;
             } elseif ($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::maybeEmit($code_base, $context, Issue::ParamSpecial1, $context->getLineNumberStart(), 2, 'glue', (string) $arg2_type, (string) $method->getFQSEN(), 'string', 1, 'array');
                     }
                 } elseif ((string) $arg1_type == 'string') {
                     if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                         Issue::maybeEmit($code_base, $context, Issue::ParamSpecial1, $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::maybeEmit($code_base, $context, Issue::ParamTooFewInternal, $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::maybeEmit($code_base, $context, Issue::ParamTooFewInternal, $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, StringType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     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;
     }
 }
Exemplo n.º 15
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeElementReferenceCounts(CodeBase $code_base, AddressableElement $element, string $issue_type)
 {
     // Don't worry about internal elements
     if ($element->isInternal()) {
         return;
     }
     // Skip methods that are overrides of other methods
     if ($element instanceof ClassElement) {
         if ($element->getIsOverride()) {
             return;
         }
         $class_fqsen = $element->getClassFQSEN();
         // Don't analyze elements defined in a parent
         // class
         if ((string) $class_fqsen !== $element->getFQSEN()) {
             return;
         }
         $defining_class = $element->getClass($code_base);
         // Don't analyze elements on interfaces or on
         // abstract classes, as they're uncallable.
         if ($defining_class->isInterface() || $defining_class->isAbstract() || $defining_class->isTrait()) {
             return;
         }
         // Ignore magic methods
         if ($element instanceof Method) {
             // Doubly nested so that `$element` shows
             // up as Method in Phan.
             if ($element->getIsMagic()) {
                 return;
             }
         }
     }
     // Skip properties on classes that have a magic
     // __get or __set method given that we can't track
     // their access
     if ($element instanceof Property) {
         $defining_class = $element->getClass($code_base);
         if ($defining_class->hasGetOrSetMethod($code_base)) {
             return;
         }
     }
     /*
     print "digraph G {\n";
     foreach ($element->getReferenceList() as $file_ref) {
         print "\t\"{$file_ref->getFile()}\" -> \"{$element->getFileRef()->getFile()}\";\n";
     }
     print "}\n";
     */
     if ($element->getReferenceCount($code_base) < 1) {
         if ($element->hasSuppressIssue($issue_type)) {
             return;
         }
         if ($element instanceof AddressableElement) {
             Issue::maybeEmit($code_base, $element->getContext(), $issue_type, $element->getFileRef()->getLineNumberStart(), (string) $element->getFQSEN());
         } else {
             Issue::maybeEmit($code_base, $element->getContext(), $issue_type, $element->getFileRef()->getLineNumberStart(), (string) $element);
         }
     }
 }