/* * 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.devtools.j2objc.translate; import static com.google.devtools.j2objc.ast.InfixExpression.Operator.CONDITIONAL_AND; import static com.google.devtools.j2objc.ast.InfixExpression.Operator.CONDITIONAL_OR; import static java.lang.Boolean.FALSE; import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.BooleanLiteral; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.ConditionalExpression; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.ExpressionStatement; import com.google.devtools.j2objc.ast.IfStatement; import com.google.devtools.j2objc.ast.InfixExpression; import com.google.devtools.j2objc.ast.ParenthesizedExpression; import com.google.devtools.j2objc.ast.PrefixExpression; import com.google.devtools.j2objc.ast.ReturnStatement; import com.google.devtools.j2objc.ast.Statement; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.ast.WhileStatement; import com.google.devtools.j2objc.util.TranslationUtil; import java.util.List; /** * Removes branches that are tested with boolean constant expressions * (like javac does). * * @author Tom Ball */ public class ConstantBranchPruner extends UnitTreeVisitor { public ConstantBranchPruner(CompilationUnit unit) { super(unit); } /** * Removes all unreachable statements that occur after a return statement in * the given Block. Also recurses into child blocks. */ private boolean removeUnreachable(Block block) { List<Statement> stmts = block.getStatements(); for (int i = 0; i < stmts.size(); i++) { Statement stmt = stmts.get(i); if (stmt instanceof ReturnStatement || (stmt instanceof Block && removeUnreachable((Block) stmt))) { stmts.subList(i + 1, stmts.size()).clear(); return true; } } return false; } @Override public void endVisit(Block node) { removeUnreachable(node); } @Override public void endVisit(ConditionalExpression node) { Expression expr = node.getExpression(); Boolean value = getReplaceableValue(expr); if (value != null) { Expression result = value ? node.getThenExpression() : node.getElseExpression(); node.replaceWith(result.copy()); } } @Override public void endVisit(IfStatement node) { Expression expr = node.getExpression(); Boolean value = getKnownValue(expr); if (value != null) { Statement sideEffects = getSideEffects(expr); if (sideEffects != null) { TreeUtil.insertBefore(node, sideEffects); } if (value) { node.replaceWith(TreeUtil.remove(node.getThenStatement())); } else if (node.getElseStatement() != null) { node.replaceWith(TreeUtil.remove(node.getElseStatement())); } else { node.remove(); } } } @Override public void endVisit(InfixExpression node) { InfixExpression.Operator operator = node.getOperator(); if (operator != CONDITIONAL_AND && operator != CONDITIONAL_OR) { return; } List<Expression> operands = node.getOperands(); int lastSideEffect = -1; for (int i = 0; i < operands.size(); i++) { Expression expr = operands.get(i); if (TranslationUtil.hasSideEffect(expr)) { lastSideEffect = i; } Boolean knownVal = getKnownValue(expr); if (knownVal == null) { continue; } if (knownVal == (operator == CONDITIONAL_OR)) { // Whole expression evaluates to 'knownVal'. operands.subList(lastSideEffect + 1, operands.size()).clear(); if (lastSideEffect < i) { operands.add(new BooleanLiteral(knownVal, typeUtil)); } break; } else if (lastSideEffect < i) { // Else remove unnecessary constant value. operands.remove(i--); } } if (operands.size() == 0) { if (operator == CONDITIONAL_OR) { // All constants must have been false, because a true value would have // caused us to return in the loop above. node.replaceWith(new BooleanLiteral(false, typeUtil)); } else { // Likewise, all constants must have been true. node.replaceWith(new BooleanLiteral(true, typeUtil)); } } else if (operands.size() == 1) { node.replaceWith(operands.remove(0)); } } /** * Invert ! boolean constant expressions. */ @Override public void endVisit(PrefixExpression node) { Boolean value = getReplaceableValue(node.getOperand()); if (node.getOperator() == PrefixExpression.Operator.NOT && value != null) { node.replaceWith(new BooleanLiteral(!value, typeUtil)); } } /** * Remove parentheses around constant booleans. */ @Override public void endVisit(ParenthesizedExpression node) { if (getReplaceableValue(node.getExpression()) != null) { node.replaceWith(node.getExpression().copy()); } } @Override public void endVisit(WhileStatement node) { Expression expr = node.getExpression(); if (getKnownValue(expr) == FALSE) { Statement sideEffects = getSideEffects(expr); if (sideEffects != null) { node.replaceWith(sideEffects); } else { node.remove(); } } } /** * Returns TRUE or FALSE if expression is a boolean constant and has no side * effects, else null (unknown statically). */ private Boolean getReplaceableValue(Expression expr) { return TranslationUtil.hasSideEffect(expr) ? null : getKnownValue(expr); } /** * Returns TRUE of FALSE if 'expr' is a boolean expression and its value is * known statically. The caller should be careful when replacing this * expression as it may have side effects. */ private Boolean getKnownValue(Expression expr) { Object value = expr.getConstantValue(); if (value instanceof Boolean) { return (Boolean) value; } switch (expr.getKind()) { case BOOLEAN_LITERAL: return ((BooleanLiteral) expr).booleanValue(); case INFIX_EXPRESSION: { InfixExpression infixExpr = (InfixExpression) expr; InfixExpression.Operator op = infixExpr.getOperator(); if (op == CONDITIONAL_AND || op == CONDITIONAL_OR) { // We assume that this node has already been visited and pruned so // if it has a known value, it will be equal to the last operand. List<Expression> operands = infixExpr.getOperands(); Boolean lastOperand = getKnownValue(operands.get(operands.size() - 1)); if (lastOperand != null && lastOperand.booleanValue() == (op == CONDITIONAL_OR)) { return lastOperand; } } return null; } case PARENTHESIZED_EXPRESSION: return getKnownValue(((ParenthesizedExpression) expr).getExpression()); default: return null; } } /** * Extracts side effects from the given expression and returns the statement * to insert. */ private Statement getSideEffects(Expression expr) { Expression sideEffectsExpr = extractSideEffects(expr); return sideEffectsExpr == null ? null : new ExpressionStatement(sideEffectsExpr); } /** * Returns an expression containing the side effects of the given expression. * The evaluated result of the expression may differ from the original. */ private Expression extractSideEffects(Expression expr) { switch (expr.getKind()) { case INFIX_EXPRESSION: { List<Expression> operands = ((InfixExpression) expr).getOperands(); Expression lastOperand = operands.remove(operands.size() - 1); lastOperand = extractSideEffects(lastOperand); if (lastOperand != null) { operands.add(lastOperand); } if (operands.size() == 1) { return operands.remove(0); } return TreeUtil.remove(expr); } case PARENTHESIZED_EXPRESSION: { Expression sideEffects = extractSideEffects( ((ParenthesizedExpression) expr).getExpression()); if (sideEffects != null) { return ParenthesizedExpression.parenthesize(sideEffects); } return null; } default: return null; } } }