| /* |
| * Copyright (c) 1999, 2021, 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 jdk.internal.org.objectweb.asm.Type; |
| 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. |
| * <p> |
| * 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_ILLEGAL_ACCESS_EX = "java/lang/IllegalAccessException"; |
| |
| 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 JLI_LOOKUP = "java/lang/invoke/MethodHandles$Lookup"; |
| private static final String JLI_METHODHANDLES = "java/lang/invoke/MethodHandles"; |
| |
| 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 = "<init>"; |
| private static final String NAME_CLINIT = "<clinit>"; |
| private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup"; |
| |
| 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 |
| */ |
| @SuppressWarnings("removal") |
| 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 static final ProxyMethod hashCodeMethod; |
| private static final ProxyMethod equalsMethod; |
| private static final 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<Class<?>> 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<String, List<ProxyMethod>> 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. |
| * <p> |
| * A ProxyGenerator object contains the state for the ongoing |
| * generation of a particular proxy class. |
| */ |
| private ProxyGenerator(ClassLoader loader, String className, List<Class<?>> 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 |
| */ |
| @SuppressWarnings("removal") |
| static byte[] generateProxyClass(ClassLoader loader, |
| final String name, |
| List<Class<?>> 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<Void>() { |
| 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 class and interface names from an array of Classes. |
| * |
| * @param classes an array of classes or interfaces |
| * @return the array of class and interface names; or null if classes is |
| * null or empty |
| */ |
| private static String[] typeNames(List<Class<?>> 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<ProxyMethod> 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<Class<?>> 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<Class<?>> 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<Class<?>> computeUniqueCatchList(Class<?>[] exceptions) { |
| List<Class<?>> 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 {@jvms 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 {@jvms 3.6.1}. |
| * <p> |
| * 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. |
| * <p> |
| * 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<Class<?>> 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(V14, 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<ProxyMethod> sigmethods : proxyMethods.values()) { |
| checkReturnTypes(sigmethods); |
| } |
| |
| generateConstructor(); |
| |
| for (List<ProxyMethod> sigmethods : proxyMethods.values()) { |
| for (ProxyMethod pm : sigmethods) { |
| // add static field for the Method object |
| visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName, |
| LJLR_METHOD, null, null); |
| |
| // Generate code for proxy method |
| pm.generateMethod(this, className); |
| } |
| } |
| |
| generateStaticInitializer(); |
| generateLookupAccessor(); |
| 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<ProxyMethod> 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<Class<?>> 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<ProxyMethod> 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<ProxyMethod> 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, |
| "<init>", "(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, |
| "<init>", "(Ljava/lang/String;)V", false); |
| mv.visitInsn(ATHROW); |
| |
| // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| } |
| |
| /** |
| * Generate the static lookup accessor method that returns the Lookup |
| * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy; |
| * otherwise, IllegalAccessException is thrown |
| */ |
| private void generateLookupAccessor() { |
| MethodVisitor mv = visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_LOOKUP_ACCESSOR, |
| "(Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;", null, |
| new String[] { JL_ILLEGAL_ACCESS_EX }); |
| mv.visitCode(); |
| Label L_illegalAccess = new Label(); |
| |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "lookupClass", |
| "()Ljava/lang/Class;", false); |
| mv.visitLdcInsn(Type.getType(Proxy.class)); |
| mv.visitJumpInsn(IF_ACMPNE, L_illegalAccess); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "hasFullPrivilegeAccess", |
| "()Z", false); |
| mv.visitJumpInsn(IFEQ, L_illegalAccess); |
| mv.visitMethodInsn(INVOKESTATIC, JLI_METHODHANDLES, "lookup", |
| "()Ljava/lang/invoke/MethodHandles$Lookup;", false); |
| mv.visitInsn(ARETURN); |
| |
| mv.visitLabel(L_illegalAccess); |
| mv.visitTypeInsn(Opcodes.NEW, JL_ILLEGAL_ACCESS_EX); |
| mv.visitInsn(DUP); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "toString", |
| "()Ljava/lang/String;", false); |
| mv.visitMethodInsn(INVOKESPECIAL, JL_ILLEGAL_ACCESS_EX, |
| "<init>", "(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(); |
| int accessFlags = ACC_PUBLIC | ACC_FINAL; |
| if (method.isVarArgs()) accessFlags |= ACC_VARARGS; |
| |
| MethodVisitor mv = cw.visitMethod(accessFlags, |
| 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<Class<?>> 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, |
| "<init>", "(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 { |
| mv.visitTypeInsn(CHECKCAST, dotToSlash(type.getName())); |
| 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. The code is written to |
| * the supplied stream. Note that the code generated by this method |
| * may cause 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); |
| } |
| |
| /** |
| * 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<Class<?>, 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); |
| } |
| } |
| } |