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