com.google.javascript.jscomp.PeepholeRemoveDeadCode.java Source code

Java tutorial

Introduction

Here is the source code for com.google.javascript.jscomp.PeepholeRemoveDeadCode.java

Source

/*
 * 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();
        }
    }
}