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); } } } } }
private function checkForMissingProperty(\PHPParser_Node_Expr_PropertyFetch $node) { if (!is_string($node->name)) { return; } if (!($objType = $node->var->getAttribute('type'))) { return; } $objType = $objType->restrictByNotNull()->toMaybeObjectType(); if (!$objType) { // TODO: Add support to check on union types. return; } if ($objType->isInterface()) { $this->phpFile->addComment($node->getLine(), Comment::warning('types.property_access_on_interface', 'Accessing "%property_name%" on the interface "%interface_name%" suggest that you code against a concrete implementation. How about adding an ``instanceof`` check?', array('property_name' => $node->name, 'interface_name' => $objType->getName()))); return; } if (!$objType->isNormalized() || $objType->hasProperty($node->name)) { return; } // Ignore all property accesses on ``stdClass`` objects. Currently, we have // no way to describe, or track RECORD types. As such, any messages related to // stdClass are likely wrong, but at least there are too many false-positives // for now. So, we just disable this. if ($objType->isSubtypeOf($this->typeRegistry->getClassOrCreate('stdClass'))) { return; } // Ignore all property reads on ``SimpleXMLElement`` objects. The reasoning // behind this is similar to the exception for ``stdClass`` objects above. // We simply have no reliable way to describe the structure of these objects // for now. So, we disable checks for them to avoid a flood of false-positives. if (!\Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::isAssignmentOp($node->getAttribute('parent')) && $objType->isSubtypeOf($this->typeRegistry->getClassOrCreate('SimpleXMLElement'))) { return; } // Property accesses inside isset() are safe, do not make any noise just yet. if ($node->getAttribute('parent') instanceof \PHPParser_Node_Expr_Isset) { return; } if (null !== ($bestName = $this->getMostSimilarName($node->name, $objType->getPropertyNames()))) { $this->phpFile->addComment($node->getLine(), Comment::error('typos.mispelled_property_name', 'The property "%offending_property_name%" does not exist. Did you mean "%closest_property_name%"?', array('offending_property_name' => $node->name, 'closest_property_name' => $bestName))); } else { // Ignore additional accesses to this property. $objType->addProperty(new Property($node->name)); if (\Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::isAssignmentOp($node->getAttribute('parent'))) { if ($objType->hasMethod('__set')) { $this->phpFile->addComment($node->getLine(), Comment::warning('strict.maybe_undocument_property_write_capability', 'The property ``%property_name%`` does not exist. Since you implemented ``__set``, maybe consider adding a [@property annotation](http://www.phpdoc.org/docs/latest/for-users/tags/property.html).', array('property_name' => $node->name))); return; } } else { if ($objType->hasMethod('__get')) { $this->phpFile->addComment($node->getLine(), Comment::warning('strict.maybe_undocument_property_read_capability', 'The property ``%property_name%`` does not exist. Since you implemented ``__get``, maybe consider adding a [@property annotation](http://www.phpdoc.org/docs/latest/for-users/tags/property.html).', array('property_name' => $node->name))); return; } } $thisType = $this->t->getScope()->getTypeOfThis(); if ($thisType && $thisType->equals($objType)) { $this->phpFile->addComment($node->getLine(), Comment::warning('strict.undeclared_property', 'The property "%property_name%" does not exist. Did you maybe forget to declare it?', array('property_name' => $node->name))); } else { $this->phpFile->addComment($node->getLine(), Comment::error('typos.non_existent_property', 'The property "%property_name%" does not exist.', array('property_name' => $node->name))); } } }
private function tryRemoveAssignment(NodeTraversal $t, \PHPParser_Node $n, \PHPParser_Node $exprRoot = null, $inState, $outState) { $parent = $n->getAttribute('parent'); if (NodeUtil::isAssignmentOp($n)) { $lhs = $n->var; $rhs = $n->expr; if ($n instanceof \PHPParser_Node_Expr_AssignList) { $i = 0; foreach ($n->vars as $var) { if (null === $var) { $i += 1; continue; } $newNode = new \PHPParser_Node_Expr_Assign($var, new \PHPParser_Node_Expr_ArrayDimFetch($rhs, new \PHPParser_Node_Scalar_LNumber($i)), $n->getLine()); $newNode->setAttribute('is_list_assign', true); $this->tryRemoveAssignment($t, $newNode, $exprRoot, $inState, $outState); $i += 1; } return; } // Recurse first. Example: dead_x = dead_y = 1; We try to clean up dead_y first. if (null !== $rhs) { $this->tryRemoveAssignment($t, $rhs, $exprRoot, $inState, $outState); $rhs = $lhs->getAttribute('next'); } $scope = $t->getScope(); if (!$lhs instanceof \PHPParser_Node_Expr_Variable || !is_string($lhs->name)) { return; } if (!$scope->isDeclared($lhs->name)) { return; } if (in_array($lhs->name, NodeUtil::$superGlobalNames, true)) { return; } $var = $scope->getVar($lhs->name); $escaped = $this->liveness->getEscapedLocals(); if (isset($escaped[$var])) { return; // Local variable that might be escaped due to closures. } // If we have an identity assignment such as a=a, always remove it // regardless of what the liveness results because it does not // change the result afterward. if (null !== $rhs && $rhs instanceof \PHPParser_Node_Expr_Variable && $var->getName() === $rhs->name && $n instanceof \PHPParser_Node_Expr_Assign) { $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.assignment_to_itself', 'Why assign ``%variable%`` to itself?', array('variable' => '$' . $rhs->name))); return; } // At the moment we miss some dead assignments if a variable is killed, // and defined in the same node of the control flow graph. // Example: $a = 'foo'; return $a = 'bar'; if ($outState->isLive($var)) { return; // Variable is not dead. } // Assignments to references do not need to be used in the // current scope. if ($var->isReference()) { return; } // if ($inState->isLive($var) // /*&& $this->isVariableStillLiveWithinExpression($n, $exprRoot, $var->getName()) */) { // The variable is killed here but it is also live before it. // This is possible if we have say: // if ($X = $a && $a = $C) {..} ; .......; $a = $S; // In this case we are safe to remove "$a = $C" because it is dead. // However if we have: // if ($a = $C && $X = $a) {..} ; .......; $a = $S; // removing "a = C" is NOT correct, although the live set at the node // is exactly the same. // TODO: We need more fine grain CFA or we need to keep track // of GEN sets when we recurse here. Maybe add the comment anyway, and let the user decide? // return; // } if ($n instanceof \PHPParser_Node_Expr_Assign) { if ($n->getAttribute('is_list_assign', false)) { $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.unnecessary_list_assign', 'The assignment to ``$%variable%`` is dead. Consider omitting it like so ``list($first,,$third)``.', array('variable' => $var->getName()))); return; } $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.unnecessary_var_assign', 'The assignment to ``$%variable%`` is dead and can be removed.', array('variable' => $var->getName()))); return; } } }
/** * @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); } } }