private function traverseCall(\PHPParser_Node $node, LinkedFlowScope $scope) { assert($node instanceof \PHPParser_Node_Expr_MethodCall || $node instanceof \PHPParser_Node_Expr_FuncCall || $node instanceof \PHPParser_Node_Expr_StaticCall); $scope = $this->traverseChildren($node, $scope); // Propagate type for constants if (NodeUtil::isConstantDefinition($node)) { $constantName = $node->args[0]->value->value; if ($constant = $this->typeRegistry->getConstant($constantName)) { $constant->setPhpType($this->getType($node->args[1]->value)); } } // If the assert function is called inside a block node, we are just going to assume // that some sort of exception is thrown if the assert fails (and it will not silently // be ignored). // TODO: This needs to be extracted as a general concept where we can have different // effects for functions/methods. For example, array_pop affects the known item // types of arrays on which it is called. if ($node->getAttribute('parent') instanceof BlockNode && NodeUtil::isMaybeFunctionCall($node, 'assert') && isset($node->args[0])) { $scope = $this->reverseInterpreter->getPreciserScopeKnowingConditionOutcome($node->args[0]->value, $scope, true); } $returnType = null; if (null !== ($function = $this->typeRegistry->getCalledFunctionByNode($node))) { $node->setAttribute('returns_by_ref', $function->isReturnByRef()); $argValues = $argTypes = array(); foreach ($node->args as $arg) { $argValues[] = $arg->value; $argTypes[] = $this->getType($arg); } if ($function instanceof ContainerMethodInterface) { $objType = $function->getContainer(); $maybeReturnType = $function->getReturnType(); if (null !== ($restrictedType = $this->methodInterpreter->getPreciserMethodReturnTypeKnowingArguments($function, $argValues, $argTypes))) { $maybeReturnType = $restrictedType; } $returnType = $this->updateThisReference($node instanceof \PHPParser_Node_Expr_MethodCall ? $node->var : $node->class, $scope->getTypeOfThis(), $objType, $maybeReturnType); } else { if ($function instanceof GlobalFunction) { if (null !== ($restrictedType = $this->functionInterpreter->getPreciserFunctionReturnTypeKnowingArguments($function, $argValues, $argTypes))) { $returnType = $restrictedType; } else { if (null === $returnType) { $returnType = $function->getReturnType(); } } } else { throw new \LogicException(sprintf('Unknown function "%s".', get_class($function))); } } } $node->setAttribute('type', $returnType ?: $this->typeRegistry->getNativeType('unknown')); return $scope; }
private function checkForTypeFunctionCall(\PHPParser_Node $node, LinkedFlowScope $blindScope, $outcome) { if ($node instanceof \PHPParser_Node_Stmt_Case) { $left = $node->getAttribute('parent')->cond; $right = $node->cond; } else { $left = $node->left; $right = $node->right; } // Handle gettype() function calls. $gettypeNode = $stringNode = null; if (NodeUtil::isGetTypeFunctionCall($left) && $right instanceof \PHPParser_Node_Scalar_String) { $gettypeNode = $left; $stringNode = $right; } else { if (NodeUtil::isGetTypeFunctionCall($right) && $left instanceof \PHPParser_Node_Scalar_String) { $gettypeNode = $right; $stringNode = $left; } } if (null !== $gettypeNode && null !== $stringNode && isset($gettypeNode->args[0])) { $operandNode = $gettypeNode->args[0]->value; $operandType = $this->getTypeIfRefinable($operandNode, $blindScope); if (null !== $operandType) { $resultEqualsValue = $node instanceof \PHPParser_Node_Expr_Equal || $node instanceof \PHPParser_Node_Expr_Identical || $node instanceof \PHPParser_Node_Stmt_Case; if (!$outcome) { $resultEqualsValue = !$resultEqualsValue; } return $this->caseGettypeFunctionCall($operandNode, $operandType, $stringNode->value, $resultEqualsValue, $blindScope); } } // Handle get_class() function calls. $getClassNode = $stringNode = null; if ('get_class' === NodeUtil::getFunctionName($left) && $right instanceof \PHPParser_Node_Scalar_String) { $getClassNode = $left; $stringNode = $right; } else { if ('get_class' === NodeUtil::getFunctionName($right) && $left instanceof \PHPParser_Node_Scalar_String) { $getClassNode = $right; $stringNode = $left; } } if (null !== $getClassNode && null !== $stringNode && isset($getClassNode->args[0])) { $operandNode = $getClassNode->args[0]->value; $operandType = $this->getTypeIfRefinable($operandNode, $blindScope); if (null !== $operandType) { $resultEqualsValue = $node instanceof \PHPParser_Node_Expr_Equal || $node instanceof \PHPParser_Node_Expr_Identical || $node instanceof \PHPParser_Node_Stmt_Case; if (!$outcome) { $resultEqualsValue = !$resultEqualsValue; } return $this->caseGetClassFunctionCall($operandNode, $operandType, $stringNode->value, $resultEqualsValue, $blindScope); } } // Handle is_??? and assert function calls as well as instanceof checks. $typeFunctionNode = $booleanNode = null; if ((NodeUtil::isTypeFunctionCall($left) || NodeUtil::isMaybeFunctionCall($left, 'assert') || $left instanceof \PHPParser_Node_Expr_Instanceof) && NodeUtil::isBoolean($right)) { $typeFunctionNode = $left; $booleanNode = $right; } else { if ((NodeUtil::isTypeFunctionCall($right) || NodeUtil::isMaybeFunctionCall($right, 'assert') || $right instanceof \PHPParser_Node_Expr_Instanceof) && NodeUtil::isBoolean($left)) { $typeFunctionNode = $right; $booleanNode = $left; } } if (null !== $booleanNode && null !== $typeFunctionNode) { $expectedOutcome = NodeUtil::getBooleanValue($booleanNode) ? $outcome : !$outcome; if ($typeFunctionNode instanceof \PHPParser_Node_Expr_Instanceof) { return $this->firstPreciserScopeKnowingConditionOutcome($typeFunctionNode, $blindScope, $expectedOutcome); } if (isset($typeFunctionNode->args[0])) { if (NodeUtil::isMaybeFunctionCall($typeFunctionNode, 'assert')) { return $this->firstPreciserScopeKnowingConditionOutcome($typeFunctionNode->args[0]->value, $blindScope, $expectedOutcome); } return $this->caseTypeFunctionCall($typeFunctionNode->name, $typeFunctionNode->args[0], $blindScope, $expectedOutcome); } } return null; }
/** * Computes the GEN and KILL set. * * @param boolean $conditional */ private function computeGenKill(\PHPParser_Node $node, BitSet $gen, BitSet $kill, $conditional) { switch (true) { case $node instanceof \PHPParser_Node_Expr_Closure: foreach ($node->uses as $use) { assert($use instanceof \PHPParser_Node_Expr_ClosureUse); $this->addToSetIfLocal($use->var, $gen); } return; case $node instanceof BlockNode: case NodeUtil::isScopeCreator($node): return; case $node instanceof \PHPParser_Node_Stmt_While: case $node instanceof \PHPParser_Node_Stmt_Do: case $node instanceof \PHPParser_Node_Stmt_If: $this->computeGenKill($node->cond, $gen, $kill, $conditional); return; case $node instanceof \PHPParser_Node_Stmt_Foreach: if (null !== $node->keyVar) { if ($node->keyVar instanceof \PHPParser_Node_Expr_Variable) { $this->addToSetIfLocal($node->keyVar, $kill); $this->addToSetIfLocal($node->keyVar, $gen); } else { $this->computeGenKill($node->keyVar, $gen, $kill, $conditional); } } if ($node->valueVar instanceof \PHPParser_Node_Expr_Variable) { $this->addToSetIfLocal($node->valueVar, $kill); $this->addToSetIfLocal($node->valueVar, $gen); } else { $this->computeGenKill($node->valueVar, $gen, $kill, $conditional); } $this->computeGenKill($node->expr, $gen, $kill, $conditional); return; case $node instanceof \PHPParser_Node_Stmt_For: foreach ($node->cond as $cond) { $this->computeGenKill($cond, $gen, $kill, $conditional); } return; case $node instanceof \PHPParser_Node_Expr_BooleanAnd: case $node instanceof \PHPParser_Node_Expr_BooleanOr: $this->computeGenKill($node->left, $gen, $kill, $conditional); // May short circuit. $this->computeGenKill($node->right, $gen, $kill, true); return; case $node instanceof \PHPParser_Node_Expr_Ternary: $this->computeGenKill($node->cond, $gen, $kill, $conditional); // Assume both sides are conditional. if (null !== $node->if) { $this->computeGenKill($node->if, $gen, $kill, true); } $this->computeGenKill($node->else, $gen, $kill, true); return; case $node instanceof \PHPParser_Node_Param: $this->markAllParametersEscaped(); return; case $node instanceof \PHPParser_Node_Expr_Variable: if (!is_string($node->name)) { $this->addAllToSetIfLocal($gen); return; } $this->addToSetIfLocal($node->name, $gen); return; case $node instanceof \PHPParser_Node_Expr_Include: $this->computeGenKill($node->expr, $gen, $kill, $conditional); if ($node->type === \PHPParser_Node_Expr_Include::TYPE_INCLUDE || $node->type === \PHPParser_Node_Expr_Include::TYPE_REQUIRE) { $this->addAllToSetIfLocal($gen); } return; case NodeUtil::isMaybeFunctionCall($node, 'get_defined_vars'): $this->addAllToSetIfLocal($gen); break; case NodeUtil::isMaybeFunctionCall($node, 'compact'): foreach ($node->args as $arg) { $this->computeGenKill($arg, $gen, $kill, $conditional); } $varNames = array(); $isDynamic = false; foreach ($node->args as $arg) { if ($arg->value instanceof \PHPParser_Node_Scalar_String) { $varNames[] = $arg->value->value; continue; } if ($arg->value instanceof \PHPParser_Node_Expr_Array) { foreach ($arg->value->items as $item) { assert($item instanceof \PHPParser_Node_Expr_ArrayItem); if ($item->value instanceof \PHPParser_Node_Scalar_String) { $varNames[] = $item->value->value; continue; } $isDynamic = true; break 2; } continue; } $isDynamic = true; break; } $this->addAllToSetIfLocal($gen, $isDynamic ? null : $varNames); return; case $node instanceof \PHPParser_Node_Expr_AssignList: foreach ($node->vars as $var) { if (!$var instanceof \PHPParser_Node_Expr_Variable) { continue; } if (!$conditional) { $this->addToSetIfLocal($var->name, $kill); } $this->addToSetIfLocal($var->name, $gen); } $this->computeGenKill($node->expr, $gen, $kill, $conditional); return; // AssignList is already handled in the previous CASE block. // AssignList is already handled in the previous CASE block. case NodeUtil::isAssignmentOp($node) && $node->var instanceof \PHPParser_Node_Expr_Variable: if ($node->var->name instanceof \PHPParser_Node_Expr) { $this->computeGenKill($node->var->name, $gen, $kill, $conditional); } if (!$conditional) { $this->addToSetIfLocal($node->var->name, $kill); } if (!$node instanceof \PHPParser_Node_Expr_Assign) { // Assignments such as a += 1 reads a first. $this->addToSetIfLocal($node->var->name, $gen); } $this->computeGenKill($node->expr, $gen, $kill, $conditional); return; default: foreach ($node as $subNode) { if (is_array($subNode)) { foreach ($subNode as $aSubNode) { if (!$aSubNode instanceof \PHPParser_Node) { continue; } $this->computeGenKill($aSubNode, $gen, $kill, $conditional); } continue; } else { if (!$subNode instanceof \PHPParser_Node) { continue; } } $this->computeGenKill($subNode, $gen, $kill, $conditional); } } }