Skip to content

Commit

Permalink
[GR-60234] Register superclass serialization constructors by default
Browse files Browse the repository at this point in the history
PullRequest: graal/19503
  • Loading branch information
loicottet committed Dec 13, 2024
2 parents ca7e8b3 + f14510d commit 6579e43
Show file tree
Hide file tree
Showing 16 changed files with 101 additions and 171 deletions.
21 changes: 4 additions & 17 deletions docs/reference-manual/native-image/ReachabilityMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": "<fully-qualified-class-name>",
"customTargetConstructorClass": "<custom-target-constructor-class>"
}
]
}
```
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

Expand Down Expand Up @@ -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"
}
]
}
Expand Down
1 change: 1 addition & 0 deletions sdk/src/org.graalvm.nativeimage/snapshot.sigtest
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

/**
Expand All @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,9 @@ static RuntimeSerializationSupport<ConfigurationCondition> 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);

Expand Down
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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.<init>", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className, null);
traceSerializeBreakpoint(jni, "ObjectStreamClass.<init>", validObjectStreamClassInstance, state.getFullStackTraceOrNull(), className);
}
}
}
Expand All @@ -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.<init>", true, state.getFullStackTraceOrNull(), serializeTargetClassName, customConstructorClassName);
traceSerializeBreakpoint(jni, "ObjectStreamClass.<init>", true, state.getFullStackTraceOrNull(), serializeTargetClassName);
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ private static void doTestResourceConfig(ResourceConfiguration resourceConfig) {
private static void doTestSerializationConfig(ConfigurationSet config) {
SerializationConfiguration serializationConfig = config.getSerializationConfiguration();
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SerializationConfigurationType> {
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;
}
Expand All @@ -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();
}

Expand All @@ -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
Expand All @@ -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<String> nullsFirstCompare = Comparator.nullsFirst(Comparator.naturalOrder());
return nullsFirstCompare.compare(qualifiedCustomTargetConstructorJavaName, other.qualifiedCustomTargetConstructorJavaName);
return condition.compareTo(other.condition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ void processEntry(EconomicMap<String, ?> entry, ConfigurationSet configurationSe
SerializationConfiguration serializationConfiguration = configurationSet.getSerializationConfiguration();

if ("ObjectStreamClass.<init>".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;
Expand All @@ -68,7 +68,7 @@ void processEntry(EconomicMap<String, ?> 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);
Expand Down
Loading

0 comments on commit 6579e43

Please sign in to comment.