Skip to content

Commit

Permalink
Introduce serializable flag to reflection metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
loicottet committed Dec 9, 2024
1 parent f14510d commit 59ebe86
Show file tree
Hide file tree
Showing 17 changed files with 98 additions and 40 deletions.
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 @@ -19,6 +19,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 @@ -286,14 +287,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 @@ -457,6 +459,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 @@ -471,6 +477,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

0 comments on commit 59ebe86

Please sign in to comment.