| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.codehaus.groovy.classgen.asm; |
| |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.ClassHelper; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.ast.Variable; |
| import org.codehaus.groovy.ast.expr.ArgumentListExpression; |
| import org.codehaus.groovy.ast.expr.ArrayExpression; |
| import org.codehaus.groovy.ast.expr.BinaryExpression; |
| import org.codehaus.groovy.ast.expr.ClassExpression; |
| import org.codehaus.groovy.ast.expr.ConstantExpression; |
| import org.codehaus.groovy.ast.expr.ElvisOperatorExpression; |
| import org.codehaus.groovy.ast.expr.EmptyExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.expr.FieldExpression; |
| import org.codehaus.groovy.ast.expr.ListExpression; |
| import org.codehaus.groovy.ast.expr.MethodCallExpression; |
| import org.codehaus.groovy.ast.expr.NotExpression; |
| import org.codehaus.groovy.ast.expr.PostfixExpression; |
| import org.codehaus.groovy.ast.expr.PrefixExpression; |
| import org.codehaus.groovy.ast.expr.PropertyExpression; |
| import org.codehaus.groovy.ast.expr.TernaryExpression; |
| import org.codehaus.groovy.ast.expr.TupleExpression; |
| import org.codehaus.groovy.ast.expr.VariableExpression; |
| import org.codehaus.groovy.ast.tools.GeneralUtils; |
| import org.codehaus.groovy.ast.tools.GenericsUtils; |
| import org.codehaus.groovy.ast.tools.WideningCategories; |
| import org.codehaus.groovy.classgen.AsmClassGenerator; |
| import org.codehaus.groovy.classgen.BytecodeExpression; |
| import org.codehaus.groovy.runtime.ScriptBytecodeAdapter; |
| import org.codehaus.groovy.syntax.Token; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| |
| import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.args; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.binX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; |
| import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; |
| import static org.codehaus.groovy.syntax.Types.ASSIGN; |
| import static org.codehaus.groovy.syntax.Types.BITWISE_AND; |
| import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.BITWISE_OR; |
| import static org.codehaus.groovy.syntax.Types.BITWISE_OR_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.BITWISE_XOR; |
| import static org.codehaus.groovy.syntax.Types.BITWISE_XOR_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_IDENTICAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IDENTICAL; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IN; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_INSTANCEOF; |
| import static org.codehaus.groovy.syntax.Types.COMPARE_TO; |
| import static org.codehaus.groovy.syntax.Types.DIVIDE; |
| import static org.codehaus.groovy.syntax.Types.DIVIDE_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.ELVIS_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.EQUAL; |
| import static org.codehaus.groovy.syntax.Types.FIND_REGEX; |
| import static org.codehaus.groovy.syntax.Types.INTDIV; |
| import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.KEYWORD_IN; |
| import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF; |
| import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT; |
| import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.LEFT_SQUARE_BRACKET; |
| import static org.codehaus.groovy.syntax.Types.LOGICAL_AND; |
| import static org.codehaus.groovy.syntax.Types.LOGICAL_OR; |
| import static org.codehaus.groovy.syntax.Types.MATCH_REGEX; |
| import static org.codehaus.groovy.syntax.Types.MINUS; |
| import static org.codehaus.groovy.syntax.Types.MINUS_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.MINUS_MINUS; |
| import static org.codehaus.groovy.syntax.Types.MOD; |
| import static org.codehaus.groovy.syntax.Types.MOD_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.MULTIPLY; |
| import static org.codehaus.groovy.syntax.Types.MULTIPLY_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.PLUS; |
| import static org.codehaus.groovy.syntax.Types.PLUS_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.PLUS_PLUS; |
| import static org.codehaus.groovy.syntax.Types.POWER; |
| import static org.codehaus.groovy.syntax.Types.POWER_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT; |
| import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_EQUAL; |
| import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED; |
| import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED_EQUAL; |
| import static org.objectweb.asm.Opcodes.ACONST_NULL; |
| import static org.objectweb.asm.Opcodes.GOTO; |
| import static org.objectweb.asm.Opcodes.IFEQ; |
| import static org.objectweb.asm.Opcodes.IFNE; |
| import static org.objectweb.asm.Opcodes.INSTANCEOF; |
| |
| public class BinaryExpressionHelper { |
| // compare |
| private static final MethodCaller compareIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareIdentical"); |
| private static final MethodCaller compareNotIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotIdentical"); |
| private static final MethodCaller compareEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareEqual"); |
| private static final MethodCaller compareNotEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotEqual"); |
| private static final MethodCaller compareToMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareTo"); |
| private static final MethodCaller compareLessThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThan"); |
| private static final MethodCaller compareLessThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThanEqual"); |
| private static final MethodCaller compareGreaterThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThan"); |
| private static final MethodCaller compareGreaterThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThanEqual"); |
| // regexp |
| private static final MethodCaller findRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "findRegex"); |
| private static final MethodCaller matchRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "matchRegex"); |
| // isCase/isNotCase |
| private static final MethodCaller isCaseMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "isCase"); |
| private static final MethodCaller isNotCaseMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "isNotCase"); |
| |
| protected final WriterController controller; |
| private final UnaryExpressionHelper unaryExpressionHelper; |
| |
| public BinaryExpressionHelper(final WriterController wc) { |
| this.controller = wc; |
| this.unaryExpressionHelper = new UnaryExpressionHelper(this.controller); |
| } |
| |
| public WriterController getController() { |
| return controller; |
| } |
| |
| public MethodCaller getIsCaseMethod() { |
| return isCaseMethod; |
| } |
| |
| public void eval(final BinaryExpression expression) { |
| switch (expression.getOperation().getType()) { |
| case EQUAL: // = (aka assignment) |
| evaluateEqual(expression, false); |
| break; |
| |
| case COMPARE_EQUAL: // == |
| evaluateCompareExpression(compareEqualMethod, expression); |
| break; |
| |
| case COMPARE_NOT_EQUAL: |
| evaluateCompareExpression(compareNotEqualMethod, expression); |
| break; |
| |
| case COMPARE_TO: |
| evaluateCompareTo(expression); |
| break; |
| |
| case COMPARE_GREATER_THAN: |
| evaluateCompareExpression(compareGreaterThanMethod, expression); |
| break; |
| |
| case COMPARE_GREATER_THAN_EQUAL: |
| evaluateCompareExpression(compareGreaterThanEqualMethod, expression); |
| break; |
| |
| case COMPARE_LESS_THAN: |
| evaluateCompareExpression(compareLessThanMethod, expression); |
| break; |
| |
| case COMPARE_LESS_THAN_EQUAL: |
| evaluateCompareExpression(compareLessThanEqualMethod, expression); |
| break; |
| |
| case LOGICAL_AND: |
| evaluateLogicalAndExpression(expression); |
| break; |
| |
| case LOGICAL_OR: |
| evaluateLogicalOrExpression(expression); |
| break; |
| |
| case BITWISE_AND: |
| evaluateBinaryExpression("and", expression); |
| break; |
| |
| case BITWISE_AND_EQUAL: |
| evaluateBinaryExpressionWithAssignment("and", expression); |
| break; |
| |
| case BITWISE_OR: |
| evaluateBinaryExpression("or", expression); |
| break; |
| |
| case BITWISE_OR_EQUAL: |
| evaluateBinaryExpressionWithAssignment("or", expression); |
| break; |
| |
| case BITWISE_XOR: |
| evaluateBinaryExpression("xor", expression); |
| break; |
| |
| case BITWISE_XOR_EQUAL: |
| evaluateBinaryExpressionWithAssignment("xor", expression); |
| break; |
| |
| case PLUS: |
| evaluateBinaryExpression("plus", expression); |
| break; |
| |
| case PLUS_EQUAL: |
| evaluateBinaryExpressionWithAssignment("plus", expression); |
| break; |
| |
| case MINUS: |
| evaluateBinaryExpression("minus", expression); |
| break; |
| |
| case MINUS_EQUAL: |
| evaluateBinaryExpressionWithAssignment("minus", expression); |
| break; |
| |
| case MULTIPLY: |
| evaluateBinaryExpression("multiply", expression); |
| break; |
| |
| case MULTIPLY_EQUAL: |
| evaluateBinaryExpressionWithAssignment("multiply", expression); |
| break; |
| |
| case DIVIDE: |
| evaluateBinaryExpression("div", expression); |
| break; |
| |
| case DIVIDE_EQUAL: |
| //SPG don't use divide since BigInteger implements directly |
| //and we want to dispatch through DefaultGroovyMethods to get a BigDecimal result |
| evaluateBinaryExpressionWithAssignment("div", expression); |
| break; |
| |
| case INTDIV: |
| evaluateBinaryExpression("intdiv", expression); |
| break; |
| |
| case INTDIV_EQUAL: |
| evaluateBinaryExpressionWithAssignment("intdiv", expression); |
| break; |
| |
| case MOD: |
| evaluateBinaryExpression("mod", expression); |
| break; |
| |
| case MOD_EQUAL: |
| evaluateBinaryExpressionWithAssignment("mod", expression); |
| break; |
| |
| case POWER: |
| evaluateBinaryExpression("power", expression); |
| break; |
| |
| case POWER_EQUAL: |
| evaluateBinaryExpressionWithAssignment("power", expression); |
| break; |
| |
| case ELVIS_EQUAL: |
| evaluateElvisEqual(expression); |
| break; |
| |
| case LEFT_SHIFT: |
| evaluateBinaryExpression("leftShift", expression); |
| break; |
| |
| case LEFT_SHIFT_EQUAL: |
| evaluateBinaryExpressionWithAssignment("leftShift", expression); |
| break; |
| |
| case RIGHT_SHIFT: |
| evaluateBinaryExpression("rightShift", expression); |
| break; |
| |
| case RIGHT_SHIFT_EQUAL: |
| evaluateBinaryExpressionWithAssignment("rightShift", expression); |
| break; |
| |
| case RIGHT_SHIFT_UNSIGNED: |
| evaluateBinaryExpression("rightShiftUnsigned", expression); |
| break; |
| |
| case RIGHT_SHIFT_UNSIGNED_EQUAL: |
| evaluateBinaryExpressionWithAssignment("rightShiftUnsigned", expression); |
| break; |
| |
| case KEYWORD_INSTANCEOF: |
| evaluateInstanceof(expression); |
| break; |
| |
| case COMPARE_NOT_INSTANCEOF: |
| evaluateNotInstanceof(expression); |
| break; |
| |
| case FIND_REGEX: |
| evaluateCompareExpression(findRegexMethod, expression); |
| break; |
| |
| case MATCH_REGEX: |
| evaluateCompareExpression(matchRegexMethod, expression); |
| break; |
| |
| case LEFT_SQUARE_BRACKET: |
| if (controller.getCompileStack().isLHS()) { |
| evaluateEqual(expression, false); |
| } else { |
| evaluateBinaryExpression("getAt", expression); |
| } |
| break; |
| |
| case KEYWORD_IN: |
| evaluateCompareExpression(isCaseMethod, expression); |
| break; |
| |
| case COMPARE_NOT_IN: |
| evaluateCompareExpression(isNotCaseMethod, expression); |
| break; |
| |
| case COMPARE_IDENTICAL: |
| evaluateCompareExpression(compareIdenticalMethod, expression); |
| break; |
| |
| case COMPARE_NOT_IDENTICAL: |
| evaluateCompareExpression(compareNotIdenticalMethod, expression); |
| break; |
| |
| default: |
| throw new GroovyBugError("Operation: " + expression.getOperation() + " not supported"); |
| } |
| } |
| |
| @Deprecated |
| protected void assignToArray(final Expression parent, final Expression receiver, final Expression index, final Expression rhsValueLoader) { |
| assignToArray(parent, receiver, index, rhsValueLoader, false); |
| } |
| |
| protected void assignToArray(final Expression parent, final Expression receiver, final Expression index, final Expression rhsValueLoader, final boolean safe) { |
| // let's replace this assignment to a subscript operator with a |
| // method call |
| // e.g. x[5] = 10 |
| // -> (x, [], 5), =, 10 |
| // -> methodCall(x, "putAt", [5, 10]) |
| ArgumentListExpression ae = new ArgumentListExpression(index,rhsValueLoader); |
| controller.getInvocationWriter().makeCall(parent, receiver, constX("putAt"), ae, InvocationWriter.invokeMethod, safe, false, false); |
| controller.getOperandStack().pop(); |
| // return value of assignment |
| rhsValueLoader.visit(controller.getAcg()); |
| } |
| |
| public void evaluateElvisEqual(final BinaryExpression expression) { |
| Token operation = expression.getOperation(); |
| BinaryExpression elvisAssignmentExpression = binX( |
| expression.getLeftExpression(), |
| Token.newSymbol(ASSIGN, operation.getStartLine(), operation.getStartColumn()), |
| new ElvisOperatorExpression(expression.getLeftExpression(), expression.getRightExpression()) |
| ); |
| this.evaluateEqual(elvisAssignmentExpression, false); |
| } |
| |
| public void evaluateEqual(final BinaryExpression expression, final boolean defineVariable) { |
| AsmClassGenerator acg = controller.getAcg(); |
| CompileStack compileStack = controller.getCompileStack(); |
| OperandStack operandStack = controller.getOperandStack(); |
| Expression leftExpression = expression.getLeftExpression(); |
| Expression rightExpression = expression.getRightExpression(); |
| boolean directAssignment = defineVariable && !(leftExpression instanceof TupleExpression); |
| |
| if (directAssignment && rightExpression instanceof EmptyExpression) { |
| VariableExpression ve = (VariableExpression) leftExpression; |
| BytecodeVariable var = compileStack.defineVariable(ve, controller.getTypeChooser().resolveType(ve, controller.getClassNode()), false); |
| operandStack.loadOrStoreVariable(var, false); |
| return; |
| } |
| // evaluate the RHS and store the result |
| // TODO: LHS has not been visited, it could be a variable in a closure and type chooser is not aware. |
| ClassNode lhsType = controller.getTypeChooser().resolveType(leftExpression, controller.getClassNode()); |
| if (rightExpression instanceof ListExpression && lhsType.isArray()) { |
| ListExpression list = (ListExpression) rightExpression; |
| ArrayExpression array = new ArrayExpression(lhsType.getComponentType(), list.getExpressions()); |
| array.setSourcePosition(list); |
| array.visit(acg); |
| } else if (rightExpression instanceof EmptyExpression) { |
| loadInitValue(leftExpression.getType()); // TODO: lhsType? |
| } else { |
| rightExpression.visit(acg); |
| } |
| |
| ClassNode rhsType = operandStack.getTopOperand(); |
| int rhsValueId; |
| |
| if (directAssignment) { |
| VariableExpression var = (VariableExpression) leftExpression; |
| if (var.isClosureSharedVariable() && ClassHelper.isPrimitiveType(rhsType)) { |
| // GROOVY-5570: if a closure shared variable is a primitive type, it must be boxed |
| rhsType = ClassHelper.getWrapper(rhsType); |
| operandStack.box(); |
| } |
| |
| // ensure we try to unbox null to cause a runtime NPE in case we assign |
| // null to a primitive typed variable, even if it is used only in boxed |
| // form as it is closure shared |
| if (var.isClosureSharedVariable() && ClassHelper.isPrimitiveType(var.getOriginType()) && isNullConstant(rightExpression)) { |
| operandStack.doGroovyCast(var.getOriginType()); |
| // these two are never reached in bytecode and only there |
| // to avoid verify errors and compiler infrastructure hazzle |
| operandStack.box(); |
| operandStack.doGroovyCast(lhsType); |
| } |
| // normal type transformation |
| if (!ClassHelper.isPrimitiveType(lhsType) && isNullConstant(rightExpression)) { |
| operandStack.replace(lhsType); |
| } else { |
| operandStack.doGroovyCast(lhsType); |
| } |
| rhsType = lhsType; |
| rhsValueId = compileStack.defineVariable(var, lhsType, true).getIndex(); |
| } else { |
| rhsValueId = compileStack.defineTemporaryVariable("$rhs", rhsType, true); |
| } |
| // TODO: if rhs is VariableSlotLoader already, then skip crating a new one |
| BytecodeExpression rhsValueLoader = new VariableSlotLoader(rhsType,rhsValueId,operandStack); |
| |
| // assignment for subscript |
| if (leftExpression instanceof BinaryExpression) { |
| BinaryExpression leftBinExpr = (BinaryExpression) leftExpression; |
| if (leftBinExpr.getOperation().getType() == LEFT_SQUARE_BRACKET) { |
| assignToArray(expression, leftBinExpr.getLeftExpression(), leftBinExpr.getRightExpression(), rhsValueLoader, leftBinExpr.isSafe()); |
| } |
| compileStack.removeVar(rhsValueId); |
| return; |
| } |
| |
| compileStack.pushLHS(true); |
| |
| if (leftExpression instanceof TupleExpression) { |
| // multiple declaration |
| TupleExpression tuple = (TupleExpression) leftExpression; |
| int i = 0; |
| for (Expression e : tuple.getExpressions()) { |
| callX(rhsValueLoader, "getAt", args(constX(i++))).visit(acg); |
| if (defineVariable) { |
| Variable v = (Variable) e; |
| operandStack.doGroovyCast(v); |
| compileStack.defineVariable(v, true); |
| operandStack.remove(1); |
| } else { |
| e.visit(acg); |
| } |
| } |
| } else if (defineVariable) { |
| // single declaration |
| rhsValueLoader.visit(acg); |
| operandStack.remove(1); |
| compileStack.popLHS(); |
| return; |
| } else { |
| // normal assignment |
| int mark = operandStack.getStackLength(); |
| rhsValueLoader.visit(acg); |
| leftExpression.visit(acg); |
| operandStack.remove(operandStack.getStackLength() - mark); |
| } |
| compileStack.popLHS(); |
| |
| // return value of assignment |
| rhsValueLoader.visit(acg); |
| compileStack.removeVar(rhsValueId); |
| } |
| |
| private void loadInitValue(final ClassNode type) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| if (ClassHelper.isPrimitiveType(type)) { |
| mv.visitLdcInsn(0); |
| } else { |
| mv.visitInsn(ACONST_NULL); |
| } |
| controller.getOperandStack().push(type); |
| } |
| |
| protected void evaluateCompareExpression(final MethodCaller compareMethod, final BinaryExpression expression) { |
| ClassNode classNode = controller.getClassNode(); |
| Expression leftExp = expression.getLeftExpression(); |
| Expression rightExp = expression.getRightExpression(); |
| ClassNode leftType = controller.getTypeChooser().resolveType(leftExp, classNode); |
| ClassNode rightType = controller.getTypeChooser().resolveType(rightExp, classNode); |
| |
| boolean done = false; |
| if (ClassHelper.isPrimitiveType(leftType) && ClassHelper.isPrimitiveType(rightType)) { |
| BinaryExpressionMultiTypeDispatcher helper = new BinaryExpressionMultiTypeDispatcher(controller); |
| done = helper.doPrimitiveCompare(leftType, rightType, expression); |
| } |
| if (!done) { |
| AsmClassGenerator acg = controller.getAcg(); |
| OperandStack operandStack = controller.getOperandStack(); |
| |
| leftExp.visit(acg); |
| operandStack.box(); |
| rightExp.visit(acg); |
| operandStack.box(); |
| |
| compareMethod.call(controller.getMethodVisitor()); |
| ClassNode resType = ClassHelper.boolean_TYPE; |
| if (compareMethod == findRegexMethod) { |
| resType = ClassHelper.OBJECT_TYPE; |
| } |
| operandStack.replace(resType, 2); |
| } |
| } |
| |
| private void evaluateCompareTo(final BinaryExpression expression) { |
| AsmClassGenerator acg = controller.getAcg(); |
| MethodVisitor mv = controller.getMethodVisitor(); |
| OperandStack operandStack = controller.getOperandStack(); |
| |
| expression.getLeftExpression().visit(acg); |
| operandStack.box(); |
| |
| // if the right hand side is a boolean expression, we need to autobox |
| expression.getRightExpression().visit(acg); |
| operandStack.box(); |
| |
| compareToMethod.call(mv); |
| operandStack.replace(ClassHelper.Integer_TYPE, 2); |
| } |
| |
| private void evaluateLogicalAndExpression(final BinaryExpression expression) { |
| AsmClassGenerator acg = controller.getAcg(); |
| MethodVisitor mv = controller.getMethodVisitor(); |
| OperandStack operandStack = controller.getOperandStack(); |
| |
| expression.getLeftExpression().visit(acg); |
| operandStack.doGroovyCast(ClassHelper.boolean_TYPE); |
| Label falseCase = operandStack.jump(IFEQ); |
| |
| expression.getRightExpression().visit(acg); |
| operandStack.doGroovyCast(ClassHelper.boolean_TYPE); |
| operandStack.jump(IFEQ, falseCase); |
| |
| ConstantExpression.PRIM_TRUE.visit(acg); |
| Label trueCase = new Label(); |
| mv.visitJumpInsn(GOTO, trueCase); |
| |
| mv.visitLabel(falseCase); |
| ConstantExpression.PRIM_FALSE.visit(acg); |
| |
| mv.visitLabel(trueCase); |
| operandStack.remove(1); // have to remove 1 because of the GOTO |
| } |
| |
| private void evaluateLogicalOrExpression(final BinaryExpression expression) { |
| AsmClassGenerator acg = controller.getAcg(); |
| MethodVisitor mv = controller.getMethodVisitor(); |
| OperandStack operandStack = controller.getOperandStack(); |
| |
| expression.getLeftExpression().visit(acg); |
| operandStack.doGroovyCast(ClassHelper.boolean_TYPE); |
| Label trueCase = operandStack.jump(IFNE); |
| |
| expression.getRightExpression().visit(acg); |
| operandStack.doGroovyCast(ClassHelper.boolean_TYPE); |
| Label falseCase = operandStack.jump(IFEQ); |
| |
| mv.visitLabel(trueCase); |
| ConstantExpression.PRIM_TRUE.visit(acg); |
| Label end = new Label(); |
| operandStack.jump(GOTO, end); |
| |
| mv.visitLabel(falseCase); |
| ConstantExpression.PRIM_FALSE.visit(acg); |
| |
| mv.visitLabel(end); |
| } |
| |
| protected void evaluateBinaryExpression(final String message, final BinaryExpression expression) { |
| CompileStack compileStack = controller.getCompileStack(); |
| // ensure VariableArguments are read, not stored |
| compileStack.pushLHS(false); |
| controller.getInvocationWriter().makeSingleArgumentCall( |
| expression.getLeftExpression(), |
| message, |
| expression.getRightExpression(), |
| expression.isSafe() |
| ); |
| compileStack.popLHS(); |
| } |
| |
| protected void evaluateArrayAssignmentWithOperator(final String method, final BinaryExpression expression, final BinaryExpression leftBinExpr) { |
| // e.g. x[a] += b |
| // to avoid loading x and a twice we transform the expression to use |
| // ExpressionAsVariableSlot |
| // -> subscript=a, receiver=x, receiver[subscript]+b, =, receiver[subscript] |
| // -> subscript=a, receiver=x, receiver#getAt(subscript)#plus(b), =, receiver#putAt(subscript) |
| // -> subscript=a, receiver=x, receiver#putAt(subscript, receiver#getAt(subscript)#plus(b)) |
| // the result of x[a] += b is x[a]+b, thus: |
| // -> subscript=a, receiver=x, receiver#putAt(subscript, ret=receiver#getAt(subscript)#plus(b)), ret |
| ExpressionAsVariableSlot subscript = new ExpressionAsVariableSlot(controller, leftBinExpr.getRightExpression(), "subscript"); |
| ExpressionAsVariableSlot receiver = new ExpressionAsVariableSlot(controller, leftBinExpr.getLeftExpression(), "receiver"); |
| MethodCallExpression getAt = callX(receiver, "getAt", args(subscript)); |
| MethodCallExpression operation = callX(getAt, method, expression.getRightExpression()); |
| ExpressionAsVariableSlot ret = new ExpressionAsVariableSlot(controller, operation, "ret"); |
| MethodCallExpression putAt = callX(receiver, "putAt", args(subscript, ret)); |
| |
| AsmClassGenerator acg = controller.getAcg(); |
| putAt.visit(acg); |
| OperandStack os = controller.getOperandStack(); |
| os.pop(); |
| os.load(ret.getType(), ret.getIndex()); |
| |
| CompileStack compileStack = controller.getCompileStack(); |
| compileStack.removeVar(ret.getIndex()); |
| compileStack.removeVar(subscript.getIndex()); |
| compileStack.removeVar(receiver.getIndex()); |
| } |
| |
| protected void evaluateBinaryExpressionWithAssignment(final String method, final BinaryExpression expression) { |
| Expression leftExpression = expression.getLeftExpression(); |
| if (leftExpression instanceof BinaryExpression) { |
| BinaryExpression bexp = (BinaryExpression) leftExpression; |
| if (bexp.getOperation().getType() == LEFT_SQUARE_BRACKET) { |
| evaluateArrayAssignmentWithOperator(method, expression, bexp); |
| return; |
| } |
| } |
| |
| evaluateBinaryExpression(method, expression); |
| |
| // br to leave a copy of rvalue on the stack; see also isPopRequired() |
| controller.getOperandStack().dup(); |
| controller.getCompileStack().pushLHS(true); |
| leftExpression.visit(controller.getAcg()); |
| controller.getCompileStack().popLHS(); |
| } |
| |
| private void evaluateInstanceof(final BinaryExpression expression) { |
| expression.getLeftExpression().visit(controller.getAcg()); |
| controller.getOperandStack().box(); |
| Expression rightExp = expression.getRightExpression(); |
| if (!(rightExp instanceof ClassExpression)) { |
| throw new RuntimeException("RHS of the instanceof keyword must be a class name, not: " + rightExp); |
| } |
| String classInternalName = BytecodeHelper.getClassInternalName(rightExp.getType()); |
| controller.getMethodVisitor().visitTypeInsn(INSTANCEOF, classInternalName); |
| controller.getOperandStack().replace(ClassHelper.boolean_TYPE); |
| } |
| |
| private void evaluateNotInstanceof(final BinaryExpression expression) { |
| unaryExpressionHelper.writeNotExpression( |
| new NotExpression( |
| new BinaryExpression( |
| expression.getLeftExpression(), |
| GeneralUtils.INSTANCEOF, |
| expression.getRightExpression() |
| ) |
| ) |
| ); |
| } |
| |
| private void evaluatePostfixMethod(final int op, final String method, final Expression expression, final Expression orig) { |
| CompileStack compileStack = controller.getCompileStack(); |
| OperandStack operandStack = controller.getOperandStack(); |
| |
| // load Expressions |
| VariableSlotLoader usesSubscript = loadWithSubscript(expression); |
| |
| // save copy for later |
| operandStack.dup(); |
| ClassNode expressionType = operandStack.getTopOperand(); |
| int tempIdx = compileStack.defineTemporaryVariable("postfix_" + method, expressionType, true); |
| |
| // execute method |
| execMethodAndStoreForSubscriptOperator(op, method, expression, usesSubscript, orig); |
| |
| // remove the result of the method call |
| operandStack.pop(); |
| |
| // reload saved value |
| operandStack.load(expressionType, tempIdx); |
| compileStack.removeVar(tempIdx); |
| if (usesSubscript != null) compileStack.removeVar(usesSubscript.getIndex()); |
| } |
| |
| public void evaluatePostfixMethod(final PostfixExpression expression) { |
| int op = expression.getOperation().getType(); |
| switch (op) { |
| case PLUS_PLUS: |
| evaluatePostfixMethod(op, "next", expression.getExpression(), expression); |
| break; |
| case MINUS_MINUS: |
| evaluatePostfixMethod(op, "previous", expression.getExpression(), expression); |
| break; |
| } |
| } |
| |
| public void evaluatePrefixMethod(final PrefixExpression expression) { |
| int type = expression.getOperation().getType(); |
| switch (type) { |
| case PLUS_PLUS: |
| evaluatePrefixMethod(type, "next", expression.getExpression(), expression); |
| break; |
| case MINUS_MINUS: |
| evaluatePrefixMethod(type, "previous", expression.getExpression(), expression); |
| break; |
| } |
| } |
| |
| private void evaluatePrefixMethod(final int op, final String method, final Expression expression, final Expression orig) { |
| // load expressions |
| VariableSlotLoader usesSubscript = loadWithSubscript(expression); |
| |
| // execute method |
| execMethodAndStoreForSubscriptOperator(op, method, expression, usesSubscript, orig); |
| |
| // new value is already on stack, so nothing to do here |
| if (usesSubscript != null) controller.getCompileStack().removeVar(usesSubscript.getIndex()); |
| } |
| |
| private VariableSlotLoader loadWithSubscript(final Expression expression) { |
| AsmClassGenerator acg = controller.getAcg(); |
| // if we have a BinaryExpression, check if it is with subscription |
| if (expression instanceof BinaryExpression) { |
| BinaryExpression bexp = (BinaryExpression) expression; |
| if (bexp.getOperation().getType() == LEFT_SQUARE_BRACKET) { |
| // right expression is the subscript expression |
| // we store the result of the subscription on the stack |
| Expression subscript = bexp.getRightExpression(); |
| subscript.visit(acg); |
| OperandStack operandStack = controller.getOperandStack(); |
| ClassNode subscriptType = operandStack.getTopOperand(); |
| if (subscriptType.isGenericsPlaceHolder() || GenericsUtils.hasPlaceHolders(subscriptType)) { |
| subscriptType = controller.getTypeChooser().resolveType(bexp, controller.getClassNode()); |
| } |
| int id = controller.getCompileStack().defineTemporaryVariable("$subscript", subscriptType, true); |
| VariableSlotLoader subscriptExpression = new VariableSlotLoader(subscriptType, id, operandStack); |
| BinaryExpression rewrite = binX(bexp.getLeftExpression(), bexp.getOperation(), subscriptExpression); |
| rewrite.copyNodeMetaData(bexp); |
| rewrite.setSourcePosition(bexp); |
| rewrite.visit(acg); |
| return subscriptExpression; |
| } |
| } |
| |
| // normal loading of expression |
| expression.visit(acg); |
| return null; |
| } |
| |
| private void execMethodAndStoreForSubscriptOperator(final int op, String method, final Expression expression, final VariableSlotLoader usesSubscript, final Expression orig) { |
| writePostOrPrefixMethod(op, method, expression, orig); |
| |
| // we need special code for arrays to store the result (like for a[1]++) |
| if (usesSubscript != null) { |
| BinaryExpression be = (BinaryExpression) expression; |
| CompileStack compileStack = controller.getCompileStack(); |
| OperandStack operandStack = controller.getOperandStack(); |
| ClassNode methodResultType = operandStack.getTopOperand(); |
| int resultIdx = compileStack.defineTemporaryVariable("postfix_" + method, methodResultType, true); |
| BytecodeExpression methodResultLoader = new VariableSlotLoader(methodResultType, resultIdx, operandStack); |
| |
| // execute the assignment, this will leave the right side (here the method call result) on the stack |
| assignToArray(be, be.getLeftExpression(), usesSubscript, methodResultLoader, be.isSafe()); |
| |
| compileStack.removeVar(resultIdx); |
| |
| } else if (expression instanceof VariableExpression || expression instanceof PropertyExpression || expression instanceof FieldExpression) { |
| // here we handle a++ and a.b++ |
| controller.getOperandStack().dup(); |
| controller.getCompileStack().pushLHS(true); |
| expression.visit(controller.getAcg()); |
| controller.getCompileStack().popLHS(); |
| } |
| // other cases don't need storing, so nothing to be done for them |
| } |
| |
| protected void writePostOrPrefixMethod(final int op, final String method, final Expression expression, final Expression orig) { |
| // at this point the receiver will be already on the stack |
| // in a[1]++ the method will be "++" aka "next" and the receiver a[1] |
| ClassNode exprType = controller.getTypeChooser().resolveType(expression, controller.getClassNode()); |
| Expression callSiteReceiverSwap = new BytecodeExpression(exprType) { |
| @Override |
| public void visit(MethodVisitor mv) { |
| OperandStack operandStack = controller.getOperandStack(); |
| // CallSite is normally not showing up on the |
| // operandStack, so we place a dummy here with same |
| // slot length. |
| operandStack.push(ClassHelper.OBJECT_TYPE); |
| // change (receiver,callsite) to (callsite,receiver) |
| operandStack.swap(); |
| |
| setType(operandStack.getTopOperand()); |
| |
| // no need to keep any of those on the operand stack |
| // after this expression is processed, the operand stack |
| // will contain callSiteReceiverSwap.getType() |
| operandStack.remove(2); |
| } |
| }; |
| // execute method |
| // this will load the callsite and the receiver normally in the wrong |
| // order since the receiver is already present, but before the callsite |
| // Therefore we use callSiteReceiverSwap to correct the order. |
| // After this call the JVM operand stack will contain the result of |
| // the method call... usually simply Object in operandStack |
| controller.getCallSiteWriter().makeCallSite( |
| callSiteReceiverSwap, |
| method, |
| MethodCallExpression.NO_ARGUMENTS, |
| false, false, false, false); |
| // now rhs is completely done and we need only to store. In a[1]++ this |
| // would be a.getAt(1).next() for the rhs, "lhs" code is a.putAt(1, rhs) |
| } |
| |
| private void evaluateElvisOperatorExpression(final ElvisOperatorExpression expression) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| CompileStack compileStack = controller.getCompileStack(); |
| OperandStack operandStack = controller.getOperandStack(); |
| TypeChooser typeChooser = controller.getTypeChooser(); |
| |
| Expression boolPart = expression.getBooleanExpression().getExpression(); |
| Expression falsePart = expression.getFalseExpression(); |
| |
| ClassNode truePartType = typeChooser.resolveType(boolPart, controller.getClassNode()); |
| ClassNode falsePartType = typeChooser.resolveType(falsePart, controller.getClassNode()); |
| ClassNode common = WideningCategories.lowestUpperBound(truePartType, falsePartType); |
| |
| // x?:y is equal to x?x:y, which evals to |
| // var t=x; boolean(t)?t:y |
| // first we load x, dup it, convert the dupped to boolean, then |
| // jump depending on the value. For true we are done, for false we |
| // have to load y, thus we first remove x and then load y. |
| // But since x and y may have different stack lengths, this cannot work |
| // Thus we have to have to do the following: |
| // Be X the type of x, Y the type of y and S the common supertype of |
| // X and Y, then we have to see x?:y as |
| // var t=x;boolean(t)?S(t):S(y) |
| // so we load x, dup it, store the value in a local variable (t), then |
| // do boolean conversion. In the true part load t and cast it to S, |
| // in the false part load y and cast y to S |
| |
| // load x, dup it, store one in $t and cast the remaining one to boolean |
| int mark = operandStack.getStackLength(); |
| boolPart.visit(controller.getAcg()); |
| operandStack.dup(); |
| if (ClassHelper.isPrimitiveType(truePartType) && !ClassHelper.isPrimitiveType(operandStack.getTopOperand())) { |
| truePartType = ClassHelper.getWrapper(truePartType); |
| } |
| int retValueId = compileStack.defineTemporaryVariable("$t", truePartType, true); |
| operandStack.castToBool(mark, true); |
| |
| Label l0 = operandStack.jump(IFEQ); |
| // true part: load $t and cast to S |
| operandStack.load(truePartType, retValueId); |
| operandStack.doGroovyCast(common); |
| Label l1 = new Label(); |
| mv.visitJumpInsn(GOTO, l1); |
| |
| // false part: load false expression and cast to S |
| mv.visitLabel(l0); |
| falsePart.visit(controller.getAcg()); |
| operandStack.doGroovyCast(common); |
| |
| // finish and cleanup |
| mv.visitLabel(l1); |
| compileStack.removeVar(retValueId); |
| operandStack.replace(common, 2); |
| } |
| |
| private void evaluateNormalTernary(final TernaryExpression expression) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| TypeChooser typeChooser = controller.getTypeChooser(); |
| OperandStack operandStack = controller.getOperandStack(); |
| |
| Expression boolPart = expression.getBooleanExpression(); |
| Expression truePart = expression.getTrueExpression(); |
| Expression falsePart = expression.getFalseExpression(); |
| |
| ClassNode truePartType = typeChooser.resolveType(truePart, controller.getClassNode()); |
| ClassNode falsePartType = typeChooser.resolveType(falsePart, controller.getClassNode()); |
| ClassNode common = WideningCategories.lowestUpperBound(truePartType, falsePartType); |
| |
| // we compile b?x:y as |
| // boolean(b)?S(x):S(y), S = common super type of x,y |
| // so we load b, do boolean conversion. |
| // In the true part load x and cast it to S, |
| // in the false part load y and cast y to S |
| |
| // load b and convert to boolean |
| int mark = operandStack.getStackLength(); |
| boolPart.visit(controller.getAcg()); |
| operandStack.castToBool(mark, true); |
| |
| Label l0 = operandStack.jump(IFEQ); |
| // true part: load x and cast to S |
| truePart.visit(controller.getAcg()); |
| operandStack.doGroovyCast(common); |
| Label l1 = new Label(); |
| mv.visitJumpInsn(GOTO, l1); |
| |
| // false part: load y and cast to S |
| mv.visitLabel(l0); |
| falsePart.visit(controller.getAcg()); |
| operandStack.doGroovyCast(common); |
| |
| // finish and cleanup |
| mv.visitLabel(l1); |
| operandStack.replace(common, 2); |
| } |
| |
| public void evaluateTernary(final TernaryExpression expression) { |
| if (expression instanceof ElvisOperatorExpression) { |
| evaluateElvisOperatorExpression((ElvisOperatorExpression) expression); |
| } else { |
| evaluateNormalTernary(expression); |
| } |
| } |
| } |