| /* |
| * 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.ConstructorNode; |
| import org.codehaus.groovy.ast.MethodNode; |
| import org.codehaus.groovy.ast.Variable; |
| import org.codehaus.groovy.ast.expr.CastExpression; |
| import org.codehaus.groovy.ast.expr.ConstantExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.tools.WideningCategories; |
| import org.codehaus.groovy.classgen.ClassGeneratorException; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static org.objectweb.asm.Opcodes.ACONST_NULL; |
| import static org.objectweb.asm.Opcodes.ALOAD; |
| import static org.objectweb.asm.Opcodes.BIPUSH; |
| import static org.objectweb.asm.Opcodes.CHECKCAST; |
| import static org.objectweb.asm.Opcodes.D2F; |
| import static org.objectweb.asm.Opcodes.D2I; |
| import static org.objectweb.asm.Opcodes.D2L; |
| import static org.objectweb.asm.Opcodes.DCONST_0; |
| import static org.objectweb.asm.Opcodes.DCONST_1; |
| import static org.objectweb.asm.Opcodes.DUP; |
| import static org.objectweb.asm.Opcodes.DUP2; |
| import static org.objectweb.asm.Opcodes.DUP2_X1; |
| import static org.objectweb.asm.Opcodes.DUP2_X2; |
| import static org.objectweb.asm.Opcodes.DUP_X2; |
| import static org.objectweb.asm.Opcodes.F2D; |
| import static org.objectweb.asm.Opcodes.F2I; |
| import static org.objectweb.asm.Opcodes.F2L; |
| import static org.objectweb.asm.Opcodes.FCONST_0; |
| import static org.objectweb.asm.Opcodes.FCONST_1; |
| import static org.objectweb.asm.Opcodes.FCONST_2; |
| import static org.objectweb.asm.Opcodes.GETSTATIC; |
| import static org.objectweb.asm.Opcodes.I2B; |
| import static org.objectweb.asm.Opcodes.I2C; |
| import static org.objectweb.asm.Opcodes.I2D; |
| import static org.objectweb.asm.Opcodes.I2F; |
| import static org.objectweb.asm.Opcodes.I2L; |
| import static org.objectweb.asm.Opcodes.I2S; |
| import static org.objectweb.asm.Opcodes.ICONST_0; |
| import static org.objectweb.asm.Opcodes.ICONST_1; |
| import static org.objectweb.asm.Opcodes.INVOKESPECIAL; |
| import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; |
| import static org.objectweb.asm.Opcodes.L2D; |
| import static org.objectweb.asm.Opcodes.L2F; |
| import static org.objectweb.asm.Opcodes.L2I; |
| import static org.objectweb.asm.Opcodes.LCONST_0; |
| import static org.objectweb.asm.Opcodes.LCONST_1; |
| import static org.objectweb.asm.Opcodes.NEW; |
| import static org.objectweb.asm.Opcodes.POP; |
| import static org.objectweb.asm.Opcodes.POP2; |
| import static org.objectweb.asm.Opcodes.SWAP; |
| |
| public class OperandStack { |
| |
| private final WriterController controller; |
| private final List<ClassNode> stack = new ArrayList<ClassNode>(); |
| |
| public OperandStack(WriterController wc) { |
| this.controller = wc; |
| } |
| |
| public int getStackLength() { |
| return stack.size(); |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| 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 + "."); |
| } |
| } |
| |
| /** |
| * returns true for long and double |
| */ |
| private static boolean isTwoSlotType(ClassNode type) { |
| return type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE; |
| } |
| |
| /** |
| * 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 == 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 { |
| BytecodeHelper.convertPrimitiveToBoolean(mv, last); |
| } |
| } else { |
| throw new GroovyBugError( |
| "operand stack contains " + size + |
| " elements, but we expected only " + mark |
| ); |
| } |
| stack.set(mark, ClassHelper.boolean_TYPE); |
| } |
| |
| /** |
| * remove operand stack top element using bytecode pop |
| */ |
| public void pop() { |
| popDownTo(stack.size()-1); |
| } |
| |
| public Label jump(int ifIns) { |
| Label label = new Label(); |
| jump(ifIns,label); |
| return label; |
| } |
| |
| public void jump(int ifIns, Label label) { |
| controller.getMethodVisitor().visitJumpInsn(ifIns, label); |
| // remove the boolean from the operand stack tracker |
| remove(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 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; |
| } |
| |
| /** |
| * Remove amount elements from the operand stack, without using pop. |
| * For example after a method invocation |
| */ |
| public void remove(int amount) { |
| int size = stack.size(); |
| for (int i=size-1; i>size-1-amount; i--) { |
| popWithMessage(i); |
| } |
| } |
| |
| /** |
| * push operand on stack |
| */ |
| public void push(ClassNode type) { |
| stack.add(type); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * replace top level element with new element of given type |
| */ |
| public void replace(ClassNode type) { |
| int size = ensureStackNotEmpty(stack); |
| stack.set(size - 1, type); |
| } |
| |
| private int ensureStackNotEmpty(List<ClassNode> stack) { |
| 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 size; |
| } |
| |
| /** |
| * replace n top level elements with new element of given type |
| */ |
| public void replace(ClassNode type, int n) { |
| remove(n); |
| push(type); |
| } |
| |
| /** |
| * do Groovy cast for top level element |
| */ |
| public void doGroovyCast(ClassNode targetType) { |
| doConvertAndCast(targetType,false); |
| } |
| |
| public void doGroovyCast(Variable v) { |
| ClassNode targetType = v.getOriginType(); |
| doConvertAndCast(targetType,false); |
| } |
| |
| public void doAsType(ClassNode targetType) { |
| doConvertAndCast(targetType,true); |
| } |
| |
| 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 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 (primTarget) { |
| // top is not primitive so unbox |
| // leave that BH#doCast later |
| } else { |
| // top might be primitive, target is not |
| // so let invocation writer box if needed and do groovy cast otherwise |
| 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 boolean convertFromInt(ClassNode target) { |
| int convertCode; |
| if (target==ClassHelper.char_TYPE){ |
| convertCode = I2C; |
| } else if (target==ClassHelper.byte_TYPE){ |
| convertCode = I2B; |
| } else if (target==ClassHelper.short_TYPE){ |
| convertCode = I2S; |
| } else if (target==ClassHelper.long_TYPE){ |
| convertCode = I2L; |
| } else if (target==ClassHelper.float_TYPE){ |
| convertCode = I2F; |
| } else if (target==ClassHelper.double_TYPE){ |
| convertCode = I2D; |
| } else { |
| return false; |
| } |
| controller.getMethodVisitor().visitInsn(convertCode); |
| return true; |
| } |
| |
| private boolean convertFromLong(ClassNode target) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| if (target==ClassHelper.int_TYPE){ |
| mv.visitInsn(L2I); |
| return true; |
| } else if ( target==ClassHelper.char_TYPE || |
| target==ClassHelper.byte_TYPE || |
| target==ClassHelper.short_TYPE) |
| { |
| mv.visitInsn(L2I); |
| return convertFromInt(target); |
| } else if (target==ClassHelper.double_TYPE){ |
| mv.visitInsn(L2D); |
| return true; |
| } else if (target==ClassHelper.float_TYPE){ |
| mv.visitInsn(L2F); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean convertFromDouble(ClassNode target) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| if (target==ClassHelper.int_TYPE){ |
| mv.visitInsn(D2I); |
| return true; |
| } else if ( target==ClassHelper.char_TYPE || |
| target==ClassHelper.byte_TYPE || |
| target==ClassHelper.short_TYPE) |
| { |
| mv.visitInsn(D2I); |
| return convertFromInt(target); |
| } else if (target==ClassHelper.long_TYPE){ |
| mv.visitInsn(D2L); |
| return true; |
| } else if (target==ClassHelper.float_TYPE){ |
| mv.visitInsn(D2F); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean convertFromFloat(ClassNode target) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| if (target==ClassHelper.int_TYPE){ |
| mv.visitInsn(F2I); |
| return true; |
| } else if ( target==ClassHelper.char_TYPE || |
| target==ClassHelper.byte_TYPE || |
| target==ClassHelper.short_TYPE) |
| { |
| mv.visitInsn(F2I); |
| return convertFromInt(target); |
| } else if (target==ClassHelper.long_TYPE){ |
| mv.visitInsn(F2L); |
| return true; |
| } else if (target==ClassHelper.double_TYPE){ |
| mv.visitInsn(F2D); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean convertPrimitive(ClassNode top, ClassNode target) { |
| if (top==target) return true; |
| if (top==ClassHelper.int_TYPE) { |
| return convertFromInt(target); |
| } else if ( top==ClassHelper.char_TYPE || |
| top==ClassHelper.byte_TYPE || |
| top==ClassHelper.short_TYPE) |
| { |
| return target == ClassHelper.int_TYPE || convertFromInt(target); |
| } else if ( top==ClassHelper.float_TYPE) { |
| return convertFromFloat(target); |
| } else if ( top==ClassHelper.double_TYPE) { |
| return convertFromDouble(target); |
| } else if ( top==ClassHelper.long_TYPE) { |
| return convertFromLong(target); |
| } |
| return false; |
| } |
| |
| /** |
| * load the constant on the operand stack. |
| */ |
| public void pushConstant(ConstantExpression expression) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| Object value = expression.getValue(); |
| ClassNode origType = expression.getType().redirect(); |
| ClassNode type = ClassHelper.getUnwrapper(origType); |
| boolean boxing = origType!=type; |
| boolean asPrimitive = boxing || ClassHelper.isPrimitiveType(type); |
| |
| if (value == null) { |
| mv.visitInsn(ACONST_NULL); |
| } else if (boxing && value instanceof Boolean) { |
| // special path for boxed boolean |
| Boolean bool = (Boolean) value; |
| String text = bool ? "TRUE" : "FALSE"; |
| mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;"); |
| boxing = false; |
| type = origType; |
| } else if (asPrimitive) { |
| pushPrimitiveConstant(mv, value, type); |
| } else if (value instanceof BigDecimal) { |
| newInstance(mv, value); |
| } else if (value instanceof BigInteger) { |
| newInstance(mv, value); |
| } else if (value instanceof String) { |
| mv.visitLdcInsn(value); |
| } else { |
| throw new ClassGeneratorException( |
| "Cannot generate bytecode for constant: " + value + " of type: " + type.getName()); |
| } |
| |
| push(type); |
| if (boxing) box(); |
| } |
| |
| private static void newInstance(MethodVisitor mv, Object value) { |
| String className = BytecodeHelper.getClassInternalName(value.getClass().getName()); |
| mv.visitTypeInsn(NEW, className); |
| mv.visitInsn(DUP); |
| mv.visitLdcInsn(value.toString()); |
| mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V", false); |
| } |
| |
| private static void pushPrimitiveConstant(final MethodVisitor mv, final Object value, final ClassNode type) { |
| boolean isInt = ClassHelper.int_TYPE.equals(type); |
| boolean isShort = ClassHelper.short_TYPE.equals(type); |
| boolean isByte = ClassHelper.byte_TYPE.equals(type); |
| boolean isChar = ClassHelper.char_TYPE.equals(type); |
| if (isInt || isShort || isByte || isChar) { |
| int val = isInt?(Integer)value:isShort?(Short)value:isChar?(Character)value:(Byte)value; |
| BytecodeHelper.pushConstant(mv, val); |
| } else if (ClassHelper.long_TYPE.equals(type)) { |
| if ((Long)value==0L) { |
| mv.visitInsn(LCONST_0); |
| } else if ((Long)value==1L) { |
| mv.visitInsn(LCONST_1); |
| } else { |
| mv.visitLdcInsn(value); |
| } |
| } else if (ClassHelper.float_TYPE.equals(type)) { |
| if ((Float)value==0f) { |
| mv.visitInsn(FCONST_0); |
| } else if ((Float)value==1f) { |
| mv.visitInsn(FCONST_1); |
| } else if ((Float)value==2f) { |
| mv.visitInsn(FCONST_2); |
| } else { |
| mv.visitLdcInsn(value); |
| } |
| } else if (ClassHelper.double_TYPE.equals(type)) { |
| if ((Double)value==0d) { |
| mv.visitInsn(DCONST_0); |
| } else if ((Double)value==1d) { |
| mv.visitInsn(DCONST_1); |
| } else { |
| mv.visitLdcInsn(value); |
| } |
| } else if (ClassHelper.boolean_TYPE.equals(type)) { |
| boolean b = (Boolean) value; |
| if (b) { |
| mv.visitInsn(ICONST_1); |
| } else { |
| mv.visitInsn(ICONST_0); |
| } |
| } else { |
| mv.visitLdcInsn(value); |
| } |
| } |
| |
| 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 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); |
| } |
| } |
| } |
| |
| 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); |
| BytecodeHelper.store(mv, type, idx); |
| } |
| // remove RHS value from operand stack |
| remove(1); |
| } |
| |
| public void load(ClassNode type, int idx) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| BytecodeHelper.load(mv, type, idx); |
| push(type); |
| } |
| |
| public void pushBool(boolean inclusive) { |
| MethodVisitor mv = controller.getMethodVisitor(); |
| mv.visitLdcInsn(inclusive); |
| push(ClassHelper.boolean_TYPE); |
| } |
| |
| public String toString() { |
| return "OperandStack(size="+stack.size()+":"+stack.toString()+")"; |
| } |
| |
| public ClassNode getTopOperand() { |
| int size = ensureStackNotEmpty(stack); |
| return stack.get(size - 1); |
| } |
| } |