/* * 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 com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.DoStatement; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.FunctionInvocation; import com.google.devtools.j2objc.ast.IfStatement; import com.google.devtools.j2objc.ast.InfixExpression; import com.google.devtools.j2objc.ast.MethodDeclaration; import com.google.devtools.j2objc.ast.MethodInvocation; import com.google.devtools.j2objc.ast.ParenthesizedExpression; import com.google.devtools.j2objc.ast.PrefixExpression; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.Statement; import com.google.devtools.j2objc.ast.TreeNode; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TreeVisitor; import com.google.devtools.j2objc.ast.VariableDeclarationStatement; import com.google.devtools.j2objc.ast.WhileStatement; import com.google.devtools.j2objc.types.GeneratedVariableElement; import com.google.devtools.j2objc.util.TypeUtil; import java.util.Collection; import java.util.List; import java.util.Map; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; /** * Detects deep expression trees and extracts them into separate statements. * * @author Keith Stanger */ public class ComplexExpressionExtractor extends TreeVisitor { // The ObjC compiler tends to fail with roughly 100 chained method calls. private static final int DEFAULT_MAX_DEPTH = 50; private static int maxDepth = DEFAULT_MAX_DEPTH; private Map<Expression, Integer> depths = Maps.newHashMap(); private ExecutableElement currentMethod; private Statement currentStatement; private int count = 1; @VisibleForTesting static void setMaxDepth(int newMaxDepth) { maxDepth = newMaxDepth; } @VisibleForTesting static void resetMaxDepth() { maxDepth = DEFAULT_MAX_DEPTH; } private void handleNode(Expression node, Collection<Expression> children) { if (node.getParent() instanceof Statement) { return; } int depth = 0; for (Expression child : children) { Integer childDepth = depths.get(child); depth = Math.max(depth, childDepth != null ? childDepth : 1); } if (depth >= maxDepth) { VariableElement newVar = GeneratedVariableElement.newLocalVar( "complex$" + count++, node.getTypeMirror(), currentMethod); Statement newStmt = new VariableDeclarationStatement(newVar, node.copy()); assert currentStatement != null; TreeUtil.insertBefore(currentStatement, newStmt); node.replaceWith(new SimpleName(newVar)); } else { depths.put(node, depth + 1); } } @Override public boolean preVisit(TreeNode node) { super.preVisit(node); if (node instanceof Statement) { currentStatement = (Statement) node; } return true; } @Override public boolean visit(MethodDeclaration node) { currentMethod = node.getExecutableElement(); return true; } @Override public void endVisit(MethodDeclaration node) { currentMethod = null; } @Override public void endVisit(InfixExpression node) { handleNode(node, node.getOperands()); } @Override public void endVisit(MethodInvocation node) { Expression receiver = node.getExpression(); List<Expression> args = node.getArguments(); List<Expression> children = Lists.newArrayListWithCapacity(args.size() + 1); if (receiver != null) { children.add(receiver); } children.addAll(args); handleNode(node, children); } @Override public void endVisit(FunctionInvocation node) { handleNode(node, node.getArguments()); } @Override public void endVisit(PrefixExpression node) { TreeNode parent = node.getParent(); if (parent == null) { return; } // Check for balancing dereference and address-of operators. if (parent instanceof PrefixExpression) { PrefixExpression.Operator thisOp = node.getOperator(); PrefixExpression.Operator parentOp = ((PrefixExpression) parent).getOperator(); if ((thisOp == PrefixExpression.Operator.DEREFERENCE && parentOp == PrefixExpression.Operator.ADDRESS_OF) || (thisOp == PrefixExpression.Operator.ADDRESS_OF && parentOp == PrefixExpression.Operator.DEREFERENCE)) { parent.replaceWith(TreeUtil.remove(node.getOperand())); return; } } // Some other translation passes may have inserted prefix expressions // without checking if parentheses were necessary. switch (parent.getKind()) { case POSTFIX_EXPRESSION: case PREFIX_EXPRESSION: // Parentheses not needed, but better for readability. ParenthesizedExpression.parenthesizeAndReplace(node); break; default: // Ignore. } } @Override public void endVisit(Assignment node) { if (TypeUtil.isBoolean(node.getTypeMirror())) { if (node.getRightHandSide() instanceof InfixExpression) { // Avoid clang precedence warning by putting parentheses around expression. ParenthesizedExpression.parenthesizeAndReplace(node.getRightHandSide()); } // Avoid clang parentheses warning when assignments are used as conditional expressions // in statements. ConditionalExpressions don't need to change, though, since it's a // Java syntax error if an assignment-as-conditional use isn't parenthesized already. TreeNode parent = node.getParent(); if ((parent instanceof DoStatement && node == ((DoStatement) parent).getExpression()) || (parent instanceof IfStatement && node == ((IfStatement) parent).getExpression()) || (parent instanceof WhileStatement && node == ((WhileStatement) parent).getExpression())) { ParenthesizedExpression.parenthesizeAndReplace(node); } } } /** * If an equality (==) expression has double-parentheses, remove one set. * This avoids clang's -Wparentheses-equality warning. */ @Override public void endVisit(ParenthesizedExpression node) { Expression expr = node.getExpression(); if (expr instanceof ParenthesizedExpression) { Expression inner = ((ParenthesizedExpression) expr).getExpression(); if (isEqualityExpression(inner)) { node.replaceWith(TreeUtil.remove(expr)); } } else if (!(node.getParent() instanceof Expression) && isEqualityExpression(expr)) { node.replaceWith(TreeUtil.remove(expr)); } } private boolean isEqualityExpression(Expression expr) { return expr instanceof InfixExpression && ((InfixExpression) expr).getOperator() == InfixExpression.Operator.EQUALS; } }