diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp
index 92a3b08b1a6..3e0d5b1eb45 100644
--- a/src/hotspot/share/prims/jvm.cpp
+++ b/src/hotspot/share/prims/jvm.cpp
@@ -1016,8 +1016,11 @@ static jclass jvm_lookup_define_class(jclass lookup, const char *name,
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) {
ik->initialize(CHECK_NULL);
diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
index 69d975a7289..36f10cad1e5 100644
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,19 +25,44 @@
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.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.SharedSecrets;
-import sun.invoke.WrapperInstance;
-import java.util.ArrayList;
-
+import jdk.internal.classfile.ClassHierarchyResolver;
+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.Reflection;
+import jdk.internal.util.ClassFileDumper;
import sun.reflect.misc.ReflectUtil;
+
+import static java.lang.constant.ConstantDescs.*;
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
@@ -61,7 +86,8 @@ public class MethodHandleProxies {
* even though it re-declares the {@code Object.equals} method and also
* declares default methods, such as {@code Comparator.reverse}.
*
- * 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.
*
* The resulting instance of the required type will respond to
@@ -133,37 +159,20 @@ public class MethodHandleProxies {
* @throws WrongMethodTypeException if the target cannot
* be converted to the type required by the requested interface
*/
- // Other notes to implementors:
- //
- // 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.
- //
- // 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.
- //
- // 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
+ @SuppressWarnings("doclint:reference") // cross-module links
@CallerSensitive
public static T asInterfaceInstance(final Class intfc, final MethodHandle target) {
if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers()))
throw newIllegalArgumentException("not a public interface", intfc.getName());
if (intfc.isSealed())
throw newIllegalArgumentException("a sealed interface", intfc.getName());
+ if (intfc.isHidden())
+ throw newIllegalArgumentException("a hidden interface", intfc.getName());
+ Objects.requireNonNull(target);
final MethodHandle mh;
- if (System.getSecurityManager() != null) {
+ @SuppressWarnings("removal")
+ var sm = System.getSecurityManager();
+ if (sm != null) {
final Class> caller = Reflection.getCallerClass();
final ClassLoader ccl = caller != null ? caller.getClassLoader() : null;
ReflectUtil.checkProxyPackageAccess(ccl, intfc);
@@ -171,66 +180,300 @@ public class MethodHandleProxies {
} else {
mh = target;
}
- ClassLoader proxyLoader = intfc.getClassLoader();
- if (proxyLoader == null) {
- ClassLoader cl = Thread.currentThread().getContextClassLoader(); // avoid use of BCP
- proxyLoader = cl != null ? cl : ClassLoader.getSystemClassLoader();
- }
- final Method[] methods = getSingleNameMethods(intfc);
- if (methods == null)
- 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) {
- if ((Object)name == "getWrapperInstanceTarget") return target;
- if ((Object)name == "getWrapperInstanceType") return intfc;
- throw new AssertionError();
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- for (int i = 0; i < methods.length; i++) {
- if (method.equals(methods[i]))
- return vaTargets[i].invokeExact(args);
- }
- 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;
- if (System.getSecurityManager() != null) {
- // sun.invoke.WrapperInstance is a restricted interface not accessible
- // by any non-null class loader.
- final ClassLoader loader = proxyLoader;
- proxy = AccessController.doPrivileged(new PrivilegedAction<>() {
- public Object run() {
- return Proxy.newProxyInstance(
- loader,
- new Class>[]{ intfc, WrapperInstance.class },
- ih);
- }
- });
- } else {
- proxy = Proxy.newProxyInstance(proxyLoader,
- new Class>[]{ intfc, WrapperInstance.class },
- ih);
+ // Define one hidden class for each interface. Create an instance of
+ // the hidden class for a given target method handle which will be
+ // 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);
}
+ assert proxy.getClass().getModule().isNamed() : proxy.getClass() + " " + proxy.getClass().getModule();
return intfc.cast(proxy);
}
+ private record MethodInfo(MethodTypeDesc desc, List thrown, String fieldName) {}
+
+ private static final ClassFileDumper DUMPER = ClassFileDumper.getInstance(
+ "jdk.invoke.MethodHandleProxies.dumpClassFiles", "DUMP_MH_PROXY_CLASSFILES");
+
+ private static final Set> WRAPPER_TYPES = Collections.newSetFromMap(new WeakHashMap<>());
+ private static final ClassValue>> PROXIES = new ClassValue<>() {
+ @Override
+ protected WeakReferenceHolder> computeValue(Class> intfc) {
+ return new WeakReferenceHolder<>(newProxyClass(intfc));
+ }
+ };
+
+ private static Class> newProxyClass(Class> intfc) {
+ List methods = new ArrayList<>();
+ Set> referencedTypes = new HashSet<>();
+ referencedTypes.add(intfc);
+ String uniqueName = null;
+ int count = 0;
+ for (Method m : intfc.getMethods()) {
+ if (!Modifier.isAbstract(m.getModifiers()))
+ continue;
+
+ 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) () ->
+ 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 {
+ private volatile WeakReference 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> 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 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 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);
+ }
+
+ //
+ clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
+ cob.constantInstruction(ifaceDesc);
+ cob.putstatic(proxyDesc, TYPE_NAME, CD_Class);
+ cob.return_();
+ });
+
+ // (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 = 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()
+ )
+ ));
+ }
+ });
+ }
+
private static MethodHandle bindCaller(MethodHandle target, Class> hostClass) {
return MethodHandleImpl.bindCaller(target, hostClass).withVarargs(target.isVarargsCollector());
}
@@ -241,16 +484,7 @@ public class MethodHandleProxies {
* @return true if the reference is not null and points to an object produced by {@code asInterfaceInstance}
*/
public static boolean isWrapperInstance(Object x) {
- return x instanceof WrapperInstance;
- }
-
- private static WrapperInstance asWrapperInstance(Object x) {
- try {
- if (x != null)
- return (WrapperInstance) x;
- } catch (ClassCastException ex) {
- }
- throw newIllegalArgumentException("not a wrapper instance");
+ return x != null && WRAPPER_TYPES.contains(x.getClass());
}
/**
@@ -263,7 +497,17 @@ public class MethodHandleProxies {
* @throws IllegalArgumentException if the reference x is not to a wrapper instance
*/
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,52 +519,105 @@ public class MethodHandleProxies {
* @throws IllegalArgumentException if the reference x is not to a wrapper instance
*/
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.
+ *
+ * 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> 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) {
return switch (m.getName()) {
case "toString" -> m.getReturnType() == String.class
- && m.getParameterCount() == 0;
+ && m.getParameterCount() == 0;
case "hashCode" -> m.getReturnType() == int.class
- && m.getParameterCount() == 0;
+ && m.getParameterCount() == 0;
case "equals" -> m.getReturnType() == boolean.class
- && m.getParameterCount() == 1
- && m.getParameterTypes()[0] == Object.class;
+ && m.getParameterCount() == 1
+ && JLRA.getExecutableSharedParameterTypes(m)[0] == Object.class;
default -> false;
};
}
- private static Object callObjectMethod(Object self, Method m, Object[] args) {
- assert(isObjectMethod(m)) : m;
- return switch (m.getName()) {
- case "toString" -> java.util.Objects.toIdentityString(self);
- case "hashCode" -> System.identityHashCode(self);
- case "equals" -> (self == args[0]);
- default -> null;
- };
- }
-
- private static Method[] getSingleNameMethods(Class> intfc) {
- ArrayList methods = new ArrayList<>();
- String uniqueName = null;
- 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);
+ /*
+ * Ensure the given module can access the given class.
+ */
+ private static void ensureAccess(Module target, Class> c) {
+ Module m = c.getModule();
+ // add read edge and qualified export for the target module to access
+ if (!target.canRead(m)) {
+ Modules.addReads(target, m);
+ }
+ String pn = c.getPackageName();
+ if (!m.isExported(pn, target)) {
+ Modules.addExports(m, pn, target);
}
- if (uniqueName == null) return null;
- return methods.toArray(new Method[methods.size()]);
}
- private static boolean isDefaultMethod(Method m) {
- return !Modifier.isAbstract(m.getModifiers());
+ private static void addElementTypes(Set> types, Class>... classes) {
+ for (var cls : classes) {
+ addElementType(types, cls);
+ }
}
- private static final JavaLangReflectAccess JLRA = SharedSecrets.getJavaLangReflectAccess();
+ private static void addElementType(Set> types, Class> cls) {
+ Class> e = cls;
+ while (e.isArray()) {
+ e = e.getComponentType();
+ }
+
+ if (!e.isPrimitive()) {
+ types.add(e);
+ }
+ }
}
diff --git a/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java b/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java
index fa5a5d453ec..f815862edf6 100644
--- a/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java
+++ b/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java
@@ -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.
*
* 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();
}
+ public Class>[] getExecutableSharedExceptionTypes(Executable ex) {
+ return ex.getSharedExceptionTypes();
+ }
+
//
// Copying routines, needed to quickly fabricate new Field,
// Method, and Constructor objects from templates
@@ -127,9 +131,4 @@ class ReflectAccess implements jdk.internal.access.JavaLangReflectAccess {
{
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);
- }
}
diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java
index 9e3ce846f02..f49221e44ed 100644
--- a/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java
+++ b/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java
@@ -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.
*
* 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. */
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,
// 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 */
public T newInstance(Constructor ctor, Object[] args, Class> caller)
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;
}
diff --git a/src/java.base/share/classes/sun/invoke/WrapperInstance.java b/src/java.base/share/classes/sun/invoke/WrapperInstance.java
deleted file mode 100644
index f7cba5acc45..00000000000
--- a/src/java.base/share/classes/sun/invoke/WrapperInstance.java
+++ /dev/null
@@ -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();
-}
-
diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java b/test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java
new file mode 100644
index 00000000000..656083e3a6e
--- /dev/null
+++ b/test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java
@@ -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 proxy = (Comparator) 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 func = (Function) asInterfaceInstance(Function.class, mh);
+ assertEquals(32423432L * 32423432L, func.apply(32423432));
+ @SuppressWarnings("unchecked")
+ ToLongFunction func1 = (ToLongFunction) asInterfaceInstance(ToLongFunction.class, mh);
+ assertEquals(32423432L * 32423432L, func1.applyAsLong(32423432));
+ @SuppressWarnings("unchecked")
+ IntFunction func2 = (IntFunction) 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 Closeable throwing(Class 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> 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 {
+ }
+}
diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/Client.java b/test/jdk/java/lang/invoke/MethodHandleProxies/Client.java
new file mode 100644
index 00000000000..7fe03288f37
--- /dev/null
+++ b/test/jdk/java/lang/invoke/MethodHandleProxies/Client.java
@@ -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();
+}
diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/MethodHandlesProxiesTest.java b/test/jdk/java/lang/invoke/MethodHandleProxies/MethodHandlesProxiesTest.java
deleted file mode 100644
index 48624b1c925..00000000000
--- a/test/jdk/java/lang/invoke/MethodHandleProxies/MethodHandlesProxiesTest.java
+++ /dev/null
@@ -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");
- }
-}
diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/WithSecurityManagerTest.java b/test/jdk/java/lang/invoke/MethodHandleProxies/WithSecurityManagerTest.java
new file mode 100644
index 00000000000..7e1882237c4
--- /dev/null
+++ b/test/jdk/java/lang/invoke/MethodHandleProxies/WithSecurityManagerTest.java
@@ -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");
+ }
+}
diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/WrapperHiddenClassTest.java b/test/jdk/java/lang/invoke/MethodHandleProxies/WrapperHiddenClassTest.java
new file mode 100644
index 00000000000..d3d488385ae
--- /dev/null
+++ b/test/jdk/java/lang/invoke/MethodHandleProxies/WrapperHiddenClassTest.java
@@ -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 hostile = createHostileInstance();
+ var mh = MethodHandles.publicLookup()
+ .findVirtual(Integer.class, "compareTo", methodType(int.class, Integer.class));
+ @SuppressWarnings("unchecked")
+ Comparator proxy = (Comparator) 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 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);
+
+ //
+ clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
+ cob.constantInstruction(CD_Comparator);
+ cob.putstatic(CD_HostileWrapper, TYPE, CD_Class);
+ cob.return_();
+ });
+
+ //
+ 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) 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");
+ }
+ }
+}
diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/jtreg.security.policy b/test/jdk/java/lang/invoke/MethodHandleProxies/jtreg.security.policy
new file mode 100644
index 00000000000..d32c7af9b3f
--- /dev/null
+++ b/test/jdk/java/lang/invoke/MethodHandleProxies/jtreg.security.policy
@@ -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";
+};
diff --git a/test/jdk/java/lang/reflect/Proxy/ProxyForMethodHandle.java b/test/jdk/java/lang/reflect/Proxy/ProxyForMethodHandle.java
deleted file mode 100644
index a5877018705..00000000000
--- a/test/jdk/java/lang/reflect/Proxy/ProxyForMethodHandle.java
+++ /dev/null
@@ -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()); };
-}
diff --git a/test/jdk/java/lang/reflect/Proxy/ProxyModuleMapping.java b/test/jdk/java/lang/reflect/Proxy/ProxyModuleMapping.java
index 4e263d000a4..f75abf02946 100644
--- a/test/jdk/java/lang/reflect/Proxy/ProxyModuleMapping.java
+++ b/test/jdk/java/lang/reflect/Proxy/ProxyModuleMapping.java
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -28,7 +28,7 @@ import java.lang.reflect.Proxy;
/*
* @test
* @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 {
@@ -37,8 +37,8 @@ public class ProxyModuleMapping {
Module unnamed = ld.getUnnamedModule();
new ProxyModuleMapping(Runnable.class).test();
- // unnamed module gets access to sun.invoke package (e.g. via --add-exports)
- new ProxyModuleMapping(sun.invoke.WrapperInstance.class).test();
+ // unnamed module gets access to jdk.internal.misc package (e.g. via --add-exports)
+ new ProxyModuleMapping(jdk.internal.misc.VM.BufferPool.class).test();
Class> modulePrivateIntf = Class.forName("sun.net.PlatformSocketImpl");
new ProxyModuleMapping(modulePrivateIntf).test();