1 /*
  2  * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package java.lang.reflect;
 27 
 28 import java.io.IOException;
 29 import java.lang.classfile.*;
 30 import java.lang.classfile.attribute.ExceptionsAttribute;
 31 import java.lang.classfile.constantpool.*;
 32 import java.lang.constant.ClassDesc;
 33 import java.lang.constant.MethodTypeDesc;
 34 import java.nio.file.Files;
 35 import java.nio.file.Path;
 36 import java.util.ArrayList;
 37 import java.util.LinkedHashMap;
 38 import java.util.List;
 39 import java.util.ListIterator;
 40 import java.util.Map;
 41 import java.util.Objects;
 42 
 43 import jdk.internal.constant.ClassOrInterfaceDescImpl;
 44 import jdk.internal.constant.ConstantUtils;
 45 import jdk.internal.constant.MethodTypeDescImpl;
 46 import sun.security.action.GetBooleanAction;
 47 
 48 import static java.lang.classfile.ClassFile.*;
 49 import java.lang.classfile.attribute.StackMapFrameInfo;
 50 import java.lang.classfile.attribute.StackMapTableAttribute;
 51 
 52 import static java.lang.constant.ConstantDescs.*;
 53 import static jdk.internal.constant.ConstantUtils.*;
 54 
 55 /**
 56  * ProxyGenerator contains the code to generate a dynamic proxy class
 57  * for the java.lang.reflect.Proxy API.
 58  * <p>
 59  * The external interface to ProxyGenerator is the static
 60  * "generateProxyClass" method.
 61  */
 62 final class ProxyGenerator {
 63 
 64     private static final ClassFile CF_CONTEXT =
 65             ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS);
 66 
 67     private static final ClassDesc
 68             CD_ClassLoader = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/ClassLoader;"),
 69             CD_Class_array = CD_Class.arrayType(),
 70             CD_ClassNotFoundException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/ClassNotFoundException;"),
 71             CD_NoClassDefFoundError = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/NoClassDefFoundError;"),
 72             CD_IllegalAccessException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/IllegalAccessException;"),
 73             CD_InvocationHandler = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/InvocationHandler;"),
 74             CD_Method = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/Method;"),
 75             CD_NoSuchMethodError = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/NoSuchMethodError;"),
 76             CD_NoSuchMethodException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/NoSuchMethodException;"),
 77             CD_Object_array = ConstantUtils.CD_Object_array,
 78             CD_Proxy = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/Proxy;"),
 79             CD_UndeclaredThrowableException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/UndeclaredThrowableException;");
 80 
 81     private static final MethodTypeDesc
 82             MTD_boolean = MethodTypeDescImpl.ofValidated(CD_boolean),
 83             MTD_void_InvocationHandler = MethodTypeDescImpl.ofValidated(CD_void, CD_InvocationHandler),
 84             MTD_void_String = MethodTypeDescImpl.ofValidated(CD_void, CD_String),
 85             MTD_void_Throwable = MethodTypeDescImpl.ofValidated(CD_void, CD_Throwable),
 86             MTD_Class = MethodTypeDescImpl.ofValidated(CD_Class),
 87             MTD_Class_String_boolean_ClassLoader = MethodTypeDescImpl.ofValidated(CD_Class, CD_String, CD_boolean, CD_ClassLoader),
 88             MTD_ClassLoader = MethodTypeDescImpl.ofValidated(CD_ClassLoader),
 89             MTD_Method_String_Class_array = MethodTypeDescImpl.ofValidated(CD_Method, CD_String, CD_Class_array),
 90             MTD_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup),
 91             MTD_MethodHandles$Lookup_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup, CD_MethodHandles_Lookup),
 92             MTD_Object_Object_Method_ObjectArray = MethodTypeDescImpl.ofValidated(CD_Object, CD_Object, CD_Method, CD_Object_array),
 93             MTD_String = MethodTypeDescImpl.ofValidated(CD_String);
 94 
 95     private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup";
 96 
 97     private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
 98 
 99     /**
100      * name of field for storing a proxy instance's invocation handler
101      */
102     private static final String NAME_HANDLER_FIELD = "h";
103 
104     /**
105      * debugging flag for saving generated class files
106      */
107     @SuppressWarnings("removal")
108     private static final boolean SAVE_GENERATED_FILES =
109             java.security.AccessController.doPrivileged(
110                     new GetBooleanAction(
111                             "jdk.proxy.ProxyGenerator.saveGeneratedFiles"));
112 
113     /* Preloaded ProxyMethod objects for methods in java.lang.Object */
114     private static final Method OBJECT_HASH_CODE_METHOD;
115     private static final Method OBJECT_EQUALS_METHOD;
116     private static final Method OBJECT_TO_STRING_METHOD;
117 
118     private static final String OBJECT_HASH_CODE_SIG;
119     private static final String OBJECT_EQUALS_SIG;
120     private static final String OBJECT_TO_STRING_SIG;
121 
122     static {
123         try {
124             OBJECT_HASH_CODE_METHOD = Object.class.getMethod("hashCode");
125             OBJECT_HASH_CODE_SIG = OBJECT_HASH_CODE_METHOD.toShortSignature();
126             OBJECT_EQUALS_METHOD = Object.class.getMethod("equals", Object.class);
127             OBJECT_EQUALS_SIG = OBJECT_EQUALS_METHOD.toShortSignature();
128             OBJECT_TO_STRING_METHOD = Object.class.getMethod("toString");
129             OBJECT_TO_STRING_SIG = OBJECT_TO_STRING_METHOD.toShortSignature();
130         } catch (NoSuchMethodException e) {
131             throw new NoSuchMethodError(e.getMessage());
132         }
133     }
134 
135     private final ConstantPoolBuilder cp;
136     private final List<StackMapFrameInfo.VerificationTypeInfo> classLoaderLocal, throwableStack;
137     private final NameAndTypeEntry exInit;
138     private final ClassEntry objectCE, proxyCE, uteCE, classCE;
139     private final FieldRefEntry handlerField;
140     private final InterfaceMethodRefEntry invocationHandlerInvoke;
141     private final MethodRefEntry uteInit, classGetMethod, classForName, throwableGetMessage;
142 
143 
144     /**
145      * ClassEntry for this proxy class
146      */
147     private final ClassEntry thisClassCE;
148 
149     /**
150      * Proxy interfaces
151      */
152     private final List<Class<?>> interfaces;
153 
154     /**
155      * Proxy class access flags
156      */
157     private final int accessFlags;
158 
159     /**
160      * Maps method signature string to list of ProxyMethod objects for
161      * proxy methods with that signature.
162      * Kept in insertion order to make it easier to compare old and new.
163      */
164     private final Map<String, List<ProxyMethod>> proxyMethods = new LinkedHashMap<>();
165 
166     /**
167      * Ordinal of next ProxyMethod object added to proxyMethods.
168      * Indexes are reserved for hashcode(0), equals(1), toString(2).
169      */
170     private int proxyMethodCount = 3;
171 
172     /**
173      * Construct a ProxyGenerator to generate a proxy class with the
174      * specified name and for the given interfaces.
175      * <p>
176      * A ProxyGenerator object contains the state for the ongoing
177      * generation of a particular proxy class.
178      */
179     private ProxyGenerator(String className, List<Class<?>> interfaces,
180                            int accessFlags) {
181         this.cp = ConstantPoolBuilder.of();
182         this.thisClassCE = cp.classEntry(ConstantUtils.binaryNameToDesc(className));
183         this.interfaces = interfaces;
184         this.accessFlags = accessFlags;
185         var throwable = cp.classEntry(CD_Throwable);
186         this.classLoaderLocal = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(cp.classEntry(CD_ClassLoader)));
187         this.throwableStack = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(throwable));
188         this.exInit = cp.nameAndTypeEntry(INIT_NAME, MTD_void_String);
189         this.objectCE = cp.classEntry(CD_Object);
190         this.proxyCE = cp.classEntry(CD_Proxy);
191         this.classCE = cp.classEntry(CD_Class);
192         this.handlerField = cp.fieldRefEntry(proxyCE, cp.nameAndTypeEntry(NAME_HANDLER_FIELD, CD_InvocationHandler));
193         this.invocationHandlerInvoke = cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray);
194         this.uteCE = cp.classEntry(CD_UndeclaredThrowableException);
195         this.uteInit = cp.methodRefEntry(uteCE, cp.nameAndTypeEntry(INIT_NAME, MTD_void_Throwable));
196         this.classGetMethod = cp.methodRefEntry(classCE, cp.nameAndTypeEntry("getMethod", MTD_Method_String_Class_array));
197         this.classForName = cp.methodRefEntry(classCE, cp.nameAndTypeEntry("forName", MTD_Class_String_boolean_ClassLoader));
198         this.throwableGetMessage = cp.methodRefEntry(throwable, cp.nameAndTypeEntry("getMessage", MTD_String));
199     }
200 
201     /**
202      * Generate a proxy class given a name and a list of proxy interfaces.
203      *
204      * @param name        the class name of the proxy class
205      * @param interfaces  proxy interfaces
206      * @param accessFlags access flags of the proxy class
207      */
208     @SuppressWarnings("removal")
209     static byte[] generateProxyClass(ClassLoader loader,
210                                      final String name,
211                                      List<Class<?>> interfaces,
212                                      int accessFlags) {
213         Objects.requireNonNull(interfaces);
214         ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
215         final byte[] classFile = gen.generateClassFile();
216 
217         if (SAVE_GENERATED_FILES) {
218             java.security.AccessController.doPrivileged(
219                     new java.security.PrivilegedAction<Void>() {
220                         public Void run() {
221                             try {
222                                 int i = name.lastIndexOf('.');
223                                 Path path;
224                                 if (i > 0) {
225                                     Path dir = Path.of(name.substring(0, i).replace('.', '/'));
226                                     Files.createDirectories(dir);
227                                     path = dir.resolve(name.substring(i + 1) + ".class");
228                                 } else {
229                                     path = Path.of(name + ".class");
230                                 }
231                                 Files.write(path, classFile);
232                                 return null;
233                             } catch (IOException e) {
234                                 throw new InternalError(
235                                         "I/O exception saving generated file: " + e);
236                             }
237                         }
238                     });
239         }
240 
241         return classFile;
242     }
243 
244     /**
245      * {@return the entries of the given type}
246      * @param types the {@code Class} objects, not primitive types nor array types
247      */
248     private static List<ClassEntry> toClassEntries(ConstantPoolBuilder cp, List<Class<?>> types) {
249         var ces = new ArrayList<ClassEntry>(types.size());
250         for (var t : types)
251             ces.add(cp.classEntry(ConstantUtils.binaryNameToDesc(t.getName())));
252         return ces;
253     }
254 
255     /**
256      * For a given set of proxy methods with the same signature, check
257      * that their return types are compatible according to the Proxy
258      * specification.
259      *
260      * Specifically, if there is more than one such method, then all
261      * of the return types must be reference types, and there must be
262      * one return type that is assignable to each of the rest of them.
263      */
264     private static void checkReturnTypes(List<ProxyMethod> methods) {
265         /*
266          * If there is only one method with a given signature, there
267          * cannot be a conflict.  This is the only case in which a
268          * primitive (or void) return type is allowed.
269          */
270         if (methods.size() < 2) {
271             return;
272         }
273 
274         /*
275          * List of return types that are not yet known to be
276          * assignable from ("covered" by) any of the others.
277          */
278         List<Class<?>> uncoveredReturnTypes = new ArrayList<>(1);
279 
280         nextNewReturnType:
281         for (ProxyMethod pm : methods) {
282             Class<?> newReturnType = pm.returnType;
283             if (newReturnType.isPrimitive()) {
284                 throw new IllegalArgumentException(
285                         "methods with same signature " +
286                                 pm.shortSignature +
287                                 " but incompatible return types: " +
288                                 newReturnType.getName() + " and others");
289             }
290             boolean added = false;
291 
292             /*
293              * Compare the new return type to the existing uncovered
294              * return types.
295              */
296             ListIterator<Class<?>> liter = uncoveredReturnTypes.listIterator();
297             while (liter.hasNext()) {
298                 Class<?> uncoveredReturnType = liter.next();
299 
300                 /*
301                  * If an existing uncovered return type is assignable
302                  * to this new one, then we can forget the new one.
303                  */
304                 if (newReturnType.isAssignableFrom(uncoveredReturnType)) {
305                     assert !added;
306                     continue nextNewReturnType;
307                 }
308 
309                 /*
310                  * If the new return type is assignable to an existing
311                  * uncovered one, then should replace the existing one
312                  * with the new one (or just forget the existing one,
313                  * if the new one has already be put in the list).
314                  */
315                 if (uncoveredReturnType.isAssignableFrom(newReturnType)) {
316                     // (we can assume that each return type is unique)
317                     if (!added) {
318                         liter.set(newReturnType);
319                         added = true;
320                     } else {
321                         liter.remove();
322                     }
323                 }
324             }
325 
326             /*
327              * If we got through the list of existing uncovered return
328              * types without an assignability relationship, then add
329              * the new return type to the list of uncovered ones.
330              */
331             if (!added) {
332                 uncoveredReturnTypes.add(newReturnType);
333             }
334         }
335 
336         /*
337          * We shouldn't end up with more than one return type that is
338          * not assignable from any of the others.
339          */
340         if (uncoveredReturnTypes.size() > 1) {
341             ProxyMethod pm = methods.getFirst();
342             throw new IllegalArgumentException(
343                     "methods with same signature " +
344                             pm.shortSignature +
345                             " but incompatible return types: " + uncoveredReturnTypes);
346         }
347     }
348 
349     /**
350      * Given the exceptions declared in the throws clause of a proxy method,
351      * compute the exceptions that need to be caught from the invocation
352      * handler's invoke method and rethrown intact in the method's
353      * implementation before catching other Throwables and wrapping them
354      * in UndeclaredThrowableExceptions.
355      *
356      * The exceptions to be caught are returned in a List object.  Each
357      * exception in the returned list is guaranteed to not be a subclass of
358      * any of the other exceptions in the list, so the catch blocks for
359      * these exceptions may be generated in any order relative to each other.
360      *
361      * Error and RuntimeException are each always contained by the returned
362      * list (if none of their superclasses are contained), since those
363      * unchecked exceptions should always be rethrown intact, and thus their
364      * subclasses will never appear in the returned list.
365      *
366      * The returned List will be empty if java.lang.Throwable is in the
367      * given list of declared exceptions, indicating that no exceptions
368      * need to be caught.
369      */
370     private static List<Class<?>> computeUniqueCatchList(Class<?>[] exceptions) {
371         List<Class<?>> uniqueList = new ArrayList<>();
372         // unique exceptions to catch
373 
374         uniqueList.add(Error.class);            // always catch/rethrow these
375         uniqueList.add(RuntimeException.class);
376 
377         nextException:
378         for (Class<?> ex : exceptions) {
379             if (ex.isAssignableFrom(Throwable.class)) {
380                 /*
381                  * If Throwable is declared to be thrown by the proxy method,
382                  * then no catch blocks are necessary, because the invoke
383                  * can, at most, throw Throwable anyway.
384                  */
385                 uniqueList.clear();
386                 break;
387             } else if (!Throwable.class.isAssignableFrom(ex)) {
388                 /*
389                  * Ignore types that cannot be thrown by the invoke method.
390                  */
391                 continue;
392             }
393             /*
394              * Compare this exception against the current list of
395              * exceptions that need to be caught:
396              */
397             for (int j = 0; j < uniqueList.size(); ) {
398                 Class<?> ex2 = uniqueList.get(j);
399                 if (ex2.isAssignableFrom(ex)) {
400                     /*
401                      * if a superclass of this exception is already on
402                      * the list to catch, then ignore this one and continue;
403                      */
404                     continue nextException;
405                 } else if (ex.isAssignableFrom(ex2)) {
406                     /*
407                      * if a subclass of this exception is on the list
408                      * to catch, then remove it;
409                      */
410                     uniqueList.remove(j);
411                 } else {
412                     j++;        // else continue comparing.
413                 }
414             }
415             // This exception is unique (so far): add it to the list to catch.
416             uniqueList.add(ex);
417         }
418         return uniqueList;
419     }
420 
421     /**
422      * Add to the given list all of the types in the "from" array that
423      * are not already contained in the list and are assignable to at
424      * least one of the types in the "with" array.
425      * <p>
426      * This method is useful for computing the greatest common set of
427      * declared exceptions from duplicate methods inherited from
428      * different interfaces.
429      */
430     private static void collectCompatibleTypes(Class<?>[] from,
431                                                Class<?>[] with,
432                                                List<Class<?>> list) {
433         for (Class<?> fc : from) {
434             if (!list.contains(fc)) {
435                 for (Class<?> wc : with) {
436                     if (wc.isAssignableFrom(fc)) {
437                         list.add(fc);
438                         break;
439                     }
440                 }
441             }
442         }
443     }
444 
445     /**
446      * Generate a class file for the proxy class.  This method drives the
447      * class file generation process.
448      *
449      * If a proxy interface references any value classes, the value classes
450      * are listed in the loadable descriptors attribute of the interface class.  The
451      * classes that are referenced by the proxy interface have already
452      * been loaded before the proxy class.  Hence the proxy class is
453      * generated with no loadable descriptors attributes as it essentially has no effect.
454      */
455     private byte[] generateClassFile() {
456         /*
457          * Add proxy methods for the hashCode, equals,
458          * and toString methods of java.lang.Object.  This is done before
459          * the methods from the proxy interfaces so that the methods from
460          * java.lang.Object take precedence over duplicate methods in the
461          * proxy interfaces.
462          */
463         addProxyMethod(new ProxyMethod(OBJECT_HASH_CODE_METHOD, OBJECT_HASH_CODE_SIG, "m0"));
464         addProxyMethod(new ProxyMethod(OBJECT_EQUALS_METHOD, OBJECT_EQUALS_SIG, "m1"));
465         addProxyMethod(new ProxyMethod(OBJECT_TO_STRING_METHOD, OBJECT_TO_STRING_SIG, "m2"));
466 
467         /*
468          * Accumulate all of the methods from the proxy interfaces.
469          */
470         for (Class<?> intf : interfaces) {
471             for (Method m : intf.getMethods()) {
472                 if (!Modifier.isStatic(m.getModifiers())) {
473                     addProxyMethod(m, intf);
474                 }
475             }
476         }
477 
478         /*
479          * For each set of proxy methods with the same signature,
480          * verify that the methods' return types are compatible.
481          */
482         for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
483             checkReturnTypes(sigmethods);
484         }
485 
486         return CF_CONTEXT.build(thisClassCE, cp, clb -> {
487             clb.withSuperclass(proxyCE);
488             clb.withFlags(accessFlags);
489             clb.withInterfaces(toClassEntries(cp, interfaces));
490             generateConstructor(clb);
491 
492             for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
493                 for (ProxyMethod pm : sigmethods) {
494                     // add static field for the Method object
495                     clb.withField(pm.methodFieldName, CD_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL);
496 
497                     // Generate code for proxy method
498                     pm.generateMethod(clb);
499                 }
500             }
501 
502             generateStaticInitializer(clb);
503             generateLookupAccessor(clb);
504         });
505     }
506 
507     /**
508      * Add another method to be proxied, either by creating a new
509      * ProxyMethod object or augmenting an old one for a duplicate
510      * method.
511      *
512      * "fromClass" indicates the proxy interface that the method was
513      * found through, which may be different from (a subinterface of)
514      * the method's "declaring class".  Note that the first Method
515      * object passed for a given name and descriptor identifies the
516      * Method object (and thus the declaring class) that will be
517      * passed to the invocation handler's "invoke" method for a given
518      * set of duplicate methods.
519      */
520     private void addProxyMethod(Method m, Class<?> fromClass) {
521         Class<?> returnType = m.getReturnType();
522         Class<?>[] exceptionTypes = m.getSharedExceptionTypes();
523 
524         String sig = m.toShortSignature();
525         List<ProxyMethod> sigmethods = proxyMethodsFor(sig);
526         for (ProxyMethod pm : sigmethods) {
527             if (returnType == pm.returnType) {
528                 /*
529                  * Found a match: reduce exception types to the
530                  * greatest set of exceptions that can be thrown
531                  * compatibly with the throws clauses of both
532                  * overridden methods.
533                  */
534                 List<Class<?>> legalExceptions = new ArrayList<>();
535                 collectCompatibleTypes(
536                         exceptionTypes, pm.exceptionTypes, legalExceptions);
537                 collectCompatibleTypes(
538                         pm.exceptionTypes, exceptionTypes, legalExceptions);
539                 pm.exceptionTypes = legalExceptions.toArray(EMPTY_CLASS_ARRAY);
540                 return;
541             }
542         }
543         sigmethods.add(new ProxyMethod(m, sig, returnType,
544                 exceptionTypes, fromClass, "m" + proxyMethodCount++));
545     }
546 
547     private List<ProxyMethod> proxyMethodsFor(String sig) {
548         return proxyMethods.computeIfAbsent(sig, _ -> new ArrayList<>(3));
549     }
550 
551     /**
552      * Add an existing ProxyMethod (hashcode, equals, toString).
553      *
554      * @param pm an existing ProxyMethod
555      */
556     private void addProxyMethod(ProxyMethod pm) {
557         proxyMethodsFor(pm.shortSignature).add(pm);
558     }
559 
560     /**
561      * Generate the constructor method for the proxy class.
562      */
563     private void generateConstructor(ClassBuilder clb) {
564         clb.withMethodBody(INIT_NAME, MTD_void_InvocationHandler, ACC_PUBLIC, cob -> cob
565                .aload(0)
566                .aload(1)
567                .invokespecial(cp.methodRefEntry(proxyCE,
568                    cp.nameAndTypeEntry(INIT_NAME, MTD_void_InvocationHandler)))
569                .return_());
570     }
571 
572     /**
573      * Generate the class initializer.
574      * Discussion: Currently, for Proxy to work with SecurityManager,
575      * we rely on the parameter classes of the methods to be computed
576      * from Proxy instead of via user code paths like bootstrap method
577      * lazy evaluation. That might change if we can pass in the live
578      * Method objects directly..
579      */
580     private void generateStaticInitializer(ClassBuilder clb) {
581         clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
582             // Put ClassLoader at local variable index 0, used by
583             // Class.forName(String, boolean, ClassLoader) calls
584             cob.ldc(thisClassCE)
585                .invokevirtual(cp.methodRefEntry(classCE,
586                        cp.nameAndTypeEntry("getClassLoader", MTD_ClassLoader)))
587                .astore(0);
588             var ts = cob.newBoundLabel();
589             for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
590                 for (ProxyMethod pm : sigmethods) {
591                     pm.codeFieldInitialization(cob);
592                 }
593             }
594             cob.return_();
595             var c1 = cob.newBoundLabel();
596             var nsmError = cp.classEntry(CD_NoSuchMethodError);
597             cob.exceptionCatch(ts, c1, c1, CD_NoSuchMethodException)
598                .new_(nsmError)
599                .dup_x1()
600                .swap()
601                .invokevirtual(throwableGetMessage)
602                .invokespecial(cp.methodRefEntry(nsmError, exInit))
603                .athrow();
604             var c2 = cob.newBoundLabel();
605             var ncdfError = cp.classEntry(CD_NoClassDefFoundError);
606             cob.exceptionCatch(ts, c1, c2, CD_ClassNotFoundException)
607                .new_(ncdfError)
608                .dup_x1()
609                .swap()
610                .invokevirtual(throwableGetMessage)
611                .invokespecial(cp.methodRefEntry(ncdfError, exInit))
612                .athrow();
613             cob.with(StackMapTableAttribute.of(List.of(
614                        StackMapFrameInfo.of(c1, classLoaderLocal, throwableStack),
615                        StackMapFrameInfo.of(c2, classLoaderLocal, throwableStack))));
616 
617         });
618     }
619 
620     /**
621      * Generate the static lookup accessor method that returns the Lookup
622      * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy;
623      * otherwise, IllegalAccessException is thrown
624      */
625     private void generateLookupAccessor(ClassBuilder clb) {
626         clb.withMethod(NAME_LOOKUP_ACCESSOR,
627                 MTD_MethodHandles$Lookup_MethodHandles$Lookup,
628                 ACC_PRIVATE | ACC_STATIC,
629                 mb -> mb.with(ExceptionsAttribute.of(List.of(mb.constantPool().classEntry(CD_IllegalAccessException))))
630                         .withCode(cob -> {
631                             Label failLabel = cob.newLabel();
632                             ClassEntry mhl = cp.classEntry(CD_MethodHandles_Lookup);
633                             ClassEntry iae = cp.classEntry(CD_IllegalAccessException);
634                             cob.aload(0)
635                                .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("lookupClass", MTD_Class)))
636                                .ldc(proxyCE)
637                                .if_acmpne(failLabel)
638                                .aload(0)
639                                .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("hasFullPrivilegeAccess", MTD_boolean)))
640                                .ifeq(failLabel)
641                                .invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup)
642                                .areturn()
643                                .labelBinding(failLabel)
644                                .new_(iae)
645                                .dup()
646                                .aload(0)
647                                .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("toString", MTD_String)))
648                                .invokespecial(cp.methodRefEntry(iae, exInit))
649                                .athrow()
650                                .with(StackMapTableAttribute.of(List.of(
651                                        StackMapFrameInfo.of(failLabel,
652                                                List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(mhl)),
653                                                List.of()))));
654                         }));
655     }
656 
657     /**
658      * A ProxyMethod object represents a proxy method in the proxy class
659      * being generated: a method whose implementation will encode and
660      * dispatch invocations to the proxy instance's invocation handler.
661      */
662     private class ProxyMethod {
663 
664         private final Method method;
665         private final String shortSignature;
666         private final Class<?> fromClass;
667         private final Class<?> returnType;
668         private final String methodFieldName;
669         private Class<?>[] exceptionTypes;
670         private final FieldRefEntry methodField;
671 
672         private ProxyMethod(Method method, String sig,
673                             Class<?> returnType, Class<?>[] exceptionTypes,
674                             Class<?> fromClass, String methodFieldName) {
675             this.method = method;
676             this.shortSignature = sig;
677             this.returnType = returnType;
678             this.exceptionTypes = exceptionTypes;
679             this.fromClass = fromClass;
680             this.methodFieldName = methodFieldName;
681             this.methodField = cp.fieldRefEntry(thisClassCE,
682                 cp.nameAndTypeEntry(methodFieldName, CD_Method));
683         }
684 
685         private Class<?>[] parameterTypes() {
686             return method.getSharedParameterTypes();
687         }
688 
689         /**
690          * Create a new specific ProxyMethod with a specific field name
691          *
692          * @param method          The method for which to create a proxy
693          */
694         private ProxyMethod(Method method, String sig, String methodFieldName) {
695             this(method, sig, method.getReturnType(),
696                  method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName);
697         }
698 
699         /**
700          * Generate this method, including the code and exception table entry.
701          */
702         private void generateMethod(ClassBuilder clb) {
703             var desc = methodTypeDesc(returnType, parameterTypes());
704             int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL
705                                                    : ACC_PUBLIC | ACC_FINAL;
706             clb.withMethod(method.getName(), desc, accessFlags, mb ->
707                   mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes))))
708                     .withCode(cob -> {
709                         var catchList = computeUniqueCatchList(exceptionTypes);
710                         cob.aload(cob.receiverSlot())
711                            .getfield(handlerField)
712                            .aload(cob.receiverSlot())
713                            .getstatic(methodField);
714                         Class<?>[] parameterTypes = parameterTypes();
715                         if (parameterTypes.length > 0) {
716                             // Create an array and fill with the parameters converting primitives to wrappers
717                             cob.loadConstant(parameterTypes.length)
718                                .anewarray(objectCE);
719                             for (int i = 0; i < parameterTypes.length; i++) {
720                                 cob.dup()
721                                    .loadConstant(i);
722                                 codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i));
723                                 cob.aastore();
724                             }
725                         } else {
726                             cob.aconst_null();
727                         }
728 
729                         cob.invokeinterface(invocationHandlerInvoke);
730 
731                         if (returnType == void.class) {
732                             cob.pop()
733                                .return_();
734                         } else {
735                             codeUnwrapReturnValue(cob, returnType);
736                         }
737                         if (!catchList.isEmpty()) {
738                             var c1 = cob.newBoundLabel();
739                             for (var exc : catchList) {
740                                 cob.exceptionCatch(cob.startLabel(), c1, c1, referenceClassDesc(exc));
741                             }
742                             cob.athrow();   // just rethrow the exception
743                             var c2 = cob.newBoundLabel();
744                             cob.exceptionCatchAll(cob.startLabel(), c1, c2)
745                                .new_(uteCE)
746                                .dup_x1()
747                                .swap()
748                                .invokespecial(uteInit)
749                                .athrow()
750                                .with(StackMapTableAttribute.of(List.of(
751                                     StackMapFrameInfo.of(c1, List.of(), throwableStack),
752                                     StackMapFrameInfo.of(c2, List.of(), throwableStack))));
753                         }
754                     }));
755         }
756 
757         /**
758          * Generate code for wrapping an argument of the given type
759          * whose value can be found at the specified local variable
760          * index, in order for it to be passed (as an Object) to the
761          * invocation handler's "invoke" method.
762          */
763         private void codeWrapArgument(CodeBuilder cob, Class<?> type, int slot) {
764             if (type.isPrimitive()) {
765                 cob.loadLocal(TypeKind.from(type).asLoadable(), slot);
766                 PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type);
767                 cob.invokestatic(prim.wrapperMethodRef(cp));
768             } else {
769                 cob.aload(slot);
770             }
771         }
772 
773         /**
774          * Generate code for unwrapping a return value of the given
775          * type from the invocation handler's "invoke" method (as type
776          * Object) to its correct type.
777          */
778         private void codeUnwrapReturnValue(CodeBuilder cob, Class<?> type) {
779             if (type.isPrimitive()) {
780                 PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type);
781 
782                 cob.checkcast(prim.wrapperClass)
783                    .invokevirtual(prim.unwrapMethodRef(cp))
784                    .return_(TypeKind.from(type).asLoadable());
785             } else {
786                 cob.checkcast(referenceClassDesc(type))
787                    .areturn();
788             }
789         }
790 
791         /**
792          * Generate code for initializing the static field that stores
793          * the Method object for this proxy method. A class loader is
794          * anticipated at local variable index 0.
795          * The generated code must be run in an AccessController.doPrivileged
796          * block if a SecurityManager is present, as otherwise the code
797          * cannot pass {@code null} ClassLoader to forName.
798          */
799         private void codeFieldInitialization(CodeBuilder cob) {
800             var cp = cob.constantPool();
801             codeClassForName(cob, fromClass);
802 
803             Class<?>[] parameterTypes = parameterTypes();
804             cob.ldc(method.getName())
805                .loadConstant(parameterTypes.length)
806                .anewarray(classCE);
807 
808             // Construct an array with the parameter types mapping primitives to Wrapper types
809             for (int i = 0; i < parameterTypes.length; i++) {
810                 cob.dup()
811                    .loadConstant(i);
812                 if (parameterTypes[i].isPrimitive()) {
813                     PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]);
814                     cob.getstatic(prim.typeFieldRef(cp));
815                 } else {
816                     codeClassForName(cob, parameterTypes[i]);
817                 }
818                 cob.aastore();
819             }
820             // lookup the method
821             cob.invokevirtual(classGetMethod)
822                .putstatic(methodField);
823         }
824 
825         /*
826          * =============== Code Generation Utility Methods ===============
827          */
828 
829         /**
830          * Generate code to invoke the Class.forName with the name of the given
831          * class to get its Class object at runtime.  The code is written to
832          * the supplied stream.  Note that the code generated by this method
833          * may cause the checked ClassNotFoundException to be thrown. A class
834          * loader is anticipated at local variable index 0.
835          */
836         private void codeClassForName(CodeBuilder cob, Class<?> cl) {
837             if (cl == Object.class) {
838                 cob.ldc(objectCE);
839             } else {
840                 cob.ldc(cl.getName())
841                         .iconst_0() // false
842                         .aload(0)// classLoader
843                         .invokestatic(classForName);
844             }
845         }
846 
847         @Override
848         public String toString() {
849             return method.toShortString();
850         }
851     }
852 
853     /**
854      * A PrimitiveTypeInfo object contains bytecode-related information about
855      * a primitive type in its instance fields. The struct for a particular
856      * primitive type can be obtained using the static "get" method.
857      */
858     private enum PrimitiveTypeInfo {
859         BYTE(byte.class, CD_byte, CD_Byte),
860         CHAR(char.class, CD_char, CD_Character),
861         DOUBLE(double.class, CD_double, CD_Double),
862         FLOAT(float.class, CD_float, CD_Float),
863         INT(int.class, CD_int, CD_Integer),
864         LONG(long.class, CD_long, CD_Long),
865         SHORT(short.class, CD_short, CD_Short),
866         BOOLEAN(boolean.class, CD_boolean, CD_Boolean);
867 
868         /**
869          * wrapper class
870          */
871         private final ClassDesc wrapperClass;
872         /**
873          * wrapper factory method type
874          */
875         private final MethodTypeDesc wrapperMethodType;
876         /**
877          * wrapper class method name for retrieving primitive value
878          */
879         private final String unwrapMethodName;
880         /**
881          * wrapper class method type for retrieving primitive value
882          */
883         private final MethodTypeDesc unwrapMethodType;
884 
885         PrimitiveTypeInfo(Class<?> primitiveClass, ClassDesc baseType, ClassDesc wrapperClass) {
886             assert baseType.isPrimitive();
887             this.wrapperClass = wrapperClass;
888             this.wrapperMethodType = MethodTypeDescImpl.ofValidated(wrapperClass, baseType);
889             this.unwrapMethodName = primitiveClass.getName() + "Value";
890             this.unwrapMethodType = MethodTypeDescImpl.ofValidated(baseType);
891         }
892 
893         public static PrimitiveTypeInfo get(Class<?> cl) {
894             // Uses if chain for speed: 8284880
895             if (cl == int.class)     return INT;
896             if (cl == long.class)    return LONG;
897             if (cl == boolean.class) return BOOLEAN;
898             if (cl == short.class)   return SHORT;
899             if (cl == byte.class)    return BYTE;
900             if (cl == char.class)    return CHAR;
901             if (cl == float.class)   return FLOAT;
902             if (cl == double.class)  return DOUBLE;
903             throw new AssertionError(cl);
904         }
905 
906         public MethodRefEntry wrapperMethodRef(ConstantPoolBuilder cp) {
907             return cp.methodRefEntry(wrapperClass, "valueOf", wrapperMethodType);
908         }
909 
910         public MethodRefEntry unwrapMethodRef(ConstantPoolBuilder cp) {
911             return cp.methodRefEntry(wrapperClass, unwrapMethodName, unwrapMethodType);
912         }
913 
914         public FieldRefEntry typeFieldRef(ConstantPoolBuilder cp) {
915             return cp.fieldRefEntry(wrapperClass, "TYPE", CD_Class);
916         }
917     }
918 }