Skip to content

[GR-60237] Introduce serializable flag to reflection metadata #10215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions docs/reference-manual/native-image/ReachabilityMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Computing metadata in code can be achieved in two ways:
## Specifying Metadata with JSON

All metadata specified in the _reachability-metadata.json_ file that is located in any of the classpath entries at _META-INF/native-image/\<group.Id>\/\<artifactId>\/_.
The JSON schema for the reachability metadata is defined in [reachability-metadata-schema-v1.0.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json).
The JSON schema for the reachability metadata is defined in [reachability-metadata-schema-v1.1.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json).

A sample _reachability-metadata.json_ file can be found [in the sample section](#sample-reachability-metadata).
The _reachability-metadata.json_ configuration contains a single object with one field for each type of metadata. Each field in the top-level object contains an array of *metadata entries*:
Expand All @@ -132,7 +132,6 @@ The _reachability-metadata.json_ configuration contains a single object with one
"reflection":[],
"resources":[],
"bundles":[],
"serialization":[],
"jni":[]
}
```
Expand Down Expand Up @@ -640,14 +639,15 @@ To create a custom constructor for serialization use:
Proxy classes can only be registered for serialization via the JSON files.

### Serialization Metadata in JSON
Serialization metadata is specified in the `serialization` section of _reachability-metadata.json_.
Serialization metadata is specified in the `reflection` section of _reachability-metadata.json_.

To specify a regular `serialized.Type` use
```json
{
"serialization": [
"reflection": [
{
"type": "serialized.Type"
"type": "serialized.Type",
"serializable": true
}
]
}
Expand All @@ -656,10 +656,11 @@ To specify a regular `serialized.Type` use
To specify a proxy class for serialization, use the following entry:
```json
{
"serialization": [
"reflection": [
{
"type": {
"proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
"proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"],
"serializable": true
}
}
]
Expand Down Expand Up @@ -700,7 +701,8 @@ See below is a sample reachability metadata configuration that you can use in _r
"allPublicFields": true,
"allDeclaredMethods": true,
"allPublicMethods": true,
"unsafeAllocated": true
"unsafeAllocated": true,
"serializable": true
}
],
"jni": [
Expand Down Expand Up @@ -736,11 +738,6 @@ See below is a sample reachability metadata configuration that you can use in _r
"name": "fully.qualified.bundle.name",
"locales": ["en", "de", "other_optional_locales"]
}
],
"serialization": [
{
"type": "serialized.Type"
}
]
}
```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json",
"$id": "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json",
"title": "JSON schema for the reachability metadata used by GraalVM Native Image",
"type": "object",
"default": {},
Expand Down Expand Up @@ -202,6 +202,11 @@
"title": "Allow objects of this class to be instantiated with a call to jdk.internal.misc.Unsafe#allocateInstance or JNI's AllocObject",
"type": "boolean",
"default": false
},
"serializable": {
"title": "Allow objects of this class to be serialized and deserialized",
"type": "boolean",
"default": false
}
},
"additionalProperties": false
Expand Down
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ At runtime, premain runtime options are set along with main class' arguments in
* (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.
* (GR-60237) Include serialization JSON reachability metadata as part of reflection metadata by introducing the `"serializable"` flag for reflection entries.

## 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 @@ -105,6 +105,7 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType
private ConfigurationMemberAccessibility allPublicMethodsAccess = ConfigurationMemberAccessibility.NONE;
private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE;
private ConfigurationMemberAccessibility allPublicConstructorsAccess = ConfigurationMemberAccessibility.NONE;
private boolean serializable = false;

public ConfigurationType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean includeAllElements) {
this.condition = condition;
Expand Down Expand Up @@ -294,14 +295,15 @@ private void setFlagsFromOther(ConfigurationType other, BiPredicate<Boolean, Boo
allPublicMethodsAccess = accessCombiner.apply(allPublicMethodsAccess, other.allPublicMethodsAccess);
allDeclaredConstructorsAccess = accessCombiner.apply(allDeclaredConstructorsAccess, other.allDeclaredConstructorsAccess);
allPublicConstructorsAccess = accessCombiner.apply(allPublicConstructorsAccess, other.allPublicConstructorsAccess);
serializable = flagPredicate.test(serializable, other.serializable);
}

private boolean isEmpty() {
return methods == null && fields == null && allFlagsFalse();
}

private boolean allFlagsFalse() {
return !(allDeclaredClasses || allRecordComponents || allPermittedSubclasses || allNestMembers || allSigners || allPublicClasses ||
return !(allDeclaredClasses || allRecordComponents || allPermittedSubclasses || allNestMembers || allSigners || allPublicClasses || serializable ||
allDeclaredFieldsAccess != ConfigurationMemberAccessibility.NONE || allPublicFieldsAccess != ConfigurationMemberAccessibility.NONE ||
allDeclaredMethodsAccess != ConfigurationMemberAccessibility.NONE || allPublicMethodsAccess != ConfigurationMemberAccessibility.NONE ||
allDeclaredConstructorsAccess != ConfigurationMemberAccessibility.NONE || allPublicConstructorsAccess != ConfigurationMemberAccessibility.NONE);
Expand Down Expand Up @@ -465,6 +467,10 @@ public synchronized void setAllPublicConstructors(ConfigurationMemberAccessibili
}
}

public synchronized void setSerializable() {
serializable = true;
}

@Override
public synchronized void printJson(JsonWriter writer) throws IOException {
writer.appendObjectStart();
Expand All @@ -479,6 +485,7 @@ public synchronized void printJson(JsonWriter writer) throws IOException {
printJsonBooleanIfSet(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors");
printJsonBooleanIfSet(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors");
printJsonBooleanIfSet(writer, unsafeAllocated, "unsafeAllocated");
printJsonBooleanIfSet(writer, serializable, "serializable");

if (fields != null) {
writer.appendSeparator().quote("fields").appendFieldSeparator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ public void registerDeclaredConstructors(UnresolvedConfigurationCondition condit
type.setAllDeclaredConstructors(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED);
}

@Override
public void registerAsSerializable(UnresolvedConfigurationCondition condition, ConfigurationType type) {
VMError.guarantee(condition.isAlwaysTrue() || condition.equals(type.getCondition()), "condition is already a part of the type");
type.setSerializable();
}

@Override
public String getTypeName(ConfigurationType type) {
return type.getTypeDescriptor().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

import com.oracle.svm.configure.config.ConfigurationSet;
import com.oracle.svm.configure.config.SerializationConfiguration;
import com.oracle.svm.configure.config.TypeConfiguration;
import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor;

import jdk.graal.compiler.java.LambdaUtils;

Expand All @@ -55,6 +57,7 @@ void processEntry(EconomicMap<String, ?> entry, ConfigurationSet configurationSe
String function = (String) entry.get("function");
List<?> args = (List<?>) entry.get("args");
SerializationConfiguration serializationConfiguration = configurationSet.getSerializationConfiguration();
TypeConfiguration reflectionConfiguration = configurationSet.getReflectionConfiguration();

if ("ObjectStreamClass.<init>".equals(function) || "ObjectInputStream.readClassDescriptor".equals(function)) {
expectSize(args, 1);
Expand All @@ -68,7 +71,7 @@ void processEntry(EconomicMap<String, ?> entry, ConfigurationSet configurationSe
if (className.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)) {
serializationConfiguration.registerLambdaCapturingClass(condition, className);
} else {
serializationConfiguration.register(condition, className);
reflectionConfiguration.getOrCreateType(condition, new NamedConfigurationTypeDescriptor(className)).setSerializable();
}
} else if ("SerializedLambda.readResolve".equals(function)) {
expectSize(args, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static final class Options {
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

@Option(help = "Resources describing reachability metadata needed for the program " +
"https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json", type = OptionType.User)//
"https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json", type = OptionType.User)//
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> ReachabilityMetadataResources = new HostedOptionKey<>(
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class LegacyReflectionConfigurationParser<C, T> extends ReflectionConfigur
"allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields",
"allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners",
"allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY,
"queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated");
"queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated", "serializable");

private final boolean treatAllNameEntriesAsType;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public interface ReflectionConfigurationParserDelegate<C, T> {

void registerUnsafeAllocated(C condition, T clazz);

void registerAsSerializable(C condition, T clazz);

String getTypeName(T type);

String getSimpleName(T type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
class ReflectionMetadataParser<C, T> extends ReflectionConfigurationParser<C, T> {
private static final List<String> OPTIONAL_REFLECT_METADATA_ATTRS = Arrays.asList(CONDITIONAL_KEY,
"allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields",
"methods", "fields", "unsafeAllocated");
"methods", "fields", "unsafeAllocated", "serializable");

private final String combinedFileKey;

Expand Down Expand Up @@ -107,6 +107,8 @@ protected void parseClass(EconomicMap<String, Object> data) {
registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz));
registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz));

registerIfNotDefault(data, false, clazz, "serializable", () -> delegate.registerAsSerializable(condition, clazz));

MapCursor<String, Object> cursor = data.getEntries();
while (cursor.advance()) {
String name = cursor.getKey();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@
import com.oracle.svm.hosted.reflect.proxy.ProxyRegistry;

import jdk.graal.compiler.util.json.JsonParserException;
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;

public final class ConfigurationParserUtils {

public static ReflectionConfigurationParser<ConfigurationCondition, Class<?>> create(String combinedFileKey, boolean strictMetadata,
ConfigurationConditionResolver<ConfigurationCondition> conditionResolver, ReflectionRegistry registry, ProxyRegistry proxyRegistry, ImageClassLoader imageClassLoader) {
ConfigurationConditionResolver<ConfigurationCondition> conditionResolver, ReflectionRegistry registry, ProxyRegistry proxyRegistry,
RuntimeSerializationSupport<ConfigurationCondition> serializationSupport, ImageClassLoader imageClassLoader) {
return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, conditionResolver,
RegistryAdapter.create(registry, proxyRegistry, imageClassLoader),
RegistryAdapter.create(registry, proxyRegistry, serializationSupport, imageClassLoader),
ConfigurationFiles.Options.StrictConfiguration.getValue(),
ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue(), TreatAllNameEntriesAsType.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,28 @@
import java.lang.reflect.Proxy;
import java.util.Arrays;

import com.oracle.svm.hosted.reflect.ReflectionDataBuilder;
import org.graalvm.nativeimage.impl.ConfigurationCondition;
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;

import com.oracle.svm.core.TypeResult;
import com.oracle.svm.core.configure.ConfigurationTypeDescriptor;
import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor;
import com.oracle.svm.hosted.ImageClassLoader;
import com.oracle.svm.hosted.reflect.ReflectionDataBuilder;
import com.oracle.svm.hosted.reflect.proxy.ProxyRegistry;

public class ReflectionRegistryAdapter extends RegistryAdapter {
private final RuntimeReflectionSupport reflectionSupport;
private final ProxyRegistry proxyRegistry;
private final RuntimeSerializationSupport<ConfigurationCondition> serializationSupport;

ReflectionRegistryAdapter(RuntimeReflectionSupport reflectionSupport, ProxyRegistry proxyRegistry, ImageClassLoader classLoader) {
ReflectionRegistryAdapter(RuntimeReflectionSupport reflectionSupport, ProxyRegistry proxyRegistry, RuntimeSerializationSupport<ConfigurationCondition> serializationSupport,
ImageClassLoader classLoader) {
super(reflectionSupport, classLoader);
this.reflectionSupport = reflectionSupport;
this.proxyRegistry = proxyRegistry;
this.serializationSupport = serializationSupport;
}

@Override
Expand Down Expand Up @@ -126,4 +130,9 @@ public void registerPublicConstructors(ConfigurationCondition condition, boolean
public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, Class<?> type) {
reflectionSupport.registerAllDeclaredConstructorsQuery(condition, queriedOnly, type);
}

@Override
public void registerAsSerializable(ConfigurationCondition condition, Class<?> clazz) {
serializationSupport.register(condition, clazz);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.graalvm.nativeimage.impl.ConfigurationCondition;
import org.graalvm.nativeimage.impl.ReflectionRegistry;
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;

import com.oracle.svm.core.TypeResult;
import com.oracle.svm.core.configure.ConfigurationTypeDescriptor;
Expand All @@ -52,9 +53,10 @@ public class RegistryAdapter implements ReflectionConfigurationParserDelegate<Co
private final ReflectionRegistry registry;
private final ImageClassLoader classLoader;

public static RegistryAdapter create(ReflectionRegistry registry, ProxyRegistry proxyRegistry, ImageClassLoader classLoader) {
public static RegistryAdapter create(ReflectionRegistry registry, ProxyRegistry proxyRegistry, RuntimeSerializationSupport<ConfigurationCondition> serializationSupport,
ImageClassLoader classLoader) {
if (registry instanceof RuntimeReflectionSupport) {
return new ReflectionRegistryAdapter((RuntimeReflectionSupport) registry, proxyRegistry, classLoader);
return new ReflectionRegistryAdapter((RuntimeReflectionSupport) registry, proxyRegistry, serializationSupport, classLoader);
} else {
return new RegistryAdapter(registry, classLoader);
}
Expand Down Expand Up @@ -289,6 +291,11 @@ private void registerExecutable(ConfigurationCondition condition, boolean querie
registry.register(condition, queriedOnly, executable);
}

@Override
public void registerAsSerializable(ConfigurationCondition condition, Class<?> clazz) {
/* Serializable has no effect on JNI registrations */
}

@Override
public String getTypeName(Class<?> type) {
return type.getTypeName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,10 @@ public void afterRegistration(AfterRegistrationAccess arg) {

ConfigurationConditionResolver<ConfigurationCondition> conditionResolver = new NativeImageConditionResolver(access.getImageClassLoader(),
ClassInitializationSupport.singleton());
ReflectionConfigurationParser<ConfigurationCondition, Class<?>> parser = ConfigurationParserUtils.create(JNI_KEY, true, conditionResolver, runtimeSupport, null, access.getImageClassLoader());
ReflectionConfigurationParser<ConfigurationCondition, Class<?>> parser = ConfigurationParserUtils.create(JNI_KEY, true, conditionResolver, runtimeSupport, null, null,
access.getImageClassLoader());
loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "JNI");
ReflectionConfigurationParser<ConfigurationCondition, Class<?>> legacyParser = ConfigurationParserUtils.create(null, false, conditionResolver, runtimeSupport, null,
ReflectionConfigurationParser<ConfigurationCondition, Class<?>> legacyParser = ConfigurationParserUtils.create(null, false, conditionResolver, runtimeSupport, null, null,
access.getImageClassLoader());
loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "JNI",
ConfigurationFiles.Options.JNIConfigurationFiles, ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName());
Expand Down
Loading