mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-10 08:01:54 +00:00
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:
parent
58e1882f3c
commit
7feabee426
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user