Java tutorial
/* * 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.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CompileUnit; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.classgen.asm.util.TypeUtil; import org.codehaus.groovy.reflection.ReflectionCache; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.lang.reflect.Modifier; import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE; import static org.codehaus.groovy.ast.ClassHelper.char_TYPE; import static org.codehaus.groovy.ast.ClassHelper.double_TYPE; import static org.codehaus.groovy.ast.ClassHelper.float_TYPE; import static org.codehaus.groovy.ast.ClassHelper.int_TYPE; import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; /** * A helper class for bytecode generation with AsmClassGenerator. */ public class BytecodeHelper implements Opcodes { private static String DTT_CLASSNAME = BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class); /** * @return the ASM internal name of the type */ public static String getClassInternalName(ClassNode t) { if (t.isArray()) { return TypeUtil.getDescriptionByType(t); } return getClassInternalName(t.getName()); } /** * @return the ASM internal name of the type */ 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('.', '/'); } /** * 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(ClassNode returnType, Parameter[] parameters) { ClassNode[] parameterTypes = new ClassNode[parameters.length]; for (int i = 0, n = parameters.length; i < n; i += 1) { parameterTypes[i] = parameters[i].getType(); } return getMethodDescriptor(returnType, parameterTypes); } /** * @return the ASM method type descriptor */ public static String getMethodDescriptor(ClassNode returnType, ClassNode[] parameterTypes) { StringBuilder buffer = new StringBuilder(100); buffer.append('('); for (ClassNode parameterType : parameterTypes) { buffer.append(getTypeDescription(parameterType)); } 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 StringBuilder buffer = new StringBuilder(100); buffer.append('('); for (Class paramType : paramTypes) { buffer.append(getTypeDescription(paramType)); } buffer.append(')'); buffer.append(getTypeDescription(returnType)); return buffer.toString(); } /** * 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) { String desc = TypeUtil.getDescriptionByType(c); if (!c.isArray()) { if (desc.startsWith("L") && desc.endsWith(";")) { desc = desc.substring(1, desc.length() - 1); // remove "L" and ";" } } return desc.replace('/', '.'); } public static String getTypeDescription(Class c) { return org.objectweb.asm.Type.getDescriptor(c); } /** * 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) { ClassNode d = c; if (ClassHelper.isPrimitiveType(d.redirect())) { d = d.redirect(); } String desc = TypeUtil.getDescriptionByType(d); if (!end && desc.endsWith(";")) { desc = desc.substring(0, desc.length() - 1); } return desc; } /** * @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 += 1) { 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(value); } } } /** * Negate a boolean on stack. */ 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); } /** * returns a name that Class.forName() can take. Notably for arrays: * [I, [Ljava.lang.String; etc * Regular object type: java.lang.String */ public static String formatNameForClassLoading(String name) { if (name == null) { return "java.lang.Object;"; } if (TypeUtil.isPrimitiveType(name)) { return name; } 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); return prefix + TypeUtil.getDescriptionByName(name); } return name.replace('/', '.'); } private static boolean hasGenerics(Parameter[] param) { if (param.length == 0) return false; for (Parameter parameter : param) { ClassNode type = parameter.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; StringBuilder ret = new StringBuilder(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 (ClassNode anInterface : interfaces) { if (anInterface.isUsingGenerics()) return true; } } return false; } public static String getGenericsSignature(ClassNode node) { if (!usesGenericsInClassSignature(node)) return null; GenericsType[] genericsTypes = node.getGenericsTypes(); StringBuilder ret = new StringBuilder(100); getGenericsTypeSpec(ret, genericsTypes); GenericsType extendsPart = new GenericsType(node.getUnresolvedSuperClass(false)); writeGenericsBounds(ret, extendsPart, true); ClassNode[] interfaces = node.getInterfaces(); for (ClassNode anInterface : interfaces) { GenericsType interfacePart = new GenericsType(anInterface); writeGenericsBounds(ret, interfacePart, false); } return ret.toString(); } private static void getGenericsTypeSpec(StringBuilder ret, GenericsType[] genericsTypes) { if (genericsTypes == null) return; ret.append('<'); for (GenericsType genericsType : genericsTypes) { String name = genericsType.getName(); ret.append(name); ret.append(':'); writeGenericsBounds(ret, genericsType, true); } ret.append('>'); } public static String getGenericsBounds(ClassNode type) { GenericsType[] genericsTypes = type.getGenericsTypes(); if (genericsTypes == null) return null; StringBuilder ret = new StringBuilder(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(StringBuilder ret, ClassNode printType, boolean writeInterfaceMarker) { if (writeInterfaceMarker && printType.isInterface()) ret.append(":"); if (printType.isGenericsPlaceHolder() && 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(StringBuilder ret, GenericsType type, boolean writeInterfaceMarker) { if (type.getUpperBounds() != null) { ClassNode[] bounds = type.getUpperBounds(); for (ClassNode bound : bounds) { writeGenericsBoundType(ret, bound, writeInterfaceMarker); } } else if (type.getLowerBound() != null) { writeGenericsBoundType(ret, type.getLowerBound(), writeInterfaceMarker); } else { writeGenericsBoundType(ret, type.getType(), writeInterfaceMarker); } } private static void addSubTypes(StringBuilder ret, GenericsType[] types, String start, String end) { if (types == null) return; ret.append(start); for (GenericsType type : types) { if (type.getType().isArray()) { ret.append("["); addSubTypes(ret, new GenericsType[] { new GenericsType(type.getType().getComponentType()) }, "", ""); } else { if (type.isPlaceholder()) { ret.append('T'); String name = type.getName(); ret.append(name); ret.append(';'); } else if (type.isWildcard()) { if (type.getUpperBounds() != null) { ret.append('+'); writeGenericsBounds(ret, type, false); } else if (type.getLowerBound() != null) { ret.append('-'); writeGenericsBounds(ret, type, false); } else { ret.append('*'); } } else { writeGenericsBounds(ret, type, false); } } } ret.append(end); } public static void doCast(MethodVisitor mv, ClassNode type) { if (type == ClassHelper.OBJECT_TYPE) return; if (ClassHelper.isPrimitiveType(type) && type != 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), false); } /** * 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), false); } 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 autobox the current value on the stack. */ @Deprecated public static boolean box(MethodVisitor mv, ClassNode type) { if (ClassHelper.isPrimitiveType(type) && !ClassHelper.VOID_TYPE.equals(type)) { box(mv, BytecodeHelper.getTypeDescription(type)); return true; } return false; } /** * Generates the bytecode to autobox the current value on the stack. */ @Deprecated public static boolean box(MethodVisitor mv, Class type) { if (ReflectionCache.getCachedClass(type).isPrimitive && type != void.class) { box(mv, BytecodeHelper.getTypeDescription(type)); return true; } return false; } private static void box(MethodVisitor mv, String typeDescription) { mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, "box", "(" + typeDescription + ")Ljava/lang/Object;", false); } /** * Generates the bytecode to unbox the current value on the stack. */ public static void unbox(MethodVisitor mv, ClassNode type) { if (ClassHelper.isPrimitiveType(type) && !ClassHelper.VOID_TYPE.equals(type)) { unbox(mv, type.getName(), BytecodeHelper.getTypeDescription(type)); } } /** * 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) { unbox(mv, type.getName(), BytecodeHelper.getTypeDescription(type)); } } private static void unbox(MethodVisitor mv, String typeName, String typeDescription) { mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, typeName + "Unbox", "(Ljava/lang/Object;)" + typeDescription, 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 && cu1 == cu2; } /** * Computes a hash code for a string. The purpose of this hashcode is to be constant independently of * the JDK being used. * @param str the string for which to compute the hashcode * @return hashcode of the string */ public static int hashCode(String str) { final char[] chars = str.toCharArray(); int h = 0; for (char aChar : chars) { h = 31 * h + aChar; } return h; } /** * Converts a primitive type to boolean. * * @param mv method visitor * @param type primitive type to convert */ public static void convertPrimitiveToBoolean(MethodVisitor mv, ClassNode type) { if (type == boolean_TYPE) { return; } // Special handling is done for floating point types in order to // handle checking for 0 or NaN values. if (type == double_TYPE) { convertDoubleToBoolean(mv); return; } else if (type == float_TYPE) { convertFloatToBoolean(mv); return; } Label trueLabel = new Label(); Label falseLabel = new Label(); // Convert long to int for IFEQ comparison using LCMP if (type == long_TYPE) { mv.visitInsn(LCONST_0); mv.visitInsn(LCMP); } // This handles byte, short, char and int mv.visitJumpInsn(IFEQ, falseLabel); mv.visitInsn(ICONST_1); mv.visitJumpInsn(GOTO, trueLabel); mv.visitLabel(falseLabel); mv.visitInsn(ICONST_0); mv.visitLabel(trueLabel); } private static void convertDoubleToBoolean(MethodVisitor mv) { Label trueLabel = new Label(); Label falseLabel = new Label(); Label falseLabelWithPop = new Label(); mv.visitInsn(DUP2); // will need the extra for isNaN call if required mv.visitInsn(DCONST_0); mv.visitInsn(DCMPL); mv.visitJumpInsn(IFEQ, falseLabelWithPop); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "isNaN", "(D)Z", false); mv.visitJumpInsn(IFNE, falseLabel); mv.visitInsn(ICONST_1); mv.visitJumpInsn(GOTO, trueLabel); mv.visitLabel(falseLabelWithPop); mv.visitInsn(POP2); mv.visitLabel(falseLabel); mv.visitInsn(ICONST_0); mv.visitLabel(trueLabel); } private static void convertFloatToBoolean(MethodVisitor mv) { Label trueLabel = new Label(); Label falseLabel = new Label(); Label falseLabelWithPop = new Label(); mv.visitInsn(DUP); // will need the extra for isNaN call if required mv.visitInsn(FCONST_0); mv.visitInsn(FCMPL); mv.visitJumpInsn(IFEQ, falseLabelWithPop); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "isNaN", "(F)Z", false); mv.visitJumpInsn(IFNE, falseLabel); mv.visitInsn(ICONST_1); mv.visitJumpInsn(GOTO, trueLabel); mv.visitLabel(falseLabelWithPop); mv.visitInsn(POP); mv.visitLabel(falseLabel); mv.visitInsn(ICONST_0); mv.visitLabel(trueLabel); } public static void doReturn(MethodVisitor mv, ClassNode type) { new ReturnVarHandler(mv, type).handle(); } public static void load(MethodVisitor mv, ClassNode type, int idx) { new LoadVarHandler(mv, type, idx).handle(); } public static void store(MethodVisitor mv, ClassNode type, int idx) { new StoreVarHandler(mv, type, idx).handle(); } private static class ReturnVarHandler extends PrimitiveTypeHandler { private MethodVisitor mv; public ReturnVarHandler(MethodVisitor mv, ClassNode type) { super(type); this.mv = mv; } @Override protected void handleDoubleType() { mv.visitInsn(DRETURN); } @Override protected void handleFloatType() { mv.visitInsn(FRETURN); } @Override protected void handleLongType() { mv.visitInsn(LRETURN); } @Override protected void handleIntType() { mv.visitInsn(IRETURN); } @Override protected void handleVoidType() { mv.visitInsn(RETURN); } @Override protected void handleRefType() { mv.visitInsn(ARETURN); } } private static class LoadVarHandler extends PrimitiveTypeHandler { private MethodVisitor mv; private int idx; public LoadVarHandler(MethodVisitor mv, ClassNode type, int idx) { super(type); this.mv = mv; this.idx = idx; } @Override protected void handleDoubleType() { mv.visitVarInsn(DLOAD, idx); } @Override protected void handleFloatType() { mv.visitVarInsn(FLOAD, idx); } @Override protected void handleLongType() { mv.visitVarInsn(LLOAD, idx); } @Override protected void handleIntType() { mv.visitVarInsn(ILOAD, idx); } @Override protected void handleVoidType() { // do nothing } @Override protected void handleRefType() { mv.visitVarInsn(ALOAD, idx); } } private static class StoreVarHandler extends PrimitiveTypeHandler { private MethodVisitor mv; private int idx; public StoreVarHandler(MethodVisitor mv, ClassNode type, int idx) { super(type); this.mv = mv; this.idx = idx; } @Override protected void handleDoubleType() { mv.visitVarInsn(DSTORE, idx); } @Override protected void handleFloatType() { mv.visitVarInsn(FSTORE, idx); } @Override protected void handleLongType() { mv.visitVarInsn(LSTORE, idx); } @Override protected void handleIntType() { mv.visitVarInsn(ISTORE, idx); } @Override protected void handleVoidType() { // do nothing } @Override protected void handleRefType() { mv.visitVarInsn(ASTORE, idx); } } private static abstract class PrimitiveTypeHandler { private ClassNode type; public PrimitiveTypeHandler(ClassNode type) { this.type = type; } public void handle() { if (type == double_TYPE) { handleDoubleType(); } else if (type == float_TYPE) { handleFloatType(); } else if (type == long_TYPE) { handleLongType(); } else if (type == boolean_TYPE || type == char_TYPE || type == byte_TYPE || type == int_TYPE || type == short_TYPE) { handleIntType(); } else if (type == VOID_TYPE) { handleVoidType(); } else { handleRefType(); } } protected abstract void handleDoubleType(); protected abstract void handleFloatType(); protected abstract void handleLongType(); /** * boolean, char, byte, int, short types are handle in the same way */ protected abstract void handleIntType(); protected abstract void handleVoidType(); protected abstract void handleRefType(); } }