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 }
--- EOF ---