8271820: Implementation of JEP 416: Reimplement Core Reflection with Method Handle

8013527: calling MethodHandles.lookup on itself leads to errors

Co-authored-by: Peter Levart <plevart@openjdk.org>
Co-authored-by: Claes Redestad <redestad@openjdk.org>
Co-authored-by: Mandy Chung <mchung@openjdk.org>
Reviewed-by: mcimadamore, plevart, egahlin, redestad, cjplummer, alanb
This commit is contained in:
Mandy Chung 2021-10-28 18:32:50 +00:00
parent 5a768f75c9
commit c6339cb8a2
78 changed files with 6118 additions and 544 deletions

View file

@ -26,7 +26,6 @@
package java.lang.invoke;
import jdk.internal.misc.CDS;
import jdk.internal.misc.VM;
import jdk.internal.org.objectweb.asm.*;
import sun.invoke.util.BytecodeDescriptor;
import sun.invoke.util.VerifyAccess;
@ -37,7 +36,6 @@ import java.io.FilePermission;
import java.io.Serializable;
import java.lang.constant.ConstantDescs;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
@ -46,8 +44,10 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.PropertyPermission;
import java.util.Set;
import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION;
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG;
import static java.lang.invoke.MethodType.methodType;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
@ -57,7 +57,6 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
* @see LambdaMetafactory
*/
/* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory {
private static final int CLASSFILE_VERSION = VM.classFileVersion();
private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE);
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
private static final String NAME_CTOR = "<init>";
@ -106,7 +105,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey);
// condy to load implMethod from class data
MethodType classDataMType = MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class);
MethodType classDataMType = methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class);
Handle classDataBsm = new Handle(H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData",
classDataMType.descriptorString(), false);
implMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm);
@ -227,50 +226,28 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
@Override
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
if (factoryType.parameterCount() == 0) {
// In the case of a non-capturing lambda, we optimize linkage by pre-computing a single instance,
// unless we've suppressed eager initialization
if (disableEagerInitialization) {
try {
return new ConstantCallSite(caller.findStaticGetter(innerClass, LAMBDA_INSTANCE_FIELD,
factoryType.returnType()));
} catch (ReflectiveOperationException e) {
throw new LambdaConversionException(
"Exception finding " + LAMBDA_INSTANCE_FIELD + " static field", e);
}
} else {
@SuppressWarnings("removal")
final Constructor<?>[] ctrs = AccessController.doPrivileged(
new PrivilegedAction<>() {
@Override
public Constructor<?>[] run() {
Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
if (ctrs.length == 1) {
// The lambda implementing inner class constructor is private, set
// it accessible (by us) before creating the constant sole instance
ctrs[0].setAccessible(true);
}
return ctrs;
}
});
if (ctrs.length != 1) {
throw new LambdaConversionException("Expected one lambda constructor for "
+ innerClass.getCanonicalName() + ", got " + ctrs.length);
}
try {
Object inst = ctrs[0].newInstance();
return new ConstantCallSite(MethodHandles.constant(interfaceClass, inst));
} catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception instantiating lambda object", e);
}
if (factoryType.parameterCount() == 0 && disableEagerInitialization) {
try {
return new ConstantCallSite(caller.findStaticGetter(innerClass, LAMBDA_INSTANCE_FIELD,
factoryType.returnType()));
} catch (ReflectiveOperationException e) {
throw new LambdaConversionException(
"Exception finding " + LAMBDA_INSTANCE_FIELD + " static field", e);
}
} else {
try {
MethodHandle mh = caller.findConstructor(innerClass, constructorType);
return new ConstantCallSite(mh.asType(factoryType));
if (factoryType.parameterCount() == 0) {
// In the case of a non-capturing lambda, we optimize linkage by pre-computing a single instance
Object inst = mh.asType(methodType(Object.class)).invokeExact();
return new ConstantCallSite(MethodHandles.constant(interfaceClass, inst));
} else {
return new ConstantCallSite(mh.asType(factoryType));
}
} catch (ReflectiveOperationException e) {
throw new LambdaConversionException("Exception finding constructor", e);
} catch (Throwable e) {
throw new LambdaConversionException("Exception instantiating lambda object", e);
}
}
}

View file

@ -44,6 +44,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import static java.lang.invoke.LambdaForm.BasicType;
@ -316,7 +317,7 @@ class InvokerBytecodeGenerator {
* Extract the MemberName of a newly-defined method.
*/
private MemberName loadMethod(byte[] classFile) {
Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(className, classFile)
Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(className, classFile, Set.of())
.defineClass(true, classDataValues());
return resolveInvokerMember(invokerClass, invokerName, invokerType);
}
@ -376,7 +377,7 @@ class InvokerBytecodeGenerator {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitLdcInsn(Type.getType("L" + className + ";"));
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandleNatives",
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles",
"classData", "(Ljava/lang/Class;)Ljava/lang/Object;", false);
// we should optimize one single element case that does not need to create a List
mv.visitTypeInsn(Opcodes.CHECKCAST, "java/util/List");

View file

@ -42,6 +42,8 @@ import sun.invoke.util.Wrapper;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Collections;
@ -50,6 +52,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Stream;
@ -57,6 +60,7 @@ import java.util.stream.Stream;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.MethodHandleStatics.*;
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
@ -1030,6 +1034,7 @@ abstract class MethodHandleImpl {
// That way we can lazily load the code and set up the constants.
private static class BindCaller {
private static MethodType INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class);
private static MethodType REFLECT_INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object.class, Object[].class);
static MethodHandle bindCaller(MethodHandle mh, Class<?> hostClass) {
// Code in the boot layer should now be careful while creating method handles or
@ -1042,15 +1047,48 @@ abstract class MethodHandleImpl {
hostClass.getName().startsWith("java.lang.invoke."))) {
throw new InternalError(); // does not happen, and should not anyway
}
MemberName member = mh.internalMemberName();
if (member != null) {
// Look up the CSM adapter method with the same method name
// but with an additional caller class parameter. If present,
// bind the adapter's method handle with the lookup class as
// the caller class argument
MemberName csmAdapter = IMPL_LOOKUP.resolveOrNull(member.getReferenceKind(),
new MemberName(member.getDeclaringClass(),
member.getName(),
member.getMethodType().appendParameterTypes(Class.class),
member.getReferenceKind()));
if (csmAdapter != null) {
assert !csmAdapter.isCallerSensitive();
MethodHandle dmh = DirectMethodHandle.make(csmAdapter);
dmh = MethodHandles.insertArguments(dmh, dmh.type().parameterCount() - 1, hostClass);
dmh = new WrappedMember(dmh, mh.type(), member, mh.isInvokeSpecial(), hostClass);
return dmh;
}
}
// If no adapter method for CSM with an additional Class parameter
// is present, then inject an invoker class that is the caller
// invoking the method handle of the CSM
try {
return bindCallerWithInjectedInvoker(mh, hostClass);
} catch (ReflectiveOperationException ex) {
throw uncaughtException(ex);
}
}
private static MethodHandle bindCallerWithInjectedInvoker(MethodHandle mh, Class<?> hostClass)
throws ReflectiveOperationException
{
// For simplicity, convert mh to a varargs-like method.
MethodHandle vamh = prepareForInvoker(mh);
// Cache the result of makeInjectedInvoker once per argument class.
MethodHandle bccInvoker = CV_makeInjectedInvoker.get(hostClass);
MethodHandle bccInvoker = CV_makeInjectedInvoker.get(hostClass).invoker();
return restoreToType(bccInvoker.bindTo(vamh), mh, hostClass);
}
private static MethodHandle makeInjectedInvoker(Class<?> targetClass) {
try {
private static Class<?> makeInjectedInvoker(Class<?> targetClass) {
/*
* The invoker class defined to the same class loader as the lookup class
* but in an unnamed package so that the class bytes can be cached and
@ -1064,21 +1102,80 @@ abstract class MethodHandleImpl {
name = name.replace('/', '_');
}
Class<?> invokerClass = new Lookup(targetClass)
.makeHiddenClassDefiner(name, INJECTED_INVOKER_TEMPLATE)
.defineClass(true);
.makeHiddenClassDefiner(name, INJECTED_INVOKER_TEMPLATE, Set.of(NESTMATE))
.defineClass(true, targetClass);
assert checkInjectedInvoker(targetClass, invokerClass);
return IMPL_LOOKUP.findStatic(invokerClass, "invoke_V", INVOKER_MT);
} catch (ReflectiveOperationException ex) {
throw uncaughtException(ex);
}
return invokerClass;
}
private static ClassValue<MethodHandle> CV_makeInjectedInvoker = new ClassValue<MethodHandle>() {
@Override protected MethodHandle computeValue(Class<?> hostClass) {
return makeInjectedInvoker(hostClass);
private static ClassValue<InjectedInvokerHolder> CV_makeInjectedInvoker = new ClassValue<>() {
@Override
protected InjectedInvokerHolder computeValue(Class<?> hostClass) {
return new InjectedInvokerHolder(makeInjectedInvoker(hostClass));
}
};
/*
* Returns a method handle of an invoker class injected for reflection
* implementation use with the following signature:
* reflect_invoke_V(MethodHandle mh, Object target, Object[] args)
*
* Method::invoke on a caller-sensitive method will call
* MethodAccessorImpl::invoke(Object, Object[]) through reflect_invoke_V
* target.csm(args)
* NativeMethodAccesssorImpl::invoke(target, args)
* MethodAccessImpl::invoke(target, args)
* InjectedInvoker::reflect_invoke_V(vamh, target, args);
* method::invoke(target, args)
* p.Foo::m
*
* An injected invoker class is a hidden class which has the same
* defining class loader, runtime package, and protection domain
* as the given caller class.
*/
static MethodHandle reflectiveInvoker(Class<?> caller) {
return BindCaller.CV_makeInjectedInvoker.get(caller).reflectInvoker();
}
private static final class InjectedInvokerHolder {
private final Class<?> invokerClass;
// lazily resolved and cached DMH(s) of invoke_V methods
private MethodHandle invoker;
private MethodHandle reflectInvoker;
private InjectedInvokerHolder(Class<?> invokerClass) {
this.invokerClass = invokerClass;
}
private MethodHandle invoker() {
var mh = invoker;
if (mh == null) {
try {
invoker = mh = IMPL_LOOKUP.findStatic(invokerClass, "invoke_V", INVOKER_MT);
} catch (Error | RuntimeException ex) {
throw ex;
} catch (Throwable ex) {
throw new InternalError(ex);
}
}
return mh;
}
private MethodHandle reflectInvoker() {
var mh = reflectInvoker;
if (mh == null) {
try {
reflectInvoker = mh = IMPL_LOOKUP.findStatic(invokerClass, "reflect_invoke_V", REFLECT_INVOKER_MT);
} catch (Error | RuntimeException ex) {
throw ex;
} catch (Throwable ex) {
throw new InternalError(ex);
}
}
return mh;
}
}
// Adapt mh so that it can be called directly from an injected invoker:
private static MethodHandle prepareForInvoker(MethodHandle mh) {
mh = mh.asFixedArity();
@ -1115,6 +1212,8 @@ abstract class MethodHandleImpl {
MethodHandle invoker = IMPL_LOOKUP.findStatic(invokerClass, "invoke_V", INVOKER_MT);
MethodHandle vamh = prepareForInvoker(MH_checkCallerClass);
return (boolean)invoker.invoke(vamh, new Object[]{ invokerClass });
} catch (Error|RuntimeException ex) {
throw ex;
} catch (Throwable ex) {
throw new InternalError(ex);
}
@ -1151,27 +1250,50 @@ abstract class MethodHandleImpl {
ClassWriter cw = new ClassWriter(0);
// private static class InjectedInvoker {
// /* this is used to wrap DMH(s) of caller-sensitive methods */
// @Hidden
// static Object invoke_V(MethodHandle vamh, Object[] args) throws Throwable {
// return vamh.invokeExact(args);
// }
// /* this is used in caller-sensitive reflective method accessor */
// @Hidden
// static Object reflect_invoke_V(MethodHandle vamh, Object target, Object[] args) throws Throwable {
// return vamh.invokeExact(target, args);
// }
// }
// }
cw.visit(CLASSFILE_VERSION, ACC_PRIVATE | ACC_SUPER, "InjectedInvoker", null, "java/lang/Object", null);
{
var mv = cw.visitMethod(ACC_STATIC, "invoke_V",
"(Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;",
null, null);
MethodVisitor mv = cw.visitMethod(ACC_STATIC, "invoke_V",
"(Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;",
null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact",
"([Ljava/lang/Object;)Ljava/lang/Object;", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact",
"([Ljava/lang/Object;)Ljava/lang/Object;", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
cw.visitEnd();
}
cw.visitEnd();
{
var mv = cw.visitMethod(ACC_STATIC, "reflect_invoke_V",
"(Ljava/lang/invoke/MethodHandle;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(3, 3);
mv.visitEnd();
}
return cw.toByteArray();
}
}
@ -1503,6 +1625,48 @@ abstract class MethodHandleImpl {
public VarHandle insertCoordinates(VarHandle target, int pos, Object... values) {
return VarHandles.insertCoordinates(target, pos, values);
}
@Override
public MethodHandle unreflectConstructor(Constructor<?> ctor) throws IllegalAccessException {
return IMPL_LOOKUP.unreflectConstructor(ctor);
}
@Override
public MethodHandle unreflectField(Field field, boolean isSetter) throws IllegalAccessException {
return isSetter ? IMPL_LOOKUP.unreflectSetter(field) : IMPL_LOOKUP.unreflectGetter(field);
}
@Override
public MethodHandle findVirtual(Class<?> defc, String name, MethodType type) throws IllegalAccessException {
try {
return IMPL_LOOKUP.findVirtual(defc, name, type);
} catch (NoSuchMethodException e) {
return null;
}
}
@Override
public MethodHandle findStatic(Class<?> defc, String name, MethodType type) throws IllegalAccessException {
try {
return IMPL_LOOKUP.findStatic(defc, name, type);
} catch (NoSuchMethodException e) {
return null;
}
}
@Override
public MethodHandle reflectiveInvoker(Class<?> caller) {
Objects.requireNonNull(caller);
return BindCaller.reflectiveInvoker(caller);
}
@Override
public Lookup defineHiddenClassWithClassData(Lookup caller, String name, byte[] bytes, Object classData, boolean initialize) {
// skip name and access flags validation
return caller.makeHiddenClassDefiner(name, bytes, Set.of()).defineClassAsLookup(initialize, classData);
}
});
}

View file

@ -25,8 +25,7 @@
package java.lang.invoke;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.VM;
import jdk.internal.ref.CleanerFactory;
import sun.invoke.util.Wrapper;
@ -35,7 +34,6 @@ import java.lang.reflect.Field;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE;
import static java.lang.invoke.MethodHandleStatics.UNSAFE;
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
/**
@ -248,6 +246,7 @@ class MethodHandleNatives {
return true;
}
static {
VM.setJavaLangInvokeInited();
assert(verifyConstants());
}
@ -665,8 +664,7 @@ class MethodHandleNatives {
static boolean canBeCalledVirtual(MemberName mem) {
assert(mem.isInvocable());
return mem.getName().equals("getContextClassLoader") &&
canBeCalledVirtual(mem, java.lang.Thread.class);
return mem.getName().equals("getContextClassLoader") && canBeCalledVirtual(mem, java.lang.Thread.class);
}
static boolean canBeCalledVirtual(MemberName symbolicRef, Class<?> definingClass) {
@ -676,16 +674,4 @@ class MethodHandleNatives {
return (definingClass.isAssignableFrom(symbolicRefClass) || // Msym overrides Mdef
symbolicRefClass.isInterface()); // Mdef implements Msym
}
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
/*
* Returns the class data set by the VM in the Class::classData field.
*
* This is also invoked by LambdaForms as it cannot use condy via
* MethodHandles.classData due to bootstrapping issue.
*/
static Object classData(Class<?> c) {
UNSAFE.ensureClassInitialized(c);
return JLA.classData(c);
}
}

View file

@ -32,6 +32,7 @@ import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.CallerSensitiveAdapter;
import jdk.internal.reflect.Reflection;
import jdk.internal.vm.annotation.ForceInline;
import sun.invoke.util.ValueConversions;
@ -63,7 +64,9 @@ import java.util.stream.Stream;
import static java.lang.invoke.LambdaForm.BasicType.V_TYPE;
import static java.lang.invoke.MethodHandleImpl.Intrinsic;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.UNSAFE;
import static java.lang.invoke.MethodHandleStatics.newIllegalArgumentException;
import static java.lang.invoke.MethodHandleStatics.newInternalError;
import static java.lang.invoke.MethodType.methodType;
/**
@ -117,14 +120,15 @@ public class MethodHandles {
}
/**
* This reflected$lookup method is the alternate implementation of
* the lookup method when being invoked by reflection.
* This lookup method is the alternate implementation of
* the lookup method with a leading caller class argument which is
* non-caller-sensitive. This method is only invoked by reflection
* and method handle.
*/
@CallerSensitive
private static Lookup reflected$lookup() {
Class<?> caller = Reflection.getCallerClass();
@CallerSensitiveAdapter
private static Lookup lookup(Class<?> caller) {
if (caller.getClassLoader() == null) {
throw newIllegalArgumentException("illegal lookupClass: "+caller);
throw newInternalError("calling lookup() reflectively is not supported: "+caller);
}
return new Lookup(caller);
}
@ -329,7 +333,7 @@ public class MethodHandles {
throw new IllegalAccessException(caller + " does not have ORIGINAL access");
}
Object classdata = MethodHandleNatives.classData(caller.lookupClass());
Object classdata = classData(caller.lookupClass());
if (classdata == null) return null;
try {
@ -341,6 +345,17 @@ public class MethodHandles {
}
}
/*
* Returns the class data set by the VM in the Class::classData field.
*
* This is also invoked by LambdaForms as it cannot use condy via
* MethodHandles::classData due to bootstrapping issue.
*/
static Object classData(Class<?> c) {
UNSAFE.ensureClassInitialized(c);
return SharedSecrets.getJavaLangAccess().classData(c);
}
/**
* Returns the element at the specified index in the
* {@linkplain #classData(Lookup, String, Class) class data},
@ -2359,15 +2374,16 @@ public class MethodHandles {
/**
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
* from the given bytes. No package name check on the given name.
* from the given bytes and the given options. No package name check on the given name.
*
* @param name fully-qualified name that specifies the prefix of the hidden class
* @param bytes class bytes
* @return ClassDefiner that defines a hidden class of the given bytes.
* @param options class options
* @return ClassDefiner that defines a hidden class of the given bytes and options.
*/
ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes) {
ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes, Set<ClassOption> options) {
// skip name and access flags validation
return makeHiddenClassDefiner(ClassFile.newInstanceNoCheck(name, bytes), Set.of(), false);
return makeHiddenClassDefiner(ClassFile.newInstanceNoCheck(name, bytes), options, false);
}
/**