mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
6983726: Reimplement MethodHandleProxies.asInterfaceInstance
Co-authored-by: Mandy Chung <mchung@openjdk.org> Reviewed-by: jvernee, mchung
This commit is contained in:
parent
71cac8ce47
commit
5d57b5c2f0
13 changed files with 1151 additions and 403 deletions
|
@ -1016,8 +1016,11 @@ static jclass jvm_lookup_define_class(jclass lookup, const char *name,
|
||||||
ik->is_hidden() ? "is hidden" : "is not hidden");
|
ik->is_hidden() ? "is hidden" : "is not hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(Reflection::is_same_class_package(lookup_k, ik),
|
|
||||||
"lookup class and defined class are in different packages");
|
if ((!is_hidden || is_nestmate) && !Reflection::is_same_class_package(lookup_k, ik)) {
|
||||||
|
// non-hidden class or nestmate class must be in the same package as the Lookup class
|
||||||
|
THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "Lookup class and defined class are in different packages");
|
||||||
|
}
|
||||||
|
|
||||||
if (init) {
|
if (init) {
|
||||||
ik->initialize(CHECK_NULL);
|
ik->initialize(CHECK_NULL);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -25,19 +25,44 @@
|
||||||
|
|
||||||
package java.lang.invoke;
|
package java.lang.invoke;
|
||||||
|
|
||||||
import java.lang.reflect.*;
|
import java.lang.constant.ClassDesc;
|
||||||
|
import java.lang.constant.MethodTypeDesc;
|
||||||
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
|
import java.lang.module.ModuleDescriptor;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.lang.reflect.UndeclaredThrowableException;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jdk.internal.access.JavaLangReflectAccess;
|
import jdk.internal.access.JavaLangReflectAccess;
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
import sun.invoke.WrapperInstance;
|
import jdk.internal.classfile.ClassHierarchyResolver;
|
||||||
import java.util.ArrayList;
|
import jdk.internal.classfile.Classfile;
|
||||||
|
import jdk.internal.classfile.CodeBuilder;
|
||||||
|
import jdk.internal.classfile.TypeKind;
|
||||||
|
import jdk.internal.module.Modules;
|
||||||
import jdk.internal.reflect.CallerSensitive;
|
import jdk.internal.reflect.CallerSensitive;
|
||||||
import jdk.internal.reflect.Reflection;
|
import jdk.internal.reflect.Reflection;
|
||||||
|
import jdk.internal.util.ClassFileDumper;
|
||||||
import sun.reflect.misc.ReflectUtil;
|
import sun.reflect.misc.ReflectUtil;
|
||||||
|
|
||||||
|
import static java.lang.constant.ConstantDescs.*;
|
||||||
import static java.lang.invoke.MethodHandleStatics.*;
|
import static java.lang.invoke.MethodHandleStatics.*;
|
||||||
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
|
||||||
|
import static jdk.internal.classfile.Classfile.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class consists exclusively of static methods that help adapt
|
* This class consists exclusively of static methods that help adapt
|
||||||
|
@ -61,7 +86,8 @@ public class MethodHandleProxies {
|
||||||
* even though it re-declares the {@code Object.equals} method and also
|
* even though it re-declares the {@code Object.equals} method and also
|
||||||
* declares default methods, such as {@code Comparator.reverse}.
|
* declares default methods, such as {@code Comparator.reverse}.
|
||||||
* <p>
|
* <p>
|
||||||
* The interface must be public and not {@linkplain Class#isSealed() sealed}.
|
* The interface must be public, not {@linkplain Class#isHidden() hidden},
|
||||||
|
* and not {@linkplain Class#isSealed() sealed}.
|
||||||
* No additional access checks are performed.
|
* No additional access checks are performed.
|
||||||
* <p>
|
* <p>
|
||||||
* The resulting instance of the required type will respond to
|
* The resulting instance of the required type will respond to
|
||||||
|
@ -133,37 +159,20 @@ public class MethodHandleProxies {
|
||||||
* @throws WrongMethodTypeException if the target cannot
|
* @throws WrongMethodTypeException if the target cannot
|
||||||
* be converted to the type required by the requested interface
|
* be converted to the type required by the requested interface
|
||||||
*/
|
*/
|
||||||
// Other notes to implementors:
|
@SuppressWarnings("doclint:reference") // cross-module links
|
||||||
// <p>
|
|
||||||
// No stable mapping is promised between the single-method interface and
|
|
||||||
// the implementation class C. Over time, several implementation
|
|
||||||
// classes might be used for the same type.
|
|
||||||
// <p>
|
|
||||||
// If the implementation is able
|
|
||||||
// to prove that a wrapper of the required type
|
|
||||||
// has already been created for a given
|
|
||||||
// method handle, or for another method handle with the
|
|
||||||
// same behavior, the implementation may return that wrapper in place of
|
|
||||||
// a new wrapper.
|
|
||||||
// <p>
|
|
||||||
// This method is designed to apply to common use cases
|
|
||||||
// where a single method handle must interoperate with
|
|
||||||
// an interface that implements a function-like
|
|
||||||
// API. Additional variations, such as single-abstract-method classes with
|
|
||||||
// private constructors, or interfaces with multiple but related
|
|
||||||
// entry points, must be covered by hand-written or automatically
|
|
||||||
// generated adapter classes.
|
|
||||||
//
|
|
||||||
@SuppressWarnings({"removal",
|
|
||||||
"doclint:reference"}) // cross-module links
|
|
||||||
@CallerSensitive
|
@CallerSensitive
|
||||||
public static <T> T asInterfaceInstance(final Class<T> intfc, final MethodHandle target) {
|
public static <T> T asInterfaceInstance(final Class<T> intfc, final MethodHandle target) {
|
||||||
if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers()))
|
if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers()))
|
||||||
throw newIllegalArgumentException("not a public interface", intfc.getName());
|
throw newIllegalArgumentException("not a public interface", intfc.getName());
|
||||||
if (intfc.isSealed())
|
if (intfc.isSealed())
|
||||||
throw newIllegalArgumentException("a sealed interface", intfc.getName());
|
throw newIllegalArgumentException("a sealed interface", intfc.getName());
|
||||||
|
if (intfc.isHidden())
|
||||||
|
throw newIllegalArgumentException("a hidden interface", intfc.getName());
|
||||||
|
Objects.requireNonNull(target);
|
||||||
final MethodHandle mh;
|
final MethodHandle mh;
|
||||||
if (System.getSecurityManager() != null) {
|
@SuppressWarnings("removal")
|
||||||
|
var sm = System.getSecurityManager();
|
||||||
|
if (sm != null) {
|
||||||
final Class<?> caller = Reflection.getCallerClass();
|
final Class<?> caller = Reflection.getCallerClass();
|
||||||
final ClassLoader ccl = caller != null ? caller.getClassLoader() : null;
|
final ClassLoader ccl = caller != null ? caller.getClassLoader() : null;
|
||||||
ReflectUtil.checkProxyPackageAccess(ccl, intfc);
|
ReflectUtil.checkProxyPackageAccess(ccl, intfc);
|
||||||
|
@ -171,64 +180,298 @@ public class MethodHandleProxies {
|
||||||
} else {
|
} else {
|
||||||
mh = target;
|
mh = target;
|
||||||
}
|
}
|
||||||
ClassLoader proxyLoader = intfc.getClassLoader();
|
|
||||||
if (proxyLoader == null) {
|
// Define one hidden class for each interface. Create an instance of
|
||||||
ClassLoader cl = Thread.currentThread().getContextClassLoader(); // avoid use of BCP
|
// the hidden class for a given target method handle which will be
|
||||||
proxyLoader = cl != null ? cl : ClassLoader.getSystemClassLoader();
|
// accessed via getfield. Multiple instances may be created for a
|
||||||
|
// hidden class. This approach allows the generated hidden classes
|
||||||
|
// more shareable.
|
||||||
|
//
|
||||||
|
// The implementation class is weakly referenced; a new class is
|
||||||
|
// defined if the last one has been garbage collected.
|
||||||
|
//
|
||||||
|
// An alternative approach is to define one hidden class with the
|
||||||
|
// target method handle as class data and the target method handle
|
||||||
|
// is loaded via ldc/condy. If more than one target method handles
|
||||||
|
// are used, the extra classes will pollute the same type profiles.
|
||||||
|
// In addition, hidden classes without class data is more friendly
|
||||||
|
// for pre-generation (shifting the dynamic class generation from
|
||||||
|
// runtime to an earlier phrase).
|
||||||
|
Class<?> proxyClass = getProxyClass(intfc); // throws IllegalArgumentException
|
||||||
|
Lookup lookup = new Lookup(proxyClass);
|
||||||
|
Object proxy;
|
||||||
|
try {
|
||||||
|
MethodHandle constructor = lookup.findConstructor(proxyClass,
|
||||||
|
MT_void_Lookup_MethodHandle_MethodHandle)
|
||||||
|
.asType(MT_Object_Lookup_MethodHandle_MethodHandle);
|
||||||
|
proxy = constructor.invokeExact(lookup, target, mh);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
throw uncaughtException(ex);
|
||||||
}
|
}
|
||||||
final Method[] methods = getSingleNameMethods(intfc);
|
assert proxy.getClass().getModule().isNamed() : proxy.getClass() + " " + proxy.getClass().getModule();
|
||||||
if (methods == null)
|
return intfc.cast(proxy);
|
||||||
throw newIllegalArgumentException("not a single-method interface", intfc.getName());
|
|
||||||
final MethodHandle[] vaTargets = new MethodHandle[methods.length];
|
|
||||||
for (int i = 0; i < methods.length; i++) {
|
|
||||||
Method sm = methods[i];
|
|
||||||
MethodType smMT = MethodType.methodType(sm.getReturnType(), sm.getParameterTypes());
|
|
||||||
MethodHandle checkTarget = mh.asType(smMT); // make throw WMT
|
|
||||||
checkTarget = checkTarget.asType(checkTarget.type().changeReturnType(Object.class));
|
|
||||||
vaTargets[i] = checkTarget.asSpreader(Object[].class, smMT.parameterCount());
|
|
||||||
}
|
}
|
||||||
final InvocationHandler ih = new InvocationHandler() {
|
|
||||||
private Object getArg(String name) {
|
private record MethodInfo(MethodTypeDesc desc, List<ClassDesc> thrown, String fieldName) {}
|
||||||
if ((Object)name == "getWrapperInstanceTarget") return target;
|
|
||||||
if ((Object)name == "getWrapperInstanceType") return intfc;
|
private static final ClassFileDumper DUMPER = ClassFileDumper.getInstance(
|
||||||
throw new AssertionError();
|
"jdk.invoke.MethodHandleProxies.dumpClassFiles", "DUMP_MH_PROXY_CLASSFILES");
|
||||||
}
|
|
||||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
private static final Set<Class<?>> WRAPPER_TYPES = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
for (int i = 0; i < methods.length; i++) {
|
private static final ClassValue<WeakReferenceHolder<Class<?>>> PROXIES = new ClassValue<>() {
|
||||||
if (method.equals(methods[i]))
|
@Override
|
||||||
return vaTargets[i].invokeExact(args);
|
protected WeakReferenceHolder<Class<?>> computeValue(Class<?> intfc) {
|
||||||
}
|
return new WeakReferenceHolder<>(newProxyClass(intfc));
|
||||||
if (method.getDeclaringClass() == WrapperInstance.class)
|
|
||||||
return getArg(method.getName());
|
|
||||||
if (isObjectMethod(method))
|
|
||||||
return callObjectMethod(proxy, method, args);
|
|
||||||
if (isDefaultMethod(method)) {
|
|
||||||
// no additional access check is performed
|
|
||||||
return JLRA.invokeDefault(proxy, method, args, null);
|
|
||||||
}
|
|
||||||
throw newInternalError("bad proxy method: "+method);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final Object proxy;
|
private static Class<?> newProxyClass(Class<?> intfc) {
|
||||||
if (System.getSecurityManager() != null) {
|
List<MethodInfo> methods = new ArrayList<>();
|
||||||
// sun.invoke.WrapperInstance is a restricted interface not accessible
|
Set<Class<?>> referencedTypes = new HashSet<>();
|
||||||
// by any non-null class loader.
|
referencedTypes.add(intfc);
|
||||||
final ClassLoader loader = proxyLoader;
|
String uniqueName = null;
|
||||||
proxy = AccessController.doPrivileged(new PrivilegedAction<>() {
|
int count = 0;
|
||||||
public Object run() {
|
for (Method m : intfc.getMethods()) {
|
||||||
return Proxy.newProxyInstance(
|
if (!Modifier.isAbstract(m.getModifiers()))
|
||||||
loader,
|
continue;
|
||||||
new Class<?>[]{ intfc, WrapperInstance.class },
|
|
||||||
ih);
|
if (isObjectMethod(m))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// ensure it's SAM interface
|
||||||
|
String methodName = m.getName();
|
||||||
|
if (uniqueName == null) {
|
||||||
|
uniqueName = methodName;
|
||||||
|
} else if (!uniqueName.equals(methodName)) {
|
||||||
|
// too many abstract methods
|
||||||
|
throw newIllegalArgumentException("not a single-method interface", intfc.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the field name holding the method handle for this method
|
||||||
|
String fieldName = "m" + count++;
|
||||||
|
var mt = methodType(m.getReturnType(), JLRA.getExecutableSharedParameterTypes(m), true);
|
||||||
|
var thrown = JLRA.getExecutableSharedExceptionTypes(m);
|
||||||
|
var exceptionTypeDescs =
|
||||||
|
thrown.length == 0 ? DEFAULT_RETHROWS
|
||||||
|
: Stream.concat(DEFAULT_RETHROWS.stream(),
|
||||||
|
Arrays.stream(thrown).map(MethodHandleProxies::desc))
|
||||||
|
.distinct().toList();
|
||||||
|
methods.add(new MethodInfo(desc(mt), exceptionTypeDescs, fieldName));
|
||||||
|
|
||||||
|
// find the types referenced by this method
|
||||||
|
addElementType(referencedTypes, m.getReturnType());
|
||||||
|
addElementTypes(referencedTypes, JLRA.getExecutableSharedParameterTypes(m));
|
||||||
|
addElementTypes(referencedTypes, JLRA.getExecutableSharedExceptionTypes(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniqueName == null)
|
||||||
|
throw newIllegalArgumentException("no method in ", intfc.getName());
|
||||||
|
|
||||||
|
// create a dynamic module for each proxy class, which needs access
|
||||||
|
// to the types referenced by the members of the interface including
|
||||||
|
// the parameter types, return type and exception types
|
||||||
|
var loader = intfc.getClassLoader();
|
||||||
|
Module targetModule = newDynamicModule(loader, referencedTypes);
|
||||||
|
|
||||||
|
// generate a class file in the package of the dynamic module
|
||||||
|
String packageName = targetModule.getName();
|
||||||
|
String intfcName = intfc.getName();
|
||||||
|
int i = intfcName.lastIndexOf('.');
|
||||||
|
// jdk.MHProxy#.Interface
|
||||||
|
String className = packageName + "." + (i > 0 ? intfcName.substring(i + 1) : intfcName);
|
||||||
|
byte[] template = createTemplate(loader, ClassDesc.of(className), desc(intfc), uniqueName, methods);
|
||||||
|
// define the dynamic module to the class loader of the interface
|
||||||
|
var definer = new Lookup(intfc).makeHiddenClassDefiner(className, template, Set.of(), DUMPER);
|
||||||
|
|
||||||
|
@SuppressWarnings("removal")
|
||||||
|
var sm = System.getSecurityManager();
|
||||||
|
Lookup lookup;
|
||||||
|
if (sm != null) {
|
||||||
|
@SuppressWarnings("removal")
|
||||||
|
var l = AccessController.doPrivileged((PrivilegedAction<Lookup>) () ->
|
||||||
|
definer.defineClassAsLookup(true));
|
||||||
|
lookup = l;
|
||||||
|
} else {
|
||||||
|
lookup = definer.defineClassAsLookup(true);
|
||||||
|
}
|
||||||
|
// cache the wrapper type
|
||||||
|
var ret = lookup.lookupClass();
|
||||||
|
WRAPPER_TYPES.add(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class WeakReferenceHolder<T> {
|
||||||
|
private volatile WeakReference<T> ref;
|
||||||
|
|
||||||
|
WeakReferenceHolder(T value) {
|
||||||
|
set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(T value) {
|
||||||
|
ref = new WeakReference<>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
T get() {
|
||||||
|
return ref.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> getProxyClass(Class<?> intfc) {
|
||||||
|
WeakReferenceHolder<Class<?>> r = PROXIES.get(intfc);
|
||||||
|
Class<?> cl = r.get();
|
||||||
|
if (cl != null)
|
||||||
|
return cl;
|
||||||
|
|
||||||
|
// avoid spinning multiple classes in a race
|
||||||
|
synchronized (r) {
|
||||||
|
cl = r.get();
|
||||||
|
if (cl != null)
|
||||||
|
return cl;
|
||||||
|
|
||||||
|
// If the referent is cleared, create a new value and update cached weak reference.
|
||||||
|
cl = newProxyClass(intfc);
|
||||||
|
r.set(cl);
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final List<ClassDesc> DEFAULT_RETHROWS = List.of(desc(RuntimeException.class), desc(Error.class));
|
||||||
|
private static final ClassDesc CD_UndeclaredThrowableException = desc(UndeclaredThrowableException.class);
|
||||||
|
private static final ClassDesc CD_IllegalAccessException = desc(IllegalAccessException.class);
|
||||||
|
private static final MethodTypeDesc MTD_void_Throwable = MethodTypeDesc.of(CD_void, CD_Throwable);
|
||||||
|
private static final MethodType MT_void_Lookup_MethodHandle_MethodHandle =
|
||||||
|
methodType(void.class, Lookup.class, MethodHandle.class, MethodHandle.class);
|
||||||
|
private static final MethodType MT_Object_Lookup_MethodHandle_MethodHandle =
|
||||||
|
MT_void_Lookup_MethodHandle_MethodHandle.changeReturnType(Object.class);
|
||||||
|
private static final MethodType MT_MethodHandle_Object = methodType(MethodHandle.class, Object.class);
|
||||||
|
private static final MethodTypeDesc MTD_void_Lookup_MethodHandle_MethodHandle =
|
||||||
|
desc(MT_void_Lookup_MethodHandle_MethodHandle);
|
||||||
|
private static final MethodTypeDesc MTD_void_Lookup = MethodTypeDesc.of(CD_void, CD_MethodHandles_Lookup);
|
||||||
|
private static final MethodTypeDesc MTD_MethodHandle_MethodType = MethodTypeDesc.of(CD_MethodHandle, CD_MethodType);
|
||||||
|
private static final MethodTypeDesc MTD_Class = MethodTypeDesc.of(CD_Class);
|
||||||
|
private static final MethodTypeDesc MTD_int = MethodTypeDesc.of(CD_int);
|
||||||
|
private static final MethodTypeDesc MTD_String = MethodTypeDesc.of(CD_String);
|
||||||
|
private static final MethodTypeDesc MTD_void_String = MethodTypeDesc.of(CD_void, CD_String);
|
||||||
|
private static final String TARGET_NAME = "target";
|
||||||
|
private static final String TYPE_NAME = "interfaceType";
|
||||||
|
private static final String ENSURE_ORIGINAL_LOOKUP = "ensureOriginalLookup";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an implementation class file for a given interface. One implementation class is
|
||||||
|
* defined for each interface.
|
||||||
|
*
|
||||||
|
* @param ifaceDesc the given interface
|
||||||
|
* @param methodName the name of the single abstract method
|
||||||
|
* @param methods the information for implementation methods
|
||||||
|
* @return the bytes of the implementation classes
|
||||||
|
*/
|
||||||
|
private static byte[] createTemplate(ClassLoader loader, ClassDesc proxyDesc, ClassDesc ifaceDesc,
|
||||||
|
String methodName, List<MethodInfo> methods) {
|
||||||
|
return Classfile.of(ClassHierarchyResolverOption.of(ClassHierarchyResolver.ofClassLoading(loader)))
|
||||||
|
.build(proxyDesc, clb -> {
|
||||||
|
clb.withSuperclass(CD_Object);
|
||||||
|
clb.withFlags(ACC_FINAL | ACC_SYNTHETIC);
|
||||||
|
clb.withInterfaceSymbols(ifaceDesc);
|
||||||
|
|
||||||
|
// static and instance fields
|
||||||
|
clb.withField(TYPE_NAME, CD_Class, ACC_PRIVATE | ACC_STATIC | ACC_FINAL);
|
||||||
|
clb.withField(TARGET_NAME, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL);
|
||||||
|
for (var mi : methods) {
|
||||||
|
clb.withField(mi.fieldName, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// <clinit>
|
||||||
|
clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
|
||||||
|
cob.constantInstruction(ifaceDesc);
|
||||||
|
cob.putstatic(proxyDesc, TYPE_NAME, CD_Class);
|
||||||
|
cob.return_();
|
||||||
|
});
|
||||||
|
|
||||||
|
// <init>(Lookup, MethodHandle target, MethodHandle callerBoundTarget)
|
||||||
|
clb.withMethodBody(INIT_NAME, MTD_void_Lookup_MethodHandle_MethodHandle, 0, cob -> {
|
||||||
|
cob.aload(0);
|
||||||
|
cob.invokespecial(CD_Object, INIT_NAME, MTD_void);
|
||||||
|
|
||||||
|
// call ensureOriginalLookup to verify the given Lookup has access
|
||||||
|
cob.aload(1);
|
||||||
|
cob.invokestatic(proxyDesc, "ensureOriginalLookup", MTD_void_Lookup);
|
||||||
|
|
||||||
|
// this.target = target;
|
||||||
|
cob.aload(0);
|
||||||
|
cob.aload(2);
|
||||||
|
cob.putfield(proxyDesc, TARGET_NAME, CD_MethodHandle);
|
||||||
|
|
||||||
|
// method handles adjusted to the method type of each method
|
||||||
|
for (var mi : methods) {
|
||||||
|
// this.m<i> = callerBoundTarget.asType(xxType);
|
||||||
|
cob.aload(0);
|
||||||
|
cob.aload(3);
|
||||||
|
cob.constantInstruction(mi.desc);
|
||||||
|
cob.invokevirtual(CD_MethodHandle, "asType", MTD_MethodHandle_MethodType);
|
||||||
|
cob.putfield(proxyDesc, mi.fieldName, CD_MethodHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete
|
||||||
|
cob.return_();
|
||||||
|
});
|
||||||
|
|
||||||
|
// private static void ensureOriginalLookup(Lookup) checks if the given Lookup
|
||||||
|
// has ORIGINAL access to this class, i.e. the lookup class is this class;
|
||||||
|
// otherwise, IllegalAccessException is thrown
|
||||||
|
clb.withMethodBody(ENSURE_ORIGINAL_LOOKUP, MTD_void_Lookup, ACC_PRIVATE | ACC_STATIC, cob -> {
|
||||||
|
var failLabel = cob.newLabel();
|
||||||
|
// check lookupClass
|
||||||
|
cob.aload(0);
|
||||||
|
cob.invokevirtual(CD_MethodHandles_Lookup, "lookupClass", MTD_Class);
|
||||||
|
cob.constantInstruction(proxyDesc);
|
||||||
|
cob.if_acmpne(failLabel);
|
||||||
|
// check original access
|
||||||
|
cob.aload(0);
|
||||||
|
cob.invokevirtual(CD_MethodHandles_Lookup, "lookupModes", MTD_int);
|
||||||
|
cob.constantInstruction(Lookup.ORIGINAL);
|
||||||
|
cob.iand();
|
||||||
|
cob.ifeq(failLabel);
|
||||||
|
// success
|
||||||
|
cob.return_();
|
||||||
|
// throw exception
|
||||||
|
cob.labelBinding(failLabel);
|
||||||
|
cob.new_(CD_IllegalAccessException);
|
||||||
|
cob.dup();
|
||||||
|
cob.aload(0); // lookup
|
||||||
|
cob.invokevirtual(CD_Object, "toString", MTD_String);
|
||||||
|
cob.invokespecial(CD_IllegalAccessException, INIT_NAME, MTD_void_String);
|
||||||
|
cob.athrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
// implementation methods
|
||||||
|
for (MethodInfo mi : methods) {
|
||||||
|
// no need to generate thrown exception attribute
|
||||||
|
clb.withMethodBody(methodName, mi.desc, ACC_PUBLIC, cob -> cob
|
||||||
|
.trying(bcb -> {
|
||||||
|
// return this.handleField.invokeExact(arguments...);
|
||||||
|
bcb.aload(0);
|
||||||
|
bcb.getfield(proxyDesc, mi.fieldName, CD_MethodHandle);
|
||||||
|
for (int j = 0; j < mi.desc.parameterCount(); j++) {
|
||||||
|
bcb.loadInstruction(TypeKind.from(mi.desc.parameterType(j)),
|
||||||
|
bcb.parameterSlot(j));
|
||||||
|
}
|
||||||
|
bcb.invokevirtual(CD_MethodHandle, "invokeExact", mi.desc);
|
||||||
|
bcb.returnInstruction(TypeKind.from(mi.desc.returnType()));
|
||||||
|
}, ctb -> ctb
|
||||||
|
// catch (Error | RuntimeException | Declared ex) { throw ex; }
|
||||||
|
.catchingMulti(mi.thrown, CodeBuilder::athrow)
|
||||||
|
// catch (Throwable ex) { throw new UndeclaredThrowableException(ex); }
|
||||||
|
.catchingAll(cb -> cb
|
||||||
|
.new_(CD_UndeclaredThrowableException)
|
||||||
|
.dup_x1()
|
||||||
|
.swap()
|
||||||
|
.invokespecial(CD_UndeclaredThrowableException,
|
||||||
|
INIT_NAME, MTD_void_Throwable)
|
||||||
|
.athrow()
|
||||||
|
)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
proxy = Proxy.newProxyInstance(proxyLoader,
|
|
||||||
new Class<?>[]{ intfc, WrapperInstance.class },
|
|
||||||
ih);
|
|
||||||
}
|
|
||||||
return intfc.cast(proxy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MethodHandle bindCaller(MethodHandle target, Class<?> hostClass) {
|
private static MethodHandle bindCaller(MethodHandle target, Class<?> hostClass) {
|
||||||
|
@ -241,16 +484,7 @@ public class MethodHandleProxies {
|
||||||
* @return true if the reference is not null and points to an object produced by {@code asInterfaceInstance}
|
* @return true if the reference is not null and points to an object produced by {@code asInterfaceInstance}
|
||||||
*/
|
*/
|
||||||
public static boolean isWrapperInstance(Object x) {
|
public static boolean isWrapperInstance(Object x) {
|
||||||
return x instanceof WrapperInstance;
|
return x != null && WRAPPER_TYPES.contains(x.getClass());
|
||||||
}
|
|
||||||
|
|
||||||
private static WrapperInstance asWrapperInstance(Object x) {
|
|
||||||
try {
|
|
||||||
if (x != null)
|
|
||||||
return (WrapperInstance) x;
|
|
||||||
} catch (ClassCastException ex) {
|
|
||||||
}
|
|
||||||
throw newIllegalArgumentException("not a wrapper instance");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -263,7 +497,17 @@ public class MethodHandleProxies {
|
||||||
* @throws IllegalArgumentException if the reference x is not to a wrapper instance
|
* @throws IllegalArgumentException if the reference x is not to a wrapper instance
|
||||||
*/
|
*/
|
||||||
public static MethodHandle wrapperInstanceTarget(Object x) {
|
public static MethodHandle wrapperInstanceTarget(Object x) {
|
||||||
return asWrapperInstance(x).getWrapperInstanceTarget();
|
if (!isWrapperInstance(x))
|
||||||
|
throw new IllegalArgumentException("not a wrapper instance: " + x);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> type = x.getClass();
|
||||||
|
MethodHandle getter = new Lookup(type).findGetter(type, TARGET_NAME, MethodHandle.class)
|
||||||
|
.asType(MT_MethodHandle_Object);
|
||||||
|
return (MethodHandle) getter.invokeExact(x);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
throw uncaughtException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,7 +519,61 @@ public class MethodHandleProxies {
|
||||||
* @throws IllegalArgumentException if the reference x is not to a wrapper instance
|
* @throws IllegalArgumentException if the reference x is not to a wrapper instance
|
||||||
*/
|
*/
|
||||||
public static Class<?> wrapperInstanceType(Object x) {
|
public static Class<?> wrapperInstanceType(Object x) {
|
||||||
return asWrapperInstance(x).getWrapperInstanceType();
|
if (!isWrapperInstance(x))
|
||||||
|
throw new IllegalArgumentException("not a wrapper instance: " + x);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> type = x.getClass();
|
||||||
|
MethodHandle originalTypeField = new Lookup(type).findStaticGetter(type, TYPE_NAME, Class.class);
|
||||||
|
return (Class<?>) originalTypeField.invokeExact();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw uncaughtException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassDesc desc(Class<?> cl) {
|
||||||
|
return cl.describeConstable().orElseThrow(() -> newInternalError("Cannot convert class "
|
||||||
|
+ cl.getName() + " to a constant"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodTypeDesc desc(MethodType mt) {
|
||||||
|
return mt.describeConstable().orElseThrow(() -> newInternalError("Cannot convert method type "
|
||||||
|
+ mt + " to a constant"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final JavaLangReflectAccess JLRA = SharedSecrets.getJavaLangReflectAccess();
|
||||||
|
private static final AtomicInteger counter = new AtomicInteger();
|
||||||
|
|
||||||
|
private static String nextModuleName() {
|
||||||
|
return "jdk.MHProxy" + counter.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a dynamic module defined to the given class loader and has
|
||||||
|
* access to the given types.
|
||||||
|
* <p>
|
||||||
|
* The dynamic module contains only one single package named the same as
|
||||||
|
* the name of the dynamic module. It's not exported or open.
|
||||||
|
*/
|
||||||
|
private static Module newDynamicModule(ClassLoader ld, Set<Class<?>> types) {
|
||||||
|
Objects.requireNonNull(types);
|
||||||
|
|
||||||
|
// create a dynamic module and setup module access
|
||||||
|
String mn = nextModuleName();
|
||||||
|
ModuleDescriptor descriptor = ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC))
|
||||||
|
.packages(Set.of(mn))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Module dynModule = Modules.defineModule(ld, descriptor, null);
|
||||||
|
Module javaBase = Object.class.getModule();
|
||||||
|
|
||||||
|
Modules.addReads(dynModule, javaBase);
|
||||||
|
Modules.addOpens(dynModule, mn, javaBase);
|
||||||
|
|
||||||
|
for (Class<?> c : types) {
|
||||||
|
ensureAccess(dynModule, c);
|
||||||
|
}
|
||||||
|
return dynModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isObjectMethod(Method m) {
|
private static boolean isObjectMethod(Method m) {
|
||||||
|
@ -286,41 +584,40 @@ public class MethodHandleProxies {
|
||||||
&& m.getParameterCount() == 0;
|
&& m.getParameterCount() == 0;
|
||||||
case "equals" -> m.getReturnType() == boolean.class
|
case "equals" -> m.getReturnType() == boolean.class
|
||||||
&& m.getParameterCount() == 1
|
&& m.getParameterCount() == 1
|
||||||
&& m.getParameterTypes()[0] == Object.class;
|
&& JLRA.getExecutableSharedParameterTypes(m)[0] == Object.class;
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object callObjectMethod(Object self, Method m, Object[] args) {
|
/*
|
||||||
assert(isObjectMethod(m)) : m;
|
* Ensure the given module can access the given class.
|
||||||
return switch (m.getName()) {
|
*/
|
||||||
case "toString" -> java.util.Objects.toIdentityString(self);
|
private static void ensureAccess(Module target, Class<?> c) {
|
||||||
case "hashCode" -> System.identityHashCode(self);
|
Module m = c.getModule();
|
||||||
case "equals" -> (self == args[0]);
|
// add read edge and qualified export for the target module to access
|
||||||
default -> null;
|
if (!target.canRead(m)) {
|
||||||
};
|
Modules.addReads(target, m);
|
||||||
|
}
|
||||||
|
String pn = c.getPackageName();
|
||||||
|
if (!m.isExported(pn, target)) {
|
||||||
|
Modules.addExports(m, pn, target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Method[] getSingleNameMethods(Class<?> intfc) {
|
private static void addElementTypes(Set<Class<?>> types, Class<?>... classes) {
|
||||||
ArrayList<Method> methods = new ArrayList<>();
|
for (var cls : classes) {
|
||||||
String uniqueName = null;
|
addElementType(types, cls);
|
||||||
for (Method m : intfc.getMethods()) {
|
|
||||||
if (isObjectMethod(m)) continue;
|
|
||||||
if (!Modifier.isAbstract(m.getModifiers())) continue;
|
|
||||||
String mname = m.getName();
|
|
||||||
if (uniqueName == null)
|
|
||||||
uniqueName = mname;
|
|
||||||
else if (!uniqueName.equals(mname))
|
|
||||||
return null; // too many abstract methods
|
|
||||||
methods.add(m);
|
|
||||||
}
|
}
|
||||||
if (uniqueName == null) return null;
|
|
||||||
return methods.toArray(new Method[methods.size()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDefaultMethod(Method m) {
|
private static void addElementType(Set<Class<?>> types, Class<?> cls) {
|
||||||
return !Modifier.isAbstract(m.getModifiers());
|
Class<?> e = cls;
|
||||||
|
while (e.isArray()) {
|
||||||
|
e = e.getComponentType();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final JavaLangReflectAccess JLRA = SharedSecrets.getJavaLangReflectAccess();
|
if (!e.isPrimitive()) {
|
||||||
|
types.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -94,6 +94,10 @@ class ReflectAccess implements jdk.internal.access.JavaLangReflectAccess {
|
||||||
return ex.getSharedParameterTypes();
|
return ex.getSharedParameterTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Class<?>[] getExecutableSharedExceptionTypes(Executable ex) {
|
||||||
|
return ex.getSharedExceptionTypes();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Copying routines, needed to quickly fabricate new Field,
|
// Copying routines, needed to quickly fabricate new Field,
|
||||||
// Method, and Constructor objects from templates
|
// Method, and Constructor objects from templates
|
||||||
|
@ -127,9 +131,4 @@ class ReflectAccess implements jdk.internal.access.JavaLangReflectAccess {
|
||||||
{
|
{
|
||||||
return ctor.newInstanceWithCaller(args, true, caller);
|
return ctor.newInstanceWithCaller(args, true, caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object invokeDefault(Object proxy, Method method, Object[] args, Class<?> caller)
|
|
||||||
throws Throwable {
|
|
||||||
return Proxy.invokeDefault(proxy, method, args, caller);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -76,6 +76,9 @@ public interface JavaLangReflectAccess {
|
||||||
/** Gets the shared array of parameter types of an Executable. */
|
/** Gets the shared array of parameter types of an Executable. */
|
||||||
public Class<?>[] getExecutableSharedParameterTypes(Executable ex);
|
public Class<?>[] getExecutableSharedParameterTypes(Executable ex);
|
||||||
|
|
||||||
|
/** Gets the shared array of exception types of an Executable. */
|
||||||
|
public Class<?>[] getExecutableSharedExceptionTypes(Executable ex);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Copying routines, needed to quickly fabricate new Field,
|
// Copying routines, needed to quickly fabricate new Field,
|
||||||
// Method, and Constructor objects from templates
|
// Method, and Constructor objects from templates
|
||||||
|
@ -102,11 +105,4 @@ public interface JavaLangReflectAccess {
|
||||||
/** Returns a new instance created by the given constructor with access check */
|
/** Returns a new instance created by the given constructor with access check */
|
||||||
public <T> T newInstance(Constructor<T> ctor, Object[] args, Class<?> caller)
|
public <T> T newInstance(Constructor<T> ctor, Object[] args, Class<?> caller)
|
||||||
throws IllegalAccessException, InstantiationException, InvocationTargetException;
|
throws IllegalAccessException, InstantiationException, InvocationTargetException;
|
||||||
|
|
||||||
/** Invokes the given default method if the method's declaring interface is
|
|
||||||
* accessible to the given caller. Otherwise, IllegalAccessException will
|
|
||||||
* be thrown. If the caller is null, no access check is performed.
|
|
||||||
*/
|
|
||||||
public Object invokeDefault(Object proxy, Method method, Object[] args, Class<?> caller)
|
|
||||||
throws Throwable;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
||||||
*
|
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License version 2 only, as
|
|
||||||
* published by the Free Software Foundation. Oracle designates this
|
|
||||||
* particular file as subject to the "Classpath" exception as provided
|
|
||||||
* by Oracle in the LICENSE file that accompanied this code.
|
|
||||||
*
|
|
||||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* version 2 for more details (a copy is included in the LICENSE file that
|
|
||||||
* accompanied this code).
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License version
|
|
||||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*
|
|
||||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
||||||
* or visit www.oracle.com if you need additional information or have any
|
|
||||||
* questions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sun.invoke;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private API used inside of java.lang.invoke.MethodHandles.
|
|
||||||
* Interface implemented by every object which is produced by
|
|
||||||
* {@link java.lang.invoke.MethodHandleProxies#asInterfaceInstance MethodHandleProxies.asInterfaceInstance}.
|
|
||||||
* The methods of this interface allow a caller to recover the parameters
|
|
||||||
* to {@code asInstance}.
|
|
||||||
* This allows applications to repeatedly convert between method handles
|
|
||||||
* and SAM objects, without the risk of creating unbounded delegation chains.
|
|
||||||
*/
|
|
||||||
public interface WrapperInstance {
|
|
||||||
/** Produce or recover a target method handle which is behaviorally
|
|
||||||
* equivalent to the SAM method of this object.
|
|
||||||
*/
|
|
||||||
public MethodHandle getWrapperInstanceTarget();
|
|
||||||
/** Recover the SAM type for which this object was created.
|
|
||||||
*/
|
|
||||||
public Class<?> getWrapperInstanceType();
|
|
||||||
}
|
|
||||||
|
|
378
test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java
Normal file
378
test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java
Normal file
|
@ -0,0 +1,378 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jdk.internal.classfile.ClassHierarchyResolver;
|
||||||
|
import jdk.internal.classfile.Classfile;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.constant.ClassDesc;
|
||||||
|
import java.lang.constant.MethodTypeDesc;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.WrongMethodTypeException;
|
||||||
|
import java.lang.reflect.AccessFlag;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.UndeclaredThrowableException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
import java.util.function.IntSupplier;
|
||||||
|
import java.util.function.ToLongFunction;
|
||||||
|
|
||||||
|
import static java.lang.constant.ConstantDescs.*;
|
||||||
|
import static java.lang.invoke.MethodHandleProxies.*;
|
||||||
|
import static java.lang.invoke.MethodType.genericMethodType;
|
||||||
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
import static jdk.internal.classfile.Classfile.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 6983726 8206955 8269351
|
||||||
|
* @modules java.base/jdk.internal.classfile
|
||||||
|
* java.base/jdk.internal.classfile.attribute
|
||||||
|
* java.base/jdk.internal.classfile.constantpool
|
||||||
|
* @summary Basic sanity tests for MethodHandleProxies
|
||||||
|
* @build BasicTest Client
|
||||||
|
* @run junit BasicTest
|
||||||
|
*/
|
||||||
|
public class BasicTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUsual() throws Throwable {
|
||||||
|
AtomicInteger ai = new AtomicInteger(5);
|
||||||
|
var mh = MethodHandles.lookup().findVirtual(AtomicInteger.class, "getAndIncrement", methodType(int.class));
|
||||||
|
IntSupplier is = asInterfaceInstance(IntSupplier.class, mh.bindTo(ai));
|
||||||
|
assertEquals(5, is.getAsInt());
|
||||||
|
assertEquals(6, is.getAsInt());
|
||||||
|
assertEquals(7, is.getAsInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Established null behaviors of MHP API.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNulls() {
|
||||||
|
assertThrows(NullPointerException.class, () ->
|
||||||
|
asInterfaceInstance(null, MethodHandles.zero(void.class)),
|
||||||
|
"asInterfaceInstance - intfc");
|
||||||
|
assertThrows(NullPointerException.class, () ->
|
||||||
|
asInterfaceInstance(Runnable.class, null),
|
||||||
|
"asInterfaceInstance - target");
|
||||||
|
|
||||||
|
assertFalse(isWrapperInstance(null), "isWrapperInstance");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> wrapperInstanceTarget(null),
|
||||||
|
"wrapperInstanceTarget");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> wrapperInstanceType(null),
|
||||||
|
"wrapperInstanceType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWrapperInstance() throws Throwable {
|
||||||
|
var mh = MethodHandles.publicLookup()
|
||||||
|
.findVirtual(Integer.class, "compareTo", methodType(int.class, Integer.class));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Comparator<Integer> proxy = (Comparator<Integer>) asInterfaceInstance(Comparator.class, mh);
|
||||||
|
|
||||||
|
assertTrue(isWrapperInstance(proxy));
|
||||||
|
assertSame(mh, wrapperInstanceTarget(proxy));
|
||||||
|
assertSame(Comparator.class, wrapperInstanceType(proxy));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests undeclared exceptions and declared exceptions in proxies.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testThrowables() {
|
||||||
|
// don't wrap
|
||||||
|
assertThrows(Error.class, throwing(Error.class, new Error())::close,
|
||||||
|
"Errors should be propagated");
|
||||||
|
assertThrows(RuntimeException.class, throwing(RuntimeException.class, new RuntimeException())::close,
|
||||||
|
"RuntimeException should be propagated");
|
||||||
|
assertThrows(IOException.class, throwing(IOException.class, new IOException())::close,
|
||||||
|
"Declared IOException should be propagated");
|
||||||
|
// wrap
|
||||||
|
assertThrows(UndeclaredThrowableException.class, throwing(IllegalAccessException.class,
|
||||||
|
new IllegalAccessException())::close,
|
||||||
|
"Undeclared IllegalAccessException should be wrapped");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that invalid interfaces are rejected.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRejects() {
|
||||||
|
var mh = MethodHandles.constant(String.class, "42");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(PackagePrivate.class, mh),
|
||||||
|
"non-public interface");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(loadHidden(), mh),
|
||||||
|
"hidden interface");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(MultiAbstractMethods.class, mh),
|
||||||
|
"multiple abstract method names");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(NoAbstractMethods.class, mh),
|
||||||
|
"no abstract method");
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(Sealed.class, mh),
|
||||||
|
"sealed interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that non-sealed interfaces can be implemented.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNonSealed() {
|
||||||
|
MethodHandle target = MethodHandles.constant(String.class, "Non-Sealed");
|
||||||
|
NonSealed proxy = asInterfaceInstance(NonSealed.class, target);
|
||||||
|
assertEquals(proxy.m(), "Non-Sealed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that Proxy correctly proxies potential bridge abstract methods.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testMultiSameName() throws Throwable {
|
||||||
|
var baseAndChild = loadBaseAndChild();
|
||||||
|
var baseClass = baseAndChild.get(0);
|
||||||
|
var childClass = baseAndChild.get(1);
|
||||||
|
checkMethods(childClass.getMethods());
|
||||||
|
checkMethods(childClass.getDeclaredMethods());
|
||||||
|
|
||||||
|
var lookup = MethodHandles.lookup();
|
||||||
|
var baseValueMh = lookup.findVirtual(baseClass, "value", genericMethodType(0))
|
||||||
|
.asType(genericMethodType(1));
|
||||||
|
var childIntegerValueMh = lookup.findVirtual(childClass, "value", methodType(Integer.class))
|
||||||
|
.asType(methodType(Integer.class, Object.class));
|
||||||
|
var childIntValueMh = lookup.findVirtual(childClass, "value", methodType(int.class))
|
||||||
|
.asType(methodType(int.class, Object.class));
|
||||||
|
|
||||||
|
Object child = asInterfaceInstance(childClass, MethodHandles.constant(Integer.class, 7));
|
||||||
|
|
||||||
|
assertEquals(7, (Object) baseValueMh.invokeExact(child));
|
||||||
|
assertEquals(7, (Integer) childIntegerValueMh.invokeExact(child));
|
||||||
|
assertEquals(7, (int) childIntValueMh.invokeExact(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that default methods can be used.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDefaultMethods() {
|
||||||
|
MethodHandle target = MethodHandles.constant(String.class, "F");
|
||||||
|
C proxy = asInterfaceInstance(C.class, target);
|
||||||
|
|
||||||
|
assertEquals(proxy.f(), "F");
|
||||||
|
assertEquals(proxy.a(), "A");
|
||||||
|
assertEquals(proxy.b(), "B");
|
||||||
|
assertEquals(proxy.c(), "C");
|
||||||
|
assertEquals(proxy.concat(), "ABC");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that correct implementation of default methods are called,
|
||||||
|
* and correct abstract methods are implemented.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOverrides() {
|
||||||
|
MethodHandle target = MethodHandles.constant(String.class, "concat");
|
||||||
|
D proxy = asInterfaceInstance(D.class, target);
|
||||||
|
|
||||||
|
assertEquals(proxy.a(), "OA");
|
||||||
|
assertEquals(proxy.b(), "OB");
|
||||||
|
assertEquals(proxy.c(), "OC");
|
||||||
|
assertEquals(proxy.f(), "OF");
|
||||||
|
assertEquals(proxy.concat(), "concat");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests primitive type conversions in proxies.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPrimitiveConversion() throws Throwable {
|
||||||
|
var mh = MethodHandles.lookup().findStatic(BasicTest.class, "mul",
|
||||||
|
methodType(long.class, int.class));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Function<Integer, Long> func = (Function<Integer, Long>) asInterfaceInstance(Function.class, mh);
|
||||||
|
assertEquals(32423432L * 32423432L, func.apply(32423432));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ToLongFunction<Integer> func1 = (ToLongFunction<Integer>) asInterfaceInstance(ToLongFunction.class, mh);
|
||||||
|
assertEquals(32423432L * 32423432L, func1.applyAsLong(32423432));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
IntFunction<Long> func2 = (IntFunction<Long>) asInterfaceInstance(IntFunction.class, mh);
|
||||||
|
assertEquals(32423432L * 32423432L, func2.apply(32423432));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests common type conversions in proxies.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testBasicConversion() {
|
||||||
|
var mh = MethodHandles.constant(String.class, "42");
|
||||||
|
asInterfaceInstance(Client.class, mh).exec(); // return value dropped, runs fine
|
||||||
|
|
||||||
|
var nullMh = MethodHandles.zero(String.class);
|
||||||
|
var badIterable = asInterfaceInstance(Iterable.class, nullMh);
|
||||||
|
assertNull(badIterable.iterator()); // null is convertible
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests incompatible type conversions in proxy construction.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWrongConversion() {
|
||||||
|
var mh = MethodHandles.constant(String.class, "42");
|
||||||
|
assertThrows(WrongMethodTypeException.class, () -> asInterfaceInstance(IntSupplier.class, mh),
|
||||||
|
"cannot convert String return to int under any circumstance");
|
||||||
|
|
||||||
|
var proxy = asInterfaceInstance(Iterable.class, mh);
|
||||||
|
assertThrows(ClassCastException.class, proxy::iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Throwable> Closeable throwing(Class<T> clz, T value) {
|
||||||
|
return asInterfaceInstance(Closeable.class, MethodHandles.throwException(void.class, clz).bindTo(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long mul(int i) {
|
||||||
|
return (long) i * i;
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkMethods(Method[] methods) {
|
||||||
|
assertTrue(methods.length > 1, () -> "Should have more than 1 declared methods, found only " + Arrays.toString(methods));
|
||||||
|
for (Method method : methods) {
|
||||||
|
assertTrue(method.accessFlags().contains(AccessFlag.ABSTRACT), () -> method + " is not abstract");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> loadHidden() {
|
||||||
|
try (var is = BasicTest.class.getResourceAsStream("Client.class")) {
|
||||||
|
var bytes = Objects.requireNonNull(is).readAllBytes();
|
||||||
|
var lookup = MethodHandles.lookup();
|
||||||
|
return lookup.defineHiddenClass(bytes, true).lookupClass();
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
return fail("Hidden interface loading failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base: Object value();
|
||||||
|
// Child: Integer value(); int value();
|
||||||
|
private List<Class<?>> loadBaseAndChild() throws IllegalAccessException {
|
||||||
|
ClassDesc baseCd = ClassDesc.of("BasicTest$Base");
|
||||||
|
ClassDesc childCd = ClassDesc.of("BasicTest$Child");
|
||||||
|
var objMtd = MethodTypeDesc.of(CD_Object);
|
||||||
|
var integerMtd = MethodTypeDesc.of(CD_Integer);
|
||||||
|
var intMtd = MethodTypeDesc.of(CD_int);
|
||||||
|
var classfile = Classfile.of(Classfile.ClassHierarchyResolverOption.of(ClassHierarchyResolver.defaultResolver().orElse(
|
||||||
|
ClassHierarchyResolver.of(List.of(baseCd, childCd), Map.ofEntries(Map.entry(baseCd, CD_Object),
|
||||||
|
Map.entry(childCd, CD_Object))))));
|
||||||
|
|
||||||
|
var baseBytes = classfile.build(baseCd, clb -> {
|
||||||
|
clb.withSuperclass(CD_Object);
|
||||||
|
clb.withFlags(ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT);
|
||||||
|
clb.withMethod("value", objMtd, ACC_PUBLIC | ACC_ABSTRACT, mb -> {});
|
||||||
|
});
|
||||||
|
|
||||||
|
var lookup = MethodHandles.lookup();
|
||||||
|
var base = lookup.ensureInitialized(lookup.defineClass(baseBytes));
|
||||||
|
|
||||||
|
var childBytes = classfile.build(childCd, clb -> {
|
||||||
|
clb.withSuperclass(CD_Object);
|
||||||
|
clb.withInterfaceSymbols(baseCd);
|
||||||
|
clb.withFlags(ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT);
|
||||||
|
clb.withMethod("value", integerMtd, ACC_PUBLIC | ACC_ABSTRACT, mb -> {});
|
||||||
|
clb.withMethod("value", intMtd, ACC_PUBLIC | ACC_ABSTRACT, mb -> {});
|
||||||
|
});
|
||||||
|
|
||||||
|
var child = lookup.ensureInitialized(lookup.defineClass(childBytes));
|
||||||
|
return List.of(base, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MultiAbstractMethods {
|
||||||
|
String a();
|
||||||
|
String b();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface NoAbstractMethods {
|
||||||
|
String toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PackagePrivate {
|
||||||
|
Object value();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface A {
|
||||||
|
default String a() {
|
||||||
|
return "A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface B {
|
||||||
|
default String b() {
|
||||||
|
return "B";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface C extends A, B {
|
||||||
|
String f();
|
||||||
|
|
||||||
|
default String c() {
|
||||||
|
return "C";
|
||||||
|
}
|
||||||
|
|
||||||
|
default String concat() {
|
||||||
|
return a() + b() + c();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface D extends C {
|
||||||
|
String concat();
|
||||||
|
|
||||||
|
default String f() {
|
||||||
|
return "OF";
|
||||||
|
}
|
||||||
|
|
||||||
|
default String a() {
|
||||||
|
return "OA";
|
||||||
|
}
|
||||||
|
|
||||||
|
default String b() {
|
||||||
|
return "OB";
|
||||||
|
}
|
||||||
|
|
||||||
|
default String c() {
|
||||||
|
return "OC";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed interface Sealed permits NonSealed {
|
||||||
|
String m();
|
||||||
|
}
|
||||||
|
|
||||||
|
public non-sealed interface NonSealed extends Sealed {
|
||||||
|
}
|
||||||
|
}
|
39
test/jdk/java/lang/invoke/MethodHandleProxies/Client.java
Normal file
39
test/jdk/java/lang/invoke/MethodHandleProxies/Client.java
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A general-purposed public client interface for testing.
|
||||||
|
*/
|
||||||
|
public interface Client {
|
||||||
|
/**
|
||||||
|
* Returns a lookup from Client.class.
|
||||||
|
*/
|
||||||
|
static Lookup lookup() {
|
||||||
|
return MethodHandles.lookup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void exec();
|
||||||
|
}
|
|
@ -1,122 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
|
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
||||||
*
|
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License version 2 only, as
|
|
||||||
* published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* version 2 for more details (a copy is included in the LICENSE file that
|
|
||||||
* accompanied this code).
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License version
|
|
||||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*
|
|
||||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
||||||
* or visit www.oracle.com if you need additional information or have any
|
|
||||||
* questions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* @test
|
|
||||||
* @bug 8206955 8269351
|
|
||||||
* @run testng/othervm -ea -esa test.java.lang.invoke.MethodHandlesProxiesTest
|
|
||||||
*/
|
|
||||||
|
|
||||||
package test.java.lang.invoke;
|
|
||||||
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandleProxies;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
|
|
||||||
import static org.testng.Assert.assertEquals;
|
|
||||||
|
|
||||||
public class MethodHandlesProxiesTest {
|
|
||||||
|
|
||||||
public interface A {
|
|
||||||
default String a() {
|
|
||||||
return "A";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface B {
|
|
||||||
default String b() {
|
|
||||||
return "B";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface C extends A, B {
|
|
||||||
String f();
|
|
||||||
|
|
||||||
default String c() {
|
|
||||||
return "C";
|
|
||||||
}
|
|
||||||
|
|
||||||
default String concat() {
|
|
||||||
return a() + b() + c();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Override extends C {
|
|
||||||
String f();
|
|
||||||
|
|
||||||
default String a() {
|
|
||||||
return "OA";
|
|
||||||
}
|
|
||||||
|
|
||||||
default String b() {
|
|
||||||
return "OB";
|
|
||||||
}
|
|
||||||
|
|
||||||
default String c() {
|
|
||||||
return "OC";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public static void testDefaultMethods() throws Throwable {
|
|
||||||
MethodHandle target = MethodHandles.constant(String.class, "F");
|
|
||||||
C proxy = MethodHandleProxies.asInterfaceInstance(C.class, target);
|
|
||||||
|
|
||||||
assertEquals(proxy.f(), "F");
|
|
||||||
assertEquals(proxy.a(), "A");
|
|
||||||
assertEquals(proxy.b(), "B");
|
|
||||||
assertEquals(proxy.c(), "C");
|
|
||||||
assertEquals(proxy.concat(), "ABC");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public static void testOverriddenDefaultMethods() throws Throwable {
|
|
||||||
MethodHandle target = MethodHandles.constant(String.class, "F");
|
|
||||||
Override proxy = MethodHandleProxies.asInterfaceInstance(Override.class, target);
|
|
||||||
|
|
||||||
assertEquals(proxy.a(), "OA");
|
|
||||||
assertEquals(proxy.b(), "OB");
|
|
||||||
assertEquals(proxy.c(), "OC");
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed interface Intf permits NonSealedInterface {
|
|
||||||
String m();
|
|
||||||
}
|
|
||||||
|
|
||||||
public non-sealed interface NonSealedInterface extends Intf {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expectedExceptions = { IllegalArgumentException.class })
|
|
||||||
public void testSealedInterface() {
|
|
||||||
MethodHandle target = MethodHandles.constant(String.class, "Sealed");
|
|
||||||
MethodHandleProxies.asInterfaceInstance(Intf.class, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNonSealedInterface() {
|
|
||||||
MethodHandle target = MethodHandles.constant(String.class, "Non-Sealed");
|
|
||||||
NonSealedInterface proxy = MethodHandleProxies.asInterfaceInstance(NonSealedInterface.class, target);
|
|
||||||
assertEquals(proxy.m(), "Non-Sealed");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandleProxies;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static jdk.test.lib.Asserts.assertSame;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 6983726
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.lib.Asserts Client
|
||||||
|
* @run main/othervm/policy=jtreg.security.policy WithSecurityManagerTest
|
||||||
|
* @summary Checks MethodHandleProxies behavior with security manager present
|
||||||
|
*/
|
||||||
|
public class WithSecurityManagerTest {
|
||||||
|
public interface NestedInterface {
|
||||||
|
void task();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) {
|
||||||
|
var originalMh = MethodHandles.zero(void.class);
|
||||||
|
|
||||||
|
// Test system and user interfaces
|
||||||
|
for (Class<?> cl : List.of(Runnable.class, Client.class, NestedInterface.class)) {
|
||||||
|
try {
|
||||||
|
Object o = MethodHandleProxies.asInterfaceInstance(cl, originalMh);
|
||||||
|
testWrapperInstanceTarget(o, originalMh);
|
||||||
|
testWrapperInstanceType(o, cl);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
throw new AssertionError("Test failed for " + cl, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testWrapperInstanceTarget(Object wrapper, MethodHandle originalMh) {
|
||||||
|
var recoveredTarget = MethodHandleProxies.wrapperInstanceTarget(wrapper);
|
||||||
|
assertSame(originalMh, recoveredTarget, "wrapperInstanceTarget recovery");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testWrapperInstanceType(Object wrapper, Class<?> type) {
|
||||||
|
var recoveredType = MethodHandleProxies.wrapperInstanceType(wrapper);
|
||||||
|
assertSame(type, recoveredType, "wrapperInstanceType recovery");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jdk.internal.classfile.Classfile;
|
||||||
|
import jdk.test.lib.util.ForceGC;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import java.lang.constant.ClassDesc;
|
||||||
|
import java.lang.constant.MethodTypeDesc;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandleProxies;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import static java.lang.constant.ConstantDescs.*;
|
||||||
|
import static java.lang.invoke.MethodHandleProxies.*;
|
||||||
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
import static jdk.internal.classfile.Classfile.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 6983726
|
||||||
|
* @library /test/lib
|
||||||
|
* @modules java.base/jdk.internal.classfile
|
||||||
|
* java.base/jdk.internal.classfile.attribute
|
||||||
|
* java.base/jdk.internal.classfile.constantpool
|
||||||
|
* @summary Tests on implementation hidden classes spinned by MethodHandleProxies
|
||||||
|
* @build WrapperHiddenClassTest Client jdk.test.lib.util.ForceGC
|
||||||
|
* @run junit WrapperHiddenClassTest
|
||||||
|
*/
|
||||||
|
public class WrapperHiddenClassTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests an adversary "implementation" class will not be
|
||||||
|
* "recovered" by the wrapperInstance* APIs
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWrapperInstance() throws Throwable {
|
||||||
|
Comparator<Integer> hostile = createHostileInstance();
|
||||||
|
var mh = MethodHandles.publicLookup()
|
||||||
|
.findVirtual(Integer.class, "compareTo", methodType(int.class, Integer.class));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Comparator<Integer> proxy = (Comparator<Integer>) asInterfaceInstance(Comparator.class, mh);
|
||||||
|
|
||||||
|
assertTrue(isWrapperInstance(proxy));
|
||||||
|
assertFalse(isWrapperInstance(hostile));
|
||||||
|
assertSame(mh, wrapperInstanceTarget(proxy));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> wrapperInstanceTarget(hostile));
|
||||||
|
assertSame(Comparator.class, wrapperInstanceType(proxy));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> wrapperInstanceType(hostile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String TYPE = "interfaceType";
|
||||||
|
private static final String TARGET = "target";
|
||||||
|
private static final ClassDesc CD_HostileWrapper = ClassDesc.of("HostileWrapper");
|
||||||
|
private static final ClassDesc CD_Comparator = ClassDesc.of("java.util.Comparator");
|
||||||
|
private static final MethodTypeDesc MTD_int_Object_Object = MethodTypeDesc.of(CD_int, CD_Object, CD_Object);
|
||||||
|
private static final MethodTypeDesc MTD_int_Integer = MethodTypeDesc.of(CD_int, CD_Integer);
|
||||||
|
|
||||||
|
// Update this template when the MHP template is updated
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Comparator<Integer> createHostileInstance() throws Throwable {
|
||||||
|
var cf = Classfile.of();
|
||||||
|
var bytes = cf.build(CD_HostileWrapper, clb -> {
|
||||||
|
clb.withSuperclass(CD_Object);
|
||||||
|
clb.withFlags(ACC_FINAL | ACC_SYNTHETIC);
|
||||||
|
clb.withInterfaceSymbols(CD_Comparator);
|
||||||
|
|
||||||
|
// static and instance fields
|
||||||
|
clb.withField(TYPE, CD_Class, ACC_PRIVATE | ACC_STATIC | ACC_FINAL);
|
||||||
|
clb.withField(TARGET, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL);
|
||||||
|
|
||||||
|
// <clinit>
|
||||||
|
clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
|
||||||
|
cob.constantInstruction(CD_Comparator);
|
||||||
|
cob.putstatic(CD_HostileWrapper, TYPE, CD_Class);
|
||||||
|
cob.return_();
|
||||||
|
});
|
||||||
|
|
||||||
|
// <init>
|
||||||
|
clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> {
|
||||||
|
cob.aload(0);
|
||||||
|
cob.invokespecial(CD_Object, INIT_NAME, MTD_void);
|
||||||
|
cob.return_();
|
||||||
|
});
|
||||||
|
|
||||||
|
// implementation
|
||||||
|
clb.withMethodBody("compare", MTD_int_Object_Object, ACC_PUBLIC, cob -> {
|
||||||
|
cob.aload(1);
|
||||||
|
cob.checkcast(CD_Integer);
|
||||||
|
cob.aload(2);
|
||||||
|
cob.checkcast(CD_Integer);
|
||||||
|
cob.invokestatic(CD_Integer, "compareTo", MTD_int_Integer);
|
||||||
|
cob.ireturn();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var l = MethodHandles.lookup().defineHiddenClass(bytes, true);
|
||||||
|
return (Comparator<Integer>) l.findConstructor(l.lookupClass(), MethodType.methodType(void.class)).invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures a user interface cannot access a Proxy implementing it.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNoAccess() {
|
||||||
|
var instance = asInterfaceInstance(Client.class, MethodHandles.zero(void.class));
|
||||||
|
var instanceClass = instance.getClass();
|
||||||
|
var interfaceLookup = Client.lookup();
|
||||||
|
assertEquals(MethodHandles.Lookup.ORIGINAL, interfaceLookup.lookupModes() & MethodHandles.Lookup.ORIGINAL,
|
||||||
|
"Missing original flag on interface's lookup");
|
||||||
|
assertThrows(IllegalAccessException.class, () -> MethodHandles.privateLookupIn(instanceClass,
|
||||||
|
interfaceLookup));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the Proxy module properties for Proxies implementing system and
|
||||||
|
* user interfaces.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {Client.class, Runnable.class})
|
||||||
|
public void testModule(Class<?> ifaceClass) {
|
||||||
|
var mh = MethodHandles.zero(void.class);
|
||||||
|
|
||||||
|
var inst = asInterfaceInstance(ifaceClass, mh);
|
||||||
|
Module ifaceModule = ifaceClass.getModule();
|
||||||
|
Class<?> implClass = inst.getClass();
|
||||||
|
Module implModule = implClass.getModule();
|
||||||
|
|
||||||
|
String implPackage = implClass.getPackageName();
|
||||||
|
assertFalse(implModule.isExported(implPackage),
|
||||||
|
"implementation should not be exported");
|
||||||
|
assertTrue(ifaceModule.isExported(ifaceClass.getPackageName(), implModule),
|
||||||
|
"interface package should be exported to implementation");
|
||||||
|
assertTrue(implModule.isOpen(implPackage, MethodHandleProxies.class.getModule()),
|
||||||
|
"implementation class is not reflectively open to MHP class");
|
||||||
|
assertTrue(implModule.isNamed(), "dynamic module must be named");
|
||||||
|
assertTrue(implModule.getName().startsWith("jdk.MHProxy"),
|
||||||
|
() -> "incorrect dynamic module name: " + implModule.getName());
|
||||||
|
|
||||||
|
assertSame(ifaceClass.getClassLoader(), implClass.getClassLoader(),
|
||||||
|
"wrapper class should use the interface's loader ");
|
||||||
|
assertSame(implClass.getClassLoader(), implModule.getClassLoader(),
|
||||||
|
"module class loader should be wrapper class's loader");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the access control of Proxies implementing system and user
|
||||||
|
* interfaces.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {Client.class, Runnable.class})
|
||||||
|
public void testNoInstantiation(Class<?> ifaceClass) throws ReflectiveOperationException {
|
||||||
|
var mh = MethodHandles.zero(void.class);
|
||||||
|
var instanceClass = asInterfaceInstance(ifaceClass, mh).getClass();
|
||||||
|
var ctor = instanceClass.getDeclaredConstructor(MethodHandles.Lookup.class, MethodHandle.class, MethodHandle.class);
|
||||||
|
|
||||||
|
assertThrows(IllegalAccessException.class, () -> ctor.newInstance(Client.lookup(), mh, mh));
|
||||||
|
assertThrows(IllegalAccessException.class, () -> ctor.newInstance(MethodHandles.lookup(), mh, mh));
|
||||||
|
assertThrows(IllegalAccessException.class, () -> ctor.newInstance(MethodHandles.publicLookup(), mh, mh));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the caching and weak reference of implementation classes for
|
||||||
|
* system and user interfaces.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {Runnable.class, Client.class})
|
||||||
|
public void testWeakImplClass(Class<?> ifaceClass) {
|
||||||
|
var mh = MethodHandles.zero(void.class);
|
||||||
|
|
||||||
|
var wrapper1 = asInterfaceInstance(ifaceClass, mh);
|
||||||
|
var implClass = wrapper1.getClass();
|
||||||
|
|
||||||
|
System.gc(); // helps debug if incorrect items are weakly referenced
|
||||||
|
var wrapper2 = asInterfaceInstance(ifaceClass, mh);
|
||||||
|
assertSame(implClass, wrapper2.getClass(),
|
||||||
|
"MHP should reuse old implementation class when available");
|
||||||
|
|
||||||
|
var implClassRef = new WeakReference<>(implClass);
|
||||||
|
// clear strong references
|
||||||
|
implClass = null;
|
||||||
|
wrapper1 = null;
|
||||||
|
wrapper2 = null;
|
||||||
|
|
||||||
|
if (!ForceGC.wait(() -> implClassRef.refersTo(null))) {
|
||||||
|
fail("MHP impl class cannot be cleared by GC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* security policy used by the test process
|
||||||
|
* must allow file reads so that jtreg itself can run
|
||||||
|
*/
|
||||||
|
|
||||||
|
grant {
|
||||||
|
// standard test activation permissions
|
||||||
|
permission java.io.FilePermission "*", "read";
|
||||||
|
};
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
||||||
*
|
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License version 2 only, as
|
|
||||||
* published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* version 2 for more details (a copy is included in the LICENSE file that
|
|
||||||
* accompanied this code).
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License version
|
|
||||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*
|
|
||||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
||||||
* or visit www.oracle.com if you need additional information or have any
|
|
||||||
* questions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandleProxies;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.InvocationHandler;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
|
|
||||||
import org.testng.annotations.BeforeTest;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
import static org.testng.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @summary test MethodHandleProxies that adds qualified export of sun.invoke
|
|
||||||
* from java.base to a dynamic module
|
|
||||||
* @run testng ProxyForMethodHandle
|
|
||||||
*/
|
|
||||||
public class ProxyForMethodHandle {
|
|
||||||
/**
|
|
||||||
* MethodHandleProxies will add qualified export of sun.invoke from java.base
|
|
||||||
* to a dynamic module
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public static void testRunnableMethodHandle() throws Exception {
|
|
||||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
|
||||||
MethodType mt = MethodType.methodType(void.class);
|
|
||||||
MethodHandle mh = lookup.findStatic(ProxyForMethodHandle.class, "runForRunnable", mt);
|
|
||||||
Runnable proxy = MethodHandleProxies.asInterfaceInstance(Runnable.class, mh);
|
|
||||||
proxy.run();
|
|
||||||
|
|
||||||
Class<?> proxyClass = proxy.getClass();
|
|
||||||
Module target = proxyClass.getModule();
|
|
||||||
assertDynamicModule(target, proxyClass.getClassLoader(), proxyClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void runForRunnable() {
|
|
||||||
System.out.println("runForRunnable");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void assertDynamicModule(Module m, ClassLoader ld, Class<?> proxyClass) {
|
|
||||||
if (!m.isNamed() || !m.getName().startsWith("jdk.proxy")) {
|
|
||||||
throw new RuntimeException(m.getName() + " not dynamic module");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ld != m.getClassLoader() || proxyClass.getClassLoader() != ld) {
|
|
||||||
throw new RuntimeException("unexpected class loader");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class);
|
|
||||||
cons.newInstance(handler);
|
|
||||||
throw new RuntimeException("Expected IllegalAccessException: " + proxyClass);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
// expected
|
|
||||||
} catch (NoSuchMethodException|InstantiationException|InvocationTargetException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private final static InvocationHandler handler =
|
|
||||||
(proxy, m, params) -> { throw new RuntimeException(m.toString()); };
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -28,7 +28,7 @@ import java.lang.reflect.Proxy;
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
* @summary Basic test of proxy module mapping and the access to Proxy class
|
* @summary Basic test of proxy module mapping and the access to Proxy class
|
||||||
* @modules java.base/sun.invoke
|
* @modules java.base/jdk.internal.misc
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class ProxyModuleMapping {
|
public class ProxyModuleMapping {
|
||||||
|
@ -37,8 +37,8 @@ public class ProxyModuleMapping {
|
||||||
Module unnamed = ld.getUnnamedModule();
|
Module unnamed = ld.getUnnamedModule();
|
||||||
new ProxyModuleMapping(Runnable.class).test();
|
new ProxyModuleMapping(Runnable.class).test();
|
||||||
|
|
||||||
// unnamed module gets access to sun.invoke package (e.g. via --add-exports)
|
// unnamed module gets access to jdk.internal.misc package (e.g. via --add-exports)
|
||||||
new ProxyModuleMapping(sun.invoke.WrapperInstance.class).test();
|
new ProxyModuleMapping(jdk.internal.misc.VM.BufferPool.class).test();
|
||||||
|
|
||||||
Class<?> modulePrivateIntf = Class.forName("sun.net.PlatformSocketImpl");
|
Class<?> modulePrivateIntf = Class.forName("sun.net.PlatformSocketImpl");
|
||||||
new ProxyModuleMapping(modulePrivateIntf).test();
|
new ProxyModuleMapping(modulePrivateIntf).test();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue