8261407: ReflectionFactory.checkInitted() is not thread-safe

Co-authored-by: Peter Levart <plevart@openjdk.org>
Reviewed-by: dholmes, mchung, plevart
This commit is contained in:
liach 2022-02-22 16:57:23 +00:00 committed by Mandy Chung
parent 58e1882f3c
commit 7feabee426

View File

@ -44,6 +44,7 @@ import java.util.Properties;
import jdk.internal.access.JavaLangReflectAccess; import jdk.internal.access.JavaLangReflectAccess;
import jdk.internal.access.SharedSecrets; import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.VM; import jdk.internal.misc.VM;
import jdk.internal.vm.annotation.Stable;
import sun.security.action.GetPropertyAction; import sun.security.action.GetPropertyAction;
import sun.security.util.SecurityConstants; import sun.security.util.SecurityConstants;
@ -61,42 +62,12 @@ import sun.security.util.SecurityConstants;
public class ReflectionFactory { public class ReflectionFactory {
private static boolean initted = false;
private static final ReflectionFactory soleInstance = new ReflectionFactory(); private static final ReflectionFactory soleInstance = new ReflectionFactory();
/* Method for static class initializer <clinit>, or null */ /* Method for static class initializer <clinit>, or null */
private static volatile Method hasStaticInitializerMethod; private static volatile Method hasStaticInitializerMethod;
//
// "Inflation" mechanism. Loading bytecodes to implement
// Method.invoke() and Constructor.newInstance() currently costs
// 3-4x more than an invocation via native code for the first
// invocation (though subsequent invocations have been benchmarked
// to be over 20x faster). Unfortunately this cost increases
// startup time for certain applications that use reflection
// intensively (but only once per class) to bootstrap themselves.
// To avoid this penalty we reuse the existing JVM entry points
// for the first few invocations of Methods and Constructors and
// then switch to the bytecode-based implementations.
//
// Package-private to be accessible to NativeMethodAccessorImpl
// and NativeConstructorAccessorImpl
private static boolean noInflation = false;
private static int inflationThreshold = 15;
//
// New implementation uses direct invocation of method handles
private static final int METHOD_MH_ACCESSOR = 0x1;
private static final int FIELD_MH_ACCESSOR = 0x2;
private static final int ALL_MH_ACCESSORS = METHOD_MH_ACCESSOR|FIELD_MH_ACCESSOR;
private static int useDirectMethodHandle = ALL_MH_ACCESSORS;
private static boolean useNativeAccessorOnly = false; // for testing only
// true if deserialization constructor checking is disabled
private static boolean disableSerialConstructorChecks = false;
private final JavaLangReflectAccess langReflectAccess; private final JavaLangReflectAccess langReflectAccess;
private ReflectionFactory() { private ReflectionFactory() {
this.langReflectAccess = SharedSecrets.getJavaLangReflectAccess(); this.langReflectAccess = SharedSecrets.getJavaLangReflectAccess();
@ -160,8 +131,6 @@ public class ReflectionFactory {
* @param override true if caller has overridden accessibility * @param override true if caller has overridden accessibility
*/ */
public FieldAccessor newFieldAccessor(Field field, boolean override) { public FieldAccessor newFieldAccessor(Field field, boolean override) {
checkInitted();
Field root = langReflectAccess.getRoot(field); Field root = langReflectAccess.getRoot(field);
if (root != null) { if (root != null) {
// FieldAccessor will use the root unless the modifiers have // FieldAccessor will use the root unless the modifiers have
@ -180,8 +149,6 @@ public class ReflectionFactory {
} }
public MethodAccessor newMethodAccessor(Method method, boolean callerSensitive) { public MethodAccessor newMethodAccessor(Method method, boolean callerSensitive) {
checkInitted();
// use the root Method that will not cache caller class // use the root Method that will not cache caller class
Method root = langReflectAccess.getRoot(method); Method root = langReflectAccess.getRoot(method);
if (root != null) { if (root != null) {
@ -191,7 +158,7 @@ public class ReflectionFactory {
if (useMethodHandleAccessor()) { if (useMethodHandleAccessor()) {
return MethodHandleAccessorFactory.newMethodAccessor(method, callerSensitive); return MethodHandleAccessorFactory.newMethodAccessor(method, callerSensitive);
} else { } else {
if (noInflation && !method.getDeclaringClass().isHidden()) { if (noInflation() && !method.getDeclaringClass().isHidden()) {
return generateMethodAccessor(method); return generateMethodAccessor(method);
} else { } else {
NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method); NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method);
@ -215,8 +182,6 @@ public class ReflectionFactory {
} }
public ConstructorAccessor newConstructorAccessor(Constructor<?> c) { public ConstructorAccessor newConstructorAccessor(Constructor<?> c) {
checkInitted();
Class<?> declaringClass = c.getDeclaringClass(); Class<?> declaringClass = c.getDeclaringClass();
if (Modifier.isAbstract(declaringClass.getModifiers())) { if (Modifier.isAbstract(declaringClass.getModifiers())) {
return new InstantiationExceptionConstructorAccessorImpl(null); return new InstantiationExceptionConstructorAccessorImpl(null);
@ -242,7 +207,7 @@ public class ReflectionFactory {
return new BootstrapConstructorAccessorImpl(c); return new BootstrapConstructorAccessorImpl(c);
} }
if (noInflation && !c.getDeclaringClass().isHidden()) { if (noInflation() && !c.getDeclaringClass().isHidden()) {
return new MethodAccessorGenerator(). return new MethodAccessorGenerator().
generateConstructor(c.getDeclaringClass(), generateConstructor(c.getDeclaringClass(),
c.getParameterTypes(), c.getParameterTypes(),
@ -430,7 +395,7 @@ public class ReflectionFactory {
while (Serializable.class.isAssignableFrom(initCl)) { while (Serializable.class.isAssignableFrom(initCl)) {
Class<?> prev = initCl; Class<?> prev = initCl;
if ((initCl = initCl.getSuperclass()) == null || if ((initCl = initCl.getSuperclass()) == null ||
(!disableSerialConstructorChecks && !superHasAccessibleConstructor(prev))) { (!disableSerialConstructorChecks() && !superHasAccessibleConstructor(prev))) {
return null; return null;
} }
} }
@ -623,41 +588,108 @@ public class ReflectionFactory {
// Internals only below this point // Internals only below this point
// //
// Package-private to be accessible to NativeMethodAccessorImpl
// and NativeConstructorAccessorImpl
static int inflationThreshold() { static int inflationThreshold() {
return inflationThreshold; return config().inflationThreshold;
} }
static boolean noInflation() { static boolean noInflation() {
return noInflation; return config().noInflation;
} }
static boolean useMethodHandleAccessor() { static boolean useMethodHandleAccessor() {
return (useDirectMethodHandle & METHOD_MH_ACCESSOR) == METHOD_MH_ACCESSOR; return (config().useDirectMethodHandle & METHOD_MH_ACCESSOR) == METHOD_MH_ACCESSOR;
} }
static boolean useFieldHandleAccessor() { static boolean useFieldHandleAccessor() {
return (useDirectMethodHandle & FIELD_MH_ACCESSOR) == FIELD_MH_ACCESSOR; return (config().useDirectMethodHandle & FIELD_MH_ACCESSOR) == FIELD_MH_ACCESSOR;
} }
static boolean useNativeAccessorOnly() { static boolean useNativeAccessorOnly() {
return useNativeAccessorOnly; return config().useNativeAccessorOnly;
} }
/** We have to defer full initialization of this class until after private static boolean disableSerialConstructorChecks() {
the static initializer is run since java.lang.reflect.Method's return config().disableSerialConstructorChecks;
static initializer (more properly, that for }
java.lang.reflect.AccessibleObject) causes this class's to be
run, before the system properties are set up. */ // New implementation uses direct invocation of method handles
private static void checkInitted() { private static final int METHOD_MH_ACCESSOR = 0x1;
if (initted) return; private static final int FIELD_MH_ACCESSOR = 0x2;
private static final int ALL_MH_ACCESSORS = METHOD_MH_ACCESSOR | FIELD_MH_ACCESSOR;
/**
* The configuration is lazily initialized after the module system is initialized. The
* default config would be used before the proper config is loaded.
*
* The static initializer of ReflectionFactory is run before the system properties are set up.
* The class initialization is caused by the class initialization of java.lang.reflect.Method
* (more properly, caused by the class initialization for java.lang.reflect.AccessibleObject)
* that happens very early VM startup, initPhase1.
*/
private static @Stable Config config;
// "Inflation" mechanism. Loading bytecodes to implement
// Method.invoke() and Constructor.newInstance() currently costs
// 3-4x more than an invocation via native code for the first
// invocation (though subsequent invocations have been benchmarked
// to be over 20x faster). Unfortunately this cost increases
// startup time for certain applications that use reflection
// intensively (but only once per class) to bootstrap themselves.
// To avoid this penalty we reuse the existing JVM entry points
// for the first few invocations of Methods and Constructors and
// then switch to the bytecode-based implementations.
private static final Config DEFAULT_CONFIG = new Config(false, // noInflation
15, // inflationThreshold
ALL_MH_ACCESSORS, // useDirectMethodHandle
false, // useNativeAccessorOnly
false); // disableSerialConstructorChecks
/**
* The configurations for the reflection factory. Configurable via
* system properties but only available after ReflectionFactory is
* loaded during early VM startup.
*
* Note that the default implementations of the object methods of
* this Config record (toString, equals, hashCode) use indy,
* which is available to use only after initPhase1. These methods
* are currently not called, but should they be needed, a workaround
* is to override them.
*/
private record Config(boolean noInflation,
int inflationThreshold,
int useDirectMethodHandle,
boolean useNativeAccessorOnly,
boolean disableSerialConstructorChecks) {
}
private static Config config() {
Config c = config;
if (c != null) {
return c;
}
// Defer initialization until module system is initialized so as // Defer initialization until module system is initialized so as
// to avoid inflation and spinning bytecode in unnamed modules // to avoid inflation and spinning bytecode in unnamed modules
// during early startup. // during early startup.
if (!VM.isModuleSystemInited()) { if (!VM.isModuleSystemInited()) {
return; return DEFAULT_CONFIG;
} }
return config = loadConfig();
}
private static Config loadConfig() {
assert VM.isModuleSystemInited();
boolean noInflation = DEFAULT_CONFIG.noInflation;
int inflationThreshold = DEFAULT_CONFIG.inflationThreshold;
int useDirectMethodHandle = DEFAULT_CONFIG.useDirectMethodHandle;
boolean useNativeAccessorOnly = DEFAULT_CONFIG.useNativeAccessorOnly;
boolean disableSerialConstructorChecks = DEFAULT_CONFIG.disableSerialConstructorChecks;
Properties props = GetPropertyAction.privilegedGetProperties(); Properties props = GetPropertyAction.privilegedGetProperties();
String val = props.getProperty("sun.reflect.noInflation"); String val = props.getProperty("sun.reflect.noInflation");
if (val != null && val.equals("true")) { if (val != null && val.equals("true")) {
@ -690,7 +722,11 @@ public class ReflectionFactory {
disableSerialConstructorChecks = disableSerialConstructorChecks =
"true".equals(props.getProperty("jdk.disableSerialConstructorChecks")); "true".equals(props.getProperty("jdk.disableSerialConstructorChecks"));
initted = true; return new Config(noInflation,
inflationThreshold,
useDirectMethodHandle,
useNativeAccessorOnly,
disableSerialConstructorChecks);
} }
/** /**