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