/** * Given a wrapped number type (Byte, Integer, Short, ...), generates bytecode to convert it to a * primitive number (int, long, double) using calls to wrapped.[targetType]Value() * * @param mv method visitor * @param sourceType the wrapped number type * @param targetType the primitive target type */ public static void doCastToPrimitive( MethodVisitor mv, ClassNode sourceType, ClassNode targetType) { mv.visitMethodInsn( INVOKEVIRTUAL, BytecodeHelper.getClassInternalName(sourceType), targetType.getName() + "Value", "()" + BytecodeHelper.getTypeDescription(targetType)); }
boolean makeGetField( final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean implicitThis, final boolean samePackage) { FieldNode field = receiverType.getField(fieldName); // direct access is allowed if we are in the same class as the declaring class // or we are in an inner class if (field != null && isDirectAccessAllowed(field, controller.getClassNode(), samePackage)) { CompileStack compileStack = controller.getCompileStack(); MethodVisitor mv = controller.getMethodVisitor(); if (field.isStatic()) { mv.visitFieldInsn( GETSTATIC, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(field.getOriginType())); controller.getOperandStack().push(field.getOriginType()); } else { if (implicitThis) { compileStack.pushImplicitThis(implicitThis); } receiver.visit(controller.getAcg()); if (implicitThis) compileStack.popImplicitThis(); if (!controller.getOperandStack().getTopOperand().isDerivedFrom(field.getOwner())) { mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(field.getOwner())); } mv.visitFieldInsn( GETFIELD, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(field.getOriginType())); } controller.getOperandStack().replace(field.getOriginType()); return true; } ClassNode superClass = receiverType.getSuperClass(); if (superClass != null) { return makeGetField(receiver, superClass, fieldName, implicitThis, false); } 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) { 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); } else if (value instanceof BigInteger) { 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); } 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(); }
public static void doCast(MethodVisitor mv, Class type) { if (type == Object.class) return; if (type.isPrimitive() && type != Void.TYPE) { unbox(mv, type); } else { mv.visitTypeInsn( CHECKCAST, type.isArray() ? BytecodeHelper.getTypeDescription(type) : BytecodeHelper.getClassInternalName(type.getName())); } }
public static void doCast(MethodVisitor mv, ClassNode type) { if (type == ClassHelper.OBJECT_TYPE) return; if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { unbox(mv, type); } else { mv.visitTypeInsn( CHECKCAST, type.isArray() ? BytecodeHelper.getTypeDescription(type) : BytecodeHelper.getClassInternalName(type.getName())); } }
/** * A helper class for bytecode generation with AsmClassGenerator. * * @author <a href="mailto:[email protected]">James Strachan</a> * @author <a href="mailto:[email protected]">Bing Ran</a> * @author <a href="mailto:[email protected]">Jochen Theodorou</a> * @version $Revision$ */ public class BytecodeHelper implements Opcodes { private static String DTT_CLASSNAME = BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class.getName()); public static String getClassInternalName(ClassNode t) { if (t.isPrimaryClassNode()) { if (t.isArray()) return "[L" + getClassInternalName(t.getComponentType()) + ";"; return getClassInternalName(t.getName()); } return getClassInternalName(t.getTypeClass()); } public static String getClassInternalName(Class t) { return org.objectweb.asm.Type.getInternalName(t); } /** @return the ASM internal name of the type */ public static String getClassInternalName(String name) { return name.replace('.', '/'); } public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) { StringBuffer buffer = new StringBuffer("("); for (int i = 0; i < parameters.length; i++) { buffer.append(getTypeDescription(parameters[i].getType())); } buffer.append(")"); buffer.append(getTypeDescription(returnType)); return buffer.toString(); } /** * Returns a method descriptor for the given {@link org.codehaus.groovy.ast.MethodNode}. * * @param methodNode the method node for which to create the descriptor * @return a method descriptor as defined in section JVMS section 4.3.3 */ public static String getMethodDescriptor(MethodNode methodNode) { return getMethodDescriptor(methodNode.getReturnType(), methodNode.getParameters()); } /** @return the ASM method type descriptor */ public static String getMethodDescriptor(Class returnType, Class[] paramTypes) { // lets avoid class loading StringBuffer buffer = new StringBuffer("("); for (int i = 0; i < paramTypes.length; i++) { buffer.append(getTypeDescription(paramTypes[i])); } buffer.append(")"); buffer.append(getTypeDescription(returnType)); return buffer.toString(); } public static String getTypeDescription(Class c) { return org.objectweb.asm.Type.getDescriptor(c); } /** * array types are special: eg.: String[]: classname: [Ljava.lang.String; Object: classname: * java.lang.Object int[] : classname: [I unlike getTypeDescription '.' is not replaced by '/'. it * seems that makes problems for the class loading if '.' is replaced by '/' * * @return the ASM type description for class loading */ public static String getClassLoadingTypeDescription(ClassNode c) { StringBuffer buf = new StringBuffer(); boolean array = false; while (true) { if (c.isArray()) { buf.append('['); c = c.getComponentType(); array = true; } else { if (ClassHelper.isPrimitiveType(c)) { buf.append(getTypeDescription(c)); } else { if (array) buf.append('L'); buf.append(c.getName()); if (array) buf.append(';'); } return buf.toString(); } } } /** * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I * * @return the ASM type description */ public static String getTypeDescription(ClassNode c) { return getTypeDescription(c, true); } /** * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I * * @return the ASM type description */ private static String getTypeDescription(ClassNode c, boolean end) { StringBuffer buf = new StringBuffer(); ClassNode d = c; while (true) { if (ClassHelper.isPrimitiveType(d)) { char car; if (d == ClassHelper.int_TYPE) { car = 'I'; } else if (d == ClassHelper.VOID_TYPE) { car = 'V'; } else if (d == ClassHelper.boolean_TYPE) { car = 'Z'; } else if (d == ClassHelper.byte_TYPE) { car = 'B'; } else if (d == ClassHelper.char_TYPE) { car = 'C'; } else if (d == ClassHelper.short_TYPE) { car = 'S'; } else if (d == ClassHelper.double_TYPE) { car = 'D'; } else if (d == ClassHelper.float_TYPE) { car = 'F'; } else /* long */ { car = 'J'; } buf.append(car); return buf.toString(); } else if (d.isArray()) { buf.append('['); d = d.getComponentType(); } else { buf.append('L'); String name = d.getName(); int len = name.length(); for (int i = 0; i < len; ++i) { char car = name.charAt(i); buf.append(car == '.' ? '/' : car); } if (end) buf.append(';'); return buf.toString(); } } } /** @return an array of ASM internal names of the type */ public static String[] getClassInternalNames(ClassNode[] names) { int size = names.length; String[] answer = new String[size]; for (int i = 0; i < size; i++) { answer[i] = getClassInternalName(names[i]); } return answer; } public static void pushConstant(MethodVisitor mv, int value) { switch (value) { case 0: mv.visitInsn(ICONST_0); break; case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; case 4: mv.visitInsn(ICONST_4); break; case 5: mv.visitInsn(ICONST_5); break; default: if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, value); } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, value); } else { mv.visitLdcInsn(Integer.valueOf(value)); } } } /** negate a boolean on stack. true->false, false->true */ public static void negateBoolean(MethodVisitor mv) { // code to negate the primitive boolean Label endLabel = new Label(); Label falseLabel = new Label(); mv.visitJumpInsn(IFNE, falseLabel); mv.visitInsn(ICONST_1); mv.visitJumpInsn(GOTO, endLabel); mv.visitLabel(falseLabel); mv.visitInsn(ICONST_0); mv.visitLabel(endLabel); } /** * load a message on the stack and remove it right away. Good for put a mark in the generated * bytecode for debugging purpose. * * @param msg */ /*public void mark(String msg) { mv.visitLdcInsn(msg); mv.visitInsn(POP); }*/ /** * returns a name that Class.forName() can take. Notably for arrays: [I, [Ljava.lang.String; etc * Regular object type: java.lang.String * * @param name */ public static String formatNameForClassLoading(String name) { if (name.equals("int") || name.equals("long") || name.equals("short") || name.equals("float") || name.equals("double") || name.equals("byte") || name.equals("char") || name.equals("boolean") || name.equals("void")) { return name; } if (name == null) { return "java.lang.Object;"; } if (name.startsWith("[")) { return name.replace('/', '.'); } if (name.startsWith("L")) { name = name.substring(1); if (name.endsWith(";")) { name = name.substring(0, name.length() - 1); } return name.replace('/', '.'); } String prefix = ""; if (name.endsWith("[]")) { // todo need process multi prefix = "["; name = name.substring(0, name.length() - 2); if (name.equals("int")) { return prefix + "I"; } else if (name.equals("long")) { return prefix + "J"; } else if (name.equals("short")) { return prefix + "S"; } else if (name.equals("float")) { return prefix + "F"; } else if (name.equals("double")) { return prefix + "D"; } else if (name.equals("byte")) { return prefix + "B"; } else if (name.equals("char")) { return prefix + "C"; } else if (name.equals("boolean")) { return prefix + "Z"; } else { return prefix + "L" + name.replace('/', '.') + ";"; } } return name.replace('/', '.'); } /*public void dup() { mv.visitInsn(DUP); }*/ public static void doReturn(MethodVisitor mv, ClassNode returnType) { if (returnType == ClassHelper.double_TYPE) { mv.visitInsn(DRETURN); } else if (returnType == ClassHelper.float_TYPE) { mv.visitInsn(FRETURN); } else if (returnType == ClassHelper.long_TYPE) { mv.visitInsn(LRETURN); } else if (returnType == ClassHelper.boolean_TYPE || returnType == ClassHelper.char_TYPE || returnType == ClassHelper.byte_TYPE || returnType == ClassHelper.int_TYPE || returnType == ClassHelper.short_TYPE) { // byte,short,boolean,int are all IRETURN mv.visitInsn(IRETURN); } else if (returnType == ClassHelper.VOID_TYPE) { mv.visitInsn(RETURN); } else { mv.visitInsn(ARETURN); } } private static boolean hasGenerics(Parameter[] param) { if (param.length == 0) return false; for (int i = 0; i < param.length; i++) { ClassNode type = param[i].getType(); if (hasGenerics(type)) return true; } return false; } private static boolean hasGenerics(ClassNode type) { return type.isArray() ? hasGenerics(type.getComponentType()) : type.getGenericsTypes() != null; } public static String getGenericsMethodSignature(MethodNode node) { GenericsType[] generics = node.getGenericsTypes(); Parameter[] param = node.getParameters(); ClassNode returnType = node.getReturnType(); if (generics == null && !hasGenerics(param) && !hasGenerics(returnType)) return null; StringBuffer ret = new StringBuffer(100); getGenericsTypeSpec(ret, generics); GenericsType[] paramTypes = new GenericsType[param.length]; for (int i = 0; i < param.length; i++) { ClassNode pType = param[i].getType(); if (pType.getGenericsTypes() == null || !pType.isGenericsPlaceHolder()) { paramTypes[i] = new GenericsType(pType); } else { paramTypes[i] = pType.getGenericsTypes()[0]; } } addSubTypes(ret, paramTypes, "(", ")"); addSubTypes(ret, new GenericsType[] {new GenericsType(returnType)}, "", ""); return ret.toString(); } private static boolean usesGenericsInClassSignature(ClassNode node) { if (!node.isUsingGenerics()) return false; if (hasGenerics(node)) return true; ClassNode sclass = node.getUnresolvedSuperClass(false); if (sclass.isUsingGenerics()) return true; ClassNode[] interfaces = node.getInterfaces(); if (interfaces != null) { for (int i = 0; i < interfaces.length; i++) { if (interfaces[i].isUsingGenerics()) return true; } } return false; } public static String getGenericsSignature(ClassNode node) { if (!usesGenericsInClassSignature(node)) return null; GenericsType[] genericsTypes = node.getGenericsTypes(); StringBuffer ret = new StringBuffer(100); getGenericsTypeSpec(ret, genericsTypes); GenericsType extendsPart = new GenericsType(node.getUnresolvedSuperClass(false)); writeGenericsBounds(ret, extendsPart, true); ClassNode[] interfaces = node.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { GenericsType interfacePart = new GenericsType(interfaces[i]); writeGenericsBounds(ret, interfacePart, false); } return ret.toString(); } private static void getGenericsTypeSpec(StringBuffer ret, GenericsType[] genericsTypes) { if (genericsTypes == null) return; ret.append('<'); for (int i = 0; i < genericsTypes.length; i++) { String name = genericsTypes[i].getName(); ret.append(name); ret.append(':'); writeGenericsBounds(ret, genericsTypes[i], true); } ret.append('>'); } public static String getGenericsBounds(ClassNode type) { GenericsType[] genericsTypes = type.getGenericsTypes(); if (genericsTypes == null) return null; StringBuffer ret = new StringBuffer(100); if (type.isGenericsPlaceHolder()) { addSubTypes(ret, type.getGenericsTypes(), "", ""); } else { GenericsType gt = new GenericsType(type); writeGenericsBounds(ret, gt, false); } return ret.toString(); } private static void writeGenericsBoundType( StringBuffer ret, ClassNode printType, boolean writeInterfaceMarker) { if (writeInterfaceMarker && printType.isInterface()) ret.append(":"); if (printType.equals(ClassHelper.OBJECT_TYPE) && printType.getGenericsTypes() != null) { ret.append("T"); ret.append(printType.getGenericsTypes()[0].getName()); ret.append(";"); } else { ret.append(getTypeDescription(printType, false)); addSubTypes(ret, printType.getGenericsTypes(), "<", ">"); if (!ClassHelper.isPrimitiveType(printType)) ret.append(";"); } } private static void writeGenericsBounds( StringBuffer ret, GenericsType type, boolean writeInterfaceMarker) { if (type.getUpperBounds() != null) { ClassNode[] bounds = type.getUpperBounds(); for (int i = 0; i < bounds.length; i++) { writeGenericsBoundType(ret, bounds[i], writeInterfaceMarker); } } else if (type.getLowerBound() != null) { writeGenericsBoundType(ret, type.getLowerBound(), writeInterfaceMarker); } else { writeGenericsBoundType(ret, type.getType(), writeInterfaceMarker); } } private static void addSubTypes( StringBuffer ret, GenericsType[] types, String start, String end) { if (types == null) return; ret.append(start); for (int i = 0; i < types.length; i++) { if (types[i].getType().isArray()) { ret.append("["); addSubTypes( ret, new GenericsType[] {new GenericsType(types[i].getType().getComponentType())}, "", ""); } else { if (types[i].isPlaceholder()) { ret.append('T'); String name = types[i].getName(); ret.append(name); ret.append(';'); } else if (types[i].isWildcard()) { if (types[i].getUpperBounds() != null) { ret.append('+'); writeGenericsBounds(ret, types[i], false); } else if (types[i].getLowerBound() != null) { ret.append('-'); writeGenericsBounds(ret, types[i], false); } else { ret.append('*'); } } else { writeGenericsBounds(ret, types[i], false); } } } ret.append(end); } public static void load(MethodVisitor mv, ClassNode type, int idx) { if (type == ClassHelper.double_TYPE) { mv.visitVarInsn(DLOAD, idx); } else if (type == ClassHelper.float_TYPE) { mv.visitVarInsn(FLOAD, idx); } else if (type == ClassHelper.long_TYPE) { mv.visitVarInsn(LLOAD, 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(ILOAD, idx); } else { mv.visitVarInsn(ALOAD, idx); } } public static void doCast(MethodVisitor mv, ClassNode type) { if (type == ClassHelper.OBJECT_TYPE) return; if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { unbox(mv, type); } else { mv.visitTypeInsn( CHECKCAST, type.isArray() ? BytecodeHelper.getTypeDescription(type) : BytecodeHelper.getClassInternalName(type.getName())); } } /** * Given a wrapped number type (Byte, Integer, Short, ...), generates bytecode to convert it to a * primitive number (int, long, double) using calls to wrapped.[targetType]Value() * * @param mv method visitor * @param sourceType the wrapped number type * @param targetType the primitive target type */ public static void doCastToPrimitive( MethodVisitor mv, ClassNode sourceType, ClassNode targetType) { mv.visitMethodInsn( INVOKEVIRTUAL, BytecodeHelper.getClassInternalName(sourceType), targetType.getName() + "Value", "()" + BytecodeHelper.getTypeDescription(targetType)); } /** * Given a primitive number type (byte, integer, short, ...), generates bytecode to convert it to * a wrapped number (Integer, Long, Double) using calls to [WrappedType].valueOf * * @param mv method visitor * @param sourceType the primitive number type * @param targetType the wrapped target type */ public static void doCastToWrappedType( MethodVisitor mv, ClassNode sourceType, ClassNode targetType) { mv.visitMethodInsn( INVOKESTATIC, getClassInternalName(targetType), "valueOf", "(" + getTypeDescription(sourceType) + ")" + getTypeDescription(targetType)); } public static void doCast(MethodVisitor mv, Class type) { if (type == Object.class) return; if (type.isPrimitive() && type != Void.TYPE) { unbox(mv, type); } else { mv.visitTypeInsn( CHECKCAST, type.isArray() ? BytecodeHelper.getTypeDescription(type) : BytecodeHelper.getClassInternalName(type.getName())); } } /** Generates the bytecode to unbox the current value on the stack */ public static void unbox(MethodVisitor mv, Class type) { if (type.isPrimitive() && type != Void.TYPE) { String returnString = "(Ljava/lang/Object;)" + BytecodeHelper.getTypeDescription(type); mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, type.getName() + "Unbox", returnString); } } public static void unbox(MethodVisitor mv, ClassNode type) { if (type.isPrimaryClassNode()) return; unbox(mv, type.getTypeClass()); } /** box top level operand */ public static boolean box(MethodVisitor mv, ClassNode type) { if (type.isPrimaryClassNode()) return false; return box(mv, type.getTypeClass()); } /** Generates the bytecode to autobox the current value on the stack */ public static boolean box(MethodVisitor mv, Class type) { if (ReflectionCache.getCachedClass(type).isPrimitive && type != void.class) { String returnString = "(" + BytecodeHelper.getTypeDescription(type) + ")Ljava/lang/Object;"; mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, "box", returnString); return true; } return false; } /** * Visits a class literal. If the type of the classnode is a primitive type, the generated * bytecode will be a GETSTATIC Integer.TYPE. If the classnode is not a primitive type, we will * generate a LDC instruction. */ public static void visitClassLiteral(MethodVisitor mv, ClassNode classNode) { if (ClassHelper.isPrimitiveType(classNode)) { mv.visitFieldInsn( GETSTATIC, getClassInternalName(ClassHelper.getWrapper(classNode)), "TYPE", "Ljava/lang/Class;"); } else { mv.visitLdcInsn(org.objectweb.asm.Type.getType(getTypeDescription(classNode))); } } /** * Tells if a class node is candidate for class literal bytecode optimization. If so, bytecode may * use LDC instructions instead of static constant Class fields to retrieve class literals. * * @param classNode the classnode for which we want to know if bytecode optimization is possible * @return true if the bytecode can be optimized */ public static boolean isClassLiteralPossible(ClassNode classNode) { // the current implementation only checks for public modifier, because Groovy used to allow // handles on classes even if they are package protected and not in the same package. // There are situations where we could make more fine grained checks, but be careful of // potential breakage of existing code. return Modifier.isPublic(classNode.getModifiers()); } /** * Returns true if the two classes share the same compilation unit. * * @param a class a * @param b class b * @return true if both classes share the same compilation unit */ public static boolean isSameCompilationUnit(ClassNode a, ClassNode b) { CompileUnit cu1 = a.getCompileUnit(); CompileUnit cu2 = b.getCompileUnit(); return cu1 != null && cu2 != null && cu1 == cu2; } }
@Override public void makeGetPropertySite( Expression receiver, final String methodName, final boolean safe, final boolean implicitThis) { Object dynamic = receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY); if (dynamic != null) { MethodNode target = safe ? INVOKERHELPER_GETPROPERTYSAFE_METHOD : INVOKERHELPER_GETPROPERTY_METHOD; MethodCallExpression mce = new MethodCallExpression( new ClassExpression(INVOKERHELPER_TYPE), target.getName(), new ArgumentListExpression(receiver, new ConstantExpression(methodName))); mce.setSafe(false); mce.setImplicitThis(false); mce.setMethodTarget(target); mce.visit(controller.getAcg()); return; } TypeChooser typeChooser = controller.getTypeChooser(); ClassNode classNode = controller.getClassNode(); ClassNode receiverType = (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER); if (receiverType == null) { receiverType = typeChooser.resolveType(receiver, classNode); } Object type = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); if (type == null && receiver instanceof VariableExpression) { Variable variable = ((VariableExpression) receiver).getAccessedVariable(); if (variable instanceof Expression) { type = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); } } if (type != null) { // in case a "flow type" is found, it is preferred to use it instead of // the declaration type receiverType = (ClassNode) type; } boolean isClassReceiver = false; if (isClassClassNodeWrappingConcreteType(receiverType)) { isClassReceiver = true; receiverType = receiverType.getGenericsTypes()[0].getType(); } MethodVisitor mv = controller.getMethodVisitor(); if (receiverType.isArray() && methodName.equals("length")) { receiver.visit(controller.getAcg()); ClassNode arrayGetReturnType = typeChooser.resolveType(receiver, classNode); controller.getOperandStack().doGroovyCast(arrayGetReturnType); mv.visitInsn(ARRAYLENGTH); controller.getOperandStack().replace(int_TYPE); return; } else if ((receiverType.implementsInterface(COLLECTION_TYPE) || COLLECTION_TYPE.equals(receiverType)) && ("size".equals(methodName) || "length".equals(methodName))) { MethodCallExpression expr = new MethodCallExpression(receiver, "size", ArgumentListExpression.EMPTY_ARGUMENTS); expr.setMethodTarget(COLLECTION_SIZE_METHOD); expr.setImplicitThis(implicitThis); expr.setSafe(safe); expr.visit(controller.getAcg()); return; } if (makeGetPropertyWithGetter(receiver, receiverType, methodName, safe, implicitThis)) return; if (makeGetField( receiver, receiverType, methodName, implicitThis, samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return; if (receiverType.isEnum()) { mv.visitFieldInsn( GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), methodName, BytecodeHelper.getTypeDescription(receiverType)); controller.getOperandStack().push(receiverType); return; } if (receiver instanceof ClassExpression) { if (makeGetField( receiver, receiver.getType(), methodName, implicitThis, samePackages(receiver.getType().getPackageName(), classNode.getPackageName()))) return; if (makeGetPropertyWithGetter(receiver, receiver.getType(), methodName, safe, implicitThis)) return; if (makeGetPrivateFieldWithBridgeMethod( receiver, receiver.getType(), methodName, safe, implicitThis)) return; } if (isClassReceiver) { // we are probably looking for a property of the class if (makeGetPropertyWithGetter(receiver, CLASS_Type, methodName, safe, implicitThis)) return; if (makeGetField(receiver, CLASS_Type, methodName, false, true)) return; } if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, methodName, safe, implicitThis)) return; // GROOVY-5580, it is still possible that we're calling a superinterface property String getterName = "get" + MetaClassHelper.capitalize(methodName); if (receiverType.isInterface()) { Set<ClassNode> allInterfaces = receiverType.getAllInterfaces(); MethodNode getterMethod = null; for (ClassNode anInterface : allInterfaces) { getterMethod = anInterface.getGetterMethod(getterName); if (getterMethod != null) break; } // GROOVY-5585 if (getterMethod == null) { getterMethod = OBJECT_TYPE.getGetterMethod(getterName); } if (getterMethod != null) { MethodCallExpression call = new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS); call.setMethodTarget(getterMethod); call.setImplicitThis(false); call.setSourcePosition(receiver); call.visit(controller.getAcg()); return; } } // GROOVY-5568, we would be facing a DGM call, but instead of foo.getText(), have foo.text List<MethodNode> methods = findDGMMethodsByNameAndArguments( controller.getSourceUnit().getClassLoader(), receiverType, getterName, ClassNode.EMPTY_ARRAY); if (!methods.isEmpty()) { List<MethodNode> methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY); if (methodNodes.size() == 1) { MethodNode getter = methodNodes.get(0); MethodCallExpression call = new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS); call.setMethodTarget(getter); call.setImplicitThis(false); call.setSourcePosition(receiver); call.visit(controller.getAcg()); return; } } boolean isStaticProperty = receiver instanceof ClassExpression && (receiverType.isDerivedFrom(receiver.getType()) || receiverType.implementsInterface(receiver.getType())); if (!isStaticProperty) { if (receiverType.implementsInterface(MAP_TYPE) || MAP_TYPE.equals(receiverType)) { // for maps, replace map.foo with map.get('foo') writeMapDotProperty(receiver, methodName, mv, safe); return; } if (receiverType.implementsInterface(LIST_TYPE) || LIST_TYPE.equals(receiverType)) { writeListDotProperty(receiver, methodName, mv, safe); return; } } controller .getSourceUnit() .addError( new SyntaxException( "Access to " + (receiver instanceof ClassExpression ? receiver.getType() : receiverType) .toString(false) + "#" + methodName + " is forbidden", receiver.getLineNumber(), receiver.getColumnNumber(), receiver.getLastLineNumber(), receiver.getLastColumnNumber())); controller.getMethodVisitor().visitInsn(ACONST_NULL); controller.getOperandStack().push(OBJECT_TYPE); }
private void writeListDotProperty( final Expression receiver, final String methodName, final MethodVisitor mv, final boolean safe) { ClassNode componentType = (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.COMPONENT_TYPE); if (componentType == null) { componentType = OBJECT_TYPE; } // for lists, replace list.foo with: // def result = new ArrayList(list.size()) // for (e in list) { result.add (e.foo) } // result CompileStack compileStack = controller.getCompileStack(); Label exit = new Label(); if (safe) { receiver.visit(controller.getAcg()); Label doGet = new Label(); mv.visitJumpInsn(IFNONNULL, doGet); controller.getOperandStack().remove(1); mv.visitInsn(ACONST_NULL); mv.visitJumpInsn(GOTO, exit); mv.visitLabel(doGet); } Variable tmpList = new VariableExpression("tmpList", make(ArrayList.class)); int var = compileStack.defineTemporaryVariable(tmpList, false); Variable iterator = new VariableExpression("iterator", Iterator_TYPE); int it = compileStack.defineTemporaryVariable(iterator, false); Variable nextVar = new VariableExpression("next", componentType); final int next = compileStack.defineTemporaryVariable(nextVar, false); mv.visitTypeInsn(NEW, "java/util/ArrayList"); mv.visitInsn(DUP); receiver.visit(controller.getAcg()); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "size", "()I", true); controller.getOperandStack().remove(1); mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "(I)V", false); mv.visitVarInsn(ASTORE, var); Label l1 = new Label(); mv.visitLabel(l1); receiver.visit(controller.getAcg()); mv.visitMethodInsn( INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true); controller.getOperandStack().remove(1); mv.visitVarInsn(ASTORE, it); Label l2 = new Label(); mv.visitLabel(l2); mv.visitVarInsn(ALOAD, it); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true); Label l3 = new Label(); mv.visitJumpInsn(IFEQ, l3); mv.visitVarInsn(ALOAD, it); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true); mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(componentType)); mv.visitVarInsn(ASTORE, next); Label l4 = new Label(); mv.visitLabel(l4); mv.visitVarInsn(ALOAD, var); final ClassNode finalComponentType = componentType; PropertyExpression pexp = new PropertyExpression( new BytecodeExpression() { @Override public void visit(final MethodVisitor mv) { mv.visitVarInsn(ALOAD, next); } @Override public ClassNode getType() { return finalComponentType; } }, methodName); pexp.visit(controller.getAcg()); controller.getOperandStack().box(); controller.getOperandStack().remove(1); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true); mv.visitInsn(POP); Label l5 = new Label(); mv.visitLabel(l5); mv.visitJumpInsn(GOTO, l2); mv.visitLabel(l3); mv.visitVarInsn(ALOAD, var); if (safe) { mv.visitLabel(exit); } controller.getOperandStack().push(make(ArrayList.class)); controller.getCompileStack().removeVar(next); controller.getCompileStack().removeVar(it); controller.getCompileStack().removeVar(var); }
/** * A helper class for bytecode generation with AsmClassGenerator. * * @author <a href="mailto:[email protected]">James Strachan</a> * @author <a href="mailto:[email protected]">Bing Ran</a> * @author <a href="mailto:[email protected]">Jochen Theodorou</a> * @version $Revision$ */ public class BytecodeHelper implements Opcodes { private static String DTT_CLASSNAME = BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class.getName()); public static String getClassInternalName(ClassNode t) { if (t.isPrimaryClassNode()) { return getClassInternalName(t.getName()); } return getClassInternalName(t.getTypeClass()); } public static String getClassInternalName(Class t) { return org.objectweb.asm.Type.getInternalName(t); } /** @return the ASM internal name of the type */ public static String getClassInternalName(String name) { return name.replace('.', '/'); } public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) { StringBuffer buffer = new StringBuffer("("); for (int i = 0; i < parameters.length; i++) { buffer.append(getTypeDescription(parameters[i].getType())); } buffer.append(")"); buffer.append(getTypeDescription(returnType)); return buffer.toString(); } /** @return the ASM method type descriptor */ public static String getMethodDescriptor(Class returnType, Class[] paramTypes) { // lets avoid class loading StringBuffer buffer = new StringBuffer("("); for (int i = 0; i < paramTypes.length; i++) { buffer.append(getTypeDescription(paramTypes[i])); } buffer.append(")"); buffer.append(getTypeDescription(returnType)); return buffer.toString(); } public static String getTypeDescription(Class c) { return org.objectweb.asm.Type.getDescriptor(c); } /** * array types are special: eg.: String[]: classname: [Ljava.lang.String; Object: classname: * java.lang.Object int[] : classname: [I unlike getTypeDescription '.' is not replaced by '/'. it * seems that makes problems for the class loading if '.' is replaced by '/' * * @return the ASM type description for class loading */ public static String getClassLoadingTypeDescription(ClassNode c) { StringBuffer buf = new StringBuffer(); boolean array = false; while (true) { if (c.isArray()) { buf.append('['); c = c.getComponentType(); array = true; } else { if (ClassHelper.isPrimitiveType(c)) { buf.append(getTypeDescription(c)); } else { if (array) buf.append('L'); buf.append(c.getName()); if (array) buf.append(';'); } return buf.toString(); } } } /** * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I * * @return the ASM type description */ public static String getTypeDescription(ClassNode c) { return getTypeDescription(c, true); } /** * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I * * @return the ASM type description */ private static String getTypeDescription(ClassNode c, boolean end) { StringBuffer buf = new StringBuffer(); ClassNode d = c; while (true) { if (ClassHelper.isPrimitiveType(d)) { char car; if (d == ClassHelper.int_TYPE) { car = 'I'; } else if (d == ClassHelper.VOID_TYPE) { car = 'V'; } else if (d == ClassHelper.boolean_TYPE) { car = 'Z'; } else if (d == ClassHelper.byte_TYPE) { car = 'B'; } else if (d == ClassHelper.char_TYPE) { car = 'C'; } else if (d == ClassHelper.short_TYPE) { car = 'S'; } else if (d == ClassHelper.double_TYPE) { car = 'D'; } else if (d == ClassHelper.float_TYPE) { car = 'F'; } else /* long */ { car = 'J'; } buf.append(car); return buf.toString(); } else if (d.isArray()) { buf.append('['); d = d.getComponentType(); } else { buf.append('L'); String name = d.getName(); int len = name.length(); for (int i = 0; i < len; ++i) { char car = name.charAt(i); buf.append(car == '.' ? '/' : car); } if (end) buf.append(';'); return buf.toString(); } } } /** @return an array of ASM internal names of the type */ public static String[] getClassInternalNames(ClassNode[] names) { int size = names.length; String[] answer = new String[size]; for (int i = 0; i < size; i++) { answer[i] = getClassInternalName(names[i]); } return answer; } public static void pushConstant(MethodVisitor mv, int value) { switch (value) { case 0: mv.visitInsn(ICONST_0); break; case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; case 4: mv.visitInsn(ICONST_4); break; case 5: mv.visitInsn(ICONST_5); break; default: if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, value); } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, value); } else { mv.visitLdcInsn(Integer.valueOf(value)); } } } /** negate a boolean on stack. true->false, false->true */ public static void negateBoolean(MethodVisitor mv) { // code to negate the primitive boolean Label endLabel = new Label(); Label falseLabel = new Label(); mv.visitJumpInsn(IFNE, falseLabel); mv.visitInsn(ICONST_1); mv.visitJumpInsn(GOTO, endLabel); mv.visitLabel(falseLabel); mv.visitInsn(ICONST_0); mv.visitLabel(endLabel); } /** * load a message on the stack and remove it right away. Good for put a mark in the generated * bytecode for debugging purpose. * * @param msg */ /*public void mark(String msg) { mv.visitLdcInsn(msg); mv.visitInsn(POP); }*/ /** * returns a name that Class.forName() can take. Notably for arrays: [I, [Ljava.lang.String; etc * Regular object type: java.lang.String * * @param name */ public static String formatNameForClassLoading(String name) { if (name.equals("int") || name.equals("long") || name.equals("short") || name.equals("float") || name.equals("double") || name.equals("byte") || name.equals("char") || name.equals("boolean") || name.equals("void")) { return name; } if (name == null) { return "java.lang.Object;"; } if (name.startsWith("[")) { return name.replace('/', '.'); } if (name.startsWith("L")) { name = name.substring(1); if (name.endsWith(";")) { name = name.substring(0, name.length() - 1); } return name.replace('/', '.'); } String prefix = ""; if (name.endsWith("[]")) { // todo need process multi prefix = "["; name = name.substring(0, name.length() - 2); if (name.equals("int")) { return prefix + "I"; } else if (name.equals("long")) { return prefix + "J"; } else if (name.equals("short")) { return prefix + "S"; } else if (name.equals("float")) { return prefix + "F"; } else if (name.equals("double")) { return prefix + "D"; } else if (name.equals("byte")) { return prefix + "B"; } else if (name.equals("char")) { return prefix + "C"; } else if (name.equals("boolean")) { return prefix + "Z"; } else { return prefix + "L" + name.replace('/', '.') + ";"; } } return name.replace('/', '.'); } /*public void dup() { mv.visitInsn(DUP); }*/ public static void doReturn(MethodVisitor mv, ClassNode returnType) { if (returnType == ClassHelper.double_TYPE) { mv.visitInsn(DRETURN); } else if (returnType == ClassHelper.float_TYPE) { mv.visitInsn(FRETURN); } else if (returnType == ClassHelper.long_TYPE) { mv.visitInsn(LRETURN); } else if (returnType == ClassHelper.boolean_TYPE || returnType == ClassHelper.char_TYPE || returnType == ClassHelper.byte_TYPE || returnType == ClassHelper.int_TYPE || returnType == ClassHelper.short_TYPE) { // byte,short,boolean,int are all IRETURN mv.visitInsn(IRETURN); } else if (returnType == ClassHelper.VOID_TYPE) { mv.visitInsn(RETURN); } else { mv.visitInsn(ARETURN); } } private static boolean hasGenerics(Parameter[] param) { if (param.length == 0) return false; for (int i = 0; i < param.length; i++) { ClassNode type = param[i].getType(); if (hasGenerics(type)) return true; } return false; } private static boolean hasGenerics(ClassNode type) { return type.isArray() ? hasGenerics(type.getComponentType()) : type.getGenericsTypes() != null; } public static String getGenericsMethodSignature(MethodNode node) { GenericsType[] generics = node.getGenericsTypes(); Parameter[] param = node.getParameters(); ClassNode returnType = node.getReturnType(); if (generics == null && !hasGenerics(param) && !hasGenerics(returnType)) return null; StringBuffer ret = new StringBuffer(100); getGenericsTypeSpec(ret, generics); GenericsType[] paramTypes = new GenericsType[param.length]; for (int i = 0; i < param.length; i++) { ClassNode pType = param[i].getType(); if (pType.getGenericsTypes() == null || !pType.isGenericsPlaceHolder()) { paramTypes[i] = new GenericsType(pType); } else { paramTypes[i] = pType.getGenericsTypes()[0]; } } addSubTypes(ret, paramTypes, "(", ")"); addSubTypes(ret, new GenericsType[] {new GenericsType(returnType)}, "", ""); return ret.toString(); } private static boolean usesGenericsInClassSignature(ClassNode node) { if (!node.isUsingGenerics()) return false; if (hasGenerics(node)) return true; ClassNode sclass = node.getUnresolvedSuperClass(false); if (sclass.isUsingGenerics()) return true; ClassNode[] interfaces = node.getInterfaces(); if (interfaces != null) { for (int i = 0; i < interfaces.length; i++) { if (interfaces[i].isUsingGenerics()) return true; } } return false; } public static String getGenericsSignature(ClassNode node) { if (!usesGenericsInClassSignature(node)) return null; GenericsType[] genericsTypes = node.getGenericsTypes(); StringBuffer ret = new StringBuffer(100); getGenericsTypeSpec(ret, genericsTypes); GenericsType extendsPart = new GenericsType(node.getUnresolvedSuperClass(false)); writeGenericsBounds(ret, extendsPart, true); ClassNode[] interfaces = node.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { GenericsType interfacePart = new GenericsType(interfaces[i]); writeGenericsBounds(ret, interfacePart, false); } return ret.toString(); } private static void getGenericsTypeSpec(StringBuffer ret, GenericsType[] genericsTypes) { if (genericsTypes == null) return; ret.append('<'); for (int i = 0; i < genericsTypes.length; i++) { String name = genericsTypes[i].getName(); ret.append(name); ret.append(':'); writeGenericsBounds(ret, genericsTypes[i], true); } ret.append('>'); } public static String getGenericsBounds(ClassNode type) { GenericsType[] genericsTypes = type.getGenericsTypes(); if (genericsTypes == null) return null; StringBuffer ret = new StringBuffer(100); if (type.isGenericsPlaceHolder()) { addSubTypes(ret, type.getGenericsTypes(), "", ""); } else { GenericsType gt = new GenericsType(type); writeGenericsBounds(ret, gt, false); } return ret.toString(); } private static void writeGenericsBoundType( StringBuffer ret, ClassNode printType, boolean writeInterfaceMarker) { if (writeInterfaceMarker && printType.isInterface()) ret.append(":"); if (printType.equals(ClassHelper.OBJECT_TYPE) && printType.getGenericsTypes() != null) { ret.append("T"); ret.append(printType.getGenericsTypes()[0].getName()); ret.append(";"); } else { ret.append(getTypeDescription(printType, false)); addSubTypes(ret, printType.getGenericsTypes(), "<", ">"); if (!ClassHelper.isPrimitiveType(printType)) ret.append(";"); } } private static void writeGenericsBounds( StringBuffer ret, GenericsType type, boolean writeInterfaceMarker) { if (type.getUpperBounds() != null) { ClassNode[] bounds = type.getUpperBounds(); for (int i = 0; i < bounds.length; i++) { writeGenericsBoundType(ret, bounds[i], writeInterfaceMarker); } } else if (type.getLowerBound() != null) { writeGenericsBoundType(ret, type.getLowerBound(), writeInterfaceMarker); } else { writeGenericsBoundType(ret, type.getType(), writeInterfaceMarker); } } private static void addSubTypes( StringBuffer ret, GenericsType[] types, String start, String end) { if (types == null) return; ret.append(start); for (int i = 0; i < types.length; i++) { if (types[i].getType().isArray()) { ret.append("["); addSubTypes( ret, new GenericsType[] {new GenericsType(types[i].getType().getComponentType())}, "", ""); } else { if (types[i].isPlaceholder()) { ret.append('T'); String name = types[i].getName(); ret.append(name); ret.append(';'); } else if (types[i].isWildcard()) { if (types[i].getUpperBounds() != null) { ret.append('+'); writeGenericsBounds(ret, types[i], false); } else if (types[i].getLowerBound() != null) { ret.append('-'); writeGenericsBounds(ret, types[i], false); } else { ret.append('*'); } } else { writeGenericsBounds(ret, types[i], false); } } } ret.append(end); } public static void load(MethodVisitor mv, ClassNode type, int idx) { if (type == ClassHelper.double_TYPE) { mv.visitVarInsn(DLOAD, idx); } else if (type == ClassHelper.float_TYPE) { mv.visitVarInsn(FLOAD, idx); } else if (type == ClassHelper.long_TYPE) { mv.visitVarInsn(LLOAD, 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(ILOAD, idx); } else { mv.visitVarInsn(ALOAD, idx); } } public static void doCast(MethodVisitor mv, ClassNode type) { if (type == ClassHelper.OBJECT_TYPE) return; if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) { unbox(mv, type); } else { mv.visitTypeInsn( CHECKCAST, type.isArray() ? BytecodeHelper.getTypeDescription(type) : BytecodeHelper.getClassInternalName(type.getName())); } } public static void doCast(MethodVisitor mv, Class type) { if (type == Object.class) return; if (type.isPrimitive() && type != Void.TYPE) { unbox(mv, type); } else { mv.visitTypeInsn( CHECKCAST, type.isArray() ? BytecodeHelper.getTypeDescription(type) : BytecodeHelper.getClassInternalName(type.getName())); } } /** Generates the bytecode to unbox the current value on the stack */ public static void unbox(MethodVisitor mv, Class type) { if (type.isPrimitive() && type != Void.TYPE) { String returnString = "(Ljava/lang/Object;)" + BytecodeHelper.getTypeDescription(type); mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, type.getName() + "Unbox", returnString); } } public static void unbox(MethodVisitor mv, ClassNode type) { if (type.isPrimaryClassNode()) return; unbox(mv, type.getTypeClass()); } /** box top level operand */ public static boolean box(MethodVisitor mv, ClassNode type) { if (type.isPrimaryClassNode()) return false; return box(mv, type.getTypeClass()); } /** Generates the bytecode to autobox the current value on the stack */ public static boolean box(MethodVisitor mv, Class type) { if (ReflectionCache.getCachedClass(type).isPrimitive && type != void.class) { String returnString = "(" + BytecodeHelper.getTypeDescription(type) + ")Ljava/lang/Object;"; mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, "box", returnString); return true; } return false; } }