public function shouldTraverse(NodeTraversal $traversal, \PHPParser_Node $node, \PHPParser_Node $parent = null) { if (NodeUtil::isScopeCreator($node) && null !== $parent) { if ($node instanceof \PHPParser_Node_Expr_Closure) { $node->setAttribute('type', $this->typeRegistry->getClassOrCreate('Closure')); } return false; } return true; }
private function addToDefIfLocal($name, \PHPParser_Node $node = null, \PHPParser_Node $rValue = null, DefinitionLattice $definitions) { $var = $this->scope->getVar($name); if (null === $var) { return; } // Invalidate other definitions if they depended on this variable as it has been changed. foreach ($definitions as $otherVar) { if (null === ($otherDef = $definitions[$otherVar])) { continue; } if ($otherDef->dependsOn($var)) { $definitions[$otherVar] = null; } } // The node can be null if we are dealing with a conditional definition. For conditional // definitions, this analysis cannot do much. if (null === $node) { $definitions[$var] = null; return; } $definition = new Definition($node, $rValue); if (null !== $rValue) { NodeTraversal::traverseWithCallback($rValue, new PreOrderCallback(function ($t, \PHPParser_Node $node) use($definition) { if (NodeUtil::isScopeCreator($node)) { return false; } if (!NodeUtil::isName($node)) { return; } if (null === ($var = $this->scope->getVar($node->name))) { if (null !== $this->logger) { $this->logger->debug(sprintf('Could not find variable "%s" in the current scope. ' . 'This could imply an error in SyntacticScopeCreator.', $node->name)); } // We simply assume that the variable was declared so that we can properly // invalidate the definition if it is changed. For this analysis, it does not // matter whether it actually exists. A later pass will add a warning anyway. $var = $this->scope->declareVar($node->name); } $definition->addDependentVar($var); })); } $definitions[$var] = $definition; }
private function handleGoto(\PHPParser_Node_Stmt_Goto $node) { $parent = $node->getAttribute('parent'); while (!NodeUtil::isScopeCreator($parent)) { $newParent = $parent->getAttribute('parent'); if (null === $newParent) { break; } $parent = $newParent; } $nodes = \PHPParser_Finder::create(array($parent))->find('Stmt_Label[name=' . $node->name . ']'); if (!$nodes) { return; } $this->graph->connectIfNotConnected($node, GraphEdge::TYPE_UNCOND, $nodes[0]); }
private function traverseScopeCreator(\PHPParser_Node $node, \PHPParser_Node $parent = null) { assert(NodeUtil::isScopeCreator($node)); $this->curNode = $node; $isScopeActive = $this->getScopeRoot() === $node; if (!$isScopeActive) { $this->pushScopeWithRoot($node); } // args foreach ($node->params as $param) { $this->traverseBranch($param, $node); } // uses from parent scope if ($node instanceof \PHPParser_Node_Expr_Closure) { foreach ($node->uses as $use) { $this->traverseBranch($use, $node); } } // body // For abstract methods the stmts property is null, which we need to check here. if (null !== $node->stmts) { $this->traverseBranch($node->stmts, $node); } if (!$isScopeActive) { $this->popScope(); } }
/** * @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); } } }
/** * 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); } } }
private function scanVars(Node $node, Node $parent) { switch (true) { case $node instanceof \PHPParser_Node_Stmt_StaticVar: $this->declareVar($node->name, null, true, $node); return; case $node instanceof \PHPParser_Node_Expr_Variable: if (($parent instanceof \PHPParser_Node_Expr_Assign || $parent instanceof \PHPParser_Node_Expr_AssignRef) && is_string($node->name) && $parent->var === $node) { $this->declareVar($node->name, null, true, $node); } // PHP automatically creates a variable (without issuing a warning) if an item is added // to a variable without the variable being declared as array previously. // ``function() { $a[] = 'foo'; }`` is perfectly permissible, and will create ``$a``. if ($parent instanceof \PHPParser_Node_Expr_ArrayDimFetch && $parent->var === $node) { $parentsParent = $parent->getAttribute('parent'); if (($parentsParent instanceof \PHPParser_Node_Expr_Assign || $parentsParent instanceof \PHPParser_Node_Expr_AssignRef) && is_string($node->name) && $parentsParent->var === $parent) { // TODO: We should better track which variables have been initialized // and which have not. This can be done by adding an undefined // type for those that have not. if (!$this->scope->isDeclared($node->name)) { $node->setAttribute('array_initializing_variable', true); } $this->declareVar($node->name, null, true, $node); } } if ($parent instanceof \PHPParser_Node_Arg && $parent->getAttribute('param_expects_ref', false) && is_string($node->name)) { $this->declareVar($node->name, null, true, $node); } return; case $node instanceof \PHPParser_Node_Expr_AssignList: foreach ($node->vars as $var) { if ($var instanceof \PHPParser_Node_Expr_Variable && is_string($var->name)) { $this->declareVar($var->name, null, true, $var); } } $this->scanVars($node->expr, $node); return; case $node instanceof \PHPParser_Node_Stmt_Trait: case $node instanceof \PHPParser_Node_Stmt_Interface: case $node instanceof \PHPParser_Node_Stmt_Class: case NodeUtil::isScopeCreator($node): return; // do not examine their children as they belong to a different scope // do not examine their children as they belong to a different scope case $node instanceof \PHPParser_Node_Stmt_Catch: $this->declareVar($node->var, null, true, $node); $this->scanVars($node->stmts, $node); return; case $node instanceof \PHPParser_Node_Stmt_Foreach: if ($node->keyVar !== null && $node->keyVar instanceof \PHPParser_Node_Expr_Variable && is_string($node->keyVar->name)) { $this->declareVar($node->keyVar->name, null, true, $node->keyVar); } if ($node->valueVar instanceof \PHPParser_Node_Expr_Variable && is_string($node->valueVar->name)) { $this->declareVar($node->valueVar->name, null, true, $node->valueVar); } $this->scanVars($node->stmts, $node); return; default: foreach ($node as $subNode) { if (is_array($subNode)) { foreach ($subNode as $aSubNode) { if (!$aSubNode instanceof \PHPParser_Node) { continue; } $this->scanVars($aSubNode, $node); } } else { if ($subNode instanceof \PHPParser_Node) { $this->scanVars($subNode, $node); } } } } }