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