Skip to content

Commit 4877ac5

Browse files
committed
Register superclass serialization constructors by default
1 parent 9fbd75d commit 4877ac5

File tree

15 files changed

+93
-155
lines changed

15 files changed

+93
-155
lines changed

docs/reference-manual/native-image/ReachabilityMetadata.md

+2-16
Original file line numberDiff line numberDiff line change
@@ -671,20 +671,7 @@ In rare cases an application might explicitly make calls to:
671671
ReflectionFactory.newConstructorForSerialization(Class<?> cl, Constructor<?> constructorToCall);
672672
```
673673
In which the passed `constructorToCall` differs from what would automatically be used if regular serialization of `cl`.
674-
675-
To also support such serialization use cases, it is possible to register serialization for a class with a
676-
custom `constructorToCall`.
677-
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:
678-
```json
679-
{
680-
"serialization": [
681-
{
682-
"type": "<fully-qualified-class-name>",
683-
"customTargetConstructorClass": "<custom-target-constructor-class>"
684-
}
685-
]
686-
}
687-
```
674+
All possible custom constructors are automatically registered when registering a class for run-time serialization. This use case therefore doesn't require additional metadata.
688675

689676
## Sample Reachability Metadata
690677

@@ -751,8 +738,7 @@ See below is a sample reachability metadata configuration that you can use in _r
751738
],
752739
"serialization": [
753740
{
754-
"type": "serialized.Type",
755-
"customTargetConstructorClass": "optional.serialized.super.Type"
741+
"type": "serialized.Type"
756742
}
757743
]
758744
}

sdk/src/org.graalvm.nativeimage/snapshot.sigtest

+1
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,7 @@ meth public !varargs static void registerProxyClass(java.lang.Class<?>[])
11431143
meth public static void registerIncludingAssociatedClasses(java.lang.Class<?>)
11441144
meth public static void registerLambdaCapturingClass(java.lang.Class<?>)
11451145
meth public static void registerWithTargetConstructorClass(java.lang.Class<?>,java.lang.Class<?>)
1146+
anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="")
11461147
supr java.lang.Object
11471148

11481149
CLSS public final org.graalvm.nativeimage.hosted.RuntimeSystemProperties

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ public static void registerIncludingAssociatedClasses(Class<?> clazz) {
8181
* @since 21.3
8282
*/
8383
public static void register(Class<?>... classes) {
84-
RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), classes);
84+
for (Class<?> clazz : classes) {
85+
RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), clazz);
86+
}
8587
}
8688

8789
/**
@@ -95,8 +97,10 @@ public static void register(Class<?>... classes) {
9597
*
9698
* @since 21.3
9799
*/
100+
@Deprecated
101+
@SuppressWarnings("unused")
98102
public static void registerWithTargetConstructorClass(Class<?> clazz, Class<?> customTargetConstructorClazz) {
99-
RuntimeSerializationSupport.singleton().registerWithTargetConstructorClass(ConfigurationCondition.alwaysTrue(), clazz, customTargetConstructorClazz);
103+
RuntimeSerializationSupport.singleton().register(ConfigurationCondition.alwaysTrue(), clazz);
100104
}
101105

102106
/**

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,9 @@ static RuntimeSerializationSupport<ConfigurationCondition> singleton() {
5555

5656
void registerIncludingAssociatedClasses(C condition, Class<?> clazz);
5757

58-
void register(C condition, Class<?>... classes);
58+
void register(C condition, Class<?> clazz);
5959

60-
void registerWithTargetConstructorClass(C condition, Class<?> clazz, Class<?> customTargetConstructorClazz);
61-
62-
void registerWithTargetConstructorClass(C condition, String className, String customTargetConstructorClassName);
60+
void register(C condition, String clazz);
6361

6462
void registerLambdaCapturingClass(C condition, String lambdaCapturingClassName);
6563

substratevm/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ At runtime, premain runtime options are set along with main class' arguments in
1616
* (GR-58383) The length of the printed stack trace when using `-XX:MissingRegistrationReportingMode=Warn` can now be set with `-XX:MissingRegistrationWarnContextLines=` and its default length is now 8.
1717
* (GR-58914) `ActiveProcessorCount` must be set at isolate or VM creation time.
1818
* (GR-59326) Ensure builder ForkJoin commonPool parallelism always respects NativeImageOptions.NumberOfThreads.
19+
* (GR-60234) Remove `"customTargetConstructorClass"` field from the serialization JSON metadata. All possible constructors are now registered by default when registering a type for serialization.
1920

2021
## GraalVM for JDK 23 (Internal Version 24.1.0)
2122
* (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.

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java

+3-9
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,7 @@ private static boolean readClassDescriptor(JNIEnvironment jni, JNIObjectHandle t
11021102
name = nullHandle();
11031103
}
11041104
var className = fromJniString(jni, name);
1105-
traceSerializeBreakpoint(jni, "ObjectInputStream.readClassDescriptor", true, state.getFullStackTraceOrNull(), className, null);
1105+
traceSerializeBreakpoint(jni, "ObjectInputStream.readClassDescriptor", true, state.getFullStackTraceOrNull(), className);
11061106
return true;
11071107
}
11081108

@@ -1158,7 +1158,7 @@ private static boolean objectStreamClassConstructor(JNIEnvironment jni, JNIObjec
11581158
Object interfaceNames = getClassArrayNames(jni, interfaces);
11591159
traceSerializeBreakpoint(jni, "ProxyClassSerialization", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), interfaceNames);
11601160
} else {
1161-
traceSerializeBreakpoint(jni, "ObjectStreamClass.<init>", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className, null);
1161+
traceSerializeBreakpoint(jni, "ObjectStreamClass.<init>", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className);
11621162
}
11631163
}
11641164
}
@@ -1177,13 +1177,7 @@ private static boolean customTargetConstructorSerialization(JNIEnvironment jni,
11771177
JNIObjectHandle serializeTargetClass = getObjectArgument(thread, 1);
11781178
if (Support.isSerializable(jni, serializeTargetClass)) {
11791179
String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass);
1180-
1181-
JNIObjectHandle customConstructorObj = getObjectArgument(thread, 2);
1182-
JNIObjectHandle customConstructorClass = jniFunctions().getGetObjectClass().invoke(jni, customConstructorObj);
1183-
JNIMethodId getDeclaringClassNameMethodID = agent.handles().getJavaLangReflectConstructorDeclaringClassName(jni, customConstructorClass);
1184-
JNIObjectHandle declaredClassNameObj = Support.callObjectMethod(jni, customConstructorObj, getDeclaringClassNameMethodID);
1185-
String customConstructorClassName = fromJniString(jni, declaredClassNameObj);
1186-
traceSerializeBreakpoint(jni, "ObjectStreamClass.<init>", true, state.getFullStackTraceOrNull(), serializeTargetClassName, customConstructorClassName);
1180+
traceSerializeBreakpoint(jni, "ObjectStreamClass.<init>", true, state.getFullStackTraceOrNull(), serializeTargetClassName);
11871181
}
11881182
return true;
11891183
}

substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ private static void doTestResourceConfig(ResourceConfiguration resourceConfig) {
200200

201201
private static void doTestSerializationConfig(SerializationConfiguration serializationConfig) {
202202
UnresolvedConfigurationCondition condition = UnresolvedConfigurationCondition.alwaysTrue();
203-
Assert.assertFalse(serializationConfig.contains(condition, "seenType", null));
204-
Assert.assertTrue(serializationConfig.contains(condition, "unseenType", null));
203+
Assert.assertFalse(serializationConfig.contains(condition, "seenType"));
204+
Assert.assertTrue(serializationConfig.contains(condition, "unseenType"));
205205
}
206206

207207
private static ConfigurationType getConfigTypeOrFail(TypeConfiguration typeConfig, String typeName) {

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java

+9-17
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ protected void removeIf(Predicate predicate) {
9999
@Override
100100
public void mergeConditional(UnresolvedConfigurationCondition condition, SerializationConfiguration other) {
101101
for (SerializationConfigurationType type : other.serializations) {
102-
serializations.add(new SerializationConfigurationType(condition, type.getQualifiedJavaName(), type.getQualifiedCustomTargetConstructorJavaName()));
102+
serializations.add(new SerializationConfigurationType(condition, type.getQualifiedJavaName()));
103103
}
104104
}
105105

106-
public boolean contains(UnresolvedConfigurationCondition condition, String serializationTargetClass, String customTargetConstructorClass) {
107-
return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass)) ||
106+
public boolean contains(UnresolvedConfigurationCondition condition, String serializationTargetClass) {
107+
return serializations.contains(createConfigurationType(condition, serializationTargetClass)) ||
108108
lambdaSerializationCapturingTypes.contains(createLambdaCapturingClassConfigurationType(condition, serializationTargetClass));
109109
}
110110

@@ -150,20 +150,13 @@ public void registerIncludingAssociatedClasses(UnresolvedConfigurationCondition
150150
}
151151

152152
@Override
153-
public void register(UnresolvedConfigurationCondition condition, Class<?>... classes) {
154-
for (Class<?> clazz : classes) {
155-
registerWithTargetConstructorClass(condition, clazz, null);
156-
}
157-
}
158-
159-
@Override
160-
public void registerWithTargetConstructorClass(UnresolvedConfigurationCondition condition, Class<?> clazz, Class<?> customTargetConstructorClazz) {
161-
registerWithTargetConstructorClass(condition, clazz.getName(), customTargetConstructorClazz == null ? null : customTargetConstructorClazz.getName());
153+
public void register(UnresolvedConfigurationCondition condition, Class<?> clazz) {
154+
register(condition, clazz.getName());
162155
}
163156

164157
@Override
165-
public void registerWithTargetConstructorClass(UnresolvedConfigurationCondition condition, String className, String customTargetConstructorClassName) {
166-
serializations.add(createConfigurationType(condition, className, customTargetConstructorClassName));
158+
public void register(UnresolvedConfigurationCondition condition, String className) {
159+
serializations.add(createConfigurationType(condition, className));
167160
}
168161

169162
@Override
@@ -181,10 +174,9 @@ public boolean isEmpty() {
181174
return serializations.isEmpty() && lambdaSerializationCapturingTypes.isEmpty() && interfaceListsSerializableProxies.isEmpty();
182175
}
183176

184-
private static SerializationConfigurationType createConfigurationType(UnresolvedConfigurationCondition condition, String className, String customTargetConstructorClassName) {
177+
private static SerializationConfigurationType createConfigurationType(UnresolvedConfigurationCondition condition, String className) {
185178
String convertedClassName = SignatureUtil.toInternalClassName(className);
186-
String convertedCustomTargetConstructorClassName = customTargetConstructorClassName == null ? null : SignatureUtil.toInternalClassName(customTargetConstructorClassName);
187-
return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName);
179+
return new SerializationConfigurationType(condition, convertedClassName);
188180
}
189181

190182
private static SerializationConfigurationLambdaCapturingType createLambdaCapturingClassConfigurationType(UnresolvedConfigurationCondition condition, String className) {

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java

+7-29
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,34 @@
2424
*/
2525
package com.oracle.svm.configure.config;
2626

27+
import static com.oracle.svm.core.configure.ConfigurationParser.NAME_KEY;
28+
import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_KEY;
29+
2730
import java.io.IOException;
28-
import java.util.Comparator;
2931
import java.util.Objects;
3032

3133
import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition;
3234

33-
import com.oracle.svm.core.configure.SerializationConfigurationParser;
34-
3535
import jdk.graal.compiler.util.json.JsonPrintable;
3636
import jdk.graal.compiler.util.json.JsonWriter;
3737

38-
import static com.oracle.svm.core.configure.ConfigurationParser.NAME_KEY;
39-
import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_KEY;
40-
4138
public class SerializationConfigurationType implements JsonPrintable, Comparable<SerializationConfigurationType> {
4239
private final UnresolvedConfigurationCondition condition;
4340
private final String qualifiedJavaName;
44-
private final String qualifiedCustomTargetConstructorJavaName;
4541

46-
public SerializationConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName, String qualifiedCustomTargetConstructorJavaName) {
42+
public SerializationConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName) {
4743
assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not the internal representation";
4844
assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]";
49-
assert qualifiedCustomTargetConstructorJavaName == null || qualifiedCustomTargetConstructorJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation";
50-
assert qualifiedCustomTargetConstructorJavaName == null || !qualifiedCustomTargetConstructorJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]";
5145
Objects.requireNonNull(condition);
5246
this.condition = condition;
5347
Objects.requireNonNull(qualifiedJavaName);
5448
this.qualifiedJavaName = qualifiedJavaName;
55-
this.qualifiedCustomTargetConstructorJavaName = qualifiedCustomTargetConstructorJavaName;
5649
}
5750

5851
public String getQualifiedJavaName() {
5952
return qualifiedJavaName;
6053
}
6154

62-
public String getQualifiedCustomTargetConstructorJavaName() {
63-
return qualifiedCustomTargetConstructorJavaName;
64-
}
65-
6655
public UnresolvedConfigurationCondition getCondition() {
6756
return condition;
6857
}
@@ -80,11 +69,6 @@ private void printJson(JsonWriter writer, boolean combinedFile) throws IOExcepti
8069
writer.appendObjectStart();
8170
ConfigurationConditionPrintable.printConditionAttribute(condition, writer, combinedFile);
8271
writer.quote(combinedFile ? TYPE_KEY : NAME_KEY).appendFieldSeparator().quote(qualifiedJavaName);
83-
if (qualifiedCustomTargetConstructorJavaName != null) {
84-
writer.appendSeparator();
85-
writer.quote(SerializationConfigurationParser.CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY).appendFieldSeparator()
86-
.quote(qualifiedCustomTargetConstructorJavaName);
87-
}
8872
writer.appendObjectEnd();
8973
}
9074

@@ -98,13 +82,12 @@ public boolean equals(Object o) {
9882
}
9983
SerializationConfigurationType that = (SerializationConfigurationType) o;
10084
return condition.equals(that.condition) &&
101-
qualifiedJavaName.equals(that.qualifiedJavaName) &&
102-
Objects.equals(qualifiedCustomTargetConstructorJavaName, that.qualifiedCustomTargetConstructorJavaName);
85+
qualifiedJavaName.equals(that.qualifiedJavaName);
10386
}
10487

10588
@Override
10689
public int hashCode() {
107-
return Objects.hash(condition, qualifiedJavaName, qualifiedCustomTargetConstructorJavaName);
90+
return Objects.hash(condition, qualifiedJavaName);
10891
}
10992

11093
@Override
@@ -113,11 +96,6 @@ public int compareTo(SerializationConfigurationType other) {
11396
if (compareName != 0) {
11497
return compareName;
11598
}
116-
int compareCondition = condition.compareTo(other.condition);
117-
if (compareCondition != 0) {
118-
return compareCondition;
119-
}
120-
Comparator<String> nullsFirstCompare = Comparator.nullsFirst(Comparator.naturalOrder());
121-
return nullsFirstCompare.compare(qualifiedCustomTargetConstructorJavaName, other.qualifiedCustomTargetConstructorJavaName);
99+
return condition.compareTo(other.condition);
122100
}
123101
}

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ void processEntry(EconomicMap<String, ?> entry, ConfigurationSet configurationSe
5757
SerializationConfiguration serializationConfiguration = configurationSet.getSerializationConfiguration();
5858

5959
if ("ObjectStreamClass.<init>".equals(function) || "ObjectInputStream.readClassDescriptor".equals(function)) {
60-
expectSize(args, 2);
60+
expectSize(args, 1);
6161

6262
if (advisor.shouldIgnore(LazyValueUtils.lazyValue((String) args.get(0)), LazyValueUtils.lazyValue(null), false)) {
6363
return;
@@ -68,7 +68,7 @@ void processEntry(EconomicMap<String, ?> entry, ConfigurationSet configurationSe
6868
if (className.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)) {
6969
serializationConfiguration.registerLambdaCapturingClass(condition, className);
7070
} else {
71-
serializationConfiguration.registerWithTargetConstructorClass(condition, className, (String) args.get(1));
71+
serializationConfiguration.register(condition, className);
7272
}
7373
} else if ("SerializedLambda.readResolve".equals(function)) {
7474
expectSize(args, 1);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java

+11-10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;
3434
import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition;
3535

36+
import com.oracle.svm.util.LogUtils;
37+
3638
import jdk.graal.compiler.util.json.JsonParserException;
3739

3840
final class LegacySerializationConfigurationParser<C> extends SerializationConfigurationParser<C> {
@@ -79,6 +81,8 @@ private void parseNewConfiguration(EconomicMap<String, Object> listOfSerializati
7981
}
8082
}
8183

84+
private boolean customConstructorWarningTriggered = false;
85+
8286
@Override
8387
protected void parseSerializationDescriptorObject(EconomicMap<String, Object> data, boolean lambdaCapturingType) {
8488
if (lambdaCapturingType) {
@@ -87,26 +91,23 @@ protected void parseSerializationDescriptorObject(EconomicMap<String, Object> da
8791
checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY));
8892
}
8993

90-
ConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY)));
94+
NamedConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY)));
9195
UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, false);
9296
var condition = conditionResolver.resolveCondition(unresolvedCondition);
9397
if (!condition.isPresent()) {
9498
return;
9599
}
96100

97101
if (lambdaCapturingType) {
98-
String className = ((NamedConfigurationTypeDescriptor) targetSerializationClass).name();
102+
String className = targetSerializationClass.name();
99103
serializationSupport.registerLambdaCapturingClass(condition.get(), className);
100104
} else {
101-
Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY);
102-
String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null;
103-
if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) {
104-
serializationSupport.registerWithTargetConstructorClass(condition.get(), namedClass.name(), customTargetConstructorClass);
105-
} else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) {
106-
serializationSupport.registerProxyClass(condition.get(), proxyClass.interfaceNames());
107-
} else {
108-
throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString()));
105+
if (!customConstructorWarningTriggered && data.containsKey(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)) {
106+
customConstructorWarningTriggered = true;
107+
LogUtils.warning("\"" + CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY +
108+
"\" is deprecated in serialization-config.json. All serializable classes can be instantiated through any superclass constructor without the use of the flag.");
109109
}
110+
serializationSupport.register(condition.get(), targetSerializationClass.name());
110111
}
111112
}
112113
}

0 commit comments

Comments
 (0)