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