public boolean addGeneratedClosureConstructorCall(ConstructorCallExpression call) { ClassNode classNode = controller.getClassNode(); if (!classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) return false; AsmClassGenerator acg = controller.getAcg(); OperandStack operandStack = controller.getOperandStack(); MethodVisitor mv = controller.getMethodVisitor(); mv.visitVarInsn(ALOAD, 0); ClassNode callNode = classNode.getSuperClass(); TupleExpression arguments = (TupleExpression) call.getArguments(); if (arguments.getExpressions().size() != 2) throw new GroovyBugError( "expected 2 arguments for closure constructor super call, but got" + arguments.getExpressions().size()); arguments.getExpression(0).visit(acg); operandStack.box(); arguments.getExpression(1).visit(acg); operandStack.box(); // TODO: replace with normal String, p not needed Parameter p = new Parameter(ClassHelper.OBJECT_TYPE, "_p"); String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, new Parameter[] {p, p}); mv.visitMethodInsn( INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "<init>", descriptor, false); operandStack.remove(2); return true; }
/** * ensure last marked parameter on the stack is a primitive boolean if mark==stack size, we assume * an empty expression or statement. was used and we will use the value given in emptyDefault as * boolean if mark==stack.size()-1 the top element will be cast to boolean using Groovy truth. In * other cases we throw a GroovyBugError */ public void castToBool(int mark, boolean emptyDefault) { int size = stack.size(); MethodVisitor mv = controller.getMethodVisitor(); if (mark == size) { // no element, so use emptyDefault if (emptyDefault) { mv.visitIntInsn(BIPUSH, 1); } else { mv.visitIntInsn(BIPUSH, 0); } stack.add(null); } else if (mark == stack.size() - 1) { ClassNode last = stack.get(size - 1); // nothing to do in that case if (last == ClassHelper.boolean_TYPE) return; // not a primitive type, so call booleanUnbox if (!ClassHelper.isPrimitiveType(last)) { controller.getInvocationWriter().castNonPrimitiveToBool(last); } else { primitive2b(mv, last); } } else { throw new GroovyBugError( "operand stack contains " + stack.size() + " elements, but we expected only " + mark); } stack.set(mark, ClassHelper.boolean_TYPE); }
public void loadOrStoreVariable(BytecodeVariable variable, boolean useReferenceDirectly) { CompileStack compileStack = controller.getCompileStack(); if (compileStack.isLHS()) { storeVar(variable); } else { MethodVisitor mv = controller.getMethodVisitor(); int idx = variable.getIndex(); ClassNode type = variable.getType(); if (variable.isHolder()) { mv.visitVarInsn(ALOAD, idx); if (!useReferenceDirectly) { mv.visitMethodInsn( INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;", false); BytecodeHelper.doCast(mv, type); push(type); } else { push(ClassHelper.REFERENCE_TYPE); } } else { load(type, idx); } } }
private void throwExceptionForNoStackElement(int size, ClassNode targetType, boolean coerce) { if (size > 0) return; StringBuilder sb = new StringBuilder(); sb.append("Internal compiler error while compiling ") .append(controller.getSourceUnit().getName()) .append("\n"); MethodNode methodNode = controller.getMethodNode(); if (methodNode != null) { sb.append("Method: "); sb.append(methodNode); sb.append("\n"); } ConstructorNode constructorNode = controller.getConstructorNode(); if (constructorNode != null) { sb.append("Constructor: "); sb.append(methodNode); sb.append("\n"); } sb.append("Line ").append(controller.getLineNumber()).append(","); sb.append(" expecting ") .append(coerce ? "coercion" : "casting") .append(" to ") .append(targetType.toString(false)); sb.append(" but operand stack is empty"); throw new ArrayIndexOutOfBoundsException(sb.toString()); }
private VariableSlotLoader loadWithSubscript(Expression expression) { final OperandStack operandStack = controller.getOperandStack(); // if we have a BinaryExpression, let us check if it is with // subscription if (expression instanceof BinaryExpression) { BinaryExpression be = (BinaryExpression) expression; if (be.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) { // right expression is the subscript expression // we store the result of the subscription on the stack Expression subscript = be.getRightExpression(); subscript.visit(controller.getAcg()); ClassNode subscriptType = operandStack.getTopOperand(); int id = controller.getCompileStack().defineTemporaryVariable("$subscript", subscriptType, true); VariableSlotLoader subscriptExpression = new VariableSlotLoader(subscriptType, id, operandStack); // do modified visit BinaryExpression newBe = new BinaryExpression(be.getLeftExpression(), be.getOperation(), subscriptExpression); newBe.copyNodeMetaData(be); newBe.setSourcePosition(be); newBe.visit(controller.getAcg()); return subscriptExpression; } } // normal loading of expression expression.visit(controller.getAcg()); return null; }
protected void evaluateCompareExpression( MethodCaller compareMethod, BinaryExpression expression) { Expression leftExp = expression.getLeftExpression(); TypeChooser typeChooser = controller.getTypeChooser(); ClassNode cn = controller.getClassNode(); ClassNode leftType = typeChooser.resolveType(leftExp, cn); Expression rightExp = expression.getRightExpression(); ClassNode rightType = typeChooser.resolveType(rightExp, cn); boolean done = false; if (ClassHelper.isPrimitiveType(leftType) && ClassHelper.isPrimitiveType(rightType)) { BinaryExpressionMultiTypeDispatcher helper = new BinaryExpressionMultiTypeDispatcher(getController()); 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 loadInitValue(ClassNode type) { MethodVisitor mv = controller.getMethodVisitor(); if (ClassHelper.isPrimitiveType(type)) { mv.visitLdcInsn(0); } else { mv.visitInsn(ACONST_NULL); } controller.getOperandStack().push(type); }
protected void evaluateBinaryExpression(String message, BinaryExpression binExp) { CompileStack compileStack = controller.getCompileStack(); Expression receiver = binExp.getLeftExpression(); Expression arguments = binExp.getRightExpression(); // ensure VariableArguments are read, not stored compileStack.pushLHS(false); controller.getInvocationWriter().makeSingleArgumentCall(receiver, message, arguments); compileStack.popLHS(); }
private void loadThis() { MethodVisitor mv = controller.getMethodVisitor(); mv.visitVarInsn(ALOAD, 0); if (controller.isInClosure()) { mv.visitMethodInsn( INVOKEVIRTUAL, "groovy/lang/Closure", "getThisObject", "()Ljava/lang/Object;", false); controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); } else { controller.getOperandStack().push(controller.getClassNode()); } }
private void evaluateElvisOperatorExpression(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); controller.getOperandStack().replace(common, 2); }
private void doConvertAndCast(ClassNode targetType, boolean coerce) { int size = stack.size(); throwExceptionForNoStackElement(size, targetType, coerce); ClassNode top = stack.get(size - 1); targetType = targetType.redirect(); if (targetType == top) return; if (coerce) { controller.getInvocationWriter().coerce(top, targetType); return; } boolean primTarget = ClassHelper.isPrimitiveType(targetType); boolean primTop = ClassHelper.isPrimitiveType(top); if (primTop && primTarget) { // here we box and unbox to get the goal type if (convertPrimitive(top, targetType)) { replace(targetType); return; } box(); } else if (primTop) { // top is primitive, target is not // so box and do groovy cast controller.getInvocationWriter().castToNonPrimitiveIfNecessary(top, targetType); } else if (primTarget) { // top is not primitive so unbox // leave that BH#doCast later } else { controller.getInvocationWriter().castToNonPrimitiveIfNecessary(top, targetType); } MethodVisitor mv = controller.getMethodVisitor(); if (primTarget && !ClassHelper.boolean_TYPE.equals(targetType) && !primTop && ClassHelper.getWrapper(targetType).equals(top)) { BytecodeHelper.doCastToPrimitive(mv, top, targetType); } else { top = stack.get(size - 1); if (!WideningCategories.implementsInterfaceOrSubclassOf(top, targetType)) { BytecodeHelper.doCast(mv, targetType); } } replace(targetType); }
private ClassNode popWithMessage(int last) { try { return stack.remove(last); } catch (ArrayIndexOutOfBoundsException ai) { String method = controller.getMethodNode() == null ? controller.getConstructorNode().getTypeDescriptor() : controller.getMethodNode().getTypeDescriptor(); throw new GroovyBugError( "Error while popping argument from operand stack tracker in class " + controller.getClassName() + " method " + method + "."); } }
/** swap two top level operands */ public void swap() { MethodVisitor mv = controller.getMethodVisitor(); int size = stack.size(); ClassNode b = stack.get(size - 1); ClassNode a = stack.get(size - 2); // dup_x1: --- // dup_x2: aab -> baab // dup2_x1: abb -> bbabb // dup2_x2: aabb -> bbaabb // b = top element, a = element under b // top element at right if (isTwoSlotType(a)) { // aa if (isTwoSlotType(b)) { // aabb // aabb -> bbaa mv.visitInsn(DUP2_X2); // bbaabb mv.visitInsn(POP2); // bbaa } else { // aab -> baa mv.visitInsn(DUP_X2); // baab mv.visitInsn(POP); // baa } } else { // a if (isTwoSlotType(b)) { // abb // abb -> bba mv.visitInsn(DUP2_X1); // bbabb mv.visitInsn(POP2); // bba } else { // ab -> ba mv.visitInsn(SWAP); } } stack.set(size - 1, a); stack.set(size - 2, b); }
public void storeVar(BytecodeVariable variable) { MethodVisitor mv = controller.getMethodVisitor(); int idx = variable.getIndex(); ClassNode type = variable.getType(); // value is on stack if (variable.isHolder()) { doGroovyCast(type); box(); mv.visitVarInsn(ALOAD, idx); mv.visitTypeInsn(CHECKCAST, "groovy/lang/Reference"); mv.visitInsn(SWAP); mv.visitMethodInsn( INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V", false); } else { doGroovyCast(type); if (type == ClassHelper.double_TYPE) { mv.visitVarInsn(DSTORE, idx); } else if (type == ClassHelper.float_TYPE) { mv.visitVarInsn(FSTORE, idx); } else if (type == ClassHelper.long_TYPE) { mv.visitVarInsn(LSTORE, idx); } else if (type == ClassHelper.boolean_TYPE || type == ClassHelper.char_TYPE || type == ClassHelper.byte_TYPE || type == ClassHelper.int_TYPE || type == ClassHelper.short_TYPE) { mv.visitVarInsn(ISTORE, idx); } else { mv.visitVarInsn(ASTORE, idx); } } // remove RHS value from operand stack remove(1); }
private void evaluateCompareTo(BinaryExpression expression) { Expression leftExpression = expression.getLeftExpression(); AsmClassGenerator acg = controller.getAcg(); OperandStack operandStack = controller.getOperandStack(); leftExpression.visit(acg); operandStack.box(); // if the right hand side is a boolean expression, we need to autobox Expression rightExpression = expression.getRightExpression(); rightExpression.visit(acg); operandStack.box(); compareToMethod.call(controller.getMethodVisitor()); operandStack.replace(ClassHelper.Integer_TYPE, 2); }
protected void writePostOrPrefixMethod( int op, String method, Expression expression, Expression orig) { final OperandStack operandStack = controller.getOperandStack(); // 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 BEType = controller.getTypeChooser().resolveType(expression, controller.getClassNode()); Expression callSiteReceiverSwap = new BytecodeExpression(BEType) { @Override public void visit(MethodVisitor mv) { // 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 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) }
public static void loadReference(String name, WriterController controller) { CompileStack compileStack = controller.getCompileStack(); MethodVisitor mv = controller.getMethodVisitor(); ClassNode classNode = controller.getClassNode(); AsmClassGenerator acg = controller.getAcg(); // compileStack.containsVariable(name) means to ask if the variable is already declared // compileStack.getScope().isReferencedClassVariable(name) means to ask if the variable is a // field // If it is no field and is not yet declared, then it is either a closure shared variable or // an already declared variable. if (!compileStack.containsVariable(name) && compileStack.getScope().isReferencedClassVariable(name)) { acg.visitFieldExpression(new FieldExpression(classNode.getDeclaredField(name))); } else { BytecodeVariable v = compileStack.getVariable(name, !classNodeUsesReferences(controller.getClassNode())); if (v == null) { // variable is not on stack because we are // inside a nested Closure and this variable // was not used before // then load it from the Closure field FieldNode field = classNode.getDeclaredField(name); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn( GETFIELD, controller.getInternalClassName(), name, BytecodeHelper.getTypeDescription(field.getType())); } else { mv.visitVarInsn(ALOAD, v.getIndex()); } controller.getOperandStack().push(ClassHelper.REFERENCE_TYPE); } }
private void evaluateInstanceof(BinaryExpression expression) { OperandStack operandStack = controller.getOperandStack(); expression.getLeftExpression().visit(controller.getAcg()); operandStack.box(); Expression rightExp = expression.getRightExpression(); ClassNode classType; if (rightExp instanceof ClassExpression) { ClassExpression classExp = (ClassExpression) rightExp; classType = classExp.getType(); } else { throw new RuntimeException( "Right hand side of the instanceof keyword must be a class name, not: " + rightExp); } String classInternalName = BytecodeHelper.getClassInternalName(classType); controller.getMethodVisitor().visitTypeInsn(INSTANCEOF, classInternalName); operandStack.replace(ClassHelper.boolean_TYPE); }
/** replace top level element with new element of given type */ public void replace(ClassNode type) { int size = stack.size(); try { if (size == 0) throw new ArrayIndexOutOfBoundsException("size==0"); } catch (ArrayIndexOutOfBoundsException ai) { System.err.println("index problem in " + controller.getSourceUnit().getName()); throw ai; } stack.set(size - 1, type); }
private void evaluatePrefixMethod(int op, String method, Expression expression, 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()); }
public ClassNode getTopOperand() { int size = stack.size(); try { if (size == 0) throw new ArrayIndexOutOfBoundsException("size==0"); } catch (ArrayIndexOutOfBoundsException ai) { System.err.println("index problem in " + controller.getSourceUnit().getName()); throw ai; } return stack.get(size - 1); }
/** duplicate top element */ public void dup() { ClassNode type = getTopOperand(); stack.add(type); MethodVisitor mv = controller.getMethodVisitor(); if (type == ClassHelper.double_TYPE || type == ClassHelper.long_TYPE) { mv.visitInsn(DUP2); } else { mv.visitInsn(DUP); } }
public void pushDynamicName(Expression name) { if (name instanceof ConstantExpression) { ConstantExpression ce = (ConstantExpression) name; Object value = ce.getValue(); if (value instanceof String) { pushConstant(ce); return; } } new CastExpression(ClassHelper.STRING_TYPE, name).visit(controller.getAcg()); }
public ClassNode getOrAddClosureClass(ClosureExpression expression, int mods) { ClassNode closureClass = closureClassMap.get(expression); if (closureClass == null) { closureClass = createClosureClass(expression, mods); closureClassMap.put(expression, closureClass); controller.getAcg().addInnerClass(closureClass); closureClass.addInterface(ClassHelper.GENERATED_CLOSURE_Type); closureClass.putNodeMetaData(WriterControllerFactory.class, factory); } return closureClass; }
public ClassNode box() { MethodVisitor mv = controller.getMethodVisitor(); int size = stack.size(); ClassNode type = stack.get(size - 1); if (ClassHelper.isPrimitiveType(type) && ClassHelper.VOID_TYPE != type) { ClassNode wrapper = ClassHelper.getWrapper(type); BytecodeHelper.doCastToWrappedType(mv, type, wrapper); type = wrapper; } // else nothing to box stack.set(size - 1, type); return type; }
public void writeClosure(ClosureExpression expression) { CompileStack compileStack = controller.getCompileStack(); MethodVisitor mv = controller.getMethodVisitor(); ClassNode classNode = controller.getClassNode(); AsmClassGenerator acg = controller.getAcg(); // generate closure as public class to make sure it can be properly invoked by classes of the // Groovy runtime without circumventing JVM access checks (see CachedMethod for example). ClassNode closureClass = getOrAddClosureClass(expression, ACC_PUBLIC); String closureClassinternalName = BytecodeHelper.getClassInternalName(closureClass); List constructors = closureClass.getDeclaredConstructors(); ConstructorNode node = (ConstructorNode) constructors.get(0); Parameter[] localVariableParams = node.getParameters(); mv.visitTypeInsn(NEW, closureClassinternalName); mv.visitInsn(DUP); if (controller.isStaticMethod() || compileStack.isInSpecialConstructorCall()) { (new ClassExpression(classNode)).visit(acg); (new ClassExpression(controller.getOutermostClass())).visit(acg); } else { mv.visitVarInsn(ALOAD, 0); controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); loadThis(); } // now let's load the various parameters we're passing // we start at index 2 because the first variable we pass // is the owner instance and at this point it is already // on the stack for (int i = 2; i < localVariableParams.length; i++) { Parameter param = localVariableParams[i]; String name = param.getName(); loadReference(name, controller); if (param.getNodeMetaData(ClosureWriter.UseExistingReference.class) == null) { param.setNodeMetaData(ClosureWriter.UseExistingReference.class, Boolean.TRUE); } } // we may need to pass in some other constructors // cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "<init>", prototype + ")V"); mv.visitMethodInsn( INVOKESPECIAL, closureClassinternalName, "<init>", BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, localVariableParams), false); controller.getOperandStack().replace(ClassHelper.CLOSURE_TYPE, localVariableParams.length); }
public void popDownTo(int elements) { int last = stack.size(); MethodVisitor mv = controller.getMethodVisitor(); while (last > elements) { last--; ClassNode element = popWithMessage(last); if (isTwoSlotType(element)) { mv.visitInsn(POP2); } else { mv.visitInsn(POP); } } }
protected void assignToArray( Expression parent, Expression receiver, Expression index, Expression rhsValueLoader) { // 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, new ConstantExpression("putAt"), ae, InvocationWriter.invokeMethod, false, false, false); controller.getOperandStack().pop(); // return value of assignment rhsValueLoader.visit(controller.getAcg()); }
private void evaluateLogicalAndExpression(BinaryExpression expression) { MethodVisitor mv = controller.getMethodVisitor(); AsmClassGenerator acg = controller.getAcg(); 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 }
protected void evaluateBinaryExpressionWithAssignment( String method, BinaryExpression expression) { Expression leftExpression = expression.getLeftExpression(); AsmClassGenerator acg = controller.getAcg(); OperandStack operandStack = controller.getOperandStack(); if (leftExpression instanceof BinaryExpression) { BinaryExpression leftBinExpr = (BinaryExpression) leftExpression; if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) { evaluateArrayAssignmentWithOperator(method, expression, leftBinExpr); return; } } evaluateBinaryExpression(method, expression); // br to leave a copy of rvalue on the stack. see also isPopRequired() operandStack.dup(); controller.getCompileStack().pushLHS(true); leftExpression.visit(acg); controller.getCompileStack().popLHS(); }