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