From f14510d05690da9d7e17aa2c781d7ee684b9e90e Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Mon, 18 Nov 2024 15:44:18 +0100 Subject: [PATCH] Register superclass serialization constructors by default --- .../native-image/ReachabilityMetadata.md | 21 +---- .../org.graalvm.nativeimage/snapshot.sigtest | 1 + .../hosted/RuntimeSerialization.java | 11 ++- .../impl/RuntimeSerializationSupport.java | 6 +- substratevm/CHANGELOG.md | 1 + .../svm/agent/BreakpointInterceptor.java | 12 +-- .../test/config/OmitPreviousConfigTests.java | 4 +- .../config/SerializationConfiguration.java | 26 ++---- .../SerializationConfigurationType.java | 36 ++------ .../trace/SerializationProcessor.java | 4 +- ...egacySerializationConfigurationParser.java | 21 +++-- .../SerializationConfigurationParser.java | 5 +- .../SerializationMetadataParser.java | 12 ++- .../SerializationConfigurationFilesHelp.txt | 18 +--- .../serialize/SerializationFeature.java | 92 +++++++------------ .../SubstrateGraphBuilderPlugins.java | 2 +- 16 files changed, 101 insertions(+), 171 deletions(-) diff --git a/docs/reference-manual/native-image/ReachabilityMetadata.md b/docs/reference-manual/native-image/ReachabilityMetadata.md index 3263fef98384..f1d349eadfdd 100644 --- a/docs/reference-manual/native-image/ReachabilityMetadata.md +++ b/docs/reference-manual/native-image/ReachabilityMetadata.md @@ -670,21 +670,9 @@ In rare cases an application might explicitly make calls to: ```java ReflectionFactory.newConstructorForSerialization(Class cl, Constructor constructorToCall); ``` -In which the passed `constructorToCall` differs from what would automatically be used if regular serialization of `cl`. - -To also support such serialization use cases, it is possible to register serialization for a class with a -custom `constructorToCall`. -For example, to allow serialization of `org.apache.spark.SparkContext$$anonfun$hadoopFile$1`, use the declared constructor of `java.lang.Object` as a custom `targetConstructor`, use: -```json -{ - "serialization": [ - { - "type": "", - "customTargetConstructorClass": "" - } - ] -} -``` +The specified `constructorToCall` differs from the one that would be automatically used during regular serialization of `cl`. +When a class is registered for run-time serialization, all potential custom constructors are automatically registered. +As a result, this use case does not require any additional metadata. ## Sample Reachability Metadata @@ -751,8 +739,7 @@ See below is a sample reachability metadata configuration that you can use in _r ], "serialization": [ { - "type": "serialized.Type", - "customTargetConstructorClass": "optional.serialized.super.Type" + "type": "serialized.Type" } ] } diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index ef23e446d560..98ef0490aa52 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -1143,6 +1143,7 @@ meth public !varargs static void registerProxyClass(java.lang.Class[]) meth public static void registerIncludingAssociatedClasses(java.lang.Class) meth public static void registerLambdaCapturingClass(java.lang.Class) meth public static void registerWithTargetConstructorClass(java.lang.Class,java.lang.Class) + anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="24.2") supr java.lang.Object CLSS public final org.graalvm.nativeimage.hosted.RuntimeSystemProperties diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java index 09d2c4a42e99..8de8de2ea716 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java @@ -81,7 +81,9 @@ public static void registerIncludingAssociatedClasses(Class clazz) { * @since 21.3 */ public static void register(Class... classes) { - RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), classes); + for (Class clazz : classes) { + RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), clazz); + } } /** @@ -92,11 +94,16 @@ public static void register(Class... classes) { * {@code ReflectionFactory.newConstructorForSerialization(Class cl, Constructor constructorToCall)} * where the passed `constructorToCall` differs from what would automatically be used if regular * deserialization of `cl` would happen. This method exists to also support such usecases. + * + * @deprecated Use {@link #register(Class[])} instead. All possible custom constructors will be + * registered automatically. * * @since 21.3 */ + @Deprecated(since = "24.2") + @SuppressWarnings("unused") public static void registerWithTargetConstructorClass(Class clazz, Class customTargetConstructorClazz) { - RuntimeSerializationSupport.singleton().registerWithTargetConstructorClass(ConfigurationCondition.alwaysTrue(), clazz, customTargetConstructorClazz); + RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), clazz); } /** diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java index 6cecab043977..3237047e86d8 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java @@ -55,11 +55,9 @@ static RuntimeSerializationSupport singleton() { void registerIncludingAssociatedClasses(C condition, Class clazz); - void register(C condition, Class... classes); + void register(C condition, Class clazz); - void registerWithTargetConstructorClass(C condition, Class clazz, Class customTargetConstructorClazz); - - void registerWithTargetConstructorClass(C condition, String className, String customTargetConstructorClassName); + void register(C condition, String clazz); void registerLambdaCapturingClass(C condition, String lambdaCapturingClassName); diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index dc702c150655..8ea781830591 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -18,6 +18,7 @@ At runtime, premain runtime options are set along with main class' arguments in * (GR-58914) `ActiveProcessorCount` must be set at isolate or VM creation time. * (GR-59326) Ensure builder ForkJoin commonPool parallelism always respects NativeImageOptions.NumberOfThreads. * (GR-60081) Native Image now targets `armv8.1-a` by default on AArch64. Use `-march=compatibility` for best compatibility or `-march=native` for best performance if the native executable is deployed on the same machine or on a machine with the same CPU features. To list all available machine types, use `-march=list`. +* (GR-60234) Remove `"customTargetConstructorClass"` field from the serialization JSON metadata. All possible constructors are now registered by default when registering a type for serialization. `RuntimeSerialization.registerWithTargetConstructorClass` is now deprecated. ## GraalVM for JDK 23 (Internal Version 24.1.0) * (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect. 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 268e8129ad61..346e16fe3532 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 @@ -1102,7 +1102,7 @@ private static boolean readClassDescriptor(JNIEnvironment jni, JNIObjectHandle t name = nullHandle(); } var className = fromJniString(jni, name); - traceSerializeBreakpoint(jni, "ObjectInputStream.readClassDescriptor", true, state.getFullStackTraceOrNull(), className, null); + traceSerializeBreakpoint(jni, "ObjectInputStream.readClassDescriptor", true, state.getFullStackTraceOrNull(), className); return true; } @@ -1158,7 +1158,7 @@ private static boolean objectStreamClassConstructor(JNIEnvironment jni, JNIObjec Object interfaceNames = getClassArrayNames(jni, interfaces); traceSerializeBreakpoint(jni, "ProxyClassSerialization", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), interfaceNames); } else { - traceSerializeBreakpoint(jni, "ObjectStreamClass.", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className, null); + traceSerializeBreakpoint(jni, "ObjectStreamClass.", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className); } } } @@ -1177,13 +1177,7 @@ private static boolean customTargetConstructorSerialization(JNIEnvironment jni, JNIObjectHandle serializeTargetClass = getObjectArgument(thread, 1); if (Support.isSerializable(jni, serializeTargetClass)) { String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass); - - JNIObjectHandle customConstructorObj = getObjectArgument(thread, 2); - JNIObjectHandle customConstructorClass = jniFunctions().getGetObjectClass().invoke(jni, customConstructorObj); - JNIMethodId getDeclaringClassNameMethodID = agent.handles().getJavaLangReflectConstructorDeclaringClassName(jni, customConstructorClass); - JNIObjectHandle declaredClassNameObj = Support.callObjectMethod(jni, customConstructorObj, getDeclaringClassNameMethodID); - String customConstructorClassName = fromJniString(jni, declaredClassNameObj); - traceSerializeBreakpoint(jni, "ObjectStreamClass.", true, state.getFullStackTraceOrNull(), serializeTargetClassName, customConstructorClassName); + traceSerializeBreakpoint(jni, "ObjectStreamClass.", true, state.getFullStackTraceOrNull(), serializeTargetClassName); } return true; } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index d29330488c6e..b457990527b2 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -200,8 +200,8 @@ private static void doTestResourceConfig(ResourceConfiguration resourceConfig) { private static void doTestSerializationConfig(SerializationConfiguration serializationConfig) { UnresolvedConfigurationCondition condition = UnresolvedConfigurationCondition.alwaysTrue(); - Assert.assertFalse(serializationConfig.contains(condition, "seenType", null)); - Assert.assertTrue(serializationConfig.contains(condition, "unseenType", null)); + Assert.assertFalse(serializationConfig.contains(condition, "seenType")); + Assert.assertTrue(serializationConfig.contains(condition, "unseenType")); } private static ConfigurationType getConfigTypeOrFail(TypeConfiguration typeConfig, String typeName) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java index 438fe8f50ab5..24df956eae32 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java @@ -99,12 +99,12 @@ protected void removeIf(Predicate predicate) { @Override public void mergeConditional(UnresolvedConfigurationCondition condition, SerializationConfiguration other) { for (SerializationConfigurationType type : other.serializations) { - serializations.add(new SerializationConfigurationType(condition, type.getQualifiedJavaName(), type.getQualifiedCustomTargetConstructorJavaName())); + serializations.add(new SerializationConfigurationType(condition, type.getQualifiedJavaName())); } } - public boolean contains(UnresolvedConfigurationCondition condition, String serializationTargetClass, String customTargetConstructorClass) { - return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass)) || + public boolean contains(UnresolvedConfigurationCondition condition, String serializationTargetClass) { + return serializations.contains(createConfigurationType(condition, serializationTargetClass)) || lambdaSerializationCapturingTypes.contains(createLambdaCapturingClassConfigurationType(condition, serializationTargetClass)); } @@ -150,20 +150,13 @@ public void registerIncludingAssociatedClasses(UnresolvedConfigurationCondition } @Override - public void register(UnresolvedConfigurationCondition condition, Class... classes) { - for (Class clazz : classes) { - registerWithTargetConstructorClass(condition, clazz, null); - } - } - - @Override - public void registerWithTargetConstructorClass(UnresolvedConfigurationCondition condition, Class clazz, Class customTargetConstructorClazz) { - registerWithTargetConstructorClass(condition, clazz.getName(), customTargetConstructorClazz == null ? null : customTargetConstructorClazz.getName()); + public void register(UnresolvedConfigurationCondition condition, Class clazz) { + register(condition, clazz.getName()); } @Override - public void registerWithTargetConstructorClass(UnresolvedConfigurationCondition condition, String className, String customTargetConstructorClassName) { - serializations.add(createConfigurationType(condition, className, customTargetConstructorClassName)); + public void register(UnresolvedConfigurationCondition condition, String className) { + serializations.add(createConfigurationType(condition, className)); } @Override @@ -181,10 +174,9 @@ public boolean isEmpty() { return serializations.isEmpty() && lambdaSerializationCapturingTypes.isEmpty() && interfaceListsSerializableProxies.isEmpty(); } - private static SerializationConfigurationType createConfigurationType(UnresolvedConfigurationCondition condition, String className, String customTargetConstructorClassName) { + private static SerializationConfigurationType createConfigurationType(UnresolvedConfigurationCondition condition, String className) { String convertedClassName = SignatureUtil.toInternalClassName(className); - String convertedCustomTargetConstructorClassName = customTargetConstructorClassName == null ? null : SignatureUtil.toInternalClassName(customTargetConstructorClassName); - return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName); + return new SerializationConfigurationType(condition, convertedClassName); } private static SerializationConfigurationLambdaCapturingType createLambdaCapturingClassConfigurationType(UnresolvedConfigurationCondition condition, String className) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java index bc6bc8fcadb0..99b429f4905b 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java @@ -24,45 +24,34 @@ */ package com.oracle.svm.configure.config; +import static com.oracle.svm.core.configure.ConfigurationParser.NAME_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_KEY; + import java.io.IOException; -import java.util.Comparator; import java.util.Objects; import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; -import com.oracle.svm.core.configure.SerializationConfigurationParser; - import jdk.graal.compiler.util.json.JsonPrintable; import jdk.graal.compiler.util.json.JsonWriter; -import static com.oracle.svm.core.configure.ConfigurationParser.NAME_KEY; -import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_KEY; - public class SerializationConfigurationType implements JsonPrintable, Comparable { private final UnresolvedConfigurationCondition condition; private final String qualifiedJavaName; - private final String qualifiedCustomTargetConstructorJavaName; - public SerializationConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName) { + public SerializationConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName) { assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not the internal representation"; assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; - assert qualifiedCustomTargetConstructorJavaName == null || qualifiedCustomTargetConstructorJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; - assert qualifiedCustomTargetConstructorJavaName == null || !qualifiedCustomTargetConstructorJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; Objects.requireNonNull(condition); this.condition = condition; Objects.requireNonNull(qualifiedJavaName); this.qualifiedJavaName = qualifiedJavaName; - this.qualifiedCustomTargetConstructorJavaName = qualifiedCustomTargetConstructorJavaName; } public String getQualifiedJavaName() { return qualifiedJavaName; } - public String getQualifiedCustomTargetConstructorJavaName() { - return qualifiedCustomTargetConstructorJavaName; - } - public UnresolvedConfigurationCondition getCondition() { return condition; } @@ -80,11 +69,6 @@ private void printJson(JsonWriter writer, boolean combinedFile) throws IOExcepti writer.appendObjectStart(); ConfigurationConditionPrintable.printConditionAttribute(condition, writer, combinedFile); writer.quote(combinedFile ? TYPE_KEY : NAME_KEY).appendFieldSeparator().quote(qualifiedJavaName); - if (qualifiedCustomTargetConstructorJavaName != null) { - writer.appendSeparator(); - writer.quote(SerializationConfigurationParser.CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY).appendFieldSeparator() - .quote(qualifiedCustomTargetConstructorJavaName); - } writer.appendObjectEnd(); } @@ -98,13 +82,12 @@ public boolean equals(Object o) { } SerializationConfigurationType that = (SerializationConfigurationType) o; return condition.equals(that.condition) && - qualifiedJavaName.equals(that.qualifiedJavaName) && - Objects.equals(qualifiedCustomTargetConstructorJavaName, that.qualifiedCustomTargetConstructorJavaName); + qualifiedJavaName.equals(that.qualifiedJavaName); } @Override public int hashCode() { - return Objects.hash(condition, qualifiedJavaName, qualifiedCustomTargetConstructorJavaName); + return Objects.hash(condition, qualifiedJavaName); } @Override @@ -113,11 +96,6 @@ public int compareTo(SerializationConfigurationType other) { if (compareName != 0) { return compareName; } - int compareCondition = condition.compareTo(other.condition); - if (compareCondition != 0) { - return compareCondition; - } - Comparator nullsFirstCompare = Comparator.nullsFirst(Comparator.naturalOrder()); - return nullsFirstCompare.compare(qualifiedCustomTargetConstructorJavaName, other.qualifiedCustomTargetConstructorJavaName); + return condition.compareTo(other.condition); } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java index 2ef063061a2b..d20a5cf0d131 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java @@ -57,7 +57,7 @@ void processEntry(EconomicMap entry, ConfigurationSet configurationSe SerializationConfiguration serializationConfiguration = configurationSet.getSerializationConfiguration(); if ("ObjectStreamClass.".equals(function) || "ObjectInputStream.readClassDescriptor".equals(function)) { - expectSize(args, 2); + expectSize(args, 1); if (advisor.shouldIgnore(LazyValueUtils.lazyValue((String) args.get(0)), LazyValueUtils.lazyValue(null), false)) { return; @@ -68,7 +68,7 @@ void processEntry(EconomicMap entry, ConfigurationSet configurationSe if (className.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)) { serializationConfiguration.registerLambdaCapturingClass(condition, className); } else { - serializationConfiguration.registerWithTargetConstructorClass(condition, className, (String) args.get(1)); + serializationConfiguration.register(condition, className); } } else if ("SerializedLambda.readResolve".equals(function)) { expectSize(args, 1); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java index 46c92f8b389c..bc4aeba5c112 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java @@ -33,6 +33,8 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import com.oracle.svm.util.LogUtils; + import jdk.graal.compiler.util.json.JsonParserException; final class LegacySerializationConfigurationParser extends SerializationConfigurationParser { @@ -79,6 +81,8 @@ private void parseNewConfiguration(EconomicMap listOfSerializati } } + private boolean customConstructorWarningTriggered = false; + @Override protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { if (lambdaCapturingType) { @@ -87,7 +91,7 @@ protected void parseSerializationDescriptorObject(EconomicMap da checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); } - ConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); + NamedConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, false); var condition = conditionResolver.resolveCondition(unresolvedCondition); if (!condition.isPresent()) { @@ -95,18 +99,15 @@ protected void parseSerializationDescriptorObject(EconomicMap da } if (lambdaCapturingType) { - String className = ((NamedConfigurationTypeDescriptor) targetSerializationClass).name(); + String className = targetSerializationClass.name(); serializationSupport.registerLambdaCapturingClass(condition.get(), className); } else { - Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); - String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; - if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { - serializationSupport.registerWithTargetConstructorClass(condition.get(), namedClass.name(), customTargetConstructorClass); - } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { - serializationSupport.registerProxyClass(condition.get(), proxyClass.interfaceNames()); - } else { - throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); + if (!customConstructorWarningTriggered && data.containsKey(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)) { + customConstructorWarningTriggered = true; + LogUtils.warning("\"" + CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY + + "\" is deprecated in serialization-config.json. All serializable classes can be instantiated through any superclass constructor without the use of the flag."); } + serializationSupport.register(condition.get(), targetSerializationClass.name()); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java index e34d651a3030..e79b76ed074f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -62,10 +62,9 @@ protected void parseSerializationTypes(List listOfSerializationTypes, bo protected abstract void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType); - protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, C condition, Object optionalCustomCtorValue) { - String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, C condition) { if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { - serializationSupport.registerWithTargetConstructorClass(condition, namedClass.name(), customTargetConstructorClass); + serializationSupport.register(condition, namedClass.name()); } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { serializationSupport.registerProxyClass(condition, proxyClass.interfaceNames()); } else { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java index 9b74ed811243..cc20eb92e2da 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java @@ -32,6 +32,8 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import com.oracle.svm.util.LogUtils; + final class SerializationMetadataParser extends SerializationConfigurationParser { SerializationMetadataParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { @@ -46,6 +48,8 @@ public void parseAndRegister(Object json, URI origin) { } } + private boolean customConstructorWarningTriggered = false; + @Override protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { checkAttributes(data, "serialization descriptor object", List.of(TYPE_KEY), List.of(CONDITIONAL_KEY, CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)); @@ -61,7 +65,11 @@ protected void parseSerializationDescriptorObject(EconomicMap da return; } - Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); - registerType(targetSerializationClass.get(), condition.get(), optionalCustomCtorValue); + if (!customConstructorWarningTriggered && data.containsKey(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)) { + customConstructorWarningTriggered = true; + LogUtils.warning("\"" + CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY + + "\" is deprecated in reachability-metadata.json. All serializable classes can be instantiated through any superclass constructor without the use of the flag."); + } + registerType(targetSerializationClass.get(), condition.get()); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt index fa8efe7afe28..3bcedff33109 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt @@ -22,18 +22,6 @@ In rare cases an application might explicitly make calls to ReflectionFactory.newConstructorForSerialization(Class cl, Constructor constructorToCall) -where the passed `constructorToCall` differs from what would automatically be used if regular serialization of `cl` -would happen. To also support such serialization usecases it is possible to register serialization for a class with a -custom constructorToCall. For example, to allow serialization of `org.apache.spark.SparkContext$$anonfun$hadoopFile$1` -using the DeclaredConstructor of java.lang.Object as custom targetConstructor the following can be used in -serialization-config.json: - - [ - { - "condition" : { - "typeReachable" : "org.apache.spark.SparkContext" - }, - "name" : "org.apache.spark.SparkContext$$anonfun$hadoopFile$1", - "customTargetConstructorClass" : "java.lang.Object" - } - ] +The specified `constructorToCall` differs from the one that would be automatically used during regular serialization of `cl`. +When a class is registered for run-time serialization, all potential custom constructors are automatically registered. +As a result, this use case does not require any additional metadata. \ No newline at end of file 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 4335f139eb23..c878db324403 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 @@ -225,22 +225,15 @@ public void registerIncludingAssociatedClasses(ConfigurationCondition condition, } @Override - public void register(ConfigurationCondition condition, Class... classes) { - for (Class clazz : classes) { - registerWithTargetConstructorClass(condition, clazz, null); + public void register(ConfigurationCondition condition, Class clazz) { + if (clazz != null) { + deniedClasses.put(clazz, true); } } @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName) { - registerWithTargetConstructorClass(condition, typeResolver.resolveType(className), null); - } - - @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, Class clazz, Class customTargetConstructorClazz) { - if (clazz != null) { - deniedClasses.put(clazz, true); - } + public void register(ConfigurationCondition condition, String className) { + this.register(condition, typeResolver.resolveType(className)); } @Override @@ -361,13 +354,6 @@ private void registerIncludingAssociatedClasses(ConfigurationCondition condition } } - @Override - public void register(ConfigurationCondition condition, Class... classes) { - for (Class clazz : classes) { - registerWithTargetConstructorClass(condition, clazz, null); - } - } - @Override public void registerLambdaCapturingClass(ConfigurationCondition condition, String lambdaCapturingClassName) { abortIfSealed(); @@ -394,12 +380,12 @@ public void registerLambdaCapturingClass(ConfigurationCondition condition, Strin public void registerProxyClass(ConfigurationCondition condition, List implementedInterfaces) { registerConditionalConfiguration(condition, (cnd) -> { Class proxyClass = proxyRegistry.createProxyClassForSerialization(implementedInterfaces); - registerWithTargetConstructorClass(cnd, proxyClass, Object.class); + register(cnd, proxyClass); }); } @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, String targetClassName, String customTargetConstructorClassName) { + public void register(ConfigurationCondition condition, String targetClassName) { abortIfSealed(); Class serializationTargetClass = typeResolver.resolveType(targetClassName); /* With invalid streams we have to register the class for lookup */ @@ -407,20 +393,11 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, if (serializationTargetClass == null) { return; } - - if (customTargetConstructorClassName != null) { - Class customTargetConstructorClass = typeResolver.resolveType(customTargetConstructorClassName); - if (customTargetConstructorClass == null) { - return; - } - registerWithTargetConstructorClass(condition, serializationTargetClass, customTargetConstructorClass); - } else { - registerWithTargetConstructorClass(condition, serializationTargetClass, null); - } + register(condition, serializationTargetClass); } @Override - public void registerWithTargetConstructorClass(ConfigurationCondition condition, Class serializationTargetClass, Class customTargetConstructorClass) { + public void register(ConfigurationCondition condition, Class serializationTargetClass) { abortIfSealed(); registerConditionalConfiguration(condition, (cnd) -> { /* @@ -439,18 +416,7 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, RuntimeReflection.register(java.io.ObjectOutputStream.class); if (denyRegistry.isAllowed(serializationTargetClass)) { - if (customTargetConstructorClass != null) { - if (!customTargetConstructorClass.isAssignableFrom(serializationTargetClass)) { - LogUtils.warning("The given customTargetConstructorClass %s is not a superclass of the serialization target %s.", customTargetConstructorClass.getName(), - serializationTargetClass); - return; - } - if (ReflectionUtil.lookupConstructor(true, customTargetConstructorClass) == null) { - LogUtils.warning("The given customTargetConstructorClass %s does not declare a parameterless constructor.", customTargetConstructorClass.getName()); - return; - } - } - addOrQueueConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass); + addOrQueueConstructorAccessors(cnd, serializationTargetClass); Class superclass = serializationTargetClass.getSuperclass(); if (superclass != null) { @@ -466,20 +432,28 @@ public void registerWithTargetConstructorClass(ConfigurationCondition condition, }); } - private void addOrQueueConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { + private void addOrQueueConstructorAccessors(ConfigurationCondition cnd, Class serializationTargetClass) { if (pendingConstructorRegistrations != null) { // cannot yet create constructor accessor -> add to pending - pendingConstructorRegistrations.add(() -> registerConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass)); + pendingConstructorRegistrations.add(() -> registerConstructorAccessors(cnd, serializationTargetClass)); } else { // can already run the registration - registerConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass); + registerConstructorAccessors(cnd, serializationTargetClass); } } - private void registerConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { - Optional.ofNullable(addConstructorAccessor(cnd, serializationTargetClass, customTargetConstructorClass)) + private void registerConstructorAccessors(ConfigurationCondition cnd, Class serializationTargetClass) { + serializationSupport.registerSerializationTargetClass(cnd, serializationTargetClass); + registerConstructorAccessor(cnd, serializationTargetClass, null); + for (Class superclass = serializationTargetClass; superclass != null; superclass = superclass.getSuperclass()) { + registerConstructorAccessor(cnd, serializationTargetClass, superclass); + } + } + + private void registerConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class targetConstructorClass) { + Optional.ofNullable(addConstructorAccessor(serializationTargetClass, targetConstructorClass)) .map(ReflectionUtil::lookupConstructor) - .ifPresent(methods -> ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), false, methods)); + .ifPresent(methods -> ImageSingletons.lookup(RuntimeReflectionSupport.class).register(cnd, false, methods)); } void beforeAnalysis(Feature.BeforeAnalysisAccess beforeAnalysisAccess) { @@ -683,9 +657,7 @@ private static Constructor getExternalizableConstructor(Class serializatio return ReflectionUtil.invokeMethod(getExternalizableConstructorMethod, null, serializationTargetClass); } - private Class addConstructorAccessor(ConfigurationCondition cnd, Class serializationTargetClass, Class customTargetConstructorClass) { - serializationSupport.registerSerializationTargetClass(cnd, serializationTargetClass); - + private Class addConstructorAccessor(Class serializationTargetClass, Class customTargetConstructorClass) { // Don't generate SerializationConstructorAccessor class for Externalizable case if (Externalizable.class.isAssignableFrom(serializationTargetClass)) { try { @@ -706,13 +678,17 @@ private Class addConstructorAccessor(ConfigurationCondition cnd, Class ser VMError.guarantee(stubConstructor != null, "stubConstructor is null, calling this too early"); targetConstructor = stubConstructor; } else { - if (customTargetConstructorClass == serializationTargetClass) { - /* No custom constructor needed. Simply use existing no-arg constructor. */ - return customTargetConstructorClass; - } Constructor customConstructorToCall = null; if (customTargetConstructorClass != null) { - customConstructorToCall = ReflectionUtil.lookupConstructor(customTargetConstructorClass); + customConstructorToCall = ReflectionUtil.lookupConstructor(true, customTargetConstructorClass); + if (customConstructorToCall == null) { + /* No suitable constructor, no need to register */ + return null; + } + if (customTargetConstructorClass == serializationTargetClass) { + /* No custom constructor needed. Simply use existing no-arg constructor. */ + return customTargetConstructorClass; + } } targetConstructor = newConstructorForSerialization(serializationTargetClass, customConstructorToCall); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index e229c0a8be34..1bb561e5a132 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -260,7 +260,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec var clazz = asConstantObject(b, Class.class, clazzNode); var constructor = asConstantObject(b, Constructor.class, constructorNode); if (clazz != null && constructor != null) { - b.add(ReachabilityRegistrationNode.create(() -> RuntimeSerialization.registerWithTargetConstructorClass(clazz, constructor.getDeclaringClass()), reason)); + b.add(ReachabilityRegistrationNode.create(() -> RuntimeSerialization.register(clazz), reason)); return true; } return false;