8315810: Reimplement sun.reflect.ReflectionFactory::newConstructorForSerialization with method handles

Co-authored-by: Chen Liang <liach@openjdk.org>
Reviewed-by: rriggs
This commit is contained in:
Mandy Chung 2023-09-14 16:10:55 +00:00
parent eb1f67b160
commit 5cea53d372
10 changed files with 141 additions and 41 deletions

View file

@ -1034,12 +1034,12 @@ public final class ObjectStreamClass implements Serializable {
new AccessControlContext(domains)); new AccessControlContext(domains));
} catch (UndeclaredThrowableException x) { } catch (UndeclaredThrowableException x) {
Throwable cause = x.getCause(); Throwable cause = x.getCause();
if (cause instanceof InstantiationException) if (cause instanceof InstantiationException ie)
throw (InstantiationException) cause; throw ie;
if (cause instanceof InvocationTargetException) if (cause instanceof InvocationTargetException ite)
throw (InvocationTargetException) cause; throw ite;
if (cause instanceof IllegalAccessException) if (cause instanceof IllegalAccessException iae)
throw (IllegalAccessException) cause; throw iae;
// not supposed to happen // not supposed to happen
throw x; throw x;
} }
@ -1047,6 +1047,12 @@ public final class ObjectStreamClass implements Serializable {
} catch (IllegalAccessException ex) { } catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed // should not occur, as access checks have been suppressed
throw new InternalError(ex); throw new InternalError(ex);
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof Error err)
throw err;
else
throw ex;
} catch (InstantiationError err) { } catch (InstantiationError err) {
var ex = new InstantiationException(); var ex = new InstantiationException();
ex.initCause(err); ex.initCause(err);

View file

@ -128,12 +128,11 @@ sealed class DirectMethodHandle extends MethodHandle {
} }
static DirectMethodHandle make(MemberName member) { static DirectMethodHandle make(MemberName member) {
if (member.isConstructor()) if (member.isConstructor())
return makeAllocator(member); return makeAllocator(member.getDeclaringClass(), member);
return make(member.getDeclaringClass(), member); return make(member.getDeclaringClass(), member);
} }
private static DirectMethodHandle makeAllocator(MemberName ctor) { static DirectMethodHandle makeAllocator(Class<?> instanceClass, MemberName ctor) {
assert(ctor.isConstructor() && ctor.getName().equals("<init>")); assert(ctor.isConstructor() && ctor.getName().equals("<init>"));
Class<?> instanceClass = ctor.getDeclaringClass();
ctor = ctor.asConstructor(); ctor = ctor.asConstructor();
assert(ctor.isConstructor() && ctor.getReferenceKind() == REF_newInvokeSpecial) : ctor; assert(ctor.isConstructor() && ctor.getReferenceKind() == REF_newInvokeSpecial) : ctor;
MethodType mtype = ctor.getMethodType().changeReturnType(instanceClass); MethodType mtype = ctor.getMethodType().changeReturnType(instanceClass);

View file

@ -1647,6 +1647,12 @@ abstract class MethodHandleImpl {
public Class<?>[] exceptionTypes(MethodHandle handle) { public Class<?>[] exceptionTypes(MethodHandle handle) {
return VarHandles.exceptionTypes(handle); return VarHandles.exceptionTypes(handle);
} }
@Override
public MethodHandle serializableConstructor(Class<?> decl, Constructor<?> ctorToCall) throws IllegalAccessException {
return IMPL_LOOKUP.serializableConstructor(decl, ctorToCall);
}
}); });
} }

View file

@ -3525,6 +3525,33 @@ return mh1;
return lookup.getDirectConstructorNoSecurityManager(ctor.getDeclaringClass(), ctor); return lookup.getDirectConstructorNoSecurityManager(ctor.getDeclaringClass(), ctor);
} }
/*
* Produces a method handle that is capable of creating instances of the given class
* and instantiated by the given constructor. No security manager check.
*
* This method should only be used by ReflectionFactory::newConstructorForSerialization.
*/
/* package-private */ MethodHandle serializableConstructor(Class<?> decl, Constructor<?> c) throws IllegalAccessException {
MemberName ctor = new MemberName(c);
assert(ctor.isConstructor() && constructorInSuperclass(decl, c));
checkAccess(REF_newInvokeSpecial, decl, ctor);
assert(!MethodHandleNatives.isCallerSensitive(ctor)); // maybeBindCaller not relevant here
return DirectMethodHandle.makeAllocator(decl, ctor).setVarargs(ctor);
}
private static boolean constructorInSuperclass(Class<?> decl, Constructor<?> ctor) {
if (decl == ctor.getDeclaringClass())
return true;
Class<?> cl = decl;
while ((cl = cl.getSuperclass()) != null) {
if (cl == ctor.getDeclaringClass()) {
return true;
}
}
return false;
}
/** /**
* Produces a method handle giving read access to a reflected field. * Produces a method handle giving read access to a reflected field.
* The type of the method handle will have a return type of the field's * The type of the method handle will have a return type of the field's

View file

@ -162,4 +162,12 @@ public interface JavaLangInvokeAccess {
* @return an array of exceptions, or {@code null}. * @return an array of exceptions, or {@code null}.
*/ */
Class<?>[] exceptionTypes(MethodHandle handle); Class<?>[] exceptionTypes(MethodHandle handle);
/**
* Returns a method handle that allocates an instance of the given class
* and then invoke the given constructor of one of its superclasses.
*
* This method should only be used by ReflectionFactory::newConstructorForSerialization.
*/
MethodHandle serializableConstructor(Class<?> decl, Constructor<?> ctorToCall) throws IllegalAccessException;
} }

View file

@ -194,7 +194,8 @@ class DirectMethodHandleAccessor extends MethodAccessorImpl {
private void checkReceiver(Object o) { private void checkReceiver(Object o) {
// NOTE: will throw NullPointerException, as specified, if o is null // NOTE: will throw NullPointerException, as specified, if o is null
if (!declaringClass.isAssignableFrom(o.getClass())) { if (!declaringClass.isAssignableFrom(o.getClass())) {
throw new IllegalArgumentException("object is not an instance of declaring class"); throw new IllegalArgumentException("object of type " + o.getClass().getName()
+ " is not an instance of " + declaringClass.getName());
} }
} }

View file

@ -101,21 +101,61 @@ final class MethodHandleAccessorFactory {
// Ensure class initialized outside the invocation of method handle // Ensure class initialized outside the invocation of method handle
// so that EIIE is propagated (not wrapped with ITE) // so that EIIE is propagated (not wrapped with ITE)
ensureClassInitialized(ctor.getDeclaringClass()); ensureClassInitialized(ctor.getDeclaringClass());
try { try {
MethodHandle mh = JLIA.unreflectConstructor(ctor); MethodHandle target = makeConstructorHandle(JLIA.unreflectConstructor(ctor));
int paramCount = mh.type().parameterCount(); return DirectConstructorHandleAccessor.constructorAccessor(ctor, target);
MethodHandle target = mh.asFixedArity(); } catch (IllegalAccessException e) {
throw new InternalError(e);
}
}
/**
* Creates a ConstructorAccessor that is capable of creating instances
* of the given class and instantiated by the given constructor.
*
* @param decl the class to instantiate
* @param ctor the constructor to call
* @return an accessible constructor
*/
static ConstructorAccessorImpl newSerializableConstructorAccessor(Class<?> decl, Constructor<?> ctor) {
if (!constructorInSuperclass(decl, ctor)) {
throw new UnsupportedOperationException(ctor + " not a superclass of " + decl.getName());
}
// ExceptionInInitializerError may be thrown during class initialization
// Ensure class initialized outside the invocation of method handle
// so that EIIE is propagated (not wrapped with ITE)
ensureClassInitialized(decl);
try {
MethodHandle target = makeConstructorHandle(JLIA.serializableConstructor(decl, ctor));
return DirectConstructorHandleAccessor.constructorAccessor(ctor, target);
} catch (IllegalAccessException e) {
throw new InternalError(e);
}
}
private static boolean constructorInSuperclass(Class<?> decl, Constructor<?> ctor) {
if (decl == ctor.getDeclaringClass())
return true;
Class<?> cl = decl;
while ((cl = cl.getSuperclass()) != null) {
if (cl == ctor.getDeclaringClass()) {
return true;
}
}
return false;
}
private static MethodHandle makeConstructorHandle(MethodHandle ctor) {
int paramCount = ctor.type().parameterCount();
MethodHandle target = ctor.asFixedArity();
MethodType mtype = specializedMethodTypeForConstructor(paramCount); MethodType mtype = specializedMethodTypeForConstructor(paramCount);
if (paramCount > SPECIALIZED_PARAM_COUNT) { if (paramCount > SPECIALIZED_PARAM_COUNT) {
// spread the parameters only for the non-specialized case // spread the parameters only for the non-specialized case
target = target.asSpreader(Object[].class, paramCount); target = target.asSpreader(Object[].class, paramCount);
} }
target = target.asType(mtype); return target.asType(mtype);
return DirectConstructorHandleAccessor.constructorAccessor(ctor, target);
} catch (IllegalAccessException e) {
throw new InternalError(e);
}
} }
/** /**

View file

@ -370,27 +370,27 @@ public class ReflectionFactory {
private final Constructor<?> generateConstructor(Class<?> cl, private final Constructor<?> generateConstructor(Class<?> cl,
Constructor<?> constructorToCall) { Constructor<?> constructorToCall) {
Constructor<?> ctor = newConstructor(constructorToCall.getDeclaringClass(),
ConstructorAccessor acc = new SerializationConstructorAccessorGenerator(). constructorToCall.getParameterTypes(),
constructorToCall.getExceptionTypes(),
constructorToCall.getModifiers(),
langReflectAccess.getConstructorSlot(constructorToCall),
langReflectAccess.getConstructorSignature(constructorToCall),
langReflectAccess.getConstructorAnnotations(constructorToCall),
langReflectAccess.getConstructorParameterAnnotations(constructorToCall));
ConstructorAccessor acc;
if (useOldSerializableConstructor()) {
acc = new SerializationConstructorAccessorGenerator().
generateSerializationConstructor(cl, generateSerializationConstructor(cl,
constructorToCall.getParameterTypes(), constructorToCall.getParameterTypes(),
constructorToCall.getModifiers(), constructorToCall.getModifiers(),
constructorToCall.getDeclaringClass()); constructorToCall.getDeclaringClass());
Constructor<?> c = newConstructor(constructorToCall.getDeclaringClass(), } else {
constructorToCall.getParameterTypes(), acc = MethodHandleAccessorFactory.newSerializableConstructorAccessor(cl, ctor);
constructorToCall.getExceptionTypes(), }
constructorToCall.getModifiers(), setConstructorAccessor(ctor, acc);
langReflectAccess. ctor.setAccessible(true);
getConstructorSlot(constructorToCall), return ctor;
langReflectAccess.
getConstructorSignature(constructorToCall),
langReflectAccess.
getConstructorAnnotations(constructorToCall),
langReflectAccess.
getConstructorParameterAnnotations(constructorToCall));
setConstructorAccessor(c, acc);
c.setAccessible(true);
return c;
} }
public final MethodHandle readObjectForSerialization(Class<?> cl) { public final MethodHandle readObjectForSerialization(Class<?> cl) {
@ -548,6 +548,10 @@ public class ReflectionFactory {
return config().useNativeAccessorOnly; return config().useNativeAccessorOnly;
} }
static boolean useOldSerializableConstructor() {
return config().useOldSerializableConstructor;
}
private static boolean disableSerialConstructorChecks() { private static boolean disableSerialConstructorChecks() {
return config().disableSerialConstructorChecks; return config().disableSerialConstructorChecks;
} }
@ -564,6 +568,7 @@ public class ReflectionFactory {
private static @Stable Config config; private static @Stable Config config;
private static final Config DEFAULT_CONFIG = new Config(false, // useNativeAccessorOnly private static final Config DEFAULT_CONFIG = new Config(false, // useNativeAccessorOnly
false, // useOldSerializeableConstructor
false); // disableSerialConstructorChecks false); // disableSerialConstructorChecks
/** /**
@ -578,6 +583,7 @@ public class ReflectionFactory {
* is to override them. * is to override them.
*/ */
private record Config(boolean useNativeAccessorOnly, private record Config(boolean useNativeAccessorOnly,
boolean useOldSerializableConstructor,
boolean disableSerialConstructorChecks) { boolean disableSerialConstructorChecks) {
} }
@ -601,10 +607,12 @@ public class ReflectionFactory {
Properties props = GetPropertyAction.privilegedGetProperties(); Properties props = GetPropertyAction.privilegedGetProperties();
boolean useNativeAccessorOnly = boolean useNativeAccessorOnly =
"true".equals(props.getProperty("jdk.reflect.useNativeAccessorOnly")); "true".equals(props.getProperty("jdk.reflect.useNativeAccessorOnly"));
boolean useOldSerializableConstructor =
"true".equals(props.getProperty("jdk.reflect.useOldSerializableConstructor"));
boolean disableSerialConstructorChecks = boolean disableSerialConstructorChecks =
"true".equals(props.getProperty("jdk.disableSerialConstructorChecks")); "true".equals(props.getProperty("jdk.disableSerialConstructorChecks"));
return new Config(useNativeAccessorOnly, disableSerialConstructorChecks); return new Config(useNativeAccessorOnly, useOldSerializableConstructor, disableSerialConstructorChecks);
} }
/** /**

View file

@ -397,7 +397,7 @@ public class MethodHandleAccessorsTest {
}; };
private static final Throwable[] mismatched_target_type = new Throwable[] { private static final Throwable[] mismatched_target_type = new Throwable[] {
new IllegalArgumentException("argument type mismatch"), new IllegalArgumentException("argument type mismatch"),
new IllegalArgumentException("object is not an instance of declaring class"), new IllegalArgumentException("object of type java.lang.Object is not an instance of MethodHandleAccessorsTest"),
}; };
private static final Throwable[] cannot_get_field = new Throwable[] { private static final Throwable[] cannot_get_field = new Throwable[] {
new IllegalArgumentException("Can not get") new IllegalArgumentException("Can not get")

View file

@ -136,6 +136,11 @@ public class ReflectionFactoryTest {
} }
} }
@Test(expectedExceptions = UnsupportedOperationException.class)
static void testConstructorNotSuperClass() throws ReflectiveOperationException {
factory.newConstructorForSerialization(Bar.class, Baz.class.getDeclaredConstructor());
}
static class Foo { static class Foo {
private int foo; private int foo;
public Foo() { public Foo() {