diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index d5f365b33912..268e8129ad61 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -1300,7 +1300,8 @@ private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvm return; } } - if (jniFunctions().getIsInstanceOf().invoke(jni, loader, agent.handles().jdkInternalReflectDelegatingClassLoader)) { + JNIObjectHandle jdkInternalReflectDelegatingClassLoader = agent.handles().jdkInternalReflectDelegatingClassLoader; + if (!jdkInternalReflectDelegatingClassLoader.equal(nullHandle()) && jniFunctions().getIsInstanceOf().invoke(jni, loader, jdkInternalReflectDelegatingClassLoader)) { return; } byte[] data = new byte[classDataLen]; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index f6fb9635d8b9..44b500c4715a 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -114,11 +114,8 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaLangClassLoader = newClassGlobalRef(env, "java/lang/ClassLoader"); javaLangClassLoaderGetResource = getMethodId(env, javaLangClassLoader, "getResource", "(Ljava/lang/String;)Ljava/net/URL;", false); - JNIObjectHandle reflectLoader = findClassOptional(env, "jdk/internal/reflect/DelegatingClassLoader"); // JDK11+ - if (reflectLoader.equal(nullHandle())) { - reflectLoader = findClass(env, "sun/reflect/DelegatingClassLoader"); // JDK 8 - } - jdkInternalReflectDelegatingClassLoader = newTrackedGlobalRef(env, reflectLoader); + JNIObjectHandle reflectLoader = findClassOptional(env, "jdk/internal/reflect/DelegatingClassLoader"); // JDK11-23 + jdkInternalReflectDelegatingClassLoader = reflectLoader.equal(nullHandle()) ? nullHandle() : newTrackedGlobalRef(env, reflectLoader); JNIObjectHandle javaLangObject = findClass(env, "java/lang/Object"); javaLangObjectGetClass = getMethodId(env, javaLangObject, "getClass", "()Ljava/lang/Class;", false); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index cdcfc5239521..a0e6fb6249ba 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -124,10 +124,12 @@ import jdk.graal.compiler.core.common.NumUtil; import jdk.graal.compiler.core.common.SuppressFBWarnings; import jdk.graal.compiler.replacements.ReplacementsUtil; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import jdk.internal.access.JavaLangReflectAccess; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.CallerSensitiveAdapter; +import jdk.internal.reflect.ConstructorAccessor; import jdk.internal.reflect.FieldAccessor; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; @@ -2114,7 +2116,7 @@ final class Target_jdk_internal_reflect_ReflectionFactory { private static ReflectionFactory soleInstance; @Alias // - private JavaLangReflectAccess langReflectAccess; + JavaLangReflectAccess langReflectAccess; /** * This substitution eliminates the SecurityManager check in the original method, which would @@ -2145,6 +2147,36 @@ public FieldAccessor newFieldAccessor(Field field0, boolean override) { boolean isReadOnly = isFinal && (!override || langReflectAccess.isTrustedFinalField(field)); return UnsafeFieldAccessorFactory.newFieldAccessor(field, isReadOnly); } + + @Substitute + @TargetElement(onlyWith = JDKLatest.class) + private Constructor generateConstructor(Class cl, Constructor constructorToCall) { + SerializationRegistry serializationRegistry = ImageSingletons.lookup(SerializationRegistry.class); + ConstructorAccessor acc = (ConstructorAccessor) serializationRegistry.getSerializationConstructorAccessor(cl, constructorToCall.getDeclaringClass()); + /* + * Unlike other root constructors, this constructor is not copied for mutation but directly + * mutated, as it is not cached. To cache this constructor, setAccessible call must be done + * on a copy and return that copy instead. + */ + Constructor ctor = Helper_jdk_internal_reflect_ReflectionFactory.newConstructorWithAccessor(this, constructorToCall, acc); + ctor.setAccessible(true); + return ctor; + } + +} + +/** + * Reflectively access {@code JavaLangReflectAccess.newConstructorWithAccessor}. Once we drop JDK + * 21, this can be replaced by a direct call to the method. (GR-55515) + */ +final class Helper_jdk_internal_reflect_ReflectionFactory { + private static final Method NEW_CONSTRUCTOR_WITH_ACCESSOR = JavaVersionUtil.JAVA_SPEC > 21 + ? ReflectionUtil.lookupMethod(JavaLangReflectAccess.class, "newConstructorWithAccessor", Constructor.class, ConstructorAccessor.class) + : null; + + static Constructor newConstructorWithAccessor(Target_jdk_internal_reflect_ReflectionFactory reflectionFactory, Constructor constructorToCall, ConstructorAccessor acc) { + return ReflectionUtil.invokeMethod(NEW_CONSTRUCTOR_WITH_ACCESSOR, reflectionFactory.langReflectAccess, constructorToCall, acc); + } } /** diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java index 70f0cfbf92dd..bb3dcc0ad476 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java @@ -41,10 +41,12 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.configure.RuntimeConditionSet; +import com.oracle.svm.core.reflect.SubstrateConstructorAccessor; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.java.LambdaUtils; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; public class SerializationSupport implements SerializationRegistry { @@ -92,7 +94,7 @@ private StubForAbstractClass() { } } - private final Constructor stubConstructor; + private Constructor stubConstructor; public static final class SerializationLookupKey { private final Class declaringClass; @@ -133,13 +135,20 @@ public int hashCode() { private final EconomicMap constructorAccessors; @Platforms(Platform.HOSTED_ONLY.class) - public SerializationSupport(Constructor stubConstructor) { + public SerializationSupport() { constructorAccessors = ImageHeapMap.create(); + } + + public void setStubConstructor(Constructor stubConstructor) { + VMError.guarantee(this.stubConstructor == null, "Cannot reset stubConstructor"); this.stubConstructor = stubConstructor; } @Platforms(Platform.HOSTED_ONLY.class) public Object addConstructorAccessor(Class declaringClass, Class targetConstructorClass, Object constructorAccessor) { + if (JavaVersionUtil.JAVA_SPEC > 21) { + VMError.guarantee(constructorAccessor instanceof SubstrateConstructorAccessor, "Not a SubstrateConstructorAccessor: %s", constructorAccessor); + } SerializationLookupKey key = new SerializationLookupKey(declaringClass, targetConstructorClass); return constructorAccessors.putIfAbsent(key, constructorAccessor); } @@ -214,6 +223,7 @@ public Object getSerializationConstructorAccessor(Class rawDeclaringClass, Cl declaringClass = SerializedLambda.class; } + VMError.guarantee(stubConstructor != null, "Called too early, no stub constructor yet."); Class targetConstructorClass = Modifier.isAbstract(declaringClass.getModifiers()) ? stubConstructor.getDeclaringClass() : rawTargetConstructorClass; Object constructorAccessor = constructorAccessors.get(new SerializationLookupKey(declaringClass, targetConstructorClass)); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java index 782f967a9819..8c700c0d53e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ExecutableAccessorComputer.java @@ -29,6 +29,7 @@ import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.reflect.SubstrateAccessor; /** * Computes new values for the accessor fields of {@link Executable} subclasses, to be used instead @@ -39,6 +40,14 @@ public final class ExecutableAccessorComputer implements FieldValueTransformer { @Override public Object transform(Object receiver, Object originalValue) { + if (originalValue instanceof SubstrateAccessor) { + /* + * We do not want to replace existing SubstrateAccessors, since they might be more + * specialized (e.g., an explicit target class for a constructor accessor) than what + * would be created here. + */ + return originalValue; + } return ReflectionSubstitutionSupport.singleton().getOrCreateAccessor((Executable) receiver); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java index 75833e9f31ad..d2e0b455a99f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/ReflectionSubstitutionSupport.java @@ -40,6 +40,8 @@ static ReflectionSubstitutionSupport singleton() { SubstrateAccessor getOrCreateAccessor(Executable member); + SubstrateAccessor getOrCreateConstructorAccessor(Class targetClass, Executable member); + /** Offset of the field or -1 if the field was not registered for unsafe access. */ int getFieldOffset(Field field, boolean checkUnsafeAccessed); diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 21a01c3f096a..2f2c9001f8d0 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -1173,12 +1173,6 @@ private int completeImageBuild() { } imageClasspath.addAll(customImageClasspath); - /* - * Work around "JDK-8315810: Reimplement - * sun.reflect.ReflectionFactory::newConstructorForSerialization with method handles" - * [GR-48901] - */ - imageBuilderJavaArgs.add("-Djdk.reflect.useOldSerializableConstructor=true"); imageBuilderJavaArgs.add("-Djdk.internal.lambda.disableEagerInitialization=true"); // The following two are for backwards compatibility reasons. They should be removed. imageBuilderJavaArgs.add("-Djdk.internal.lambda.eagerlyInitialize=false"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java index 1aba7a6d4239..1c2baa2b91c7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java @@ -27,6 +27,7 @@ import org.graalvm.nativeimage.ImageSingletons; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.svm.core.NeverInlineTrivial; import com.oracle.svm.hosted.annotation.AnnotationValue; @@ -49,11 +50,14 @@ public final class FactoryMethod extends NonBytecodeMethod { private final ResolvedJavaMethod targetConstructor; + private final ResolvedJavaType instantiatedType; private final boolean throwAllocatedObject; - FactoryMethod(String name, ResolvedJavaMethod targetConstructor, ResolvedJavaType declaringClass, Signature signature, ConstantPool constantPool, boolean throwAllocatedObject) { + FactoryMethod(String name, ResolvedJavaMethod targetConstructor, ResolvedJavaType instantiatedType, ResolvedJavaType declaringClass, Signature signature, ConstantPool constantPool, + boolean throwAllocatedObject) { super(name, true, declaringClass, signature, constantPool); this.targetConstructor = targetConstructor; + this.instantiatedType = instantiatedType; this.throwAllocatedObject = throwAllocatedObject; assert targetConstructor.isConstructor() : targetConstructor; @@ -85,8 +89,9 @@ public StructuredGraph buildGraph(DebugContext debug, AnalysisMethod method, Hos FactoryMethodSupport support = ImageSingletons.lookup(FactoryMethodSupport.class); AnalysisMethod aTargetConstructor = kit.getMetaAccess().getUniverse().lookup(targetConstructor); + AnalysisType aInstantiatedType = kit.getMetaAccess().getUniverse().lookup(instantiatedType); - AbstractNewObjectNode newInstance = support.createNewInstance(kit, aTargetConstructor.getDeclaringClass(), true); + AbstractNewObjectNode newInstance = support.createNewInstance(kit, aInstantiatedType, true); ValueNode[] originalArgs = kit.getInitialArguments().toArray(ValueNode.EMPTY_ARRAY); ValueNode[] invokeArgs = new ValueNode[originalArgs.length + 1]; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java index 7796c1cb7aef..bf0f7f7617dc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java @@ -32,6 +32,7 @@ import com.oracle.graal.pointsto.infrastructure.ResolvedSignature; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.code.FactoryMethodHolder; import com.oracle.svm.core.code.FactoryThrowMethodHolder; @@ -54,8 +55,11 @@ public static FactoryMethodSupport singleton() { return ImageSingletons.lookup(FactoryMethodSupport.class); } - private final Map factoryMethods = new ConcurrentHashMap<>(); - private final Map factoryThrowMethods = new ConcurrentHashMap<>(); + private record ConstructorDescription(AnalysisMethod aConstructor, AnalysisType aInstantiatedType) { + } + + private final Map factoryMethods = new ConcurrentHashMap<>(); + private final Map factoryThrowMethods = new ConcurrentHashMap<>(); public static boolean isFactoryMethod(AnalysisMethod method) { var javaClass = method.getDeclaringClass().getJavaClass(); @@ -63,9 +67,15 @@ public static boolean isFactoryMethod(AnalysisMethod method) { } public AnalysisMethod lookup(AnalysisMetaAccess aMetaAccess, AnalysisMethod aConstructor, boolean throwAllocatedObject) { - VMError.guarantee(aConstructor.getDeclaringClass().isInstanceClass() && !aConstructor.getDeclaringClass().isAbstract(), "Must be a non-abstract instance class"); - Map methods = throwAllocatedObject ? factoryThrowMethods : factoryMethods; - FactoryMethod factoryMethod = methods.computeIfAbsent(aConstructor, key -> { + return lookup(aMetaAccess, aConstructor, aConstructor.getDeclaringClass(), throwAllocatedObject); + } + + public AnalysisMethod lookup(AnalysisMetaAccess aMetaAccess, AnalysisMethod aConstructor, AnalysisType aInstantiatedType, boolean throwAllocatedObject) { + AnalysisType aInstType = aInstantiatedType == null ? aConstructor.getDeclaringClass() : aInstantiatedType; + VMError.guarantee(aConstructor.getDeclaringClass().isAssignableFrom(aInstType), "Must be assignable from"); + VMError.guarantee(aInstType.isInstanceClass() && !aInstType.isAbstract(), "Must be a non-abstract instance class"); + Map methods = throwAllocatedObject ? factoryThrowMethods : factoryMethods; + FactoryMethod factoryMethod = methods.computeIfAbsent(new ConstructorDescription(aConstructor, aInstType), key -> { /* * Computing the factory method name via the analysis universe ensures that type name * modifications, like to make lambda names unique, are incorporated in the name. @@ -79,12 +89,13 @@ public AnalysisMethod lookup(AnalysisMetaAccess aMetaAccess, AnalysisMethod aCon for (int i = 0; i < unwrappedParameterTypes.length; i++) { unwrappedParameterTypes[i] = aConstructor.getSignature().getParameterType(i).getWrapped(); } - ResolvedJavaType unwrappedReturnType = (throwAllocatedObject ? aMetaAccess.lookupJavaType(void.class) : aConstructor.getDeclaringClass()).getWrapped(); + ResolvedJavaType unwrappedReturnType = (throwAllocatedObject ? aMetaAccess.lookupJavaType(void.class) : aInstType).getWrapped(); Signature unwrappedSignature = ResolvedSignature.fromArray(unwrappedParameterTypes, unwrappedReturnType); ResolvedJavaMethod unwrappedConstructor = aConstructor.getWrapped(); + ResolvedJavaType unwrappedInstatiatedType = aInstType.getWrapped(); ResolvedJavaType unwrappedDeclaringClass = (aMetaAccess.lookupJavaType(throwAllocatedObject ? FactoryThrowMethodHolder.class : FactoryMethodHolder.class)).getWrapped(); ConstantPool unwrappedConstantPool = unwrappedConstructor.getConstantPool(); - return new FactoryMethod(name, unwrappedConstructor, unwrappedDeclaringClass, unwrappedSignature, unwrappedConstantPool, throwAllocatedObject); + return new FactoryMethod(name, unwrappedConstructor, unwrappedInstatiatedType, unwrappedDeclaringClass, unwrappedSignature, unwrappedConstantPool, throwAllocatedObject); }); AnalysisMethod aMethod = aMetaAccess.getUniverse().lookup(factoryMethod); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 5d80ffe544e6..1db397c00235 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -127,7 +127,10 @@ public class ReflectionFeature implements InternalFeature, ReflectionSubstitutio private int loadedConfigurations; private UniverseMetaAccess metaAccess; - final Map accessors = new ConcurrentHashMap<>(); + private record AccessorKey(Executable member, Class targetClass) { + } + + final Map accessors = new ConcurrentHashMap<>(); private final Map expandSignatureMethods = new ConcurrentHashMap<>(); private static final Method invokePrototype = ReflectionUtil.lookupMethod(ReflectionAccessorHolder.class, "invokePrototype", @@ -143,7 +146,13 @@ public class ReflectionFeature implements InternalFeature, ReflectionSubstitutio @Override public SubstrateAccessor getOrCreateAccessor(Executable member) { - SubstrateAccessor existing = accessors.get(member); + return getOrCreateConstructorAccessor(member.getDeclaringClass(), member); + } + + @Override + public SubstrateAccessor getOrCreateConstructorAccessor(Class targetClass, Executable member) { + AccessorKey key = new AccessorKey(member, targetClass); + SubstrateAccessor existing = accessors.get(key); if (existing != null) { return existing; } @@ -151,7 +160,7 @@ public SubstrateAccessor getOrCreateAccessor(Executable member) { if (analysisAccess == null) { throw VMError.shouldNotReachHere("New Method or Constructor found as reachable after static analysis: " + member); } - return accessors.computeIfAbsent(member, this::createAccessor); + return accessors.computeIfAbsent(key, this::createAccessor); } /** @@ -171,7 +180,9 @@ public SubstrateAccessor getOrCreateAccessor(Executable member) { * {@link ConcurrentHashMap#computeIfAbsent} guarantees that this method is called only once per * member, so no further synchronization is necessary. */ - private SubstrateAccessor createAccessor(Executable member) { + private SubstrateAccessor createAccessor(AccessorKey key) { + Executable member = key.member; + Class targetClass = key.targetClass; MethodPointer expandSignature; MethodPointer directTarget = null; AnalysisMethod targetMethod = null; @@ -219,7 +230,7 @@ private SubstrateAccessor createAccessor(Executable member) { return new SubstrateMethodAccessor(member, receiverType, expandSignature, directTarget, targetMethod, vtableOffset, initializeBeforeInvoke, callerSensitiveAdapter); } else { - Class holder = member.getDeclaringClass(); + Class holder = targetClass; CFunctionPointer factoryMethodTarget = null; ResolvedJavaMethod factoryMethod = null; if (Modifier.isAbstract(holder.getModifiers()) || holder.isInterface() || holder.isPrimitive() || holder.isArray()) { @@ -233,8 +244,9 @@ private SubstrateAccessor createAccessor(Executable member) { } else { expandSignature = createExpandSignatureMethod(member, false); targetMethod = analysisAccess.getMetaAccess().lookupJavaMethod(member); + var aTargetClass = analysisAccess.getMetaAccess().lookupJavaType(targetClass); directTarget = asMethodPointer(targetMethod); - factoryMethod = FactoryMethodSupport.singleton().lookup(analysisAccess.getMetaAccess(), targetMethod, false); + factoryMethod = FactoryMethodSupport.singleton().lookup(analysisAccess.getMetaAccess(), targetMethod, aTargetClass, false); factoryMethodTarget = asMethodPointer(factoryMethod); if (!targetMethod.getDeclaringClass().isInitialized()) { initializeBeforeInvoke = analysisAccess.getHostVM().dynamicHub(targetMethod.getDeclaringClass()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java index 2775baaf1d21..f65ddaa9d271 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java @@ -35,10 +35,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -64,8 +64,11 @@ import com.oracle.svm.core.configure.SerializationConfigurationParser; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.reflect.SubstrateConstructorAccessor; import com.oracle.svm.core.reflect.serialize.SerializationRegistry; import com.oracle.svm.core.reflect.serialize.SerializationSupport; +import com.oracle.svm.core.reflect.target.ReflectionSubstitutionSupport; +import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ConditionalConfigurationRegistry; @@ -100,6 +103,9 @@ import jdk.graal.compiler.phases.tiers.HighTierContext; import jdk.graal.compiler.printer.GraalDebugHandlersFactory; import jdk.graal.compiler.replacements.MethodHandlePlugin; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; +import jdk.internal.access.JavaLangReflectAccess; +import jdk.internal.reflect.ConstructorAccessor; import jdk.internal.reflect.ReflectionFactory; import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.JavaConstant; @@ -261,7 +267,7 @@ private static void registerLambdasFromMethod(ResolvedJavaMethod method, Seriali @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - serializationBuilder.setAnalysisAccess(access); + serializationBuilder.beforeAnalysis(access); } @Override @@ -376,7 +382,7 @@ final class SerializationBuilder extends ConditionalConfigurationRegistry implem private static final Method getConstructorAccessorMethod = ReflectionUtil.lookupMethod(Constructor.class, "getConstructorAccessor"); private static final Method getExternalizableConstructorMethod = ReflectionUtil.lookupMethod(ObjectStreamClass.class, "getExternalizableConstructor", Class.class); - private final Constructor stubConstructor; + private Constructor stubConstructor; private final Field descField; private final Method getDataLayoutMethod; @@ -386,23 +392,26 @@ final class SerializationBuilder extends ConditionalConfigurationRegistry implem private final FeatureImpl.DuringSetupAccessImpl access; private final Method disableSerialConstructorChecks; private final Method superHasAccessibleConstructor; + private final Method packageEquals; private boolean sealed; private final ProxyRegistry proxyRegistry; + private List pendingConstructorRegistrations; SerializationBuilder(SerializationDenyRegistry serializationDenyRegistry, FeatureImpl.DuringSetupAccessImpl access, ConfigurationTypeResolver typeResolver, ProxyRegistry proxyRegistry) { this.access = access; Class classDataSlotClazz = access.findClassByName("java.io.ObjectStreamClass$ClassDataSlot"); this.descField = ReflectionUtil.lookupField(classDataSlotClazz, "desc"); this.getDataLayoutMethod = ReflectionUtil.lookupMethod(ObjectStreamClass.class, "getClassDataLayout"); - this.stubConstructor = newConstructorForSerialization(SerializationSupport.StubForAbstractClass.class, null); this.disableSerialConstructorChecks = ReflectionUtil.lookupMethod(true, ReflectionFactory.class, "disableSerialConstructorChecks"); this.superHasAccessibleConstructor = ReflectionUtil.lookupMethod(ReflectionFactory.class, "superHasAccessibleConstructor", Class.class); + this.packageEquals = ReflectionUtil.lookupMethod(ReflectionFactory.class, "packageEquals", Class.class, Class.class); + this.pendingConstructorRegistrations = new ArrayList<>(); this.denyRegistry = serializationDenyRegistry; this.typeResolver = typeResolver; this.proxyRegistry = proxyRegistry; - serializationSupport = new SerializationSupport(stubConstructor); + this.serializationSupport = new SerializationSupport(); ImageSingletons.add(SerializationRegistry.class, serializationSupport); } @@ -553,9 +562,7 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, return; } } - Optional.ofNullable(addConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass)) - .map(ReflectionUtil::lookupConstructor) - .ifPresent(methods -> ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), false, methods)); + addOrQueueConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass); Class superclass = serializationTargetClass.getSuperclass(); if (superclass != null) { @@ -571,6 +578,30 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, }); } + private void addOrQueueConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { + if (pendingConstructorRegistrations != null) { + // cannot yet create constructor accessor -> add to pending + pendingConstructorRegistrations.add(() -> registerConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass)); + } else { + // can already run the registration + registerConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass); + } + } + + private void registerConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { + Optional.ofNullable(addConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass)) + .map(ReflectionUtil::lookupConstructor) + .ifPresent(methods -> ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), false, methods)); + } + + void beforeAnalysis(Feature.BeforeAnalysisAccess beforeAnalysisAccess) { + setAnalysisAccess(beforeAnalysisAccess); + stubConstructor = newConstructorForSerialization(SerializationSupport.StubForAbstractClass.class, null); + pendingConstructorRegistrations.forEach(Runnable::run); + pendingConstructorRegistrations = null; + serializationSupport.setStubConstructor(stubConstructor); + } + private static void registerQueriesForInheritableMethod(Class clazz, String methodName, Class... args) { Class iter = clazz; while (iter != null) { @@ -611,16 +642,10 @@ private void registerForSerialization(ConfigurationCondition cnd, Class seria while (Serializable.class.isAssignableFrom(initCl)) { Class prev = initCl; RuntimeReflection.registerAllDeclaredConstructors(initCl); - try { - if ((initCl = initCl.getSuperclass()) == null || - (!(boolean) disableSerialConstructorChecks.invoke(null) && - !prev.isArray() && - !(Boolean) superHasAccessibleConstructor.invoke(ReflectionFactory.getReflectionFactory(), prev))) { - initClValid = false; - break; - } - } catch (InvocationTargetException | IllegalAccessException e) { - throw VMError.shouldNotReachHere(e); + if ((initCl = initCl.getSuperclass()) == null || (!disableSerialConstructorChecks() && + !prev.isArray() && !superHasAccessibleConstructor(prev))) { + initClValid = false; + break; } } @@ -687,31 +712,90 @@ private static void registerForDeserialization(ConfigurationCondition cnd, Class registerMethod(cnd, serializationTargetClass, "readResolve"); } - private static Constructor newConstructorForSerialization(Class serializationTargetClass, Constructor customConstructorToCall) { + private Constructor newConstructorForSerialization(Class serializationTargetClass, Constructor customConstructorToCall) { + if (JavaVersionUtil.JAVA_SPEC <= 21) { + if (customConstructorToCall == null) { + return ReflectionFactory.getReflectionFactory().newConstructorForSerialization(serializationTargetClass); + } else { + return ReflectionFactory.getReflectionFactory().newConstructorForSerialization(serializationTargetClass, customConstructorToCall); + } + } + Constructor constructorToCall; if (customConstructorToCall == null) { - return ReflectionFactory.getReflectionFactory().newConstructorForSerialization(serializationTargetClass); + constructorToCall = getConstructorForSerialization(serializationTargetClass); } else { - return ReflectionFactory.getReflectionFactory().newConstructorForSerialization(serializationTargetClass, customConstructorToCall); + constructorToCall = customConstructorToCall; } + ConstructorAccessor acc = getConstructorAccessor(serializationTargetClass, constructorToCall); + JavaLangReflectAccess langReflectAccess = ReflectionUtil.readField(ReflectionFactory.class, "langReflectAccess", ReflectionFactory.getReflectionFactory()); + Method newConstructorWithAccessor = ReflectionUtil.lookupMethod(JavaLangReflectAccess.class, "newConstructorWithAccessor", Constructor.class, ConstructorAccessor.class); + return ReflectionUtil.invokeMethod(newConstructorWithAccessor, langReflectAccess, constructorToCall, acc); } - static Object getConstructorAccessor(Constructor constructor) { + private static ConstructorAccessor getConstructorAccessor(Class serializationTargetClass, Constructor constructorToCall) { + return (SubstrateConstructorAccessor) ReflectionSubstitutionSupport.singleton().getOrCreateConstructorAccessor(serializationTargetClass, constructorToCall); + } + + /** + * Returns a constructor that allocates an instance of cl and that then initializes the instance + * by calling the no-arg constructor of its first non-serializable superclass. This is specified + * in the Serialization Specification, section 3.1, in step 11 of the deserialization process. + * If cl is not serializable, returns cl's no-arg constructor. If no accessible constructor is + * found, or if the class hierarchy is somehow malformed (e.g., a serializable class has no + * superclass), null is returned. + * + * @param cl the class for which a constructor is to be found + * @return the generated constructor, or null if none is available + */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+22/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java#L311-L332") + private Constructor getConstructorForSerialization(Class cl) { + Class initCl = cl; + while (Serializable.class.isAssignableFrom(initCl)) { + Class prev = initCl; + if ((initCl = initCl.getSuperclass()) == null || (!disableSerialConstructorChecks() && + !superHasAccessibleConstructor(prev))) { + return null; + } + } + Constructor constructorToCall; try { - return getConstructorAccessorMethod.invoke(constructor); - } catch (ReflectiveOperationException e) { - throw VMError.shouldNotReachHere(e); + constructorToCall = initCl.getDeclaredConstructor(); + int mods = constructorToCall.getModifiers(); + if ((mods & Modifier.PRIVATE) != 0 || + ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 && + !packageEquals(cl, initCl))) { + return null; + } + } catch (NoSuchMethodException ex) { + return null; } + return constructorToCall; } - private static Constructor getExternalizableConstructor(Class serializationTargetClass) { - try { - return (Constructor) getExternalizableConstructorMethod.invoke(null, serializationTargetClass); - } catch (ReflectiveOperationException e) { - throw VMError.shouldNotReachHere(e); + private boolean superHasAccessibleConstructor(Class prev) { + return ReflectionUtil.invokeMethod(superHasAccessibleConstructor, ReflectionFactory.getReflectionFactory(), prev); + } + + private boolean disableSerialConstructorChecks() { + if (disableSerialConstructorChecks == null) { + return false; } + return ReflectionUtil.invokeMethod(disableSerialConstructorChecks, null); + } + + private boolean packageEquals(Class cl1, Class cl2) { + return ReflectionUtil.invokeMethod(packageEquals, null, cl1, cl2); + } + + static Object getConstructorAccessor(Constructor constructor) { + return ReflectionUtil.invokeMethod(getConstructorAccessorMethod, constructor); + } + + private static Constructor getExternalizableConstructor(Class serializationTargetClass) { + return ReflectionUtil.invokeMethod(getExternalizableConstructorMethod, null, serializationTargetClass); } - Class addConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { + private Class addConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { serializationSupport.registerSerializationTargetClass(cnd, serializationTargetClass); // Don't generate SerializationConstructorAccessor class for Externalizable case @@ -731,6 +815,7 @@ Class addConstructorAccessor(ConfigurationCondition cnd, Class serializati Constructor targetConstructor; if (Modifier.isAbstract(serializationTargetClass.getModifiers())) { + VMError.guarantee(stubConstructor != null, "stubConstructor is null, calling this too early"); targetConstructor = stubConstructor; } else { if (customTargetConstructorClass == serializationTargetClass) {