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