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