private function computeUseDef($code) { $code = sprintf('<?php function foo($param1, $param2) { %s }', $code); $ast = \JMS\PhpManipulator\PhpParser\ParseUtils::parse($code); $scope = (new \Scrutinizer\PhpAnalyzer\PhpParser\Scope\SyntacticScopeCreator())->createScope($ast); $cfa = new \Scrutinizer\PhpAnalyzer\ControlFlow\ControlFlowAnalysis(); $cfa->process($ast); $cfg = $cfa->getGraph(); $this->analysis = new \Scrutinizer\PhpAnalyzer\DataFlow\VariableReachability\MayBeReachingUseAnalysis($cfg, $scope); $this->analysis->analyze(); $this->def = null; $this->uses = array(); \Scrutinizer\PhpAnalyzer\PhpParser\NodeTraversal::traverseWithCallback($ast, new \Scrutinizer\PhpAnalyzer\PhpParser\PreOrderCallback(function ($t, \PHPParser_Node $node) { if (!$node instanceof \PHPParser_Node_Stmt_Label) { return; } if ('D' === $node->name) { $this->def = $node->getAttribute('next'); } else { if (0 === strpos($node->name, 'U')) { $this->uses[] = $node->getAttribute('next'); } } })); $this->assertNotNull($this->def); $this->assertNotEmpty($this->uses); }
/** * Returns the inferred return type for the given function. * * If we cannot infer any type NO type is returned, this is also returned if we can only infer * ALL type, or UNKNOWN type. * * We use two characteristics for inferring a return type. First, we are looking at how the * return value of the function is used in the places from where it is called. Currently, we * are only looking at whether it is passed to other functions/methods and what their expected * parameter types are. * * Second, we also take a look at the exit points of the CFG of the function itself to see * whether there are some specific types which we can infer. For example, a function might return * NULL type, or ALL type in which case its return type would normally be set to ALL type. * However, for our analysis here, we just ignore the ALL type, and add the NULL type to the * list of allowed types. * * @param AbstractFunction $function * * @return PhpType */ private function inferReturnTypeForFunction(AbstractFunction $function, MethodContainer $container = null) { $types = array(); if ($container instanceof \Scrutinizer\PhpAnalyzer\Model\InterfaceC) { foreach ($container->getImplementingClasses() as $class) { if (null === ($implementedFunction = $class->getMethod($function->getName()))) { continue; } $returnType = $implementedFunction->getReturnType(); if ($returnType->isUnknownType() || $returnType->isAllType()) { continue; } $types[] = $returnType; } } if (!$types) { foreach ($function->getInCallSites() as $site) { // We can only analyze call sites which are part of the code which is currently being // scanned. Otherwise, we would need to persist the possible types to the database. // This could be a future improvement though if we deem it necessary. if (null === ($node = $site->getAstNode())) { continue; } if (null === ($parent = $node->getAttribute('parent'))) { continue; } switch (true) { case $parent instanceof \PHPParser_Node_Arg: if (null === ($callNode = $parent->getAttribute('parent'))) { break; } $paramType = $this->getSpecificParamTypeForArg($callNode, $parent); if ($paramType) { $types[] = $paramType; } break; case $parent instanceof \PHPParser_Node_Expr_Assign: case $parent instanceof \PHPParser_Node_Expr_AssignRef: foreach ($parent->var->getAttribute('maybe_using_vars', array()) as $varNode) { if (null === ($argNode = $varNode->getAttribute('parent'))) { continue; } if (!$argNode instanceof \PHPParser_Node_Arg) { continue; } $callNode = $argNode->getAttribute('parent'); $paramType = $this->getSpecificParamTypeForArg($callNode, $argNode); if (null !== $paramType) { $types[] = $paramType; } } break; } } } $cfa = new \Scrutinizer\PhpAnalyzer\ControlFlow\ControlFlowAnalysis(); $cfa->process($function->getAstNode()); $cfg = $cfa->getGraph(); foreach ($cfg->getDirectedSuccNodes($cfg->getImplicitReturn()) as $exitGraphNode) { $exitNode = $exitGraphNode->getAstNode(); if (!$exitNode instanceof \PHPParser_Node_Stmt_Return || null === $exitNode->expr) { $types[] = $this->registry->getNativeType('null'); continue; } $returnType = $exitNode->expr->getAttribute('type'); if ($returnType && !$returnType->isUnknownType() && !$returnType->isAllType()) { $types[] = $returnType; } } return $this->refineTypeForAnnotation($this->registry->createUnionType($types), true); }