Beispiel #1
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeDuplicateClass(CodeBase $code_base, Clazz $clazz)
 {
     // Determine if its a duplicate by looking to see if
     // the FQSEN is suffixed with an alternate ID.
     if (!$clazz->getFQSEN()->isAlternate()) {
         return;
     }
     $original_fqsen = $clazz->getFQSEN()->getCanonicalFQSEN();
     if (!$code_base->hasClassWithFQSEN($original_fqsen)) {
         // If there's a missing class we'll catch that
         // elsewhere
         return;
     }
     // Get the original class
     $original_class = $code_base->getClassByFQSEN($original_fqsen);
     // Check to see if the original definition was from
     // an internal class
     if ($original_class->isInternal()) {
         Issue::emit(Issue::RedefineClassInternal, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class);
         // Otherwise, print the coordinates of the original
         // definition
     } else {
         Issue::emit(Issue::RedefineClass, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $original_class, $original_class->getContext()->getFile(), $original_class->getContext()->getLineNumberStart());
     }
     return;
 }
 /**
  * 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::emit(Issue::TypeParentConstructorCalled, $clazz->getContext()->getFile(), $clazz->getContext()->getLineNumberStart(), (string) $clazz->getFQSEN(), (string) $parent_clazz->getFQSEN());
     }
 }
Beispiel #4
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);
                 }
             }
         }
     }
 }
Beispiel #5
0
 /**
  * @param string $string String to check against
  *
  * @dataProvider invalidUTF8StringsProvider
  */
 public function testUTF8CharactersDoNotCauseDOMAttrToFail($string)
 {
     $output = new BufferedOutput();
     $printer = new CheckstylePrinter();
     $printer->configureOutput($output);
     $printer->print(new IssueInstance(Issue::fromType(Issue::SyntaxError), 'test.php', 0, [$string]));
     $printer->flush();
 }
 /**
  * @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;
 }
Beispiel #7
0
 /**
  * @param string $string String to check against
  * @param string $messageExpected Message component of expected CSV line
  *
  * @dataProvider specialCharacterCasesProvider
  */
 public function testSpecialCharactersAreProperlyEncoded($string, $messageExpected)
 {
     $output = new BufferedOutput();
     $printer = new CSVPrinter();
     $printer->configureOutput($output);
     $printer->print(new IssueInstance(Issue::fromType(Issue::SyntaxError), 'test.php', 0, [$string]));
     $printer->flush();
     $expected = 'test.php,0,10,critical,UndefError,PhanSyntaxError,' . $messageExpected;
     $actual = explode("\n", $output->fetch())[1];
     // Ignore header
     $this->assertEquals($expected, $actual);
 }
 /**
  * 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 (!$code_base->hasMethod($original_fqsen)) {
         return;
     }
     $original_method = $code_base->getMethod($original_fqsen);
     $method_name = $method->getName();
     if ($original_method->isInternal()) {
         Issue::emit(Issue::RedefineFunctionInternal, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), $method_name, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart());
     } else {
         Issue::emit(Issue::RedefineFunction, $method->getFileRef()->getFile(), $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 analyzePropertyTypes(CodeBase $code_base, Clazz $clazz)
 {
     foreach ($clazz->getPropertyList($code_base) as $property) {
         $union_type = $property->getUnionType();
         // 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;
             }
             // Otherwise, make sure the class exists
             $type_fqsen = $type->asFQSEN();
             if (!$code_base->hasClassWithFQSEN($type_fqsen)) {
                 Issue::emit(Issue::UndeclaredTypeProperty, $property->getContext()->getFile(), $property->getContext()->getLineNumberStart(), (string) $type_fqsen);
             }
         }
     }
 }
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeParameterTypes(CodeBase $code_base, FunctionInterface $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->getTypeSet() 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)) {
                 Issue::emit(Issue::UndeclaredTypeParameter, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart(), (string) $type_fqsen);
             }
         }
     }
 }
Beispiel #11
0
 /**
  * @return Clazz[]
  * A list of classes associated with the given node
  *
  * @throws IssueException
  * An exception is thrown if we can't find a class for
  * the given type
  */
 private function classListFromNode(Node $node)
 {
     // Get the types associated with the node
     $union_type = self::unionTypeFromNode($this->code_base, $this->context, $node);
     // Iterate over each viable class type to see if any
     // have the constant we're looking for
     foreach ($union_type->nonNativeTypes()->getTypeSet() as $class_type) {
         // Get the class FQSEN
         $class_fqsen = $class_type->asFQSEN();
         // See if the class exists
         if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
             throw new IssueException(Issue::fromType(Issue::UndeclaredClassReference)($this->context->getFile(), $node->lineno ?? 0, [(string) $class_fqsen]));
         }
         (yield $this->code_base->getClassByFQSEN($class_fqsen));
     }
 }
Beispiel #12
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitProp(Node $node) : Context
 {
     $property_name = $node->children['prop'];
     // Things like $foo->$bar
     if (!is_string($property_name)) {
         return $this->context;
     }
     assert(is_string($property_name), "Property must be string in context {$this->context}");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList();
     } catch (CodeBaseException $exception) {
         // This really shouldn't happen since the code
         // parsed cleanly. This should fatal.
         // throw $exception;
         return $this->context;
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a class
         // this is, don't worry about it
         return $this->context;
     }
     foreach ($class_list as $clazz) {
         // Check to see if this class has the property or
         // a setter
         if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
             if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
                 continue;
             }
         }
         try {
             $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
         } catch (IssueException $exception) {
             Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
             return $this->context;
         }
         if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
             $this->emitIssue(Issue::TypeMismatchProperty, $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
             return $this->context;
         }
         // After having checked it, add this type to it
         $property->getUnionType()->addUnionType($this->right_type);
         return $this->context;
     }
     if (Config::get()->allow_missing_properties) {
         try {
             // Create the property
             (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
         } catch (\Exception $exception) {
             // swallow it
         }
     } elseif (!empty($class_list)) {
         $this->emitIssue(Issue::UndeclaredProperty, $node->lineno ?? 0, $property_name);
     } else {
         // If we hit this part, we couldn't figure out
         // the class, so we ignore the issue
     }
     return $this->context;
 }
Beispiel #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);
 }
Beispiel #14
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
 {
     // Convert the file to an Abstract Syntax Tree
     // before passing it on to the recursive version
     // of this method
     $node = \ast\parse_file($file_path, Config::get()->ast_version);
     // Set the file on the context
     $context = (new Context())->withFile($file_path);
     // Ensure we have some content
     if (empty($node)) {
         Issue::emit(Issue::EmptyFile, $file_path, 0, $file_path);
         return $context;
     }
     // Start recursively analyzing the tree
     return self::analyzeNodeInContext($code_base, $context, $node);
 }
Beispiel #15
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);
         }
     }
 }
Beispiel #16
0
 /**
  * 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, FunctionInterface $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) {
         if (!is_object($argument)) {
             continue;
         }
         $parameter = $method->getParameterForCaller($i);
         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();
             } elseif ($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 (IssueException $exception) {
                         Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
                     } 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) {
         if (!is_object($argument)) {
             continue;
         }
         $parameter = $method->getParameterForCaller($i);
         if (!$parameter) {
             continue;
         }
         if (Config::get()->dead_code_detection) {
             (new ArgumentVisitor($this->code_base, $this->context))($argument);
         }
         // 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();
             } elseif ($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 (IssueException $exception) {
                         Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
                     } 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->getVariadicElementUnionType());
             }
         }
     }
     // 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->getInternalScope();
     foreach ($argument_list->children as $i => $argument) {
         // TODO(Issue #376): Support inference on the child in **the set of vargs**, not just the first vararg
         // This is just testing the first vararg.
         // The implementer will also need to restore the original parameter list.
         $parameter = $original_parameter_list[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If the parameter has no type, pass the
         // argument's type to it
         if ($parameter->getVariadicElementUnionType()->isEmpty()) {
             $has_argument_parameter_mismatch = true;
             // 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->isInternal()) {
                 $argument_type = UnionType::fromNode($this->context, $this->code_base, $argument);
                 // Clone the parameter in the original
                 // parameter list so we can reset it
                 // later
                 // TODO: If there are varargs and this is beyond the end, ensure last arg is cloned.
                 $original_parameter_list[$i] = clone $original_parameter_list[$i];
                 // 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->getVariadicElementUnionType()->addUnionType($argument_type);
                 if (!is_object($argument)) {
                     continue;
                 }
                 // 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 ($original_parameter_list[$i]->isVariadic()) {
                         // For now, give up and work on it later.
                         // TODO(Issue #376): It's possible to have a parameter `&...$args`. Analysing that is going to be a problem.
                         // Is it possible to create `PassByReferenceVariableCollection extends Variable` or something similar?
                     } elseif ($argument->kind == \ast\AST_VAR) {
                         // Get the variable
                         $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
                         $pass_by_reference_variable = new PassByReferenceVariable($parameter, $variable);
                         $parameter_list = $method->getParameterList();
                         $parameter_list[$i] = $pass_by_reference_variable;
                         $method->setParameterList($parameter_list);
                         // Add it to the scope of the function wrapped
                         // in a way that makes it addressable as the
                         // parameter its mimicking
                         $method->getInternalScope()->addVariable($pass_by_reference_variable);
                     } else {
                         if ($argument->kind == \ast\AST_STATIC_PROP) {
                             // Get the variable
                             $property = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop'] ?? '');
                             $pass_by_reference_variable = new PassByReferenceVariable($parameter, $property);
                             $parameter_list = $method->getParameterList();
                             $parameter_list[$i] = $pass_by_reference_variable;
                             $method->setParameterList($parameter_list);
                             // Add it to the scope of the function wrapped
                             // in a way that makes it addressable as the
                             // parameter its mimicking
                             $method->getInternalScope()->addVariable($pass_by_reference_variable);
                         }
                     }
                 } else {
                     // Overwrite the method's variable representation
                     // of the parameter with the parameter with the
                     // new type
                     $method->getInternalScope()->addVariable($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->isInternal() && (!$this->context->isInFunctionLikeScope() || $method->getFQSEN() !== $this->context->getFunctionLikeFQSEN())) {
         $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->setInternalScope($original_method_scope);
 }
Beispiel #17
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()
             );
         }
     }
     */
 }
Beispiel #18
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;
 }
Beispiel #19
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitCatch(Node $node) : Context
 {
     try {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $node->children['class']);
         $class_list = (new ContextNode($this->code_base, $this->context, $node->children['class']))->getClassList();
     } catch (CodeBaseException $exception) {
         Issue::emit(Issue::UndeclaredClassCatch, $this->context->getFile(), $node->lineno ?? 0, (string) $exception->getFQSEN());
     }
     $variable_name = (new ContextNode($this->code_base, $this->context, $node->children['var']))->getVariableName();
     if (!empty($variable_name)) {
         $variable = Variable::fromNodeInContext($node->children['var'], $this->context, $this->code_base, false);
         if (!$union_type->isEmpty()) {
             $variable->setUnionType($union_type);
         }
         $this->context->addScopeVariable($variable);
     }
     return $this->context;
 }
Beispiel #20
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::emit(Issue::ParamReqAfterOpt, $context->getFile(), $node->lineno ?? 0);
         } else {
             if ($parameter->isOptional() && !$is_optional_seen && $parameter->getUnionType()->isEmpty()) {
                 $is_optional_seen = true;
             }
         }
         $parameter_list[] = $parameter;
     }
     return $parameter_list;
 }
Beispiel #21
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;
     }
 }
Beispiel #22
0
 /**
  * @param string $name
  * The name of the property
  *
  * @param Context $context
  * The context of the caller requesting the property
  *
  * @return Property
  * A property with the given name
  *
  * @throws IssueException
  * An exception may be thrown if the caller does not
  * have access to the given property from the given
  * context
  */
 public function getPropertyByNameInContext(CodeBase $code_base, string $name, Context $context) : Property
 {
     // Get the FQSEN of the property we're looking for
     $property_fqsen = FullyQualifiedPropertyName::make($this->getFQSEN(), $name);
     $property = null;
     // Figure out if we have the property
     $has_property = $code_base->hasPropertyWithFQSEN($property_fqsen);
     // Figure out if the property is accessible
     $is_property_accessible = false;
     if ($has_property) {
         $property = $code_base->getPropertyByFQSEN($property_fqsen);
         $is_remote_access = !$context->isInClassScope() || !$context->getClassInScope($code_base)->getUnionType()->canCastToExpandedUnionType($this->getUnionType(), $code_base);
         $is_property_accessible = !$is_remote_access || $property->isPublic();
     }
     // If the property exists and is accessible, return it
     if ($is_property_accessible) {
         return $property;
     }
     // Check to see if we can use a __get magic method
     if ($this->hasMethodWithName($code_base, '__get')) {
         $method = $this->getMethodByName($code_base, '__get');
         // Make sure the magic method is accessible
         if ($method->isPrivate()) {
             throw new IssueException(Issue::fromType(Issue::AccessPropertyPrivate)($context->getFile(), $context->getLineNumberStart(), [(string) $property_fqsen]));
         } else {
             if ($method->isProtected()) {
                 throw new IssueException(Issue::fromType(Issue::AccessPropertyProtected)($context->getFile(), $context->getLineNumberStart(), [(string) $property_fqsen]));
             }
         }
         $property = new Property($context, $name, $method->getUnionType(), 0, $property_fqsen);
         $this->addProperty($code_base, $property, new None());
         return $property;
     } else {
         if ($has_property) {
             // If we have a property, but its inaccessible, emit
             // an issue
             if ($property->isPrivate()) {
                 throw new IssueException(Issue::fromType(Issue::AccessPropertyPrivate)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"]));
             }
             if ($property->isProtected()) {
                 throw new IssueException(Issue::fromType(Issue::AccessPropertyProtected)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"]));
             }
         }
     }
     // Check to see if missing properties are allowed
     // or we're stdclass
     if (Config::get()->allow_missing_properties || $this->getFQSEN() == FullyQualifiedClassName::getStdClassFQSEN()) {
         $property = new Property($context, $name, new UnionType(), 0, $property_fqsen);
         $this->addProperty($code_base, $property, new None());
         return $property;
     }
     throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$name}}"]));
 }
Beispiel #23
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());
         }
     }
 }
Beispiel #24
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @return null
  */
 public static function analyzeElementReferenceCounts(CodeBase $code_base, TypedStructuralElement $element, string $issue_type)
 {
     // Don't worry about internal elements
     if ($element->getContext()->isInternal()) {
         return;
     }
     if ($element->getReferenceCount($code_base) < 1) {
         if ($element->hasSuppressIssue($issue_type)) {
             return;
         }
         if ($element instanceof Addressable) {
             Issue::emit($issue_type, $element->getContext()->getFile(), $element->getContext()->getLineNumberStart(), (string) $element->getFQSEN());
         } else {
             Issue::emit($issue_type, $element->getContext()->getFile(), $element->getContext()->getLineNumberStart(), (string) $element);
         }
     }
 }
Beispiel #25
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;
 }
Beispiel #26
0
 /**
  * Emit an issue if it is not suppressed
  *
  * @param CodeBase $code_base
  * The code base in which the issue was found
  *
  * @param Context $context
  * The context in which the issue was found
  *
  * @param string $issue_type
  * A name for the type of issue such as 'PhanPluginMyIssue'
  *
  * @param string $issue_message
  * The complete issue message to emit such as 'class with
  * fqsen \NS\Name is broken in some fashion'.
  *
  * @param int $severity
  * A value from the set {Issue::SEVERITY_LOW,
  * Issue::SEVERITY_NORMAL, Issue::SEVERITY_HIGH}.
  *
  * @param int $remediation_difficulty
  * A guess at how hard the issue will be to fix from the
  * set {Issue:REMEDIATION_A, Issue:REMEDIATION_B, ...
  * Issue::REMEDIATION_F} with F being the hardest.
  */
 public function emitIssue(CodeBase $code_base, Context $context, string $issue_type, string $issue_message, int $severity = Issue::SEVERITY_NORMAL, int $remediation_difficulty = Issue::REMEDIATION_B, int $issue_type_id = Issue::TYPE_ID_UNKNOWN)
 {
     $issue = new Issue($issue_type, Issue::CATEGORY_PLUGIN, $severity, $issue_message, $remediation_difficulty, $issue_type_id);
     $issue_instance = new IssueInstance($issue, $context->getFile(), $context->getLineNumberStart(), []);
     Issue::maybeEmitInstance($code_base, $context, $issue_instance);
 }
Beispiel #27
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);
 }
Beispiel #28
0
 /**
  * @param CodeBase $code_base
  * The code base within which we're operating
  *
  * @param Context $context
  * The context in which the node we're going to be looking
  * at exits.
  *
  * @param string $issue_type
  * The type of issue to emit such as Issue::ParentlessClass
  *
  * @param int $lineno
  * The line number where the issue was found
  *
  * @param array parameters
  * Template parameters for the issue's error message
  *
  * @return void
  */
 public static function maybeEmitWithParameters(CodeBase $code_base, Context $context, string $issue_type, int $lineno, array $parameters)
 {
     // If this issue type has been suppressed in
     // the config, ignore it
     if (in_array($issue_type, Config::get()->suppress_issue_types ?? [])) {
         return;
     }
     if ($context->hasSuppressIssue($code_base, $issue_type)) {
         return;
     }
     Issue::emitWithParameters($issue_type, $context->getFile(), $lineno, $parameters);
 }
 /**
  * @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()]);
 }
Beispiel #30
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitProp(Node $node) : Context
 {
     $property_name = $node->children['prop'];
     // Things like $foo->$bar
     if (!is_string($property_name)) {
         return $this->context;
     }
     assert(is_string($property_name), "Property must be string");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList();
     } catch (CodeBaseException $exception) {
         // This really shouldn't happen since the code
         // parsed cleanly. This should fatal.
         // throw $exception;
         return $this->context;
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a class
         // this is, don't worry about it
         return $this->context;
     }
     foreach ($class_list as $clazz) {
         // Check to see if this class has the property or
         // a setter
         if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
             if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
                 continue;
             }
         }
         try {
             $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
         } catch (IssueException $exception) {
             Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
             return $this->context;
         }
         if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
             $this->emitIssue(Issue::TypeMismatchProperty, $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
             return $this->context;
         } else {
             // If we're assigning to an array element then we don't
             // know what the constitutation of the parameter is
             // outside of the scope of this assignment, so we add to
             // its union type rather than replace it.
             if ($this->is_dim_assignment) {
                 $property->getUnionType()->addUnionType($this->right_type);
             }
         }
         // After having checked it, add this type to it
         $property->getUnionType()->addUnionType($this->right_type);
         return $this->context;
     }
     $std_class_fqsen = FullyQualifiedClassName::getStdClassFQSEN();
     if (Config::get()->allow_missing_properties || !empty($class_list) && $class_list[0]->getFQSEN() == $std_class_fqsen) {
         try {
             // Create the property
             $property = (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
             $property->getUnionType()->addUnionType($this->right_type);
         } catch (\Exception $exception) {
             // swallow it
         }
     } elseif (!empty($class_list)) {
         $this->emitIssue(Issue::UndeclaredProperty, $node->lineno ?? 0, "{$class_list[0]->getFQSEN()}->{$property_name}");
     } else {
         // If we hit this part, we couldn't figure out
         // the class, so we ignore the issue
     }
     return $this->context;
 }