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