/* * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang.reflect; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Label; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import sun.security.action.GetBooleanAction; import java.io.IOException; import java.lang.invoke.MethodType; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import static jdk.internal.org.objectweb.asm.Opcodes.*; /** * ProxyGenerator contains the code to generate a dynamic proxy class * for the java.lang.reflect.Proxy API. *

* The external interface to ProxyGenerator is the static * "generateProxyClass" method. */ final class ProxyGenerator extends ClassWriter { private static final String JL_CLASS = "java/lang/Class"; private static final String JL_OBJECT = "java/lang/Object"; private static final String JL_THROWABLE = "java/lang/Throwable"; private static final String JL_CLASS_NOT_FOUND_EX = "java/lang/ClassNotFoundException"; private static final String JL_NO_CLASS_DEF_FOUND_ERROR = "java/lang/NoClassDefFoundError"; private static final String JL_NO_SUCH_METHOD_EX = "java/lang/NoSuchMethodException"; private static final String JL_NO_SUCH_METHOD_ERROR = "java/lang/NoSuchMethodError"; private static final String JLR_INVOCATION_HANDLER = "java/lang/reflect/InvocationHandler"; private static final String JLR_PROXY = "java/lang/reflect/Proxy"; private static final String JLR_UNDECLARED_THROWABLE_EX = "java/lang/reflect/UndeclaredThrowableException"; private static final String LJL_CLASS = "Ljava/lang/Class;"; private static final String LJLR_METHOD = "Ljava/lang/reflect/Method;"; private static final String LJLR_INVOCATION_HANDLER = "Ljava/lang/reflect/InvocationHandler;"; private static final String MJLR_INVOCATIONHANDLER = "(Ljava/lang/reflect/InvocationHandler;)V"; private static final String NAME_CTOR = ""; private static final String NAME_CLINIT = ""; private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; /** * name of field for storing a proxy instance's invocation handler */ private static final String handlerFieldName = "h"; /** * debugging flag for saving generated class files */ private static final boolean saveGeneratedFiles = java.security.AccessController.doPrivileged( new GetBooleanAction( "jdk.proxy.ProxyGenerator.saveGeneratedFiles")); /* Preloaded ProxyMethod objects for methods in java.lang.Object */ private final static ProxyMethod hashCodeMethod; private final static ProxyMethod equalsMethod; private final static ProxyMethod toStringMethod; static { try { hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), "m0"); equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), "m1"); toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), "m2"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } /** * Class loader */ private final ClassLoader loader; /** * Name of proxy class */ private final String className; /** * Proxy interfaces */ private final List> interfaces; /** * Proxy class access flags */ private final int accessFlags; /** * Maps method signature string to list of ProxyMethod objects for * proxy methods with that signature. * Kept in insertion order to make it easier to compare old and new. */ private final Map> proxyMethods = new LinkedHashMap<>(); /** * Ordinal of next ProxyMethod object added to proxyMethods. * Indexes are reserved for hashcode(0), equals(1), toString(2). */ private int proxyMethodCount = 3; /** * Construct a ProxyGenerator to generate a proxy class with the * specified name and for the given interfaces. *

* A ProxyGenerator object contains the state for the ongoing * generation of a particular proxy class. */ private ProxyGenerator(ClassLoader loader, String className, List> interfaces, int accessFlags) { super(ClassWriter.COMPUTE_FRAMES); this.loader = loader; this.className = className; this.interfaces = interfaces; this.accessFlags = accessFlags; } /** * Generate a proxy class given a name and a list of proxy interfaces. * * @param name the class name of the proxy class * @param interfaces proxy interfaces * @param accessFlags access flags of the proxy class */ static byte[] generateProxyClass(ClassLoader loader, final String name, List> interfaces, int accessFlags) { ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); final byte[] classFile = gen.generateClassFile(); if (saveGeneratedFiles) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Void run() { try { int i = name.lastIndexOf('.'); Path path; if (i > 0) { Path dir = Path.of(dotToSlash(name.substring(0, i))); Files.createDirectories(dir); path = dir.resolve(name.substring(i + 1) + ".class"); } else { path = Path.of(name + ".class"); } Files.write(path, classFile); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); } } }); } return classFile; } /** * Return an array of the type names from an array of Classes. * * @param classes an array of classes or interfaces * @return the array of class names; or null if there are no classes */ private static String[] typeNames(List> classes) { if (classes == null || classes.size() == 0) return null; int size = classes.size(); String[] ifaces = new String[size]; for (int i = 0; i < size; i++) ifaces[i] = dotToSlash(classes.get(i).getName()); return ifaces; } /** * For a given set of proxy methods with the same signature, check * that their return types are compatible according to the Proxy * specification. * * Specifically, if there is more than one such method, then all * of the return types must be reference types, and there must be * one return type that is assignable to each of the rest of them. */ private static void checkReturnTypes(List methods) { /* * If there is only one method with a given signature, there * cannot be a conflict. This is the only case in which a * primitive (or void) return type is allowed. */ if (methods.size() < 2) { return; } /* * List of return types that are not yet known to be * assignable from ("covered" by) any of the others. */ LinkedList> uncoveredReturnTypes = new LinkedList<>(); nextNewReturnType: for (ProxyMethod pm : methods) { Class newReturnType = pm.returnType; if (newReturnType.isPrimitive()) { throw new IllegalArgumentException( "methods with same signature " + pm.shortSignature + " but incompatible return types: " + newReturnType.getName() + " and others"); } boolean added = false; /* * Compare the new return type to the existing uncovered * return types. */ ListIterator> liter = uncoveredReturnTypes.listIterator(); while (liter.hasNext()) { Class uncoveredReturnType = liter.next(); /* * If an existing uncovered return type is assignable * to this new one, then we can forget the new one. */ if (newReturnType.isAssignableFrom(uncoveredReturnType)) { assert !added; continue nextNewReturnType; } /* * If the new return type is assignable to an existing * uncovered one, then should replace the existing one * with the new one (or just forget the existing one, * if the new one has already be put in the list). */ if (uncoveredReturnType.isAssignableFrom(newReturnType)) { // (we can assume that each return type is unique) if (!added) { liter.set(newReturnType); added = true; } else { liter.remove(); } } } /* * If we got through the list of existing uncovered return * types without an assignability relationship, then add * the new return type to the list of uncovered ones. */ if (!added) { uncoveredReturnTypes.add(newReturnType); } } /* * We shouldn't end up with more than one return type that is * not assignable from any of the others. */ if (uncoveredReturnTypes.size() > 1) { ProxyMethod pm = methods.get(0); throw new IllegalArgumentException( "methods with same signature " + pm.shortSignature + " but incompatible return types: " + uncoveredReturnTypes); } } /** * Given the exceptions declared in the throws clause of a proxy method, * compute the exceptions that need to be caught from the invocation * handler's invoke method and rethrown intact in the method's * implementation before catching other Throwables and wrapping them * in UndeclaredThrowableExceptions. * * The exceptions to be caught are returned in a List object. Each * exception in the returned list is guaranteed to not be a subclass of * any of the other exceptions in the list, so the catch blocks for * these exceptions may be generated in any order relative to each other. * * Error and RuntimeException are each always contained by the returned * list (if none of their superclasses are contained), since those * unchecked exceptions should always be rethrown intact, and thus their * subclasses will never appear in the returned list. * * The returned List will be empty if java.lang.Throwable is in the * given list of declared exceptions, indicating that no exceptions * need to be caught. */ private static List> computeUniqueCatchList(Class[] exceptions) { List> uniqueList = new ArrayList<>(); // unique exceptions to catch uniqueList.add(Error.class); // always catch/rethrow these uniqueList.add(RuntimeException.class); nextException: for (Class ex : exceptions) { if (ex.isAssignableFrom(Throwable.class)) { /* * If Throwable is declared to be thrown by the proxy method, * then no catch blocks are necessary, because the invoke * can, at most, throw Throwable anyway. */ uniqueList.clear(); break; } else if (!Throwable.class.isAssignableFrom(ex)) { /* * Ignore types that cannot be thrown by the invoke method. */ continue; } /* * Compare this exception against the current list of * exceptions that need to be caught: */ for (int j = 0; j < uniqueList.size(); ) { Class ex2 = uniqueList.get(j); if (ex2.isAssignableFrom(ex)) { /* * if a superclass of this exception is already on * the list to catch, then ignore this one and continue; */ continue nextException; } else if (ex.isAssignableFrom(ex2)) { /* * if a subclass of this exception is on the list * to catch, then remove it; */ uniqueList.remove(j); } else { j++; // else continue comparing. } } // This exception is unique (so far): add it to the list to catch. uniqueList.add(ex); } return uniqueList; } /** * Convert a fully qualified class name that uses '.' as the package * separator, the external representation used by the Java language * and APIs, to a fully qualified class name that uses '/' as the * package separator, the representation used in the class file * format (see JVMS section 4.2). */ private static String dotToSlash(String name) { return name.replace('.', '/'); } /** * Return the number of abstract "words", or consecutive local variable * indexes, required to contain a value of the given type. See JVMS * section 3.6.1. *

* Note that the original version of the JVMS contained a definition of * this abstract notion of a "word" in section 3.4, but that definition * was removed for the second edition. */ private static int getWordsPerType(Class type) { if (type == long.class || type == double.class) { return 2; } else { return 1; } } /** * Add to the given list all of the types in the "from" array that * are not already contained in the list and are assignable to at * least one of the types in the "with" array. *

* This method is useful for computing the greatest common set of * declared exceptions from duplicate methods inherited from * different interfaces. */ private static void collectCompatibleTypes(Class[] from, Class[] with, List> list) { for (Class fc : from) { if (!list.contains(fc)) { for (Class wc : with) { if (wc.isAssignableFrom(fc)) { list.add(fc); break; } } } } } /** * Returns the {@link ClassLoader} to be used by the default implementation of {@link * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by * default. * * @return ClassLoader */ protected ClassLoader getClassLoader() { return loader; } /** * Generate a class file for the proxy class. This method drives the * class file generation process. */ private byte[] generateClassFile() { visit(V15, accessFlags, dotToSlash(className), null, JLR_PROXY, typeNames(interfaces)); /* * Add proxy methods for the hashCode, equals, * and toString methods of java.lang.Object. This is done before * the methods from the proxy interfaces so that the methods from * java.lang.Object take precedence over duplicate methods in the * proxy interfaces. */ addProxyMethod(hashCodeMethod); addProxyMethod(equalsMethod); addProxyMethod(toStringMethod); /* * Accumulate all of the methods from the proxy interfaces. */ for (Class intf : interfaces) { for (Method m : intf.getMethods()) { if (!Modifier.isStatic(m.getModifiers())) { addProxyMethod(m, intf); } } } /* * For each set of proxy methods with the same signature, * verify that the methods' return types are compatible. */ for (List sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } generateConstructor(); for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // add static field for the Method object visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName, LJLR_METHOD, null, null); // Generate code for proxy method pm.generateMethod(this, className); } } generateStaticInitializer(); return toByteArray(); } /** * Add another method to be proxied, either by creating a new * ProxyMethod object or augmenting an old one for a duplicate * method. * * "fromClass" indicates the proxy interface that the method was * found through, which may be different from (a subinterface of) * the method's "declaring class". Note that the first Method * object passed for a given name and descriptor identifies the * Method object (and thus the declaring class) that will be * passed to the invocation handler's "invoke" method for a given * set of duplicate methods. */ private void addProxyMethod(Method m, Class fromClass) { Class returnType = m.getReturnType(); Class[] exceptionTypes = m.getExceptionTypes(); String sig = m.toShortSignature(); List sigmethods = proxyMethods.computeIfAbsent(sig, (f) -> new ArrayList<>(3)); for (ProxyMethod pm : sigmethods) { if (returnType == pm.returnType) { /* * Found a match: reduce exception types to the * greatest set of exceptions that can be thrown * compatibly with the throws clauses of both * overridden methods. */ List> legalExceptions = new ArrayList<>(); collectCompatibleTypes( exceptionTypes, pm.exceptionTypes, legalExceptions); collectCompatibleTypes( pm.exceptionTypes, exceptionTypes, legalExceptions); pm.exceptionTypes = legalExceptions.toArray(EMPTY_CLASS_ARRAY); return; } } sigmethods.add(new ProxyMethod(m, sig, m.getParameterTypes(), returnType, exceptionTypes, fromClass, "m" + proxyMethodCount++)); } /** * Add an existing ProxyMethod (hashcode, equals, toString). * * @param pm an existing ProxyMethod */ private void addProxyMethod(ProxyMethod pm) { String sig = pm.shortSignature; List sigmethods = proxyMethods.computeIfAbsent(sig, (f) -> new ArrayList<>(3)); sigmethods.add(pm); } /** * Generate the constructor method for the proxy class. */ private void generateConstructor() { MethodVisitor ctor = visitMethod(Modifier.PUBLIC, NAME_CTOR, MJLR_INVOCATIONHANDLER, null, null); ctor.visitParameter(null, 0); ctor.visitCode(); ctor.visitVarInsn(ALOAD, 0); ctor.visitVarInsn(ALOAD, 1); ctor.visitMethodInsn(INVOKESPECIAL, JLR_PROXY, NAME_CTOR, MJLR_INVOCATIONHANDLER, false); ctor.visitInsn(RETURN); // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored ctor.visitMaxs(-1, -1); ctor.visitEnd(); } /** * Generate the static initializer method for the proxy class. */ private void generateStaticInitializer() { MethodVisitor mv = visitMethod(Modifier.STATIC, NAME_CLINIT, "()V", null, null); mv.visitCode(); Label L_startBlock = new Label(); Label L_endBlock = new Label(); Label L_NoMethodHandler = new Label(); Label L_NoClassHandler = new Label(); mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoMethodHandler, JL_NO_SUCH_METHOD_EX); mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoClassHandler, JL_CLASS_NOT_FOUND_EX); mv.visitLabel(L_startBlock); for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { pm.codeFieldInitialization(mv, className); } } mv.visitInsn(RETURN); mv.visitLabel(L_endBlock); // Generate exception handler mv.visitLabel(L_NoMethodHandler); mv.visitVarInsn(ASTORE, 1); mv.visitTypeInsn(Opcodes.NEW, JL_NO_SUCH_METHOD_ERROR); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, "getMessage", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, JL_NO_SUCH_METHOD_ERROR, "", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); mv.visitLabel(L_NoClassHandler); mv.visitVarInsn(ASTORE, 1); mv.visitTypeInsn(Opcodes.NEW, JL_NO_CLASS_DEF_FOUND_ERROR); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, "getMessage", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, JL_NO_CLASS_DEF_FOUND_ERROR, "", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored mv.visitMaxs(-1, -1); mv.visitEnd(); } /** * A ProxyMethod object represents a proxy method in the proxy class * being generated: a method whose implementation will encode and * dispatch invocations to the proxy instance's invocation handler. */ private static class ProxyMethod { private final Method method; private final String shortSignature; private final Class fromClass; private final Class[] parameterTypes; private final Class returnType; private final String methodFieldName; private Class[] exceptionTypes; private ProxyMethod(Method method, String sig, Class[] parameterTypes, Class returnType, Class[] exceptionTypes, Class fromClass, String methodFieldName) { this.method = method; this.shortSignature = sig; this.parameterTypes = parameterTypes; this.returnType = returnType; this.exceptionTypes = exceptionTypes; this.fromClass = fromClass; this.methodFieldName = methodFieldName; } /** * Create a new specific ProxyMethod with a specific field name * * @param method The method for which to create a proxy * @param methodFieldName the fieldName to generate */ private ProxyMethod(Method method, String methodFieldName) { this(method, method.toShortSignature(), method.getParameterTypes(), method.getReturnType(), method.getExceptionTypes(), method.getDeclaringClass(), methodFieldName); } /** * Generate this method, including the code and exception table entry. */ private void generateMethod(ClassWriter cw, String className) { MethodType mt = MethodType.methodType(returnType, parameterTypes); String desc = mt.toMethodDescriptorString(); MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_FINAL, method.getName(), desc, null, typeNames(Arrays.asList(exceptionTypes))); int[] parameterSlot = new int[parameterTypes.length]; int nextSlot = 1; for (int i = 0; i < parameterSlot.length; i++) { parameterSlot[i] = nextSlot; nextSlot += getWordsPerType(parameterTypes[i]); } mv.visitCode(); Label L_startBlock = new Label(); Label L_endBlock = new Label(); Label L_RuntimeHandler = new Label(); Label L_ThrowableHandler = new Label(); List> catchList = computeUniqueCatchList(exceptionTypes); if (catchList.size() > 0) { for (Class ex : catchList) { mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_RuntimeHandler, dotToSlash(ex.getName())); } mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_ThrowableHandler, JL_THROWABLE); } mv.visitLabel(L_startBlock); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, JLR_PROXY, handlerFieldName, LJLR_INVOCATION_HANDLER); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETSTATIC, dotToSlash(className), methodFieldName, LJLR_METHOD); if (parameterTypes.length > 0) { // Create an array and fill with the parameters converting primitives to wrappers emitIconstInsn(mv, parameterTypes.length); mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_OBJECT); for (int i = 0; i < parameterTypes.length; i++) { mv.visitInsn(DUP); emitIconstInsn(mv, i); codeWrapArgument(mv, parameterTypes[i], parameterSlot[i]); mv.visitInsn(Opcodes.AASTORE); } } else { mv.visitInsn(Opcodes.ACONST_NULL); } mv.visitMethodInsn(INVOKEINTERFACE, JLR_INVOCATION_HANDLER, "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;" + "[Ljava/lang/Object;)Ljava/lang/Object;", true); if (returnType == void.class) { mv.visitInsn(POP); mv.visitInsn(RETURN); } else { codeUnwrapReturnValue(mv, returnType); } mv.visitLabel(L_endBlock); // Generate exception handler mv.visitLabel(L_RuntimeHandler); mv.visitInsn(ATHROW); // just rethrow the exception mv.visitLabel(L_ThrowableHandler); mv.visitVarInsn(ASTORE, 1); mv.visitTypeInsn(Opcodes.NEW, JLR_UNDECLARED_THROWABLE_EX); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, JLR_UNDECLARED_THROWABLE_EX, "", "(Ljava/lang/Throwable;)V", false); mv.visitInsn(ATHROW); // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored mv.visitMaxs(-1, -1); mv.visitEnd(); } /** * Generate code for wrapping an argument of the given type * whose value can be found at the specified local variable * index, in order for it to be passed (as an Object) to the * invocation handler's "invoke" method. */ private void codeWrapArgument(MethodVisitor mv, Class type, int slot) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); if (type == int.class || type == boolean.class || type == byte.class || type == char.class || type == short.class) { mv.visitVarInsn(ILOAD, slot); } else if (type == long.class) { mv.visitVarInsn(LLOAD, slot); } else if (type == float.class) { mv.visitVarInsn(FLOAD, slot); } else if (type == double.class) { mv.visitVarInsn(DLOAD, slot); } else { throw new AssertionError(); } mv.visitMethodInsn(INVOKESTATIC, prim.wrapperClassName, "valueOf", prim.wrapperValueOfDesc, false); } else { mv.visitVarInsn(ALOAD, slot); } } /** * Generate code for unwrapping a return value of the given * type from the invocation handler's "invoke" method (as type * Object) to its correct type. */ private void codeUnwrapReturnValue(MethodVisitor mv, Class type) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); mv.visitTypeInsn(CHECKCAST, prim.wrapperClassName); mv.visitMethodInsn(INVOKEVIRTUAL, prim.wrapperClassName, prim.unwrapMethodName, prim.unwrapMethodDesc, false); if (type == int.class || type == boolean.class || type == byte.class || type == char.class || type == short.class) { mv.visitInsn(IRETURN); } else if (type == long.class) { mv.visitInsn(LRETURN); } else if (type == float.class) { mv.visitInsn(FRETURN); } else if (type == double.class) { mv.visitInsn(DRETURN); } else { throw new AssertionError(); } } else { String internalName = dotToSlash(type.getName()); if (type.isInlineClass() && !type.isIndirectType()) { internalName = 'Q' + internalName + ";"; } mv.visitTypeInsn(CHECKCAST, internalName); mv.visitInsn(ARETURN); } } /** * Generate code for initializing the static field that stores * the Method object for this proxy method. */ private void codeFieldInitialization(MethodVisitor mv, String className) { codeClassForName(mv, fromClass); mv.visitLdcInsn(method.getName()); emitIconstInsn(mv, parameterTypes.length); mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_CLASS); // Construct an array with the parameter types mapping primitives to Wrapper types for (int i = 0; i < parameterTypes.length; i++) { mv.visitInsn(DUP); emitIconstInsn(mv, i); if (parameterTypes[i].isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); mv.visitFieldInsn(GETSTATIC, prim.wrapperClassName, "TYPE", LJL_CLASS); } else { codeClassForName(mv, parameterTypes[i]); } mv.visitInsn(Opcodes.AASTORE); } // lookup the method mv.visitMethodInsn(INVOKEVIRTUAL, JL_CLASS, "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); mv.visitFieldInsn(PUTSTATIC, dotToSlash(className), methodFieldName, LJLR_METHOD); } /* * =============== Code Generation Utility Methods =============== */ /** * Generate code to invoke the Class.forName with the name of the given * class to get its Class object at runtime. And also generate code * to invoke Class.asPrimaryType if the class is regular value type. * * The code is written to the supplied stream. Note that the code generated * by this method may caused the checked ClassNotFoundException to be thrown. */ private void codeClassForName(MethodVisitor mv, Class cl) { mv.visitLdcInsn(cl.getName()); mv.visitMethodInsn(INVOKESTATIC, JL_CLASS, "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); if (cl.isInlineClass() && cl == cl.asPrimaryType()) { mv.visitMethodInsn(INVOKEVIRTUAL, JL_CLASS, "asPrimaryType", "()Ljava/lang/Class;", false); } } /** * Visit a bytecode for a constant. * * @param mv The MethodVisitor * @param cst The constant value */ private void emitIconstInsn(MethodVisitor mv, final int cst) { if (cst >= -1 && cst <= 5) { mv.visitInsn(Opcodes.ICONST_0 + cst); } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { mv.visitIntInsn(Opcodes.BIPUSH, cst); } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { mv.visitIntInsn(Opcodes.SIPUSH, cst); } else { mv.visitLdcInsn(cst); } } @Override public String toString() { return method.toShortString(); } } /** * A PrimitiveTypeInfo object contains assorted information about * a primitive type in its public fields. The struct for a particular * primitive type can be obtained using the static "get" method. */ private static class PrimitiveTypeInfo { private static Map, PrimitiveTypeInfo> table = new HashMap<>(); static { add(byte.class, Byte.class); add(char.class, Character.class); add(double.class, Double.class); add(float.class, Float.class); add(int.class, Integer.class); add(long.class, Long.class); add(short.class, Short.class); add(boolean.class, Boolean.class); } /** * name of corresponding wrapper class */ private String wrapperClassName; /** * method descriptor for wrapper class "valueOf" factory method */ private String wrapperValueOfDesc; /** * name of wrapper class method for retrieving primitive value */ private String unwrapMethodName; /** * descriptor of same method */ private String unwrapMethodDesc; private PrimitiveTypeInfo(Class primitiveClass, Class wrapperClass) { assert primitiveClass.isPrimitive(); /** * "base type" used in various descriptors (see JVMS section 4.3.2) */ String baseTypeString = Array.newInstance(primitiveClass, 0) .getClass().getName().substring(1); wrapperClassName = dotToSlash(wrapperClass.getName()); wrapperValueOfDesc = "(" + baseTypeString + ")L" + wrapperClassName + ";"; unwrapMethodName = primitiveClass.getName() + "Value"; unwrapMethodDesc = "()" + baseTypeString; } private static void add(Class primitiveClass, Class wrapperClass) { table.put(primitiveClass, new PrimitiveTypeInfo(primitiveClass, wrapperClass)); } public static PrimitiveTypeInfo get(Class cl) { return table.get(cl); } } }