Java tutorial
/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; import javax.annotation.Nullable; /** * Peephole optimization to remove useless code such as IF's with false * guard conditions, comma operator left hand sides with no side effects, etc. * */ class PeepholeRemoveDeadCode extends AbstractPeepholeOptimization { // TODO(dcc): Some (all) of these can probably be better achieved // using the control flow graph (like CheckUnreachableCode). // There is an existing CFG pass (UnreachableCodeElimination) that // could be changed to use code from CheckUnreachableCode to do this. @Override Node optimizeSubtree(Node subtree) { switch (subtree.getType()) { case Token.ASSIGN: return tryFoldAssignment(subtree); case Token.COMMA: return tryFoldComma(subtree); case Token.SCRIPT: case Token.BLOCK: return tryOptimizeBlock(subtree); case Token.EXPR_RESULT: subtree = tryFoldExpr(subtree); return subtree; case Token.HOOK: return tryFoldHook(subtree); case Token.SWITCH: return tryOptimizeSwitch(subtree); case Token.IF: return tryFoldIf(subtree); case Token.WHILE: return tryFoldWhile(subtree); case Token.FOR: { Node condition = NodeUtil.getConditionExpression(subtree); if (condition != null) { tryFoldForCondition(condition); } return tryFoldFor(subtree); } case Token.DO: Node foldedDo = tryFoldDoAway(subtree); if (foldedDo.isDo()) { return tryFoldEmptyDo(foldedDo); } return foldedDo; case Token.TRY: return tryFoldTry(subtree); default: return subtree; } } /** * Remove try blocks without catch blocks and with empty or not * existent finally blocks. * Or, only leave the finally blocks if try body blocks are empty * @return the replacement node, if changed, or the original if not */ private Node tryFoldTry(Node n) { Preconditions.checkState(n.isTry()); Node body = n.getFirstChild(); Node catchBlock = body.getNext(); Node finallyBlock = catchBlock.getNext(); // Removes TRYs that had its CATCH removed and/or empty FINALLY. if (!catchBlock.hasChildren() && (finallyBlock == null || !finallyBlock.hasChildren())) { n.removeChild(body); n.getParent().replaceChild(n, body); reportCodeChange(); return body; } // Only leave FINALLYs if TRYs are empty if (!body.hasChildren()) { NodeUtil.redeclareVarsInsideBranch(catchBlock); if (finallyBlock != null) { n.removeChild(finallyBlock); n.getParent().replaceChild(n, finallyBlock); } else { n.getParent().removeChild(n); } reportCodeChange(); return finallyBlock; } return n; } /** * Try removing identity assignments * @return the replacement node, if changed, or the original if not */ private Node tryFoldAssignment(Node subtree) { Preconditions.checkState(subtree.isAssign()); Node left = subtree.getFirstChild(); Node right = subtree.getLastChild(); // Only names if (left.isName() && right.isName() && left.getString().equals(right.getString())) { subtree.getParent().replaceChild(subtree, right.detachFromParent()); reportCodeChange(); return right; } return subtree; } /** * Try folding EXPR_RESULT nodes by removing useless Ops and expressions. * @return the replacement node, if changed, or the original if not */ private Node tryFoldExpr(Node subtree) { Node result = trySimplifyUnusedResult(subtree.getFirstChild()); if (result == null) { Node parent = subtree.getParent(); // If the EXPR_RESULT no longer has any children, remove it as well. if (parent.isLabel()) { Node replacement = IR.block().srcref(subtree); parent.replaceChild(subtree, replacement); subtree = replacement; } else { subtree.detachFromParent(); subtree = null; } } return subtree; } /** * General cascading unused operation node removal. * @param n The root of the expression to simplify. * @return The replacement node, or null if the node was is not useful. */ private Node trySimplifyUnusedResult(Node n) { return trySimplifyUnusedResult(n, true); } /** * General cascading unused operation node removal. * @param n The root of the expression to simplify. * @param removeUnused If true, the node is removed from the AST if * it is not useful, otherwise it replaced with an EMPTY node. * @return The replacement node, or null if the node was is not useful. */ private Node trySimplifyUnusedResult(Node n, boolean removeUnused) { Node result = n; // Simplify the results of conditional expressions switch (n.getType()) { case Token.HOOK: Node trueNode = trySimplifyUnusedResult(n.getFirstChild().getNext()); Node falseNode = trySimplifyUnusedResult(n.getLastChild()); // If one or more of the conditional children were removed, // transform the HOOK to an equivalent operation: // x() ? foo() : 1 --> x() && foo() // x() ? 1 : foo() --> x() || foo() // x() ? 1 : 1 --> x() // x ? 1 : 1 --> null if (trueNode == null && falseNode != null) { n.setType(Token.OR); Preconditions.checkState(n.getChildCount() == 2); } else if (trueNode != null && falseNode == null) { n.setType(Token.AND); Preconditions.checkState(n.getChildCount() == 2); } else if (trueNode == null && falseNode == null) { result = trySimplifyUnusedResult(n.getFirstChild()); } else { // The structure didn't change. result = n; } break; case Token.AND: case Token.OR: // Try to remove the second operand from a AND or OR operations: // x() || f --> x() // x() && f --> x() Node conditionalResultNode = trySimplifyUnusedResult(n.getLastChild()); if (conditionalResultNode == null) { Preconditions.checkState(n.hasOneChild()); // The conditionally executed code was removed, so // replace the AND/OR with its LHS or remove it if it isn't useful. result = trySimplifyUnusedResult(n.getFirstChild()); } break; case Token.FUNCTION: // A function expression isn't useful if it isn't used, remove it and // don't bother to look at its children. result = null; break; case Token.COMMA: // We rewrite other operations as COMMA expressions (which will later // get split into individual EXPR_RESULT statement, if possible), so // we special case COMMA (we don't want to rewrite COMMAs as new COMMAs // nodes. Node left = trySimplifyUnusedResult(n.getFirstChild()); Node right = trySimplifyUnusedResult(n.getLastChild()); if (left == null && right == null) { result = null; } else if (left == null) { result = right; } else if (right == null) { result = left; } else { // The structure didn't change. result = n; } break; default: if (!nodeTypeMayHaveSideEffects(n)) { // This is the meat of this function. The node itself doesn't generate // any side-effects but preserve any side-effects in the children. Node resultList = null; for (Node next, c = n.getFirstChild(); c != null; c = next) { next = c.getNext(); c = trySimplifyUnusedResult(c); if (c != null) { c.detachFromParent(); if (resultList == null) { // The first side-effect can be used stand-alone. resultList = c; } else { // Leave the side-effects in-place, simplifying it to a COMMA // expression. resultList = IR.comma(resultList, c).srcref(c); } } } result = resultList; } } // Fix up the AST, replace or remove the an unused node (if requested). if (n != result) { Node parent = n.getParent(); if (result == null) { if (removeUnused) { parent.removeChild(n); } else { result = IR.empty().srcref(n); parent.replaceChild(n, result); } } else { // A new COMMA expression may not have an existing parent. if (result.getParent() != null) { result.detachFromParent(); } n.getParent().replaceChild(n, result); } reportCodeChange(); } return result; } /** * A predicate for matching anything except function nodes. */ private static class MatchUnnamedBreak implements Predicate<Node> { @Override public boolean apply(Node n) { return n.isBreak() && !n.hasChildren(); } } static final Predicate<Node> MATCH_UNNAMED_BREAK = new MatchUnnamedBreak(); /** * Remove useless switches and cases. */ private Node tryOptimizeSwitch(Node n) { Preconditions.checkState(n.isSwitch()); Node defaultCase = tryOptimizeDefaultCase(n); // Removing cases when there exists a default case is not safe. if (defaultCase == null) { Node cond = n.getFirstChild(), prev = null, next = null, cur; for (cur = cond.getNext(); cur != null; cur = next) { next = cur.getNext(); if (!mayHaveSideEffects(cur.getFirstChild()) && isUselessCase(cur, prev)) { removeCase(n, cur); } else { prev = cur; } } // Optimize switches with constant condition if (NodeUtil.isLiteralValue(cond, false)) { Node caseLabel; TernaryValue caseMatches = TernaryValue.TRUE; // Remove cases until you find one that may match for (cur = cond.getNext(); cur != null; cur = next) { next = cur.getNext(); caseLabel = cur.getFirstChild(); caseMatches = PeepholeFoldConstants.evaluateComparison(Token.SHEQ, cond, caseLabel); if (caseMatches == TernaryValue.TRUE) { break; } else if (caseMatches == TernaryValue.UNKNOWN) { break; } else { removeCase(n, cur); } } if (caseMatches != TernaryValue.UNKNOWN) { Node block, lastStm; // Skip cases until you find one whose last stm is a removable break while (cur != null) { block = cur.getLastChild(); lastStm = block.getLastChild(); cur = cur.getNext(); if (lastStm != null && lastStm.isBreak() && !lastStm.hasChildren()) { block.removeChild(lastStm); reportCodeChange(); break; } } // Remove any remaining cases for (; cur != null; cur = next) { next = cur.getNext(); removeCase(n, cur); } // If there is one case left, we may be able to fold it cur = cond.getNext(); if (cur != null && cur.getNext() == null) { block = cur.getLastChild(); if (!(NodeUtil.has(block, MATCH_UNNAMED_BREAK, NodeUtil.MATCH_NOT_FUNCTION))) { cur.removeChild(block); block.setIsSyntheticBlock(false); n.getParent().replaceChild(n, block); reportCodeChange(); return block; } } } } } // Remove the switch if there are no remaining cases. if (n.hasOneChild()) { Node condition = n.removeFirstChild(); Node replacement = IR.exprResult(condition).srcref(n); n.getParent().replaceChild(n, replacement); reportCodeChange(); return replacement; } return null; } /** * @return the default case node or null if there is no default case or * if the default case is removed. */ private Node tryOptimizeDefaultCase(Node n) { Preconditions.checkState(n.isSwitch()); Node lastNonRemovable = n.getFirstChild(); // The switch condition // The first child is the switch conditions skip it when looking for cases. for (Node c = n.getFirstChild().getNext(); c != null; c = c.getNext()) { if (c.isDefaultCase()) { // Remove cases that fall-through to the default case Node caseToRemove = lastNonRemovable.getNext(); for (Node next; caseToRemove != c; caseToRemove = next) { next = caseToRemove.getNext(); removeCase(n, caseToRemove); } // Don't use the switch condition as the previous case. Node prevCase = (lastNonRemovable == n.getFirstChild()) ? null : lastNonRemovable; // Remove the default case if we can if (isUselessCase(c, prevCase)) { removeCase(n, c); return null; } return c; } else { Preconditions.checkState(c.isCase()); if (c.getLastChild().hasChildren() || mayHaveSideEffects(c.getFirstChild())) { lastNonRemovable = c; } } } return null; } /** * Remove the case from the switch redeclaring any variables declared in it. * @param caseNode The case to remove. */ private void removeCase(Node switchNode, Node caseNode) { NodeUtil.redeclareVarsInsideBranch(caseNode); switchNode.removeChild(caseNode); reportCodeChange(); } /** * The function assumes that when checking a CASE node there is no * DEFAULT node in the SWITCH. * @return Whether the CASE or DEFAULT block does anything useful. */ private boolean isUselessCase(Node caseNode, @Nullable Node previousCase) { Preconditions.checkState(previousCase == null || previousCase.getNext() == caseNode); // A case isn't useless can't be useless if a previous case falls // through to it unless it happens to be the last case in the switch. Node switchNode = caseNode.getParent(); if (switchNode.getLastChild() != caseNode && previousCase != null) { Node previousBlock = previousCase.getLastChild(); if (!previousBlock.hasChildren() || !isExit(previousBlock.getLastChild())) { return false; } } Node executingCase = caseNode; while (executingCase != null) { Preconditions.checkState(executingCase.isDefaultCase() || executingCase.isCase()); // We only expect a DEFAULT case if the case we are checking is the // DEFAULT case. Otherwise, we assume the DEFAULT case has already // been removed. Preconditions.checkState(caseNode == executingCase || !executingCase.isDefaultCase()); Node block = executingCase.getLastChild(); Preconditions.checkState(block.isBlock()); if (block.hasChildren()) { for (Node blockChild : block.children()) { // If this is a block with a labelless break, it is useless. switch (blockChild.getType()) { case Token.BREAK: // A break to a different control structure isn't useless. return blockChild.getFirstChild() == null; case Token.VAR: if (blockChild.hasOneChild() && blockChild.getFirstChild().getFirstChild() == null) { // Variable declarations without initializations are OK. continue; } return false; default: return false; } } } // Look at the fallthrough case executingCase = executingCase.getNext(); } return true; } /** * @return Whether the node is an obvious control flow exit. */ private static boolean isExit(Node n) { switch (n.getType()) { case Token.BREAK: case Token.CONTINUE: case Token.RETURN: case Token.THROW: return true; default: return false; } } private Node tryFoldComma(Node n) { // If the left side does nothing replace the comma with the result. Node parent = n.getParent(); Node left = n.getFirstChild(); Node right = left.getNext(); left = trySimplifyUnusedResult(left); if (left == null || !mayHaveSideEffects(left)) { // Fold it! n.removeChild(right); parent.replaceChild(n, right); reportCodeChange(); return right; } return n; } /** * Try removing unneeded block nodes and their useless children */ Node tryOptimizeBlock(Node n) { // Remove any useless children for (Node c = n.getFirstChild(); c != null;) { Node next = c.getNext(); // save c.next, since 'c' may be removed if (!isUnremovableNode(c) && !mayHaveSideEffects(c)) { // TODO(johnlenz): determine what this is actually removing. Candidates // include: EMPTY nodes, control structures without children // (removing infinite loops), empty try blocks. What else? n.removeChild(c); // lazy kids reportCodeChange(); } else { tryOptimizeConditionalAfterAssign(c); } c = next; } if (n.isSyntheticBlock() || n.isScript() || n.getParent() == null) { return n; } // Try to remove the block. if (NodeUtil.tryMergeBlock(n)) { reportCodeChange(); return null; } return n; } /** * Some nodes unremovable node don't have side-effects. */ private static boolean isUnremovableNode(Node n) { return (n.isBlock() && n.isSyntheticBlock()) || n.isScript(); } // TODO(johnlenz): Consider moving this to a separate peephole pass. /** * Attempt to replace the condition of if or hook immediately that is a * reference to a name that is assigned immediately before. */ private void tryOptimizeConditionalAfterAssign(Node n) { Node next = n.getNext(); // Look for patterns like the following and replace the if-condition with // a constant value so it can later be folded: // var a = /a/; // if (a) {foo(a)} // or // a = 0; // a ? foo(a) : c; // or // a = 0; // a || foo(a); // or // a = 0; // a && foo(a) // // TODO(johnlenz): This would be better handled by control-flow sensitive // constant propagation. As the other case that I want to handle is: // i=0; for(;i<0;i++){} // as right now nothing facilitates removing a loop like that. // This is here simply to remove the cruft left behind goog.userAgent and // similar cases. if (isSimpleAssignment(n) && isConditionalStatement(next)) { Node lhsAssign = getSimpleAssignmentName(n); Node condition = getConditionalStatementCondition(next); if (lhsAssign.isName() && condition.isName() && lhsAssign.getString().equals(condition.getString())) { Node rhsAssign = getSimpleAssignmentValue(n); TernaryValue value = NodeUtil.getImpureBooleanValue(rhsAssign); if (value != TernaryValue.UNKNOWN) { Node replacementConditionNode = NodeUtil.booleanNode(value.toBoolean(true)); condition.getParent().replaceChild(condition, replacementConditionNode); reportCodeChange(); } } } } /** * @return whether the node is a assignment to a simple name, or simple var * declaration with initialization. */ private static boolean isSimpleAssignment(Node n) { // For our purposes we define a simple assignment to be a assignment // to a NAME node, or a VAR declaration with one child and a initializer. if (NodeUtil.isExprAssign(n) && n.getFirstChild().getFirstChild().isName()) { return true; } else if (n.isVar() && n.hasOneChild() && n.getFirstChild().getFirstChild() != null) { return true; } return false; } /** * @return The name being assigned to. */ private Node getSimpleAssignmentName(Node n) { Preconditions.checkState(isSimpleAssignment(n)); if (NodeUtil.isExprAssign(n)) { return n.getFirstChild().getFirstChild(); } else { // A var declaration. return n.getFirstChild(); } } /** * @return The value assigned in the simple assignment */ private Node getSimpleAssignmentValue(Node n) { Preconditions.checkState(isSimpleAssignment(n)); return n.getFirstChild().getLastChild(); } /** * @return Whether the node is a conditional statement. */ private boolean isConditionalStatement(Node n) { // We defined a conditional statement to be a IF or EXPR_RESULT rooted with // a HOOK, AND, or OR node. return n != null && (n.isIf() || isExprConditional(n)); } /** * @return Whether the node is a rooted with a HOOK, AND, or OR node. */ private static boolean isExprConditional(Node n) { if (n.isExprResult()) { switch (n.getFirstChild().getType()) { case Token.HOOK: case Token.AND: case Token.OR: return true; } } return false; } /** * @return The condition of a conditional statement. */ private Node getConditionalStatementCondition(Node n) { if (n.isIf()) { return NodeUtil.getConditionExpression(n); } else { Preconditions.checkState(isExprConditional(n)); return n.getFirstChild().getFirstChild(); } } /** * Try folding IF nodes by removing dead branches. * @return the replacement node, if changed, or the original if not */ private Node tryFoldIf(Node n) { Preconditions.checkState(n.isIf()); Node parent = n.getParent(); Preconditions.checkNotNull(parent); int type = n.getType(); Node cond = n.getFirstChild(); Node thenBody = cond.getNext(); Node elseBody = thenBody.getNext(); // if (x) { .. } else { } --> if (x) { ... } if (elseBody != null && !mayHaveSideEffects(elseBody)) { n.removeChild(elseBody); elseBody = null; reportCodeChange(); } // if (x) { } else { ... } --> if (!x) { ... } if (!mayHaveSideEffects(thenBody) && elseBody != null) { n.removeChild(elseBody); n.replaceChild(thenBody, elseBody); Node notCond = new Node(Token.NOT); n.replaceChild(cond, notCond); notCond.addChildToFront(cond); cond = notCond; thenBody = cond.getNext(); elseBody = null; reportCodeChange(); } // if (x()) { } if (!mayHaveSideEffects(thenBody) && elseBody == null) { if (mayHaveSideEffects(cond)) { // x() has side effects, just leave the condition on its own. n.removeChild(cond); Node replacement = NodeUtil.newExpr(cond); parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } else { // x() has no side effects, the whole tree is useless now. NodeUtil.removeChild(parent, n); reportCodeChange(); return null; } } // Try transforms that apply to both IF and HOOK. TernaryValue condValue = NodeUtil.getImpureBooleanValue(cond); if (condValue == TernaryValue.UNKNOWN) { return n; // We can't remove branches otherwise! } if (mayHaveSideEffects(cond)) { // Transform "if (a = 2) {x =2}" into "if (true) {a=2;x=2}" boolean newConditionValue = condValue == TernaryValue.TRUE; // Add an elseBody if it is needed. if (!newConditionValue && elseBody == null) { elseBody = IR.block().srcref(n); n.addChildToBack(elseBody); } Node newCond = NodeUtil.booleanNode(newConditionValue); n.replaceChild(cond, newCond); Node branchToKeep = newConditionValue ? thenBody : elseBody; branchToKeep.addChildToFront(IR.exprResult(cond).srcref(cond)); reportCodeChange(); cond = newCond; } boolean condTrue = condValue.toBoolean(true); if (n.getChildCount() == 2) { Preconditions.checkState(type == Token.IF); if (condTrue) { // Replace "if (true) { X }" with "X". Node thenStmt = n.getFirstChild().getNext(); n.removeChild(thenStmt); parent.replaceChild(n, thenStmt); reportCodeChange(); return thenStmt; } else { // Remove "if (false) { X }" completely. NodeUtil.redeclareVarsInsideBranch(n); NodeUtil.removeChild(parent, n); reportCodeChange(); return null; } } else { // Replace "if (true) { X } else { Y }" with X, or // replace "if (false) { X } else { Y }" with Y. Node trueBranch = n.getFirstChild().getNext(); Node falseBranch = trueBranch.getNext(); Node branchToKeep = condTrue ? trueBranch : falseBranch; Node branchToRemove = condTrue ? falseBranch : trueBranch; NodeUtil.redeclareVarsInsideBranch(branchToRemove); n.removeChild(branchToKeep); parent.replaceChild(n, branchToKeep); reportCodeChange(); return branchToKeep; } } /** * Try folding HOOK (?:) if the condition results of the condition is known. * @return the replacement node, if changed, or the original if not */ private Node tryFoldHook(Node n) { Preconditions.checkState(n.isHook()); Node parent = n.getParent(); Preconditions.checkNotNull(parent); Node cond = n.getFirstChild(); Node thenBody = cond.getNext(); Node elseBody = thenBody.getNext(); TernaryValue condValue = NodeUtil.getImpureBooleanValue(cond); if (condValue == TernaryValue.UNKNOWN) { // If the result nodes are equivalent, then one of the nodes can be // removed and it doesn't matter which. if (!areNodesEqualForInlining(thenBody, elseBody)) { return n; // We can't remove branches otherwise! } } // Transform "(a = 2) ? x =2 : y" into "a=2,x=2" Node branchToKeep = condValue.toBoolean(true) ? thenBody : elseBody; Node replacement; boolean condHasSideEffects = mayHaveSideEffects(cond); // Must detach after checking for side effects, to ensure that the parents // of nodes are set correctly. n.detachChildren(); if (condHasSideEffects) { replacement = IR.comma(cond, branchToKeep).srcref(n); } else { replacement = branchToKeep; } parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } /** * Removes WHILEs that always evaluate to false. */ Node tryFoldWhile(Node n) { Preconditions.checkArgument(n.isWhile()); Node cond = NodeUtil.getConditionExpression(n); if (NodeUtil.getPureBooleanValue(cond) != TernaryValue.FALSE) { return n; } NodeUtil.redeclareVarsInsideBranch(n); NodeUtil.removeChild(n.getParent(), n); reportCodeChange(); return null; } /** * Removes FORs that always evaluate to false. */ Node tryFoldFor(Node n) { Preconditions.checkArgument(n.isFor()); // If this is a FOR-IN loop skip it. if (NodeUtil.isForIn(n)) { return n; } Node init = n.getFirstChild(); Node cond = init.getNext(); Node increment = cond.getNext(); if (!init.isEmpty() && !init.isVar()) { init = trySimplifyUnusedResult(init, false); } if (!increment.isEmpty()) { increment = trySimplifyUnusedResult(increment, false); } // There is an initializer skip it if (!n.getFirstChild().isEmpty()) { return n; } if (NodeUtil.getImpureBooleanValue(cond) != TernaryValue.FALSE) { return n; } Node parent = n.getParent(); NodeUtil.redeclareVarsInsideBranch(n); if (!mayHaveSideEffects(cond)) { NodeUtil.removeChild(parent, n); } else { Node statement = IR.exprResult(cond.detachFromParent()).copyInformationFrom(cond); if (parent.isLabel()) { Node block = IR.block(); block.copyInformationFrom(statement); block.addChildToFront(statement); statement = block; } parent.replaceChild(n, statement); } reportCodeChange(); return null; } /** * Removes DOs that always evaluate to false. This leaves the * statements that were in the loop in a BLOCK node. * The block will be removed in a later pass, if possible. */ Node tryFoldDoAway(Node n) { Preconditions.checkArgument(n.isDo()); Node cond = NodeUtil.getConditionExpression(n); if (NodeUtil.getImpureBooleanValue(cond) != TernaryValue.FALSE) { return n; } // TODO(johnlenz): The do-while can be turned into a label with // named breaks and the label optimized away (maybe). if (hasBreakOrContinue(n)) { return n; } Preconditions.checkState(NodeUtil.isControlStructureCodeBlock(n, n.getFirstChild())); Node block = n.removeFirstChild(); Node parent = n.getParent(); parent.replaceChild(n, block); if (mayHaveSideEffects(cond)) { Node condStatement = IR.exprResult(cond.detachFromParent()).srcref(cond); parent.addChildAfter(condStatement, block); } reportCodeChange(); return block; } /** * Removes DOs that have empty bodies into FORs, which are * much easier for the CFA to analyze. */ Node tryFoldEmptyDo(Node n) { Preconditions.checkArgument(n.isDo()); Node body = NodeUtil.getLoopCodeBlock(n); if (body.isBlock() && !body.hasChildren()) { Node cond = NodeUtil.getConditionExpression(n); Node whileNode = IR.forNode(IR.empty().srcref(n), cond.detachFromParent(), IR.empty().srcref(n), body.detachFromParent()).srcref(n); n.getParent().replaceChild(n, whileNode); reportCodeChange(); return whileNode; } return n; } /** * */ static boolean hasBreakOrContinue(Node n) { // TODO(johnlenz): This is overkill as named breaks may refer to outer // loops or labels, and any break my refer to an inner loop. // More generally, this check may be more expensive than we like. return NodeUtil.has(n, Predicates.or(new NodeUtil.MatchNodeType(Token.BREAK), new NodeUtil.MatchNodeType(Token.CONTINUE)), NodeUtil.MATCH_NOT_FUNCTION); } /** * Remove always true loop conditions. */ private void tryFoldForCondition(Node forCondition) { if (NodeUtil.getPureBooleanValue(forCondition) == TernaryValue.TRUE) { forCondition.getParent().replaceChild(forCondition, IR.empty()); reportCodeChange(); } } }