/* * 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.Lists; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.BooleanLiteral; import com.google.devtools.j2objc.ast.CStringLiteral; import com.google.devtools.j2objc.ast.CharacterLiteral; import com.google.devtools.j2objc.ast.CommaExpression; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.FieldAccess; import com.google.devtools.j2objc.ast.FunctionInvocation; import com.google.devtools.j2objc.ast.InfixExpression; import com.google.devtools.j2objc.ast.NumberLiteral; import com.google.devtools.j2objc.ast.PrefixExpression; import com.google.devtools.j2objc.ast.QualifiedName; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.StringLiteral; import com.google.devtools.j2objc.ast.SuperFieldAccess; import com.google.devtools.j2objc.ast.ThisExpression; 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.VariableDeclarationFragment; import com.google.devtools.j2objc.ast.VariableDeclarationStatement; import com.google.devtools.j2objc.types.FunctionElement; import com.google.devtools.j2objc.types.GeneratedVariableElement; import com.google.devtools.j2objc.types.PointerType; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.NameTable; import com.google.devtools.j2objc.util.TranslationUtil; import com.google.devtools.j2objc.util.TypeUtil; import com.google.devtools.j2objc.util.UnicodeUtils; import com.google.j2objc.annotations.RetainedLocalRef; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; /** * Rewrites certain operators, such as object assignment, into appropriate * method calls. * * @author Keith Stanger */ public class OperatorRewriter extends UnitTreeVisitor { public OperatorRewriter(CompilationUnit unit) { super(unit); } @Override public void endVisit(Assignment node) { if (node.getOperator() == Assignment.Operator.ASSIGN) { rewriteRegularAssignment(node); } else if (isStringAppend(node)) { rewriteStringAppend(node); } else { rewriteCompoundAssign(node); } } private boolean isStringAppend(TreeNode node) { if (!(node instanceof Assignment)) { return false; } Assignment assignment = (Assignment) node; return assignment.getOperator() == Assignment.Operator.PLUS_ASSIGN && typeUtil.isAssignable( typeUtil.getJavaString().asType(), assignment.getLeftHandSide().getTypeMirror()); } @Override public void endVisit(InfixExpression node) { InfixExpression.Operator op = node.getOperator(); TypeMirror nodeType = node.getTypeMirror(); String funcName = getInfixFunction(op, nodeType); if (funcName != null) { Iterator<Expression> operandIter = node.getOperands().iterator(); Expression leftOperand = operandIter.next(); operandIter.remove(); // This takes extended operands into consideration. If a node has three operands, o1 o2 o3, // the function invocations should be like f(f(o1, o2), o3), given that the infix operators // translated here are all left-associative. while (operandIter.hasNext()) { Expression rightOperand = operandIter.next(); operandIter.remove(); FunctionElement element = new FunctionElement(funcName, nodeType, null) .addParameters(leftOperand.getTypeMirror(), rightOperand.getTypeMirror()); FunctionInvocation invocation = new FunctionInvocation(element, nodeType); List<Expression> args = invocation.getArguments(); args.add(leftOperand); args.add(rightOperand); leftOperand = invocation; } node.replaceWith(leftOperand); } else if (op == InfixExpression.Operator.PLUS && typeUtil.isString(nodeType) && !isStringAppend(node.getParent())) { rewriteStringConcatenation(node); } } @Override public boolean visit(FieldAccess node) { rewriteVolatileLoad(node); node.getExpression().accept(this); return false; } @Override public boolean visit(SuperFieldAccess node) { rewriteVolatileLoad(node); return false; } @Override public boolean visit(QualifiedName node) { rewriteVolatileLoad(node); return false; } @Override public boolean visit(SimpleName node) { rewriteVolatileLoad(node); return false; } @Override public boolean visit(VariableDeclarationFragment node) { // Skip name so that it doesn't get mistaken for a variable load. Expression initializer = node.getInitializer(); if (initializer != null) { initializer.accept(this); handleRetainedLocal(node.getVariableElement(), node.getInitializer()); } return false; } private void handleRetainedLocal(VariableElement var, Expression rhs) { if (ElementUtil.isLocalVariable(var) && ElementUtil.hasAnnotation(var, RetainedLocalRef.class) && options.useReferenceCounting()) { FunctionElement element = new FunctionElement("JreRetainedLocalValue", TypeUtil.ID_TYPE, null); FunctionInvocation invocation = new FunctionInvocation(element, rhs.getTypeMirror()); rhs.replaceWith(invocation); invocation.addArgument(rhs); } } private void rewriteVolatileLoad(Expression node) { VariableElement var = TreeUtil.getVariableElement(node); if (var != null && ElementUtil.isVolatile(var) && !TranslationUtil.isAssigned(node)) { TypeMirror type = node.getTypeMirror(); TypeMirror declaredType = type.getKind().isPrimitive() ? type : TypeUtil.ID_TYPE; String funcName = "JreLoadVolatile" + NameTable.capitalize(declaredType.toString()); FunctionElement element = new FunctionElement(funcName, declaredType, null) .addParameters(TypeUtil.ID_PTR_TYPE); FunctionInvocation invocation = new FunctionInvocation(element, type); node.replaceWith(invocation); invocation.addArgument(new PrefixExpression( new PointerType(type), PrefixExpression.Operator.ADDRESS_OF, node)); } } private String getAssignmentFunctionName( Assignment node, VariableElement var, boolean isRetainedWith) { if (!ElementUtil.isField(var)) { return null; } TypeMirror type = var.asType(); boolean isPrimitive = type.getKind().isPrimitive(); boolean isStrong = !isPrimitive && !ElementUtil.isWeakReference(var); boolean isVolatile = ElementUtil.isVolatile(var); if (isRetainedWith) { return isVolatile ? "JreVolatileRetainedWithAssign" : "JreRetainedWithAssign"; } if (isVolatile) { // We can't use the "AndConsume" optimization for volatile objects because that might leave // the newly created object vulnerable to being deallocated by another thread assigning to the // same field. return isStrong ? "JreVolatileStrongAssign" : "JreAssignVolatile" + (isPrimitive ? NameTable.capitalize(TypeUtil.getName(type)) : "Id"); } if (isStrong && options.useReferenceCounting()) { String funcName = "JreStrongAssign"; Expression retainedRhs = TranslationUtil.retainResult(node.getRightHandSide()); if (retainedRhs != null) { funcName += "AndConsume"; node.setRightHandSide(retainedRhs); } return funcName; } return null; } // Counter to create unique local variables for the RetainedWith target. private int rwCount = 0; // Gets the target object for a call to the RetainedWith wrapper. private Expression getRetainedWithTarget(Assignment node, VariableElement var) { Expression lhs = node.getLeftHandSide(); if (!(lhs instanceof FieldAccess)) { return new ThisExpression(ElementUtil.getDeclaringClass(var).asType()); } // To avoid duplicating the target expression we must save the result to a local variable. FieldAccess fieldAccess = (FieldAccess) lhs; Expression target = fieldAccess.getExpression(); VariableElement targetVar = GeneratedVariableElement.newLocalVar( "__rw$" + rwCount++, target.getTypeMirror(), null); TreeUtil.asStatementList(TreeUtil.getOwningStatement(lhs)) .add(0, new VariableDeclarationStatement(targetVar, null)); fieldAccess.setExpression(new SimpleName(targetVar)); CommaExpression commaExpr = new CommaExpression( new Assignment(new SimpleName(targetVar), target)); node.replaceWith(commaExpr); commaExpr.addExpression(node); return new SimpleName(targetVar); } private void rewriteRegularAssignment(Assignment node) { VariableElement var = TreeUtil.getVariableElement(node.getLeftHandSide()); if (var == null) { return; } handleRetainedLocal(var, node.getRightHandSide()); boolean isRetainedWith = ElementUtil.isRetainedWithField(var); String funcName = getAssignmentFunctionName(node, var, isRetainedWith); if (funcName == null) { return; } TypeMirror type = node.getTypeMirror(); TypeMirror idType = TypeUtil.ID_TYPE; TypeMirror declaredType = type.getKind().isPrimitive() ? type : idType; Expression lhs = node.getLeftHandSide(); FunctionElement element = new FunctionElement(funcName, declaredType, null); FunctionInvocation invocation = new FunctionInvocation(element, type); List<Expression> args = invocation.getArguments(); if (isRetainedWith) { element.addParameters(idType); args.add(getRetainedWithTarget(node, var)); } element.addParameters(TypeUtil.ID_PTR_TYPE, idType); args.add(new PrefixExpression( new PointerType(lhs.getTypeMirror()), PrefixExpression.Operator.ADDRESS_OF, TreeUtil.remove(lhs))); args.add(TreeUtil.remove(node.getRightHandSide())); node.replaceWith(invocation); } private static String intSizePostfix(TypeMirror type) { switch (type.getKind()) { case INT: return "32"; case LONG: return "64"; default: throw new AssertionError("Type expected to be int or long but was: " + type); } } private static String getInfixFunction(InfixExpression.Operator op, TypeMirror nodeType) { switch (op) { case REMAINDER: switch (nodeType.getKind()) { case FLOAT: return "fmodf"; case DOUBLE: return "fmod"; default: return null; } case LEFT_SHIFT: return "JreLShift" + intSizePostfix(nodeType); case RIGHT_SHIFT_SIGNED: return "JreRShift" + intSizePostfix(nodeType); case RIGHT_SHIFT_UNSIGNED: return "JreURShift" + intSizePostfix(nodeType); default: return null; } } private static boolean isVolatile(Expression varNode) { VariableElement var = TreeUtil.getVariableElement(varNode); return var != null && ElementUtil.isVolatile(var); } private static boolean shouldRewriteCompoundAssign(Assignment node) { Expression lhs = node.getLeftHandSide(); TypeMirror lhsType = lhs.getTypeMirror(); TypeMirror rhsType = node.getRightHandSide().getTypeMirror(); switch (node.getOperator()) { case LEFT_SHIFT_ASSIGN: case RIGHT_SHIFT_SIGNED_ASSIGN: case RIGHT_SHIFT_UNSIGNED_ASSIGN: return true; case PLUS_ASSIGN: case MINUS_ASSIGN: case TIMES_ASSIGN: case DIVIDE_ASSIGN: case REMAINDER_ASSIGN: return isVolatile(lhs) || TypeUtil.isFloatingPoint(lhsType) || TypeUtil.isFloatingPoint(rhsType); default: return isVolatile(lhs); } } private static boolean needsPromotionSuffix(Assignment.Operator op) { switch (op) { case PLUS_ASSIGN: case MINUS_ASSIGN: case TIMES_ASSIGN: case DIVIDE_ASSIGN: case REMAINDER_ASSIGN: return true; default: return false; } } /** * Some operator functions are given a suffix indicating the promotion type of * the operands according to JLS 5.6.2. */ private static String getPromotionSuffix(Assignment node) { if (!needsPromotionSuffix(node.getOperator())) { return ""; } TypeKind lhsKind = node.getLeftHandSide().getTypeMirror().getKind(); TypeKind rhsKind = node.getRightHandSide().getTypeMirror().getKind(); if (lhsKind == TypeKind.DOUBLE || rhsKind == TypeKind.DOUBLE) { return "D"; } if (lhsKind == TypeKind.FLOAT || rhsKind == TypeKind.FLOAT) { return "F"; } if (lhsKind == TypeKind.LONG || rhsKind == TypeKind.LONG) { return "J"; } return "I"; } private void rewriteCompoundAssign(Assignment node) { if (!shouldRewriteCompoundAssign(node)) { return; } Expression lhs = node.getLeftHandSide(); Expression rhs = node.getRightHandSide(); TypeMirror lhsType = lhs.getTypeMirror(); TypeMirror lhsPointerType = new PointerType(lhsType); String funcName = "Jre" + node.getOperator().getName() + (isVolatile(lhs) ? "Volatile" : "") + NameTable.capitalize(lhsType.toString()) + getPromotionSuffix(node); FunctionElement element = new FunctionElement(funcName, lhsType, null) .addParameters(lhsPointerType, rhs.getTypeMirror()); FunctionInvocation invocation = new FunctionInvocation(element, lhsType); List<Expression> args = invocation.getArguments(); args.add(new PrefixExpression( lhsPointerType, PrefixExpression.Operator.ADDRESS_OF, TreeUtil.remove(lhs))); args.add(TreeUtil.remove(rhs)); node.replaceWith(invocation); } private CStringLiteral getStrcatTypesCString(List<Expression> operands) { StringBuilder typeArg = new StringBuilder(); for (Expression expr : operands) { typeArg.append(getStringConcatenationTypeCharacter(expr)); } return new CStringLiteral(typeArg.toString()); } private void rewriteStringConcatenation(InfixExpression node) { List<Expression> childOperands = node.getOperands(); List<Expression> operands = Lists.newArrayListWithCapacity(childOperands.size()); TreeUtil.moveList(childOperands, operands); operands = coalesceStringLiterals(operands); if (operands.size() == 1 && typeUtil.isString(operands.get(0).getTypeMirror())) { node.replaceWith(operands.get(0)); return; } TypeMirror stringType = typeUtil.getJavaString().asType(); FunctionElement element = new FunctionElement("JreStrcat", stringType, null) .addParameters(TypeUtil.NATIVE_CHAR_PTR) .setIsVarargs(true); FunctionInvocation invocation = new FunctionInvocation(element, stringType); List<Expression> args = invocation.getArguments(); args.add(getStrcatTypesCString(operands)); args.addAll(operands); node.replaceWith(invocation); } private List<Expression> getStringAppendOperands(Assignment node) { Expression rhs = node.getRightHandSide(); if (rhs instanceof InfixExpression && typeUtil.isString(rhs.getTypeMirror())) { InfixExpression infixExpr = (InfixExpression) rhs; if (infixExpr.getOperator() == InfixExpression.Operator.PLUS) { List<Expression> operands = infixExpr.getOperands(); List<Expression> result = Lists.newArrayListWithCapacity(operands.size()); TreeUtil.moveList(operands, result); return coalesceStringLiterals(result); } } return Collections.singletonList(TreeUtil.remove(rhs)); } private void rewriteStringAppend(Assignment node) { List<Expression> operands = getStringAppendOperands(node); Expression lhs = node.getLeftHandSide(); TypeMirror lhsType = lhs.getTypeMirror(); String funcName = "JreStrAppend" + translationUtil.getOperatorFunctionModifier(lhs); FunctionElement element = new FunctionElement(funcName, TypeUtil.ID_TYPE, null) .addParameters(TypeUtil.ID_PTR_TYPE, TypeUtil.NATIVE_CHAR_PTR) .setIsVarargs(true); FunctionInvocation invocation = new FunctionInvocation(element, lhsType); List<Expression> args = invocation.getArguments(); args.add(new PrefixExpression( new PointerType(lhsType), PrefixExpression.Operator.ADDRESS_OF, TreeUtil.remove(lhs))); args.add(getStrcatTypesCString(operands)); args.addAll(operands); node.replaceWith(invocation); } private List<Expression> coalesceStringLiterals(List<Expression> rawOperands) { List<Expression> operands = Lists.newArrayListWithCapacity(rawOperands.size()); String currentLiteral = null; for (Expression expr : rawOperands) { String literalValue = getLiteralStringValue(expr); if (literalValue != null) { currentLiteral = currentLiteral == null ? literalValue : currentLiteral + literalValue; } else { if (currentLiteral != null) { addStringLiteralArgument(operands, currentLiteral); currentLiteral = null; } operands.add(expr); } } if (currentLiteral != null) { addStringLiteralArgument(operands, currentLiteral); } return operands; } private void addStringLiteralArgument(List<Expression> args, String literal) { if (literal.length() == 0) { return; // Skip it. } else if (literal.length() == 1) { args.add(new CharacterLiteral(literal.charAt(0), typeUtil)); } else { args.add(new StringLiteral(literal, typeUtil)); } } private static String getLiteralStringValue(Expression expr) { switch (expr.getKind()) { case STRING_LITERAL: String literalValue = ((StringLiteral) expr).getLiteralValue(); if (UnicodeUtils.hasValidCppCharacters(literalValue)) { return literalValue; } else { return null; } case BOOLEAN_LITERAL: return String.valueOf(((BooleanLiteral) expr).booleanValue()); case CHARACTER_LITERAL: return String.valueOf(((CharacterLiteral) expr).charValue()); case NUMBER_LITERAL: return ((NumberLiteral) expr).getValue().toString(); default: return null; } } /** * Returns a character to indicate the type of an argument. * '$' for String, '@' for other objects, and the binary name character for * the primitives. */ private char getStringConcatenationTypeCharacter(Expression operand) { TypeMirror operandType = operand.getTypeMirror(); if (operandType.getKind().isPrimitive()) { return TypeUtil.getBinaryName(operandType).charAt(0); } else if (typeUtil.isString(operandType)) { return '$'; } else { return '@'; } } }