1 /*
  2  * Copyright (c) 2008, 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.invoke;
 27 
 28 import java.lang.constant.ClassDesc;
 29 import java.lang.constant.MethodTypeDesc;
 30 import java.lang.invoke.MethodHandles.Lookup;
 31 import java.lang.module.ModuleDescriptor;
 32 import java.lang.ref.WeakReference;
 33 import java.lang.reflect.Method;
 34 import java.lang.reflect.Modifier;
 35 import java.lang.reflect.UndeclaredThrowableException;
 36 import java.util.ArrayList;
 37 import java.util.Arrays;
 38 import java.util.Collections;
 39 import java.util.HashSet;
 40 import java.util.List;
 41 import java.util.Objects;
 42 import java.util.Set;
 43 import java.util.WeakHashMap;
 44 import java.util.concurrent.atomic.AtomicInteger;
 45 import java.util.stream.Stream;
 46 
 47 import jdk.internal.access.JavaLangReflectAccess;
 48 import jdk.internal.access.SharedSecrets;
 49 import java.lang.classfile.ClassHierarchyResolver;
 50 import java.lang.classfile.ClassFile;
 51 import java.lang.classfile.CodeBuilder;
 52 import java.lang.classfile.TypeKind;
 53 
 54 import jdk.internal.constant.ConstantUtils;
 55 import jdk.internal.loader.ClassLoaders;
 56 import jdk.internal.misc.PreviewFeatures;
 57 import jdk.internal.module.Modules;
 58 import jdk.internal.util.ClassFileDumper;
 59 
 60 import static java.lang.constant.ConstantDescs.*;
 61 import static java.lang.invoke.MethodHandleStatics.*;
 62 import static java.lang.invoke.MethodType.methodType;
 63 import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
 64 import static java.lang.classfile.ClassFile.*;
 65 import static jdk.internal.constant.ConstantUtils.*;
 66 
 67 /**
 68  * This class consists exclusively of static methods that help adapt
 69  * method handles to other JVM types, such as interfaces.
 70  *
 71  * @since 1.7
 72  */
 73 public final class MethodHandleProxies {
 74 
 75     private MethodHandleProxies() { }  // do not instantiate
 76 
 77     /**
 78      * Produces an instance of the given single-method interface which redirects
 79      * its calls to the given method handle.
 80      * <p>
 81      * A single-method interface is an interface which declares a uniquely named method.
 82      * When determining the uniquely named method of a single-method interface,
 83      * the public {@code Object} methods ({@code toString}, {@code equals}, {@code hashCode})
 84      * are disregarded as are any default (non-abstract) methods.
 85      * For example, {@link java.util.Comparator} is a single-method interface,
 86      * even though it re-declares the {@code Object.equals} method and also
 87      * declares default methods, such as {@code Comparator.reverse}.
 88      * <p>
 89      * The interface must be public, not {@linkplain Class#isHidden() hidden},
 90      * and not {@linkplain Class#isSealed() sealed}.
 91      * No additional access checks are performed.
 92      * <p>
 93      * The resulting instance of the required type will respond to
 94      * invocation of the type's uniquely named method by calling
 95      * the given target on the incoming arguments,
 96      * and returning or throwing whatever the target
 97      * returns or throws.  The invocation will be as if by
 98      * {@code target.invoke}.
 99      * The target's type will be checked before the
100      * instance is created, as if by a call to {@code asType},
101      * which may result in a {@code WrongMethodTypeException}.
102      * <p>
103      * The uniquely named method is allowed to be multiply declared,
104      * with distinct type descriptors.  (E.g., it can be overloaded,
105      * or can possess bridge methods.)  All such declarations are
106      * connected directly to the target method handle.
107      * Argument and return types are adjusted by {@code asType}
108      * for each individual declaration.
109      * <p>
110      * The wrapper instance will implement the requested interface
111      * and its super-types, but no other single-method interfaces.
112      * This means that the instance will not unexpectedly
113      * pass an {@code instanceof} test for any unrequested type.
114      * <p style="font-size:smaller;">
115      * <em>Implementation Note:</em>
116      * Therefore, each instance must implement a unique single-method interface.
117      * Implementations may not bundle together
118      * multiple single-method interfaces onto single implementation classes
119      * in the style of {@link java.desktop/java.awt.AWTEventMulticaster}.
120      * <p>
121      * The method handle may throw an <em>undeclared exception</em>,
122      * which means any checked exception (or other checked throwable)
123      * not declared by the requested type's single abstract method.
124      * If this happens, the throwable will be wrapped in an instance of
125      * {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException}
126      * and thrown in that wrapped form.
127      * <p>
128      * Like {@link java.lang.Integer#valueOf Integer.valueOf},
129      * {@code asInterfaceInstance} is a factory method whose results are defined
130      * by their behavior.
131      * It is not guaranteed to return a new instance for every call.
132      * <p>
133      * Because of the possibility of {@linkplain java.lang.reflect.Method#isBridge bridge methods}
134      * and other corner cases, the interface may also have several abstract methods
135      * with the same name but having distinct descriptors (types of returns and parameters).
136      * In this case, all the methods are bound in common to the one given target.
137      * The type check and effective {@code asType} conversion is applied to each
138      * method type descriptor, and all abstract methods are bound to the target in common.
139      * Beyond this type check, no further checks are made to determine that the
140      * abstract methods are related in any way.
141      * <p>
142      * Future versions of this API may accept additional types,
143      * such as abstract classes with single abstract methods.
144      * Future versions of this API may also equip wrapper instances
145      * with one or more additional public "marker" interfaces.
146      *
147      * @param <T> the desired type of the wrapper, a single-method interface
148      * @param intfc a class object representing {@code T}
149      * @param target the method handle to invoke from the wrapper
150      * @return a correctly-typed wrapper for the given target
151      * @throws NullPointerException if either argument is null
152      * @throws IllegalArgumentException if the {@code intfc} is not a
153      *         valid argument to this method
154      * @throws WrongMethodTypeException if the target cannot
155      *         be converted to the type required by the requested interface
156      */
157     @SuppressWarnings("doclint:reference") // cross-module links
158     public static <T> T asInterfaceInstance(final Class<T> intfc, final MethodHandle target) {
159         if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers()))
160             throw newIllegalArgumentException("not a public interface", intfc.getName());
161         if (intfc.isSealed())
162             throw newIllegalArgumentException("a sealed interface", intfc.getName());
163         if (intfc.isHidden())
164             throw newIllegalArgumentException("a hidden interface", intfc.getName());
165         Objects.requireNonNull(target);
166         final MethodHandle mh = target;
167 
168         // Define one hidden class for each interface.  Create an instance of
169         // the hidden class for a given target method handle which will be
170         // accessed via getfield.  Multiple instances may be created for a
171         // hidden class.  This approach allows the generated hidden classes
172         // more shareable.
173         //
174         // The implementation class is weakly referenced; a new class is
175         // defined if the last one has been garbage collected.
176         //
177         // An alternative approach is to define one hidden class with the
178         // target method handle as class data and the target method handle
179         // is loaded via ldc/condy.  If more than one target method handles
180         // are used, the extra classes will pollute the same type profiles.
181         // In addition, hidden classes without class data is more friendly
182         // for pre-generation (shifting the dynamic class generation from
183         // runtime to an earlier phrase).
184         Class<?> proxyClass = getProxyClass(intfc);  // throws IllegalArgumentException
185         Lookup lookup = new Lookup(proxyClass);
186         Object proxy;
187         try {
188             MethodHandle constructor = lookup.findConstructor(proxyClass,
189                                                               MT_void_Lookup_MethodHandle_MethodHandle)
190                                              .asType(MT_Object_Lookup_MethodHandle_MethodHandle);
191             proxy = constructor.invokeExact(lookup, target, mh);
192         } catch (Throwable ex) {
193             throw uncaughtException(ex);
194         }
195         assert proxy.getClass().getModule().isNamed() : proxy.getClass() + " " + proxy.getClass().getModule();
196         return intfc.cast(proxy);
197     }
198 
199     private record MethodInfo(MethodTypeDesc desc, List<ClassDesc> thrown, String fieldName) {}
200 
201     private static final ClassFileDumper DUMPER = ClassFileDumper.getInstance(
202             "jdk.invoke.MethodHandleProxies.dumpClassFiles", "DUMP_MH_PROXY_CLASSFILES");
203 
204     private static final Set<Class<?>> WRAPPER_TYPES = Collections.newSetFromMap(new WeakHashMap<>());
205     private static final ClassValue<WeakReferenceHolder<Class<?>>> PROXIES = new ClassValue<>() {
206         @Override
207         protected WeakReferenceHolder<Class<?>> computeValue(Class<?> intfc) {
208             return new WeakReferenceHolder<>(newProxyClass(intfc));
209         }
210     };
211 
212     private static Class<?> newProxyClass(Class<?> intfc) {
213         List<MethodInfo> methods = new ArrayList<>();
214         Set<Class<?>> referencedTypes = new HashSet<>();
215         referencedTypes.add(intfc);
216         String uniqueName = null;
217         int count = 0;
218         for (Method m : intfc.getMethods()) {
219             if (!Modifier.isAbstract(m.getModifiers()))
220                 continue;
221 
222             if (isObjectMethod(m))
223                 continue;
224 
225             // ensure it's SAM interface
226             String methodName = m.getName();
227             if (uniqueName == null) {
228                 uniqueName = methodName;
229             } else if (!uniqueName.equals(methodName)) {
230                 // too many abstract methods
231                 throw newIllegalArgumentException("not a single-method interface", intfc.getName());
232             }
233 
234             // the field name holding the method handle for this method
235             String fieldName = "m" + count++;
236             var md = methodTypeDesc(m.getReturnType(), JLRA.getExecutableSharedParameterTypes(m));
237             var thrown = JLRA.getExecutableSharedExceptionTypes(m);
238             var exceptionTypeDescs =
239                     thrown.length == 0 ? DEFAULT_RETHROWS
240                                        : Stream.concat(DEFAULT_RETHROWS.stream(),
241                                                        Arrays.stream(thrown).map(ConstantUtils::referenceClassDesc))
242                                                .distinct().toList();
243             methods.add(new MethodInfo(md, exceptionTypeDescs, fieldName));
244 
245             // find the types referenced by this method
246             addElementType(referencedTypes, m.getReturnType());
247             addElementTypes(referencedTypes, JLRA.getExecutableSharedParameterTypes(m));
248             addElementTypes(referencedTypes, JLRA.getExecutableSharedExceptionTypes(m));
249         }
250 
251         if (uniqueName == null)
252             throw newIllegalArgumentException("no method in ", intfc.getName());
253 
254         // create a dynamic module for each proxy class, which needs access
255         // to the types referenced by the members of the interface including
256         // the parameter types, return type and exception types
257         var loader = intfc.getClassLoader();
258         Module targetModule = newDynamicModule(loader, referencedTypes);
259 
260         // generate a class file in the package of the dynamic module
261         String packageName = targetModule.getName();
262         String intfcName = intfc.getName();
263         int i = intfcName.lastIndexOf('.');
264         // jdk.MHProxy#.Interface
265         String className = packageName + "." + (i > 0 ? intfcName.substring(i + 1) : intfcName);
266         byte[] template = createTemplate(loader, binaryNameToDesc(className),
267                 referenceClassDesc(intfc), uniqueName, methods);
268         // define the dynamic module to the class loader of the interface
269         var definer = new Lookup(intfc).makeHiddenClassDefiner(className, template, DUMPER);
270 
271         Lookup lookup = definer.defineClassAsLookup(true);
272         // cache the wrapper type
273         var ret = lookup.lookupClass();
274         WRAPPER_TYPES.add(ret);
275         return ret;
276     }
277 
278     private static final class WeakReferenceHolder<T> {
279         private volatile WeakReference<T> ref;
280 
281         WeakReferenceHolder(T value) {
282             set(value);
283         }
284 
285         void set(T value) {
286             ref = new WeakReference<>(value);
287         }
288 
289         T get() {
290             return ref.get();
291         }
292     }
293 
294     private static Class<?> getProxyClass(Class<?> intfc) {
295         WeakReferenceHolder<Class<?>> r = PROXIES.get(intfc);
296         Class<?> cl = r.get();
297         if (cl != null)
298             return cl;
299 
300         // avoid spinning multiple classes in a race
301         synchronized (r) {
302             cl = r.get();
303             if (cl != null)
304                 return cl;
305 
306             // If the referent is cleared, create a new value and update cached weak reference.
307             cl = newProxyClass(intfc);
308             r.set(cl);
309             return cl;
310         }
311     }
312 
313     private static final List<ClassDesc> DEFAULT_RETHROWS = List.of(referenceClassDesc(RuntimeException.class), referenceClassDesc(Error.class));
314     private static final ClassDesc CD_UndeclaredThrowableException = referenceClassDesc(UndeclaredThrowableException.class);
315     private static final ClassDesc CD_IllegalAccessException = referenceClassDesc(IllegalAccessException.class);
316     private static final MethodTypeDesc MTD_void_Throwable = MethodTypeDesc.of(CD_void, CD_Throwable);
317     private static final MethodType MT_void_Lookup_MethodHandle_MethodHandle =
318             methodType(void.class, Lookup.class, MethodHandle.class, MethodHandle.class);
319     private static final MethodType MT_Object_Lookup_MethodHandle_MethodHandle =
320             MT_void_Lookup_MethodHandle_MethodHandle.changeReturnType(Object.class);
321     private static final MethodType MT_MethodHandle_Object = methodType(MethodHandle.class, Object.class);
322     private static final MethodTypeDesc MTD_void_Lookup_MethodHandle_MethodHandle
323             = methodTypeDesc(MT_void_Lookup_MethodHandle_MethodHandle);
324     private static final MethodTypeDesc MTD_void_Lookup = MethodTypeDesc.of(CD_void, CD_MethodHandles_Lookup);
325     private static final MethodTypeDesc MTD_MethodHandle_MethodType = MethodTypeDesc.of(CD_MethodHandle, CD_MethodType);
326     private static final MethodTypeDesc MTD_Class = MethodTypeDesc.of(CD_Class);
327     private static final MethodTypeDesc MTD_int = MethodTypeDesc.of(CD_int);
328     private static final MethodTypeDesc MTD_String = MethodTypeDesc.of(CD_String);
329     private static final MethodTypeDesc MTD_void_String = MethodTypeDesc.of(CD_void, CD_String);
330     private static final String TARGET_NAME = "target";
331     private static final String TYPE_NAME = "interfaceType";
332     private static final String ENSURE_ORIGINAL_LOOKUP = "ensureOriginalLookup";
333 
334     /**
335      * Creates an implementation class file for a given interface. One implementation class is
336      * defined for each interface.
337      *
338      * @param ifaceDesc the given interface
339      * @param methodName the name of the single abstract method
340      * @param methods the information for implementation methods
341      * @return the bytes of the implementation classes
342      */
343     private static byte[] createTemplate(ClassLoader loader, ClassDesc proxyDesc, ClassDesc ifaceDesc,
344                                          String methodName, List<MethodInfo> methods) {
345         return ClassFile.of(ClassHierarchyResolverOption.of(ClassHierarchyResolver.ofClassLoading(loader == null ?
346                         ClassLoaders.platformClassLoader() : loader)))
347                         .build(proxyDesc, clb -> {
348             clb.withSuperclass(CD_Object)
349                .withFlags((PreviewFeatures.isEnabled() ? ACC_IDENTITY  : 0) | ACC_FINAL | ACC_SYNTHETIC)
350                .withInterfaceSymbols(ifaceDesc)
351                // static and instance fields
352                .withField(TYPE_NAME, CD_Class, ACC_PRIVATE | ACC_STATIC | ACC_FINAL)
353                .withField(TARGET_NAME, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL);
354             for (var mi : methods) {
355                 clb.withField(mi.fieldName, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL);
356             }
357 
358             // <clinit>
359             clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
360                 cob.loadConstant(ifaceDesc)
361                    .putstatic(proxyDesc, TYPE_NAME, CD_Class)
362                    .return_();
363             });
364 
365             // <init>(Lookup, MethodHandle target, MethodHandle callerBoundTarget)
366             clb.withMethodBody(INIT_NAME, MTD_void_Lookup_MethodHandle_MethodHandle, 0, cob -> {
367                 cob.aload(0)
368                    .invokespecial(CD_Object, INIT_NAME, MTD_void)
369                    // call ensureOriginalLookup to verify the given Lookup has access
370                    .aload(1)
371                    .invokestatic(proxyDesc, ENSURE_ORIGINAL_LOOKUP, MTD_void_Lookup)
372                    // this.target = target;
373                    .aload(0)
374                    .aload(2)
375                    .putfield(proxyDesc, TARGET_NAME, CD_MethodHandle);
376 
377                 // method handles adjusted to the method type of each method
378                 for (var mi : methods) {
379                     // this.m<i> = callerBoundTarget.asType(xxType);
380                     cob.aload(0)
381                        .aload(3)
382                        .loadConstant(mi.desc)
383                        .invokevirtual(CD_MethodHandle, "asType", MTD_MethodHandle_MethodType)
384                        .putfield(proxyDesc, mi.fieldName, CD_MethodHandle);
385                 }
386 
387                 // complete
388                 cob.return_();
389             });
390 
391             // private static void ensureOriginalLookup(Lookup) checks if the given Lookup
392             // has ORIGINAL access to this class, i.e. the lookup class is this class;
393             // otherwise, IllegalAccessException is thrown
394             clb.withMethodBody(ENSURE_ORIGINAL_LOOKUP, MTD_void_Lookup, ACC_PRIVATE | ACC_STATIC, cob -> {
395                 var failLabel = cob.newLabel();
396                 // check lookupClass
397                 cob.aload(0)
398                    .invokevirtual(CD_MethodHandles_Lookup, "lookupClass", MTD_Class)
399                    .loadConstant(proxyDesc)
400                    .if_acmpne(failLabel)
401                    // check original access
402                    .aload(0)
403                    .invokevirtual(CD_MethodHandles_Lookup, "lookupModes", MTD_int)
404                    .loadConstant(Lookup.ORIGINAL)
405                    .iand()
406                    .ifeq(failLabel)
407                    // success
408                    .return_()
409                    // throw exception
410                    .labelBinding(failLabel)
411                    .new_(CD_IllegalAccessException)
412                    .dup()
413                    .aload(0) // lookup
414                    .invokevirtual(CD_Object, "toString", MTD_String)
415                    .invokespecial(CD_IllegalAccessException, INIT_NAME, MTD_void_String)
416                    .athrow();
417             });
418 
419             // implementation methods
420             for (MethodInfo mi : methods) {
421                 // no need to generate thrown exception attribute
422                 clb.withMethodBody(methodName, mi.desc, ACC_PUBLIC, cob -> cob
423                         .trying(bcb -> {
424                                     // return this.handleField.invokeExact(arguments...);
425                                     bcb.aload(0)
426                                        .getfield(proxyDesc, mi.fieldName, CD_MethodHandle);
427                                     for (int j = 0; j < mi.desc.parameterCount(); j++) {
428                                         bcb.loadLocal(TypeKind.from(mi.desc.parameterType(j)),
429                                                 bcb.parameterSlot(j));
430                                     }
431                                     bcb.invokevirtual(CD_MethodHandle, "invokeExact", mi.desc)
432                                        .return_(TypeKind.from(mi.desc.returnType()));
433                                 }, ctb -> ctb
434                                         // catch (Error | RuntimeException | Declared ex) { throw ex; }
435                                         .catchingMulti(mi.thrown, CodeBuilder::athrow)
436                                         // catch (Throwable ex) { throw new UndeclaredThrowableException(ex); }
437                                         .catchingAll(cb -> cb
438                                                 .new_(CD_UndeclaredThrowableException)
439                                                 .dup_x1()
440                                                 .swap()
441                                                 .invokespecial(CD_UndeclaredThrowableException,
442                                                         INIT_NAME, MTD_void_Throwable)
443                                                 .athrow()
444                                         )
445                         ));
446             }
447         });
448     }
449 
450     private static MethodHandle bindCaller(MethodHandle target, Class<?> hostClass) {
451         return MethodHandleImpl.bindCaller(target, hostClass).withVarargs(target.isVarargsCollector());
452     }
453 
454     /**
455      * Determines if the given object was produced by a call to {@link #asInterfaceInstance asInterfaceInstance}.
456      * @param x any reference
457      * @return true if the reference is not null and points to an object produced by {@code asInterfaceInstance}
458      */
459     public static boolean isWrapperInstance(Object x) {
460         return x != null && WRAPPER_TYPES.contains(x.getClass());
461     }
462 
463     /**
464      * Produces or recovers a target method handle which is behaviorally
465      * equivalent to the unique method of this wrapper instance.
466      * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}.
467      * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}.
468      * @param x any reference
469      * @return a method handle implementing the unique method
470      * @throws IllegalArgumentException if the reference x is not to a wrapper instance
471      */
472     public static MethodHandle wrapperInstanceTarget(Object x) {
473         if (!isWrapperInstance(x))
474             throw new IllegalArgumentException("not a wrapper instance: " + x);
475 
476         try {
477             Class<?> type = x.getClass();
478             MethodHandle getter = new Lookup(type).findGetter(type, TARGET_NAME, MethodHandle.class)
479                                                   .asType(MT_MethodHandle_Object);
480             return (MethodHandle) getter.invokeExact(x);
481         } catch (Throwable ex) {
482             throw uncaughtException(ex);
483         }
484     }
485 
486     /**
487      * Recovers the unique single-method interface type for which this wrapper instance was created.
488      * The object {@code x} must have been produced by a call to {@link #asInterfaceInstance asInterfaceInstance}.
489      * This requirement may be tested via {@link #isWrapperInstance isWrapperInstance}.
490      * @param x any reference
491      * @return the single-method interface type for which the wrapper was created
492      * @throws IllegalArgumentException if the reference x is not to a wrapper instance
493      */
494     public static Class<?> wrapperInstanceType(Object x) {
495         if (!isWrapperInstance(x))
496             throw new IllegalArgumentException("not a wrapper instance: " + x);
497 
498         try {
499             Class<?> type = x.getClass();
500             MethodHandle originalTypeField = new Lookup(type).findStaticGetter(type, TYPE_NAME, Class.class);
501             return (Class<?>) originalTypeField.invokeExact();
502         } catch (Throwable e) {
503             throw uncaughtException(e);
504         }
505     }
506 
507     private static final JavaLangReflectAccess JLRA = SharedSecrets.getJavaLangReflectAccess();
508     private static final AtomicInteger counter = new AtomicInteger();
509 
510     private static String nextModuleName() {
511         return "jdk.MHProxy" + counter.incrementAndGet();
512     }
513 
514     /**
515      * Create a dynamic module defined to the given class loader and has
516      * access to the given types.
517      * <p>
518      * The dynamic module contains only one single package named the same as
519      * the name of the dynamic module.  It's not exported or open.
520      */
521     private static Module newDynamicModule(ClassLoader ld, Set<Class<?>> types) {
522         Objects.requireNonNull(types);
523 
524         // create a dynamic module and setup module access
525         String mn = nextModuleName();
526         ModuleDescriptor descriptor = ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC))
527                 .packages(Set.of(mn))
528                 .build();
529 
530         Module dynModule = Modules.defineModule(ld, descriptor, null);
531         Module javaBase = Object.class.getModule();
532 
533         Modules.addReads(dynModule, javaBase);
534         Modules.addOpens(dynModule, mn, javaBase);
535 
536         for (Class<?> c : types) {
537             ensureAccess(dynModule, c);
538         }
539         return dynModule;
540     }
541 
542     private static boolean isObjectMethod(Method m) {
543         return switch (m.getName()) {
544             case "toString" -> m.getReturnType() == String.class
545                     && m.getParameterCount() == 0;
546             case "hashCode" -> m.getReturnType() == int.class
547                     && m.getParameterCount() == 0;
548             case "equals"   -> m.getReturnType() == boolean.class
549                     && m.getParameterCount() == 1
550                     && JLRA.getExecutableSharedParameterTypes(m)[0] == Object.class;
551             default -> false;
552         };
553     }
554 
555     /*
556      * Ensure the given module can access the given class.
557      */
558     private static void ensureAccess(Module target, Class<?> c) {
559         Module m = c.getModule();
560         // add read edge and qualified export for the target module to access
561         if (!target.canRead(m)) {
562             Modules.addReads(target, m);
563         }
564         String pn = c.getPackageName();
565         if (!m.isExported(pn, target)) {
566             Modules.addExports(m, pn, target);
567         }
568     }
569 
570     private static void addElementTypes(Set<Class<?>> types, Class<?>... classes) {
571         for (var cls : classes) {
572             addElementType(types, cls);
573         }
574     }
575 
576     private static void addElementType(Set<Class<?>> types, Class<?> cls) {
577         Class<?> e = cls;
578         while (e.isArray()) {
579             e = e.getComponentType();
580         }
581 
582         if (!e.isPrimitive()) {
583             types.add(e);
584         }
585     }
586 }