private function tryRemoveDeadAssignments(NodeTraversal $t, ControlFlowGraph $cfg) { $nodes = $cfg->getDirectedGraphNodes(); foreach ($nodes as $cfgNode) { $inState = $cfgNode->getAttribute(DataFlowAnalysis::ATTR_FLOW_STATE_IN); $outState = $cfgNode->getAttribute(DataFlowAnalysis::ATTR_FLOW_STATE_OUT); $n = $cfgNode->getAstNode(); if (null === $n) { continue; } switch (true) { case $n instanceof \PHPParser_Node_Stmt_If: case $n instanceof \PHPParser_Node_Stmt_ElseIf: case $n instanceof \PHPParser_Node_Stmt_While: case $n instanceof \PHPParser_Node_Stmt_Do: case $n instanceof \PHPParser_Node_Stmt_For: case $n instanceof \PHPParser_Node_Stmt_Switch: case $n instanceof \PHPParser_Node_Stmt_Case: if (null !== ($condition = NodeUtil::getConditionExpression($n))) { $this->tryRemoveAssignment($t, $condition, null, $inState, $outState); } continue 2; case $n instanceof \PHPParser_Node_Stmt_Return: if (null !== $n->expr) { $this->tryRemoveAssignment($t, $n->expr, null, $inState, $outState); } continue 2; } $this->tryRemoveAssignment($t, $n, null, $inState, $outState); } }
private function computeMustDef(\PHPParser_Node $node, \PHPParser_Node $cfgNode, DefinitionLattice $output, $conditional) { switch (true) { 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_For: case $node instanceof \PHPParser_Node_Stmt_If: if (null !== ($cond = NodeUtil::getConditionExpression($node))) { $this->computeMustDef($cond, $cfgNode, $output, $conditional); } return; case $node instanceof \PHPParser_Node_Stmt_Foreach: if (null !== $node->keyVar && $node->keyVar instanceof \PHPParser_Node_Expr_Variable && is_string($node->keyVar->name)) { $this->addToDefIfLocal($node->keyVar->name, $cfgNode, $node->expr, $output); } if ($node->valueVar instanceof \PHPParser_Node_Expr_Variable && is_string($node->valueVar->name)) { $this->addToDefIfLocal($node->valueVar->name, $cfgNode, $node->expr, $output); } return; case $node instanceof \PHPParser_Node_Stmt_Catch: $this->addToDefIfLocal($node->var, $cfgNode, null, $output); return; case $node instanceof \PHPParser_Node_Expr_BooleanAnd: case $node instanceof \PHPParser_Node_Expr_BooleanOr: case $node instanceof \PHPParser_Node_Expr_LogicalAnd: case $node instanceof \PHPParser_Node_Expr_LogicalOr: $this->computeMustDef($node->left, $cfgNode, $output, $conditional); $this->computeMustDef($node->right, $cfgNode, $output, true); return; case $node instanceof \PHPParser_Node_Expr_Ternary: $this->computeMustDef($node->cond, $cfgNode, $output, $conditional); if (null !== $node->if) { $this->computeMustDef($node->if, $cfgNode, $output, true); } $this->computeMustDef($node->else, $cfgNode, $output, true); return; case NodeUtil::isName($node): if (null !== ($var = $this->scope->getVar($node->name)) && isset($output[$var]) && null !== $output[$var]) { $node->setAttribute('defining_expr', $output[$var]->getExpr()); } return; default: if (NodeUtil::isAssignmentOp($node)) { if ($node instanceof \PHPParser_Node_Expr_AssignList) { $this->computeMustDef($node->expr, $cfgNode, $output, $conditional); foreach ($node->vars as $var) { if (null === $var) { continue; } $this->addToDefIfLocal($var->name, $conditional ? null : $cfgNode, $node->expr, $output); } return; } if (NodeUtil::isName($node->var)) { $this->computeMustDef($node->expr, $cfgNode, $output, $conditional); $this->addToDefIfLocal($node->var->name, $conditional ? null : $cfgNode, $node->expr, $output); return; } } if (($node instanceof \PHPParser_Node_Expr_PostDec || $node instanceof \PHPParser_Node_Expr_PostInc || $node instanceof \PHPParser_Node_Expr_PreDec || $node instanceof \PHPParser_Node_Expr_PreInc) && NodeUtil::isName($node->var)) { $this->addToDefIfLocal($node->var->name, $conditional ? null : $cfgNode, null, $output); return; } foreach ($node as $subNode) { if (is_array($subNode)) { foreach ($subNode as $aSubNode) { if (!$aSubNode instanceof \PHPParser_Node) { continue; } $this->computeMustDef($aSubNode, $cfgNode, $output, $conditional); } } else { if ($subNode instanceof \PHPParser_Node) { $this->computeMustDef($subNode, $cfgNode, $output, $conditional); } } } } }
/** * Calculates the out lattices for the different branches. * * Right now, we just treat ON_EX edges like UNCOND edges. If we want to be perfect, we would have to actually join * all the out lattices of this flow with the in lattice, and then make that the out lattice for the ON_EX edge. * However, that would add some extra computation for an edge case. So, the current behavior looks like a "good enough" * approximation. * * @param \PHPParser_Node $source * @param LatticeElementInterface $input * * @return array<LatticeElementInterface> */ protected function branchedFlowThrough($source, LatticeElementInterface $input) { assert($source instanceof \PHPParser_Node); assert($input instanceof LinkedFlowScope); // If we have not walked a path from the entry node to this node, we cannot infer anything // about this scope. So, just skip it for now, we will come back later. if ($input === $this->bottomScope) { $output = $input; } else { $output = $input->createChildFlowScope(); $output = $this->traverse($source, $output); } $condition = $conditionFlowScope = $conditionOutcomes = null; $result = array(); foreach ($this->cfg->getOutEdges($source) as $branchEdge) { $branch = $branchEdge->getType(); $newScope = $output; switch ($branch) { case GraphEdge::TYPE_ON_TRUE: if ($source instanceof \PHPParser_Node_Stmt_Foreach) { $informed = $this->traverse($source->expr, $output->createChildFlowScope()); $exprType = $this->getType($source->expr); if (null !== $source->keyVar && $source->keyVar instanceof \PHPParser_Node_Expr_Variable) { $keyType = null; if ($exprType->isArrayType()) { $keyType = $exprType->toMaybeArrayType()->getKeyType(); } // If we do not have the precise key type available, we can always // assume it to be either a string, or an integer. $this->redeclareSimpleVar($informed, $source->keyVar, $keyType ?: $this->typeRegistry->getNativeType('generic_array_key')); } $valueType = $this->typeRegistry->getNativeType('unknown'); if ($exprType->isArrayType()) { $valueType = $exprType->toMaybeArrayType()->getElementType(); } else { if ($exprType->toMaybeObjectType()) { $valueType = $exprType->toMaybeObjectType()->getTraversableElementType(); } } $source->valueVar->setAttribute('type', $valueType); $this->redeclareSimpleVar($informed, $source->valueVar, $valueType); $newScope = $informed; break; } // FALL THROUGH // FALL THROUGH case GraphEdge::TYPE_ON_FALSE: if (null === $condition) { $condition = NodeUtil::getConditionExpression($source); if (null === $condition && $source instanceof \PHPParser_Node_Stmt_Case && null !== $source->cond) { $condition = $source; // conditionFlowScope is cached from previous iterations of the loop if (null === $conditionFlowScope) { $conditionFlowScope = $this->traverse($source->cond, $output->createChildFlowScope()); } } } if (null !== $condition) { if ($condition instanceof \PHPParser_Node_Expr_BooleanAnd || $condition instanceof \PHPParser_Node_Expr_LogicalAnd || $condition instanceof \PHPParser_Node_Expr_BooleanOr || $condition instanceof \PHPParser_Node_Expr_LogicalOr) { // When handling the short-circuiting binary operators, the outcome scope on true can be // different than the outcome scope on false. // // TODO: // The "right" way to do this is to carry the known outcome all the way through the // recursive traversal, so that we can construct a different flow scope based on the outcome. // However, this would require a bunch of code and a bunch of extra computation for an edge // case. This seems to be a "good enough" approximation. // conditionOutcomes is cached from previous iterations of the loop. if (null === $conditionOutcomes) { $conditionOutcomes = $condition instanceof \PHPParser_Node_Expr_BooleanAnd || $condition instanceof \PHPParser_Node_Expr_LogicalAnd ? $this->traverseAnd($condition, $output->createChildFlowScope()) : $this->traverseOr($condition, $output->createChildFlowScope()); } $newScope = $this->reverseInterpreter->getPreciserScopeKnowingConditionOutcome($condition, $conditionOutcomes->getOutcomeFlowScope($condition, $branch === GraphEdge::TYPE_ON_TRUE), $branch === GraphEdge::TYPE_ON_TRUE); } else { // conditionFlowScope is cached from previous iterations of the loop. if (null === $conditionFlowScope) { // In case of a FOR loop, $condition might be an array of expressions. PHP will execute // all expressions, but only the last one is used to determine the expression outcome. if (is_array($condition)) { $conditionFlowScope = $output->createChildFlowScope(); foreach ($condition as $cond) { $conditionFlowScope = $this->traverse($cond, $conditionFlowScope); } } else { $conditionFlowScope = $this->traverse($condition, $output->createChildFlowScope()); } } // See above comment regarding the handling of FOR loops. $relevantCondition = $condition; if (is_array($condition)) { $relevantCondition = end($condition); } $newScope = $this->reverseInterpreter->getPreciserScopeKnowingConditionOutcome($relevantCondition, $conditionFlowScope, $branch === GraphEdge::TYPE_ON_TRUE); } } break; } if (null === $newScope) { throw new \LogicException('$newScope must not be null for source ' . get_class($source) . ' and branch ' . GraphEdge::getLiteral($branch) . '.'); } $result[] = $newScope->optimize(); } return $result; }
/** * @param boolean $conditional */ private function computeMayUse(\PHPParser_Node $node, \PHPParser_Node $cfgNode, UseLattice $output, $conditional) { switch (true) { case $node instanceof \JMS\PhpManipulator\PhpParser\BlockNode: case NodeUtil::isScopeCreator($node): return; case NodeUtil::isName($node): $this->addToUseIfLocal($node, $cfgNode, $output); return; case $node instanceof \PHPParser_Node_Stmt_Catch: $this->computeMayUse($node->stmts, $cfgNode, $output, $conditional); $this->addDefToVarNodes($node->var, $node, $node, $output); $this->removeFromUseIfLocal($node->var, $output); return; case $node instanceof \PHPParser_Node_Stmt_While: case $node instanceof \PHPParser_Node_Stmt_Do: case $node instanceof \PHPParser_Node_Stmt_If: case $node instanceof \PHPParser_Node_Stmt_ElseIf: case $node instanceof \PHPParser_Node_Stmt_For: if (null !== ($cond = NodeUtil::getConditionExpression($node))) { $this->computeMayUse($cond, $cfgNode, $output, $conditional); } return; case $node instanceof \PHPParser_Node_Stmt_Foreach: if (!$conditional) { if (null !== $node->keyVar && NodeUtil::isName($node->keyVar)) { $this->removeFromUseIfLocal($node->keyVar->name, $output); } if (NodeUtil::isName($node->valueVar)) { $this->removeFromUseIfLocal($node->valueVar->name, $output); } } $this->computeMayUse($node->expr, $cfgNode, $output, $conditional); return; case $node instanceof \PHPParser_Node_Expr_BooleanAnd: case $node instanceof \PHPParser_Node_Expr_LogicalAnd: case $node instanceof \PHPParser_Node_Expr_BooleanOr: case $node instanceof \PHPParser_Node_Expr_LogicalOr: $this->computeMayUse($node->right, $cfgNode, $output, true); $this->computeMayUse($node->left, $cfgNode, $output, $conditional); return; case $node instanceof \PHPParser_Node_Expr_Ternary: $this->computeMayUse($node->else, $cfgNode, $output, true); if (null !== $node->if) { $this->computeMayUse($node->if, $cfgNode, $output, true); } $this->computeMayUse($node->cond, $cfgNode, $output, $conditional); return; default: if (NodeUtil::isAssignmentOp($node)) { if ($node instanceof \PHPParser_Node_Expr_AssignList) { foreach ($node->vars as $var) { if (null === $var) { continue; } if (!$conditional) { $this->removeFromUseIfLocal($var->name, $output); } } $this->computeMayUse($node->expr, $cfgNode, $output, $conditional); return; } if (NodeUtil::isName($node->var)) { $this->addDefToVarNodes($node->var->name, $node->var, $node->expr, $output); if (!$conditional) { $this->removeFromUseIfLocal($node->var->name, $output); } // Handle the cases where we assign, and read at the same time, e.g. // ``$a += 5``. if (!$node instanceof \PHPParser_Node_Expr_Assign && !$node instanceof \PHPParser_Node_Expr_AssignRef) { $this->addToUseIfLocal($node->var, $cfgNode, $output); } $this->computeMayUse($node->expr, $cfgNode, $output, $conditional); return; } return; } $inOrder = array(); foreach ($node as $subNode) { if (is_array($subNode)) { foreach ($subNode as $aSubNode) { if (!$aSubNode instanceof \PHPParser_Node) { continue; } $inOrder[] = $aSubNode; } } else { if ($subNode instanceof \PHPParser_Node) { $inOrder[] = $subNode; } } } foreach (array_reverse($inOrder) as $subNode) { $this->computeMayUse($subNode, $cfgNode, $output, $conditional); } } }