/* * 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.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; import com.google.devtools.j2objc.ast.AssertStatement; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.BooleanLiteral; import com.google.devtools.j2objc.ast.BreakStatement; import com.google.devtools.j2objc.ast.CommaExpression; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.ConditionalExpression; import com.google.devtools.j2objc.ast.ConstructorInvocation; import com.google.devtools.j2objc.ast.DoStatement; import com.google.devtools.j2objc.ast.EnhancedForStatement; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.ExpressionStatement; import com.google.devtools.j2objc.ast.ForStatement; 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.ParenthesizedExpression; import com.google.devtools.j2objc.ast.PostfixExpression; import com.google.devtools.j2objc.ast.PrefixExpression; import com.google.devtools.j2objc.ast.ReturnStatement; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.Statement; import com.google.devtools.j2objc.ast.SuperConstructorInvocation; import com.google.devtools.j2objc.ast.SwitchStatement; import com.google.devtools.j2objc.ast.SynchronizedStatement; import com.google.devtools.j2objc.ast.ThrowStatement; import com.google.devtools.j2objc.ast.TreeNode; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.ast.VariableDeclarationExpression; import com.google.devtools.j2objc.ast.VariableDeclarationFragment; 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.ElementUtil; import java.util.Collections; import java.util.List; import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** * Detects unsequenced modifications which are errors in ObjC and extracts * portions of the expression to eliminate the errors. * * This pass must occur after rewriting of labeled break and continue statements * otherwise extracted loop conditions will be out of order with the labels * inserted by the labeled break and continue rewriting. * * @author Keith Stanger */ public class UnsequencedExpressionRewriter extends UnitTreeVisitor { private ExecutableElement currentMethod = null; private int count = 1; private List<VariableAccess> orderedAccesses = Lists.newArrayList(); private TreeNode currentTopNode = null; private boolean hasModification = false; public UnsequencedExpressionRewriter(CompilationUnit unit) { super(unit); } /** * Metadata for a given read or write access of a variable within an * expression. */ private static class VariableAccess { private final VariableElement variable; private final Expression expression; private final boolean isModification; private VariableAccess( VariableElement variable, Expression expression, boolean isModification) { this.variable = variable; this.expression = expression; this.isModification = isModification; } } private void addVariableAccess(VariableElement var, Expression node, boolean isModification) { if (var != null && !ElementUtil.isInstanceVar(var)) { hasModification |= isModification; orderedAccesses.add(new VariableAccess(var, node, isModification)); } } private void newExpression(TreeNode topNode) { orderedAccesses.clear(); currentTopNode = topNode; hasModification = false; } @Override public boolean visit(MethodDeclaration node) { currentMethod = node.getExecutableElement(); count = 1; return true; } @Override public void endVisit(MethodDeclaration node) { currentMethod = null; } private List<VariableAccess> getUnsequencedAccesses() { if (!hasModification) { return Collections.emptyList(); } ListMultimap<VariableElement, VariableAccess> accessesByVar = MultimapBuilder.hashKeys().arrayListValues().build(); for (VariableAccess access : orderedAccesses) { accessesByVar.put(access.variable, access); } Set<VariableAccess> unsequencedAccesses = Sets.newHashSet(); for (VariableElement var : accessesByVar.keySet()) { findUnsequenced(accessesByVar.get(var), unsequencedAccesses); } List<VariableAccess> orderedUnsequencedAccesses = Lists.newArrayListWithCapacity(unsequencedAccesses.size()); for (VariableAccess access : orderedAccesses) { if (unsequencedAccesses.contains(access)) { orderedUnsequencedAccesses.add(access); } } return orderedUnsequencedAccesses; } private void extractUnsequenced(Statement stmt) { List<VariableAccess> accesses = getUnsequencedAccesses(); if (!accesses.isEmpty()) { extractOrderedAccesses( TreeUtil.asStatementList(stmt).subList(0, 0), currentTopNode, accesses); } } private void visitAndExtract(Expression expr, Statement stmt) { if (expr != null) { newExpression(expr); expr.accept(this); extractUnsequenced(stmt); } } private void extractOrderedAccesses( List<Statement> stmtList, TreeNode subExpr, List<VariableAccess> toExtract) { for (int i = 0; i < toExtract.size(); i++) { VariableAccess access = toExtract.get(i); TreeNode topConditional = getTopConditional(access.expression, subExpr); if (topConditional != null) { // Conditional expressions require special handling when extracting the // access because execution of the access may not be guaranteed. // Here we collect all accesses that are decendant of the conditional // expression and pass them to an appropriate extraction method. int j = i + 1; for (; j < toExtract.size(); j++) { if (getTopConditional(toExtract.get(j).expression, subExpr) != topConditional) { break; } } if (topConditional instanceof InfixExpression) { extractInfixConditional( stmtList, (InfixExpression) topConditional, toExtract.subList(i, j)); } else if (topConditional instanceof ConditionalExpression) { extractConditionalExpression( stmtList, (ConditionalExpression) topConditional, toExtract.subList(i, j)); } else { throw new AssertionError( "Unexpected conditional node type: " + topConditional.getClass().toString()); } i = j - 1; } else { VariableElement newVar = GeneratedVariableElement.newLocalVar( "unseq$" + count++, access.expression.getTypeMirror(), currentMethod); stmtList.add(new VariableDeclarationStatement(newVar, access.expression.copy())); access.expression.replaceWith(new SimpleName(newVar)); } } } private TreeNode getTopConditional(TreeNode node, TreeNode limit) { TreeNode topConditional = null; while (node != limit) { node = node.getParent(); if (isConditional(node)) { topConditional = node; } } return topConditional; } private void extractConditionalExpression( List<Statement> stmtList, ConditionalExpression conditional, List<VariableAccess> toExtract) { Expression condition = conditional.getExpression(); Expression thenExpr = conditional.getThenExpression(); Expression elseExpr = conditional.getElseExpression(); List<VariableAccess> conditionAccesses = Lists.newArrayList(); List<VariableAccess> thenAccesses = Lists.newArrayList(); List<VariableAccess> elseAccesses = Lists.newArrayList(); boolean needsExtraction = false; for (VariableAccess access : toExtract) { TreeNode node = access.expression; while (node.getParent() != conditional) { node = node.getParent(); } if (node == condition) { conditionAccesses.add(access); } else if (node == thenExpr) { thenAccesses.add(access); } else if (node == elseExpr) { elseAccesses.add(access); } else { throw new AssertionError(); } if (node != condition) { // We need to extract an if-statement if there is an access that // executes conditionally. needsExtraction = true; } } extractOrderedAccesses(stmtList, condition, conditionAccesses); // The recursive call might replace the condition child. condition = conditional.getExpression(); if (needsExtraction) { VariableElement resultVar = GeneratedVariableElement.newLocalVar( "unseq$" + count++, conditional.getTypeMirror(), currentMethod); conditional.replaceWith(new SimpleName(resultVar)); stmtList.add(new VariableDeclarationStatement(resultVar, null)); IfStatement newIf = new IfStatement(); newIf.setExpression(condition.copy()); stmtList.add(newIf); Block thenBlock = new Block(); newIf.setThenStatement(thenBlock); List<Statement> thenStmts = thenBlock.getStatements(); extractOrderedAccesses(thenStmts, thenExpr, thenAccesses); // The recursive call might replace the then expression child. thenExpr = conditional.getThenExpression(); thenStmts.add(new ExpressionStatement( new Assignment(new SimpleName(resultVar), thenExpr.copy()))); Block elseBlock = new Block(); newIf.setElseStatement(elseBlock); List<Statement> elseStmts = elseBlock.getStatements(); extractOrderedAccesses(elseStmts, elseExpr, elseAccesses); // The recursive call might replace the else expression child. elseExpr = conditional.getElseExpression(); elseStmts.add(new ExpressionStatement( new Assignment(new SimpleName(resultVar), elseExpr.copy()))); } else { extractOrderedAccesses(stmtList, thenExpr, thenAccesses); extractOrderedAccesses(stmtList, elseExpr, elseAccesses); } } private void extractInfixConditional( List<Statement> stmtList, InfixExpression conditional, List<VariableAccess> toExtract) { InfixExpression.Operator op = conditional.getOperator(); List<Expression> branches = conditional.getOperands(); int lastIfExtractIdx = 0; VariableElement conditionalVar = null; int lastExtracted = 0; Expression lastBranch = null; for (int i = 0; i < toExtract.size(); i++) { VariableAccess access = toExtract.get(i); TreeNode node = access.expression; while (node.getParent() != conditional) { node = node.getParent(); } assert node instanceof Expression; Expression branch = (Expression) node; // Extract all accesses from the previous branch. if (lastBranch != null && branch != lastBranch) { extractOrderedAccesses(stmtList, lastBranch, toExtract.subList(lastExtracted, i)); lastExtracted = i; } lastBranch = branch; // If there's a new access in a new branch, then we extract an if-statement. if (branch != branches.get(lastIfExtractIdx)) { TypeMirror boolType = typeUtil.getBoolean(); if (conditionalVar == null) { conditionalVar = GeneratedVariableElement.newLocalVar( "unseq$" + count++, boolType, currentMethod); conditional.replaceWith(new SimpleName(conditionalVar)); stmtList.add(new VariableDeclarationStatement(conditionalVar, null)); } List<Expression> subBranches = branches.subList(lastIfExtractIdx, branches.indexOf(branch)); IfStatement newIf = new IfStatement(); Expression ifExpr = new Assignment( new SimpleName(conditionalVar), conditionalFromSubBranches(subBranches, op)); if (op == InfixExpression.Operator.CONDITIONAL_OR) { ifExpr = new PrefixExpression( boolType, PrefixExpression.Operator.NOT, ParenthesizedExpression.parenthesize(ifExpr)); } newIf.setExpression(ifExpr); stmtList.add(newIf); Block thenBlock = new Block(); stmtList = thenBlock.getStatements(); newIf.setThenStatement(thenBlock); lastIfExtractIdx = branches.indexOf(branch); } } extractOrderedAccesses( stmtList, lastBranch, toExtract.subList(lastExtracted, toExtract.size())); if (conditionalVar != null) { List<Expression> remainingBranches = Lists.newArrayList(); remainingBranches.add(new SimpleName(conditionalVar)); remainingBranches.addAll(branches.subList(lastIfExtractIdx, branches.size())); stmtList.add(new ExpressionStatement(new Assignment( new SimpleName(conditionalVar), conditionalFromSubBranches(remainingBranches, op)))); } } private Expression conditionalFromSubBranches( List<Expression> branches, InfixExpression.Operator op) { assert branches.size() >= 1; if (branches.size() == 1) { return branches.get(0).copy(); } else { InfixExpression result = new InfixExpression(typeUtil.getBoolean(), op); TreeUtil.copyList(branches, result.getOperands()); return result; } } private boolean isConditional(TreeNode node) { if (node instanceof InfixExpression) { InfixExpression infixExpr = (InfixExpression) node; InfixExpression.Operator op = infixExpr.getOperator(); if (op == InfixExpression.Operator.CONDITIONAL_AND || op == InfixExpression.Operator.CONDITIONAL_OR) { return true; } } else if (node instanceof ConditionalExpression) { return true; } return false; } private void findUnsequenced(List<VariableAccess> accesses, Set<VariableAccess> toExtract) { // No conflicts with only one access. if (accesses.size() == 1) { return; } Set<VariableAccess> modifications = Sets.newHashSet(); for (VariableAccess access : accesses) { if (access.isModification) { modifications.add(access); } } for (VariableAccess modification : modifications) { Set<TreeNode> ancestors = getAncestors(modification.expression); boolean seenMod = false; for (VariableAccess access : accesses) { if (modification == access) { seenMod = true; } else if (isUnsequenced(modification, ancestors, access)) { // Only need to extract the first access. toExtract.add(seenMod ? modification : access); } } } } private Set<TreeNode> getAncestors(TreeNode node) { Set<TreeNode> ancestors = Sets.newHashSet(); while (node != currentTopNode) { ancestors.add(node); node = node.getParent(); } return ancestors; } private boolean isUnsequenced( VariableAccess modification, Set<TreeNode> modificationAncestors, VariableAccess access) { TreeNode commonAncestor = currentTopNode; TreeNode node = access.expression; while (node != currentTopNode) { if (modificationAncestors.contains(node)) { commonAncestor = node; break; } node = node.getParent(); } // If either access is executed in a conditional branch that does not // contain the other access, then they are not unsequenced. if (isWithinConditionalBranch(modification.expression, commonAncestor) || isWithinConditionalBranch(access.expression, commonAncestor)) { return false; } else if (commonAncestor instanceof CommaExpression) { return false; } else if (commonAncestor instanceof Assignment && modification.expression == commonAncestor) { // "i = 1 + (i = 2);" is not unsequenced. // "i = 1 + i++;" is unsequenced (according to clang). return access.expression instanceof PrefixExpression || access.expression instanceof PostfixExpression; } return true; } private boolean isWithinConditionalBranch(TreeNode node, TreeNode limit) { while (node != limit) { TreeNode parent = node.getParent(); if (isConditional(parent) && getConditionChild(parent) != node) { return true; } node = parent; } return false; } private Expression getConditionChild(TreeNode conditional) { if (conditional instanceof InfixExpression) { return ((InfixExpression) conditional).getOperand(0); } else if (conditional instanceof ConditionalExpression) { return ((ConditionalExpression) conditional).getExpression(); } else { throw new AssertionError( "Unexpected conditional node type: " + conditional.getClass().toString()); } } @Override public void endVisit(SimpleName node) { addVariableAccess(TreeUtil.getVariableElement(node), node, false); } @Override public boolean visit(ExpressionStatement node) { visitAndExtract(node.getExpression(), node); return false; } @Override public boolean visit(ReturnStatement node) { visitAndExtract(node.getExpression(), node); return false; } @Override public boolean visit(AssertStatement node) { Expression expr = node.getExpression(); visitAndExtract(expr, node); Expression msg = node.getMessage(); if (msg != null) { newExpression(msg); msg.accept(this); List<VariableAccess> toExtract = getUnsequencedAccesses(); if (!toExtract.isEmpty()) { // If the message expression needs any extraction, then we first extract // the entire boolean expression to preserve ordering between the two. VariableElement exprVar = GeneratedVariableElement.newLocalVar( "unseq$" + count++, expr.getTypeMirror(), currentMethod); TreeUtil.insertBefore(node, new VariableDeclarationStatement( exprVar, node.getExpression().copy())); node.setExpression(new SimpleName(exprVar)); extractOrderedAccesses( TreeUtil.asStatementList(node).subList(0, 0), currentTopNode, toExtract); } } return false; } @Override public boolean visit(ConstructorInvocation node) { newExpression(node); for (Expression arg : node.getArguments()) { arg.accept(this); } extractUnsequenced(node); return false; } @Override public boolean visit(SuperConstructorInvocation node) { newExpression(node); for (Expression arg : node.getArguments()) { arg.accept(this); } extractUnsequenced(node); return false; } @Override public boolean visit(EnhancedForStatement node) { visitAndExtract(node.getExpression(), node); node.getBody().accept(this); return false; } @Override public boolean visit(VariableDeclarationStatement node) { extractVariableDeclarationFragments( node.getFragments(), TreeUtil.asStatementList(node).subList(0, 0)); return false; } @Override public boolean visit(IfStatement node) { visitAndExtract(node.getExpression(), node); node.getThenStatement().accept(this); Statement elseStmt = node.getElseStatement(); if (elseStmt != null) { elseStmt.accept(this); } return false; } @Override public boolean visit(SwitchStatement node) { visitAndExtract(node.getExpression(), node); for (Statement stmt : node.getStatements()) { stmt.accept(this); } return false; } @Override public boolean visit(SynchronizedStatement node) { visitAndExtract(node.getExpression(), node); node.getBody().accept(this); return false; } @Override public boolean visit(ThrowStatement node) { visitAndExtract(node.getExpression(), node); return false; } private IfStatement createLoopTermination(Expression loopCondition) { IfStatement newIf = new IfStatement(); newIf.setExpression(new PrefixExpression( typeUtil.getBoolean(), PrefixExpression.Operator.NOT, ParenthesizedExpression.parenthesize(loopCondition.copy()))); newIf.setThenStatement(new BreakStatement()); return newIf; } @Override public boolean visit(WhileStatement node) { node.getBody().accept(this); newExpression(node.getExpression()); node.getExpression().accept(this); List<VariableAccess> toExtract = getUnsequencedAccesses(); if (!toExtract.isEmpty()) { // Convert "while (cond)" into "while (true) { if (!(cond)) break; ... }". List<Statement> stmtList = TreeUtil.asStatementList(node.getBody()).subList(0, 0); extractOrderedAccesses(stmtList, currentTopNode, toExtract); stmtList.add(createLoopTermination(node.getExpression())); node.setExpression(new BooleanLiteral(true, typeUtil)); } return false; } @Override public boolean visit(DoStatement node) { node.getBody().accept(this); newExpression(node.getExpression()); node.getExpression().accept(this); List<VariableAccess> toExtract = getUnsequencedAccesses(); if (!toExtract.isEmpty()) { // Convert "while (cond)" into "while (true) { if (!(cond)) break; ... }". List<Statement> stmtList = TreeUtil.asStatementList(node.getBody()); extractOrderedAccesses(stmtList, currentTopNode, toExtract); stmtList.add(createLoopTermination(node.getExpression())); node.setExpression(new BooleanLiteral(true, typeUtil)); } return false; } private void extractVariableDeclarationFragments( List<VariableDeclarationFragment> fragments, List<Statement> stmtList) { for (int i = 0; i < fragments.size(); i++) { VariableDeclarationFragment frag = fragments.get(i); Expression init = frag.getInitializer(); if (init == null) { continue; } newExpression(init); init.accept(this); List<VariableAccess> toExtract = getUnsequencedAccesses(); if (!toExtract.isEmpty()) { if (i > 0) { // Extract all fragments before the current one to preserve ordering. VariableDeclarationStatement newDecl = new VariableDeclarationStatement(fragments.get(0).copy()); for (int j = 1; j < i; j++) { newDecl.addFragment(fragments.get(j).copy()); } stmtList.add(newDecl); fragments.subList(0, i).clear(); } extractOrderedAccesses(stmtList, currentTopNode, toExtract); i = 0; } } } private void extractExpressionList( List<Expression> expressions, List<Statement> stmtList, boolean extractModifiedExpression) { for (int i = 0; i < expressions.size(); i++) { Expression expr = expressions.get(i); newExpression(expr); expr.accept(this); List<VariableAccess> unsequencedAccesses = getUnsequencedAccesses(); if (!unsequencedAccesses.isEmpty()) { for (int j = 0; j < i; j++) { stmtList.add(new ExpressionStatement(expressions.get(j).copy())); } expressions.subList(0, i).clear(); extractOrderedAccesses(stmtList, currentTopNode, unsequencedAccesses); i = 0; if (extractModifiedExpression) { stmtList.add(new ExpressionStatement(expressions.get(0).copy())); expressions.remove(0); i = -1; } } } } @Override public boolean visit(ForStatement node) { List<Expression> initializers = node.getInitializers(); // The for-loop initializers can either be a single variable declaration // expression or a list of initializer expressions. if (initializers.size() == 1 && initializers.get(0) instanceof VariableDeclarationExpression) { VariableDeclarationExpression decl = (VariableDeclarationExpression) initializers.get(0); extractVariableDeclarationFragments( decl.getFragments(), TreeUtil.asStatementList(node).subList(0, 0)); } else { extractExpressionList(initializers, TreeUtil.asStatementList(node).subList(0, 0), false); } Expression expr = node.getExpression(); if (expr != null) { newExpression(expr); expr.accept(this); List<VariableAccess> toExtract = getUnsequencedAccesses(); if (!toExtract.isEmpty()) { // Convert "if (;cond;)" into "if (;;) { if (!(cond)) break; ...}". List<Statement> stmtList = TreeUtil.asStatementList(node.getBody()).subList(0, 0); extractOrderedAccesses(stmtList, currentTopNode, toExtract); stmtList.add(createLoopTermination(node.getExpression())); node.setExpression(null); } } extractExpressionList(node.getUpdaters(), TreeUtil.asStatementList(node.getBody()), true); node.getBody().accept(this); return false; } @Override public boolean visit(Assignment node) { Expression lhs = node.getLeftHandSide(); VariableElement lhsVar = TreeUtil.getVariableElement(lhs); // Access order is important. If the lhs is a variable, then we must record // its access after visiting the rhs. Otherwise, visit both sides. if (lhsVar == null) { lhs.accept(this); } node.getRightHandSide().accept(this); addVariableAccess(lhsVar, node, true); return false; } @Override public boolean visit(PrefixExpression node) { PrefixExpression.Operator op = node.getOperator(); if (op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT) { addVariableAccess(TreeUtil.getVariableElement(node.getOperand()), node, true); } else { node.getOperand().accept(this); } return false; } @Override public boolean visit(PostfixExpression node) { PostfixExpression.Operator op = node.getOperator(); assert op == PostfixExpression.Operator.INCREMENT || op == PostfixExpression.Operator.DECREMENT; addVariableAccess(TreeUtil.getVariableElement(node.getOperand()), node, true); return false; } }