From 987c9832f8e3c8b5f780f4b10700ec2715f2841c Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Thu, 30 May 2024 12:08:25 +0200 Subject: [PATCH] Introduce reachability-metadata.json to replace the various existing -config.json files --- .../reachability-metadata-schema-v1.0.0.json | 362 ++++++++++++++++++ .../test/config/OmitPreviousConfigTests.java | 4 +- .../config/ResourceConfigurationTest.java | 4 +- .../svm/configure/ConfigurationBase.java | 10 +- .../command/ConfigurationGenerateCommand.java | 33 +- .../config/ConfigurationFileCollection.java | 70 +++- .../configure/config/ConfigurationSet.java | 47 ++- .../configure/config/ConfigurationType.java | 24 -- .../PredefinedClassesConfiguration.java | 9 +- .../configure/config/ProxyConfiguration.java | 9 +- .../config/ResourceConfiguration.java | 58 ++- .../config/SerializationConfiguration.java | 25 +- .../SerializationConfigurationType.java | 3 +- .../configure/config/TypeConfiguration.java | 19 +- .../PartialConfigurationWithOrigins.java | 2 +- .../svm/core/configure/ConfigurationFile.java | 41 +- .../core/configure/ConfigurationParser.java | 19 + .../LegacyReflectionConfigurationParser.java | 143 +++++++ .../LegacyResourceConfigurationParser.java | 67 ++++ ...egacySerializationConfigurationParser.java | 118 ++++++ .../ReflectionConfigurationParser.java | 119 +----- .../configure/ReflectionMetadataParser.java | 128 +++++++ .../ResourceConfigurationParser.java | 110 +++--- .../configure/ResourceMetadataParser.java | 45 +++ .../SerializationConfigurationParser.java | 97 +---- .../SerializationMetadataParser.java | 67 ++++ .../oracle/svm/hosted/ResourcesFeature.java | 9 +- .../config/ConfigurationParserUtils.java | 14 +- .../svm/hosted/jni/JNIAccessFeature.java | 9 +- .../svm/hosted/reflect/ReflectionFeature.java | 10 +- .../serialize/SerializationFeature.java | 16 +- 31 files changed, 1315 insertions(+), 376 deletions(-) create mode 100644 docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java diff --git a/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json new file mode 100644 index 000000000000..a3a75dd1d5e1 --- /dev/null +++ b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json @@ -0,0 +1,362 @@ +{ + "$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", + "title": "JSON schema for the reachability metadata used by GraalVM Native Image", + "type": "object", + "default": {}, + "properties": { + "comment": { + "title": "A comment applying to the whole file (e.g., generation date, author, etc.)", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "default": "" + }, + "reflection": { + "title": "Metadata to ensure elements are reachable through reflection", + "$ref": "#/$defs/reflection" + }, + "jni": { + "title": "Metadata to ensure elements are reachable through JNI", + "$ref": "#/$defs/reflection" + }, + "serialization": { + "title": "Metadata for types that are serialized or deserialized at run time. The types must extend 'java.io.Serializable'.", + "type": "array", + "default": [], + "items": { + "title": "Enables serializing and deserializing objects of the class specified by ", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the type's inclusion in the serialization metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for serialization", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for serialization", + "$ref": "#/$defs/type" + }, + "customTargetConstructorClass": { + "title": "Fully qualified name of the class whose constructor should be used to serialize the class specified by ", + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "resources": { + "title": "Metadata to ensure resources are available", + "type": "array", + "default": [], + "items": { + "title": "Resource that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "module": { + "title": "Module containing the resource", + "type": "string", + "default": "" + }, + "glob": { + "title": "Resource name or pattern matching multiple resources (accepts * and ** wildcards)", + "type": "string" + } + }, + "required": [ + "glob" + ], + "additionalProperties": false + } + }, + "bundles": { + "title": "Metadata to ensure resource bundles are available", + "type": "array", + "default": [], + "items": { + "title": "Resource bundle that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource bundle's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource bundle should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "name": { + "title": "Name of the resource bundle", + "type": "string" + }, + "locales": { + "title": "List of locales that should be registered for this resource bundle", + "type": "array", + "default": [], + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } + }, + "required": [], + "additionalProperties": false, + + "$defs": { + "reflection": { + "type": "array", + "default": [], + "items": { + "title": "Elements that should be registered for reflection for a specified type", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the element's inclusion", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for reflection", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for reflection", + "$ref": "#/$defs/type" + }, + "methods": { + "title": "List of methods that should be registered for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Method descriptor of the method that should be registered for reflection", + "$ref": "#/$defs/method" + } + }, + "fields": { + "title": "List of class fields that can be read or written to for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Field descriptor of the field that should be registered for reflection", + "$ref": "#/$defs/field" + } + }, + "allDeclaredMethods": { + "title": "Register all declared methods from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allDeclaredFields": { + "title": "Register all declared fields from the type for reflective access", + "type": "boolean", + "default": false + }, + "allDeclaredConstructors": { + "title": "Register all declared constructors from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allPublicMethods": { + "title": "Register all public methods from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allPublicFields": { + "title": "Register all public fields from the type for reflective access", + "type": "boolean", + "default": false + }, + "allPublicConstructors": { + "title": "Register all public constructors from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "unsafeAllocated": { + "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 + } + }, + "additionalProperties": false + } + }, + "jni": { + "type": "array", + "default": [], + "items": { + "title": "Elements that should be registered for JNI for a specified type", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the element's inclusion", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for JNI", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for JNI", + "$ref": "#/$defs/type" + }, + "methods": { + "title": "List of methods that should be registered for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Method descriptor of the method that should be registered for JNI", + "$ref": "#/$defs/method" + } + }, + "fields": { + "title": "List of class fields that can be read or written to for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Field descriptor of the field that should be registered for JNI", + "$ref": "#/$defs/field" + } + }, + "allDeclaredMethods": { + "title": "Register all declared methods from the type for JNI access", + "type": "boolean", + "default": false + }, + "allDeclaredFields": { + "title": "Register all declared fields from the type for JNI access", + "type": "boolean", + "default": false + }, + "allDeclaredConstructors": { + "title": "Register all declared constructors from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicMethods": { + "title": "Register all public methods from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicFields": { + "title": "Register all public fields from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicConstructors": { + "title": "Register all public constructors from the type for JNI access", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + }, + "reason": { + "type": "string", + "default": [] + }, + "condition": { + "title": "Condition used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "typeReached": { + "title": "Type descriptor of a class that must be reached in order to enable the corresponding registration", + "$ref": "#/$defs/type" + } + }, + "required": [ + "typeReached" + ], + "additionalProperties": false + }, + "type": { + "title": "Type descriptors used by GraalVM Native Image metadata files", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "proxy": { + "title": "List of interfaces defining the proxy class", + "type": "array", + "default": [], + "items": { + "title": "Fully qualified name of the interface defining the proxy class", + "type": "string" + } + } + }, + "required": [ + "proxy" + ], + "additionalProperties": false + } + ] + }, + "method": { + "title": "Method descriptors used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "name": { + "title": "Method name that should be registered for this class", + "type": "string" + }, + "parameterTypes": { + "default": [], + "items": { + "title": "List of the method's parameter types", + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "field": { + "title": "Field descriptors used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "name": { + "title": "Name of the field that should be registered for reflection", + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } +} \ No newline at end of file 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 2d6be7200447..d29330488c6e 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 @@ -233,8 +233,8 @@ class TypeMethodsWithFlagsTest { final Map methodsThatMustExist = new HashMap<>(); final Map methodsThatMustNotExist = new HashMap<>(); - final TypeConfiguration previousConfig = new TypeConfiguration(); - final TypeConfiguration currentConfig = new TypeConfiguration(); + final TypeConfiguration previousConfig = new TypeConfiguration(""); + final TypeConfiguration currentConfig = new TypeConfiguration(""); TypeMethodsWithFlagsTest(ConfigurationMemberDeclaration methodKind) { this.methodKind = methodKind; diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java index 64dc835d313f..7c767f8e1db3 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java @@ -80,7 +80,7 @@ public void printJson() { Thread writerThread = new Thread(() -> { try (JsonWriter w = jw) { - rc.printJson(w); + rc.printLegacyJson(w); } catch (IOException e) { Assert.fail(e.getMessage()); } @@ -130,7 +130,7 @@ public void addClassBasedResourceBundle(UnresolvedConfigurationCondition conditi } }; - ResourceConfigurationParser rcp = new ResourceConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), registry, true); + ResourceConfigurationParser rcp = ResourceConfigurationParser.create(false, ConfigurationConditionResolver.identityResolver(), registry, true); writerThread.start(); rcp.parseAndRegister(pr); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java index f0eddc74ab48..22141588667d 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.configure; +import java.io.IOException; import java.util.function.Consumer; import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; @@ -31,6 +32,7 @@ import com.oracle.svm.core.configure.ConfigurationParser; import jdk.graal.compiler.util.json.JsonPrintable; +import jdk.graal.compiler.util.json.JsonWriter; public abstract class ConfigurationBase, P> implements JsonPrintable { @@ -70,5 +72,11 @@ public T copyAndFilter(P predicate) { return copyAnd(copy -> copy.removeIf(predicate)); } - public abstract ConfigurationParser createParser(); + public abstract ConfigurationParser createParser(boolean strictMetadata); + + public abstract boolean supportsCombinedFile(); + + public void printLegacyJson(JsonWriter writer) throws IOException { + printJson(writer); + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java index 03c582654778..53e688d55772 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/command/ConfigurationGenerateCommand.java @@ -49,8 +49,6 @@ import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.util.LogUtils; -import jdk.graal.compiler.util.json.JsonWriter; - public class ConfigurationGenerateCommand extends ConfigurationCommand { @Override public String getName() { @@ -304,36 +302,7 @@ protected static void generate(Iterator argumentsIterator, boolean accep if (outputCollection.isEmpty()) { LogUtils.warning("No outputs specified, validating inputs only."); } - for (URI uri : outputCollection.getReflectConfigPaths()) { - try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - configurationSet.getReflectionConfiguration().printJson(writer); - } - } - for (URI uri : outputCollection.getJniConfigPaths()) { - try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - configurationSet.getJniConfiguration().printJson(writer); - } - } - for (URI uri : outputCollection.getProxyConfigPaths()) { - try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - configurationSet.getProxyConfiguration().printJson(writer); - } - } - for (URI uri : outputCollection.getResourceConfigPaths()) { - try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - configurationSet.getResourceConfiguration().printJson(writer); - } - } - for (URI uri : outputCollection.getSerializationConfigPaths()) { - try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - configurationSet.getSerializationConfiguration().printJson(writer); - } - } - for (URI uri : outputCollection.getPredefinedClassesConfigPaths()) { - try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - configurationSet.getPredefinedClassesConfiguration().printJson(writer); - } - } + ConfigurationSet.writeConfigurationToAllPaths(outputCollection::getPaths, configurationSet::getConfiguration); } private static void parseFilterFiles(ComplexFilter filter, List filterFiles) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java index fbc031bc8402..cf2fc9046ad6 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -24,10 +24,14 @@ */ package com.oracle.svm.configure.config; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; + import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -35,13 +39,16 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.util.VMError; public class ConfigurationFileCollection { public static final Function FAIL_ON_EXCEPTION = e -> e; + private final Set reachabilityMetadataPaths = new LinkedHashSet<>(); private final Set jniConfigPaths = new LinkedHashSet<>(); private final Set reflectConfigPaths = new LinkedHashSet<>(); private final Set proxyConfigPaths = new LinkedHashSet<>(); @@ -51,6 +58,7 @@ public class ConfigurationFileCollection { private Set lockFilePaths; public void addDirectory(Path path) { + reachabilityMetadataPaths.add(path.resolve(ConfigurationFile.REACHABILITY_METADATA.getFileName()).toUri()); jniConfigPaths.add(path.resolve(ConfigurationFile.JNI.getFileName()).toUri()); reflectConfigPaths.add(path.resolve(ConfigurationFile.REFLECTION.getFileName()).toUri()); proxyConfigPaths.add(path.resolve(ConfigurationFile.DYNAMIC_PROXY.getFileName()).toUri()); @@ -70,24 +78,51 @@ private void detectAgentLock(T location, Predicate exists, Function fileResolver) { - jniConfigPaths.add(fileResolver.apply(ConfigurationFile.JNI.getFileName())); - reflectConfigPaths.add(fileResolver.apply(ConfigurationFile.REFLECTION.getFileName())); - proxyConfigPaths.add(fileResolver.apply(ConfigurationFile.DYNAMIC_PROXY.getFileName())); - resourceConfigPaths.add(fileResolver.apply(ConfigurationFile.RESOURCES.getFileName())); - serializationConfigPaths.add(fileResolver.apply(ConfigurationFile.SERIALIZATION.getFileName())); - predefinedClassesConfigPaths.add(fileResolver.apply(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName())); + addFile(reachabilityMetadataPaths, fileResolver, ConfigurationFile.REACHABILITY_METADATA); + addFile(jniConfigPaths, fileResolver, ConfigurationFile.JNI); + addFile(reflectConfigPaths, fileResolver, ConfigurationFile.REFLECTION); + addFile(proxyConfigPaths, fileResolver, ConfigurationFile.DYNAMIC_PROXY); + addFile(resourceConfigPaths, fileResolver, ConfigurationFile.RESOURCES); + addFile(serializationConfigPaths, fileResolver, ConfigurationFile.SERIALIZATION); + addFile(predefinedClassesConfigPaths, fileResolver, ConfigurationFile.PREDEFINED_CLASSES_NAME); detectAgentLock(fileResolver.apply(ConfigurationFile.LOCK_FILE_NAME), Objects::nonNull, Function.identity()); } + private static void addFile(Set metadataPaths, Function fileResolver, ConfigurationFile configurationFile) { + URI uri = fileResolver.apply(configurationFile.getFileName()); + if (uri != null) { + metadataPaths.add(uri); + } + } + public Set getDetectedAgentLockPaths() { return (lockFilePaths != null) ? lockFilePaths : Collections.emptySet(); } public boolean isEmpty() { - return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && + return reachabilityMetadataPaths.isEmpty() && jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() && predefinedClassesConfigPaths.isEmpty(); } + public Set getPaths(ConfigurationFile configurationFile) { + Set uris; + switch (configurationFile) { + case REACHABILITY_METADATA -> uris = getReachabilityMetadataPaths(); + case DYNAMIC_PROXY -> uris = getProxyConfigPaths(); + case RESOURCES -> uris = getResourceConfigPaths(); + case JNI -> uris = getJniConfigPaths(); + case REFLECTION -> uris = getReflectConfigPaths(); + case SERIALIZATION -> uris = getSerializationConfigPaths(); + case PREDEFINED_CLASSES_NAME -> uris = getPredefinedClassesConfigPaths(); + default -> throw VMError.shouldNotReachHere("Cannot get paths for configuration file " + configurationFile); + } + return uris.stream().map(Paths::get).collect(Collectors.toSet()); + } + + public Set getReachabilityMetadataPaths() { + return reachabilityMetadataPaths; + } + public Set getJniConfigPaths() { return jniConfigPaths; } @@ -113,35 +148,37 @@ public Set getPredefinedClassesConfigPaths() { } public TypeConfiguration loadJniConfig(Function exceptionHandler) throws Exception { - return loadTypeConfig(jniConfigPaths, exceptionHandler); + return loadTypeConfig(JNI_KEY, jniConfigPaths, exceptionHandler); } public TypeConfiguration loadReflectConfig(Function exceptionHandler) throws Exception { - return loadTypeConfig(reflectConfigPaths, exceptionHandler); + return loadTypeConfig(REFLECTION_KEY, reflectConfigPaths, exceptionHandler); } public ProxyConfiguration loadProxyConfig(Function exceptionHandler) throws Exception { ProxyConfiguration proxyConfiguration = new ProxyConfiguration(); - loadConfig(proxyConfigPaths, proxyConfiguration.createParser(), exceptionHandler); + loadConfig(proxyConfigPaths, proxyConfiguration.createParser(false), exceptionHandler); return proxyConfiguration; } public PredefinedClassesConfiguration loadPredefinedClassesConfig(Path[] classDestinationDirs, Predicate shouldExcludeClassesWithHash, Function exceptionHandler) throws Exception { PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(classDestinationDirs, shouldExcludeClassesWithHash); - loadConfig(predefinedClassesConfigPaths, predefinedClassesConfiguration.createParser(), exceptionHandler); + loadConfig(predefinedClassesConfigPaths, predefinedClassesConfiguration.createParser(false), exceptionHandler); return predefinedClassesConfiguration; } public ResourceConfiguration loadResourceConfig(Function exceptionHandler) throws Exception { ResourceConfiguration resourceConfiguration = new ResourceConfiguration(); - loadConfig(resourceConfigPaths, resourceConfiguration.createParser(), exceptionHandler); + loadConfig(reachabilityMetadataPaths, resourceConfiguration.createParser(true), exceptionHandler); + loadConfig(resourceConfigPaths, resourceConfiguration.createParser(false), exceptionHandler); return resourceConfiguration; } public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); - loadConfig(serializationConfigPaths, serializationConfiguration.createParser(), exceptionHandler); + loadConfig(reachabilityMetadataPaths, serializationConfiguration.createParser(true), exceptionHandler); + loadConfig(serializationConfigPaths, serializationConfiguration.createParser(false), exceptionHandler); return serializationConfiguration; } @@ -152,9 +189,10 @@ public ConfigurationSet loadConfigurationSet(Function ex loadPredefinedClassesConfig(predefinedConfigClassDestinationDirs, predefinedConfigClassWithHashExclusionPredicate, exceptionHandler)); } - private static TypeConfiguration loadTypeConfig(Collection uris, Function exceptionHandler) throws Exception { - TypeConfiguration configuration = new TypeConfiguration(); - loadConfig(uris, configuration.createParser(), exceptionHandler); + private TypeConfiguration loadTypeConfig(String combinedFileKey, Collection uris, Function exceptionHandler) throws Exception { + TypeConfiguration configuration = new TypeConfiguration(combinedFileKey); + loadConfig(reachabilityMetadataPaths, configuration.createParser(true), exceptionHandler); + loadConfig(uris, configuration.createParser(false), exceptionHandler); return configuration; } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java index 49f677be8913..d69c6cbace8e 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java @@ -24,10 +24,15 @@ */ package com.oracle.svm.configure.config; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; + import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.function.Function; import com.oracle.svm.configure.ConfigurationBase; @@ -67,7 +72,7 @@ public ConfigurationSet(ConfigurationSet other) { } public ConfigurationSet() { - this(new TypeConfiguration(), new TypeConfiguration(), new ResourceConfiguration(), new ProxyConfiguration(), new SerializationConfiguration(), + this(new TypeConfiguration(REFLECTION_KEY), new TypeConfiguration(JNI_KEY), new ResourceConfiguration(), new ProxyConfiguration(), new SerializationConfiguration(), new PredefinedClassesConfiguration(new Path[0], hash -> false)); } @@ -148,13 +153,45 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { } public static List writeConfiguration(Function configFilePathResolver, Function configSupplier) throws IOException { + return writeConfigurationToAllPaths(cf -> Collections.singleton(configFilePathResolver.apply(cf)), configSupplier); + } + + public static List writeConfigurationToAllPaths(Function> configFilePathResolver, Function configSupplier) throws IOException { List writtenFiles = new ArrayList<>(); - for (ConfigurationFile configFile : ConfigurationFile.agentGeneratedFiles()) { - Path path = configFilePathResolver.apply(configFile); + ConfigurationFile reachabilityMetadataFile = ConfigurationFile.REACHABILITY_METADATA; + for (Path path : configFilePathResolver.apply(reachabilityMetadataFile)) { writtenFiles.add(path); JsonWriter writer = new JsonWriter(path); - configSupplier.apply(configFile).printJson(writer); - writer.newline(); + writer.appendObjectStart().indent().newline(); + boolean first = true; + for (ConfigurationFile configFile : ConfigurationFile.agentGeneratedFiles()) { + JsonPrintable configuration = configSupplier.apply(configFile); + if (configuration instanceof ConfigurationBase configurationBase && !configurationBase.supportsCombinedFile()) { + /* Fallback to legacy printing */ + for (Path specificPath : configFilePathResolver.apply(configFile)) { + writtenFiles.add(specificPath); + JsonWriter specificWriter = new JsonWriter(specificPath); + configurationBase.printLegacyJson(specificWriter); + specificWriter.newline(); + specificWriter.close(); + } + } else { + if (first) { + first = false; + } else { + writer.appendSeparator().newline(); + } + if (!configFile.equals(ConfigurationFile.RESOURCES)) { + /* + * Resources are printed at the top level of the object, not in a defined + * field + */ + writer.quote(configFile.getFieldName()).appendFieldSeparator(); + } + configSupplier.apply(configFile).printJson(writer); + } + } + writer.unindent().newline().appendObjectEnd(); writer.close(); } return writtenFiles; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index a4830aabf2ed..5b0bcb4708e9 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -459,16 +459,6 @@ public synchronized void printJson(JsonWriter writer) throws IOException { printJsonBooleanIfSet(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicMethods"); printJsonBooleanIfSet(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors"); printJsonBooleanIfSet(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors"); - printJsonBooleanIfNotSet(writer, allDeclaredClasses, "allDeclaredClasses"); - printJsonBooleanIfNotSet(writer, allRecordComponents, "allRecordComponents"); - printJsonBooleanIfNotSet(writer, allPermittedSubclasses, "allPermittedSubclasses"); - printJsonBooleanIfNotSet(writer, allNestMembers, "allNestMembers"); - printJsonBooleanIfNotSet(writer, allSigners, "allSigners"); - printJsonBooleanIfNotSet(writer, allPublicClasses, "allPublicClasses"); - printJsonBooleanIfNotSet(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredMethods"); - printJsonBooleanIfNotSet(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicMethods"); - printJsonBooleanIfNotSet(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredConstructors"); - printJsonBooleanIfNotSet(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicConstructors"); printJsonBooleanIfSet(writer, unsafeAllocated, "unsafeAllocated"); if (fields != null) { @@ -484,14 +474,6 @@ public synchronized void printJson(JsonWriter writer) throws IOException { Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), JsonPrintable::printJson); } - Set queriedMethods = getMethodsByAccessibility(ConfigurationMemberAccessibility.QUERIED); - if (!queriedMethods.isEmpty()) { - writer.append(',').newline().quote("queriedMethods").append(':'); - JsonPrinter.printCollection(writer, - queriedMethods, - Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), - JsonPrintable::printJson); - } } writer.unindent().newline().append('}'); @@ -509,12 +491,6 @@ private static void printField(Map.Entry entry, JsonWriter w) w.append('}'); } - private static void printJsonBooleanIfNotSet(JsonWriter writer, boolean predicate, String attribute) throws IOException { - if (!predicate) { - printJsonBoolean(writer, predicate, attribute); - } - } - private static void printJsonBooleanIfSet(JsonWriter writer, boolean predicate, String attribute) throws IOException { if (predicate) { printJsonBoolean(writer, predicate, attribute); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java index b3ddc4dc913d..a1ebb57a9e39 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java @@ -40,6 +40,7 @@ import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.PredefinedClassesConfigurationParser; +import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.util.Digest; import jdk.graal.compiler.util.json.JsonWriter; @@ -165,7 +166,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { + public ConfigurationParser createParser(boolean strictMetadata) { + VMError.guarantee(!strictMetadata, "Predefined classes configuration is not supported with strict metadata"); return new PredefinedClassesConfigurationParser(this::add, true); } @@ -174,6 +176,11 @@ public boolean isEmpty() { return classes.isEmpty(); } + @Override + public boolean supportsCombinedFile() { + return false; + } + public boolean containsClassWithName(String className) { return classes.values().stream().anyMatch(clazz -> clazz.getNameInfo().equals(className)); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java index 4214ff6db892..73fc92121bd2 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java @@ -38,6 +38,7 @@ import com.oracle.svm.core.configure.ConfigurationConditionResolver; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.ProxyConfigurationParser; +import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.util.json.JsonWriter; @@ -131,7 +132,8 @@ public static void printProxyInterfaces(JsonWriter writer, List(ConfigurationConditionResolver.identityResolver(), true, (cond, interfaces) -> interfaceLists.add(new ConditionalElement<>(cond, interfaces))); } @@ -140,6 +142,11 @@ public boolean isEmpty() { return interfaceLists.isEmpty(); } + @Override + public boolean supportsCombinedFile() { + return false; + } + private static > int compareList(List l1, List l2) { for (int i = 0; i < l1.size() && i < l2.size(); i++) { int c = l1.get(i).compareTo(l2.get(i)); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java index d9345d3139a3..23c3d0b2c794 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java @@ -24,6 +24,10 @@ */ package com.oracle.svm.configure.config; +import static com.oracle.svm.core.configure.ConfigurationParser.BUNDLES_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.GLOBS_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.RESOURCES_KEY; + import java.io.IOException; import java.util.Collection; import java.util.Collections; @@ -315,8 +319,24 @@ public boolean anyBundleMatches(UnresolvedConfigurationCondition condition, Stri @Override public void printJson(JsonWriter writer) throws IOException { - writer.append('{').indent().newline(); - writer.quote("resources").append(':').append('{').newline().indent(); + printGlobsJson(writer, true); + writer.appendSeparator().newline(); + printBundlesJson(writer); + } + + @Override + public void printLegacyJson(JsonWriter writer) throws IOException { + writer.appendObjectStart().indent().newline(); + printResourcesJson(writer); + writer.appendSeparator().newline(); + printBundlesJson(writer); + writer.appendSeparator().newline(); + printGlobsJson(writer, false); + writer.unindent().newline().appendObjectEnd(); + } + + void printResourcesJson(JsonWriter writer) throws IOException { + writer.quote(RESOURCES_KEY).append(':').append('{').indent().newline(); writer.quote("includes").append(':'); JsonPrinter.printCollection(writer, addedResources, ConditionalElement.comparator(), ResourceConfiguration::conditionalRegexElementJson); if (!ignoredResources.isEmpty()) { @@ -324,19 +344,22 @@ public void printJson(JsonWriter writer) throws IOException { writer.quote("excludes").append(':'); JsonPrinter.printCollection(writer, ignoredResources.keySet(), ConditionalElement.comparator(), ResourceConfiguration::conditionalRegexElementJson); } - writer.unindent(); - writer.append('}').append(',').newline(); - writer.quote("globs").append(':'); - JsonPrinter.printCollection(writer, addedGlobs, ConditionalElement.comparator(ResourceEntry.comparator()), ResourceConfiguration::conditionalGlobElementJson); - writer.append(',').newline(); - writer.quote("bundles").append(':'); - JsonPrinter.printCollection(writer, bundles.keySet(), ConditionalElement.comparator(), (p, w) -> printResourceBundle(bundles.get(p), w)); writer.unindent().newline().append('}'); } + void printBundlesJson(JsonWriter writer) throws IOException { + writer.quote(BUNDLES_KEY).append(':'); + JsonPrinter.printCollection(writer, bundles.keySet(), ConditionalElement.comparator(), (p, w) -> printResourceBundle(bundles.get(p), w)); + } + + void printGlobsJson(JsonWriter writer, boolean useResourcesFieldName) throws IOException { + writer.quote(useResourcesFieldName ? RESOURCES_KEY : GLOBS_KEY).appendFieldSeparator(); + JsonPrinter.printCollection(writer, addedGlobs, ConditionalElement.comparator(ResourceEntry.comparator()), ResourceConfiguration::conditionalGlobElementJson); + } + @Override - public ConfigurationParser createParser() { - return new ResourceConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ResourceConfiguration.ParserAdapter(this), true); + public ConfigurationParser createParser(boolean strictMetadata) { + return ResourceConfigurationParser.create(strictMetadata, ConfigurationConditionResolver.identityResolver(), new ResourceConfiguration.ParserAdapter(this), true); } private static void printResourceBundle(BundleConfiguration config, JsonWriter writer) throws IOException { @@ -359,6 +382,19 @@ public boolean isEmpty() { return addedResources.isEmpty() && bundles.isEmpty(); } + @Override + public boolean supportsCombinedFile() { + if (!addedResources.isEmpty() || !ignoredResources.isEmpty()) { + return false; + } + for (ResourceConfiguration.BundleConfiguration bundleConfiguration : bundles.values()) { + if (!bundleConfiguration.classNames.isEmpty()) { + return false; + } + } + return true; + } + private static void conditionalGlobElementJson(ConditionalElement p, JsonWriter w) throws IOException { String pattern = p.element().pattern(); String module = p.element().module(); 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 e43961b8f7e5..03ec873e8ae2 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 @@ -110,14 +110,23 @@ public boolean contains(UnresolvedConfigurationCondition condition, String seria @Override public void printJson(JsonWriter writer) throws IOException { + List listOfCapturedClasses = new ArrayList<>(serializations); + Collections.sort(listOfCapturedClasses); + printSerializationClasses(writer, listOfCapturedClasses); + } + + @Override + public void printLegacyJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); List listOfCapturedClasses = new ArrayList<>(serializations); Collections.sort(listOfCapturedClasses); - printSerializationClasses(writer, "types", listOfCapturedClasses); + writer.quote("types").append(":"); + printSerializationClasses(writer, listOfCapturedClasses); writer.append(",").newline(); List listOfCapturingClasses = new ArrayList<>(lambdaSerializationCapturingTypes); listOfCapturingClasses.sort(new SerializationConfigurationLambdaCapturingType.SerializationConfigurationLambdaCapturingTypesComparator()); - printSerializationClasses(writer, "lambdaCapturingTypes", listOfCapturingClasses); + writer.quote("lambdaCapturingTypes").append(":"); + printSerializationClasses(writer, listOfCapturingClasses); writer.append(",").newline().quote("proxies").append(":"); printProxies(writer); writer.unindent().newline(); @@ -125,8 +134,13 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { - return new SerializationConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), this, true); + public ConfigurationParser createParser(boolean strictMetadata) { + return SerializationConfigurationParser.create(strictMetadata, ConfigurationConditionResolver.identityResolver(), this, true); + } + + @Override + public boolean supportsCombinedFile() { + return lambdaSerializationCapturingTypes.isEmpty() && interfaceListsSerializableProxies.isEmpty(); } private void printProxies(JsonWriter writer) throws IOException { @@ -134,8 +148,7 @@ private void printProxies(JsonWriter writer) throws IOException { ProxyConfiguration.printProxyInterfaces(writer, lists); } - private static void printSerializationClasses(JsonWriter writer, String types, List serializationConfigurationTypes) throws IOException { - writer.quote(types).append(":"); + private static void printSerializationClasses(JsonWriter writer, List serializationConfigurationTypes) throws IOException { writer.append('['); writer.indent(); 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 de3ecca42491..d5531b26c13d 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 @@ -68,8 +68,7 @@ public UnresolvedConfigurationCondition getCondition() { public void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); ConfigurationConditionPrintable.printConditionAttribute(condition, writer); - /* GR-50385: Replace with "type" */ - writer.quote(SerializationConfigurationParser.NAME_KEY).append(':').quote(qualifiedJavaName); + writer.quote(SerializationConfigurationParser.TYPE_KEY).append(':').quote(qualifiedJavaName); if (qualifiedCustomTargetConstructorJavaName != null) { writer.append(',').newline(); writer.quote(SerializationConfigurationParser.CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY).append(':') diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java index 2aaca642d75b..21c970ab8d4c 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java @@ -49,11 +49,15 @@ public final class TypeConfiguration extends ConfigurationBase, ConfigurationType> types = new ConcurrentHashMap<>(); - public TypeConfiguration() { + private final String combinedFileKey; + + public TypeConfiguration(String combinedFileKey) { + this.combinedFileKey = combinedFileKey; } public TypeConfiguration(TypeConfiguration other) { other.types.forEach((key, value) -> types.put(key, new ConfigurationType(value))); + this.combinedFileKey = other.combinedFileKey; } @Override @@ -139,19 +143,19 @@ public void printJson(JsonWriter writer) throws IOException { List typesList = new ArrayList<>(this.types.values()); typesList.sort(Comparator.comparing(ConfigurationType::getTypeDescriptor).thenComparing(ConfigurationType::getCondition)); - writer.append('['); + writer.append('[').indent(); String prefix = ""; for (ConfigurationType type : typesList) { writer.append(prefix).newline(); type.printJson(writer); prefix = ","; } - writer.newline().append(']'); + writer.unindent().newline().append(']'); } @Override - public ConfigurationParser createParser() { - return new ReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false, false); + public ConfigurationParser createParser(boolean strictMetadata) { + return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false, false); } @Override @@ -159,6 +163,11 @@ public boolean isEmpty() { return types.isEmpty(); } + @Override + public boolean supportsCombinedFile() { + return true; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java index 459cb15adc1f..9158b6143a87 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java @@ -170,7 +170,7 @@ private static void parseConfigurationSet(EconomicMap configJson, Con if (configType == null) { throw new JsonParserException("Invalid configuration type: " + configName); } - configurationSet.getConfiguration(configType).createParser().parseAndRegister(cursor.getValue(), origin); + configurationSet.getConfiguration(configType).createParser(false).parseAndRegister(cursor.getValue(), origin); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java index 98dac61e3b0c..9eccdadb416c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java @@ -24,21 +24,30 @@ */ package com.oracle.svm.core.configure; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.RESOURCES_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.SERIALIZATION_KEY; + import java.util.Arrays; public enum ConfigurationFile { - DYNAMIC_PROXY("proxy", true), - RESOURCES("resource", true), - JNI("jni", true), - FOREIGN("foreign", false), - REFLECTION("reflect", true), - SERIALIZATION("serialization", true), - SERIALIZATION_DENY("serialization-deny", false), - PREDEFINED_CLASSES_NAME("predefined-classes", true); + REACHABILITY_METADATA("reachability-metadata", null, true, true), + DYNAMIC_PROXY("proxy", null, true, false), + RESOURCES("resource", RESOURCES_KEY, true, false), + JNI("jni", JNI_KEY, true, false), + FOREIGN("foreign", null, false, false), + REFLECTION("reflect", REFLECTION_KEY, true, false), + SERIALIZATION("serialization", SERIALIZATION_KEY, true, false), + SERIALIZATION_DENY("serialization-deny", null, false, false), + PREDEFINED_CLASSES_NAME("predefined-classes", null, true, false); - public static final String DEFAULT_FILE_NAME_SUFFIX = "-config.json"; + public static final String LEGACY_FILE_NAME_SUFFIX = "-config.json"; + public static final String COMBINED_FILE_NAME_SUFFIX = ".json"; private final String name; + private final String fieldName; private final boolean canAgentGenerate; + private final boolean combinedFile; public static final String LOCK_FILE_NAME = ".lock"; public static final String PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR = "agent-extracted-predefined-classes"; @@ -47,17 +56,23 @@ public enum ConfigurationFile { private static final ConfigurationFile[] agentGeneratedFiles = computeAgentGeneratedFiles(); - ConfigurationFile(String name, boolean canAgentGenerate) { + ConfigurationFile(String name, String fieldName, boolean canAgentGenerate, boolean combinedFile) { this.name = name; + this.fieldName = fieldName; this.canAgentGenerate = canAgentGenerate; + this.combinedFile = combinedFile; } public String getName() { return name; } + public String getFieldName() { + return fieldName; + } + public String getFileName() { - return name + DEFAULT_FILE_NAME_SUFFIX; + return name + (combinedFile ? COMBINED_FILE_NAME_SUFFIX : LEGACY_FILE_NAME_SUFFIX); } public String getFileName(String suffix) { @@ -65,7 +80,7 @@ public String getFileName(String suffix) { } public boolean canBeGeneratedByAgent() { - return canAgentGenerate; + return canAgentGenerate && !combinedFile; } public static ConfigurationFile getByName(String name) { @@ -82,6 +97,6 @@ public static ConfigurationFile[] agentGeneratedFiles() { } private static ConfigurationFile[] computeAgentGeneratedFiles() { - return Arrays.stream(values()).filter(ConfigurationFile::canBeGeneratedByAgent).toArray(ConfigurationFile[]::new); + return Arrays.stream(values()).filter(f -> f.canBeGeneratedByAgent()).toArray(ConfigurationFile[]::new); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index ddef7244b9e0..289e6a82f492 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -29,6 +29,7 @@ import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHED_KEY; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -69,6 +70,12 @@ public static InputStream openStream(URI uri) throws IOException { public static final String NAME_KEY = "name"; public static final String TYPE_KEY = "type"; public static final String PROXY_KEY = "proxy"; + public static final String REFLECTION_KEY = "reflection"; + public static final String JNI_KEY = "jni"; + public static final String SERIALIZATION_KEY = "serialization"; + public static final String RESOURCES_KEY = "resources"; + public static final String BUNDLES_KEY = "bundles"; + public static final String GLOBS_KEY = "globs"; public static final String MODULE_KEY = "module"; public static final String GLOB_KEY = "glob"; private final Map> seenUnknownAttributesByType = new HashMap<>(); @@ -81,6 +88,11 @@ protected ConfigurationParser(boolean strictConfiguration) { public void parseAndRegister(URI uri) throws IOException { try (Reader reader = openReader(uri)) { parseAndRegister(new JsonParser(reader).parse(), uri); + } catch (FileNotFoundException e) { + /* + * Ignore: *-config.json files can be missing when reachability-metadata.json is + * present, and vice-versa + */ } } @@ -94,6 +106,12 @@ public void parseAndRegister(Reader reader) throws IOException { public abstract void parseAndRegister(Object json, URI origin) throws IOException; + public Object getFromGlobalFile(Object json, String key) { + EconomicMap map = asMap(json, "top level of reachability metadata file must be an object"); + checkAttributes(map, "reachability metadata", Collections.emptyList(), List.of(REFLECTION_KEY, JNI_KEY, SERIALIZATION_KEY, RESOURCES_KEY, BUNDLES_KEY, "reason", "comment")); + return map.get(key); + } + @SuppressWarnings("unchecked") public static List asList(Object data, String errorMessage) { if (data instanceof List) { @@ -269,6 +287,7 @@ protected static Optional parseTypeContents(Object * We return if we find a future version of a type descriptor (as a JSON object) instead * of failing parsing. */ + // TODO warn return Optional.empty(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java new file mode 100644 index 000000000000..8c095a6ea247 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +final class LegacyReflectionConfigurationParser extends ReflectionConfigurationParser { + + private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("name", "type", "allDeclaredConstructors", "allPublicConstructors", + "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", + "allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners", + "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, + "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); + + private final boolean treatAllNameEntriesAsType; + + LegacyReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements, boolean treatAllNameEntriesAsType) { + super(conditionResolver, delegate, strictConfiguration, printMissingElements); + this.treatAllNameEntriesAsType = treatAllNameEntriesAsType; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + parseClassArray(asList(json, "first level of document must be an array of class descriptors")); + } + + @Override + protected void parseClass(EconomicMap data) { + checkAttributes(data, "reflection class descriptor object", Collections.emptyList(), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); + checkHasExactlyOneAttribute(data, "reflection class descriptor object", List.of(NAME_KEY, TYPE_KEY)); + + Optional type = parseTypeOrName(data, treatAllNameEntriesAsType); + if (type.isEmpty()) { + return; + } + /* + * Classes registered using the old ("name") syntax requires elements (fields, methods, + * constructors, ...) to be registered for runtime queries, whereas the new ("type") syntax + * automatically registers all elements as queried. + */ + boolean isType = type.get().definedAsType(); + + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, isType); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { + return; + } + + /* + * Even if primitives cannot be queried through Class.forName, they can be registered to + * allow getDeclaredMethods() and similar bulk queries at run time. + */ + C condition = conditionResult.get(); + TypeResult result = delegate.resolveType(condition, type.get(), true); + if (!result.isPresent()) { + handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); + return; + } + + C queryCondition = isType ? conditionResolver.alwaysTrue() : condition; + T clazz = result.get(); + delegate.registerType(conditionResult.get(), clazz); + + registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); + registerIfNotDefault(data, isType, clazz, "allDeclaredClasses", () -> delegate.registerDeclaredClasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allRecordComponents", () -> delegate.registerRecordComponents(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allPermittedSubclasses", () -> delegate.registerPermittedSubclasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allNestMembers", () -> delegate.registerNestMembers(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allSigners", () -> delegate.registerSigners(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allPublicClasses", () -> delegate.registerPublicClasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllDeclaredConstructors", () -> delegate.registerDeclaredConstructors(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllPublicConstructors", () -> delegate.registerPublicConstructors(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllDeclaredMethods", () -> delegate.registerDeclaredMethods(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllPublicMethods", () -> delegate.registerPublicMethods(queryCondition, true, clazz)); + if (isType) { + /* + * Fields cannot be registered as queried only by the user, we register them + * unconditionally if the class is registered via "type". + */ + delegate.registerDeclaredFields(queryCondition, true, clazz); + delegate.registerPublicFields(queryCondition, true, clazz); + } + registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + MapCursor cursor = data.getEntries(); + while (cursor.advance()) { + String name = cursor.getKey(); + Object value = cursor.getValue(); + try { + switch (name) { + case "methods": + parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + break; + case "queriedMethods": + parseMethods(condition, true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz); + break; + case "fields": + parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); + break; + } + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java new file mode 100644 index 000000000000..0f680cb44899 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; + +final class LegacyResourceConfigurationParser extends ResourceConfigurationParser { + LegacyResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + super(conditionResolver, registry, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + parseTopLevelObject(asMap(json, "first level of document must be an object")); + } + + private void parseTopLevelObject(EconomicMap obj) { + Object resourcesObject = null; + Object bundlesObject = null; + Object globsObject = null; + MapCursor cursor = obj.getEntries(); + while (cursor.advance()) { + if (RESOURCES_KEY.equals(cursor.getKey())) { + resourcesObject = cursor.getValue(); + } else if (BUNDLES_KEY.equals(cursor.getKey())) { + bundlesObject = cursor.getValue(); + } else if (GLOBS_KEY.equals(cursor.getKey())) { + globsObject = cursor.getValue(); + } + } + + if (resourcesObject != null) { + parseResourcesObject(resourcesObject); + } + if (bundlesObject != null) { + parseBundlesObject(bundlesObject); + } + if (globsObject != null) { + parseGlobsObject(globsObject); + } + } +} 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 new file mode 100644 index 000000000000..678905f916ef --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + +import jdk.graal.compiler.util.json.JsonParserException; + +final class LegacySerializationConfigurationParser extends SerializationConfigurationParser { + + private static final String SERIALIZATION_TYPES_KEY = "types"; + private static final String LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY = "lambdaCapturingTypes"; + private static final String PROXY_SERIALIZATION_TYPES_KEY = "proxies"; + + private final ProxyConfigurationParser proxyConfigurationParser; + + LegacySerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(conditionResolver, serializationSupport, strictConfiguration); + this.proxyConfigurationParser = new ProxyConfigurationParser<>(conditionResolver, strictConfiguration, serializationSupport::registerProxyClass); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + if (json instanceof List) { + parseOldConfiguration(asList(json, "First-level of document must be an array of serialization lists")); + } else if (json instanceof EconomicMap) { + parseNewConfiguration(asMap(json, "First-level of document must be a map of serialization types")); + } else { + throw new JsonParserException("First-level of document must either be an array of serialization lists or a map of serialization types"); + } + } + + private void parseOldConfiguration(List listOfSerializationConfigurationObjects) { + parseSerializationTypes(asList(listOfSerializationConfigurationObjects, "Second-level of document must be serialization descriptor objects"), false); + } + + private void parseNewConfiguration(EconomicMap listOfSerializationConfigurationObjects) { + if (!listOfSerializationConfigurationObjects.containsKey(SERIALIZATION_TYPES_KEY) || !listOfSerializationConfigurationObjects.containsKey(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY)) { + throw new JsonParserException("Second-level of document must be arrays of serialization descriptor objects"); + } + + parseSerializationTypes(asList(listOfSerializationConfigurationObjects.get(SERIALIZATION_TYPES_KEY), "The types property must be an array of serialization descriptor objects"), false); + parseSerializationTypes( + asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), + "The lambdaCapturingTypes property must be an array of serialization descriptor objects"), + true); + + if (listOfSerializationConfigurationObjects.containsKey(PROXY_SERIALIZATION_TYPES_KEY)) { + proxyConfigurationParser.parseAndRegister(listOfSerializationConfigurationObjects.get(PROXY_SERIALIZATION_TYPES_KEY), null); + } + } + + @Override + protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { + if (lambdaCapturingType) { + checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CONDITIONAL_KEY)); + } else { + checkAttributes(data, "serialization descriptor object", Collections.emptySet(), Arrays.asList(TYPE_KEY, NAME_KEY, CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); + checkHasExactlyOneAttribute(data, "serialization descriptor object", List.of(TYPE_KEY, NAME_KEY)); + } + + Optional targetSerializationClass = parseTypeOrName(data, false); + if (targetSerializationClass.isEmpty()) { + return; + } + + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, targetSerializationClass.get().definedAsType()); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { + return; + } + + if (lambdaCapturingType) { + String className = ((NamedConfigurationTypeDescriptor) targetSerializationClass.get()).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.get() instanceof NamedConfigurationTypeDescriptor namedClass) { + serializationSupport.registerWithTargetConstructorClass(condition.get(), namedClass.name(), customTargetConstructorClass); + } else if (targetSerializationClass.get() instanceof ProxyConfigurationTypeDescriptor proxyClass) { + serializationSupport.registerProxyClass(condition.get(), Arrays.asList(proxyClass.interfaceNames())); + } else { + throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 15b0278d8a9d..349f586ff5f7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -24,17 +24,13 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; import com.oracle.svm.core.TypeResult; import com.oracle.svm.util.LogUtils; @@ -45,119 +41,40 @@ * Parses JSON describing classes, methods and fields and delegates their registration to a * {@link ReflectionConfigurationParserDelegate}. */ -public final class ReflectionConfigurationParser extends ConfigurationParser { +public abstract class ReflectionConfigurationParser extends ConfigurationParser { private static final String CONSTRUCTOR_NAME = ""; - private final ConfigurationConditionResolver conditionResolver; - private final ReflectionConfigurationParserDelegate delegate; - private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("name", "type", "allDeclaredConstructors", "allPublicConstructors", - "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", - "allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners", - "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, - "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); + protected final ConfigurationConditionResolver conditionResolver; + protected final ReflectionConfigurationParserDelegate delegate; private final boolean printMissingElements; - private final boolean treatAllNameEntriesAsType; public ReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, - boolean printMissingElements, boolean treatAllNameEntriesAsType) { + boolean printMissingElements) { super(strictConfiguration); this.conditionResolver = conditionResolver; this.printMissingElements = printMissingElements; this.delegate = delegate; - this.treatAllNameEntriesAsType = treatAllNameEntriesAsType; } - @Override - public void parseAndRegister(Object json, URI origin) { - parseClassArray(asList(json, "first level of document must be an array of class descriptors")); + public static ReflectionConfigurationParser create(String combinedFileKey, boolean strictMetadata, + ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, + boolean strictConfiguration, boolean printMissingElements, boolean treatAllEntriesAsType) { + if (strictMetadata) { + return new ReflectionMetadataParser<>(combinedFileKey, conditionResolver, delegate, strictConfiguration, printMissingElements); + } else { + return new LegacyReflectionConfigurationParser<>(conditionResolver, delegate, strictConfiguration, printMissingElements, treatAllEntriesAsType); + } } - private void parseClassArray(List classes) { + protected void parseClassArray(List classes) { for (Object clazz : classes) { parseClass(asMap(clazz, "second level of document must be class descriptor objects")); } } - private void parseClass(EconomicMap data) { - checkAttributes(data, "reflection class descriptor object", Collections.emptyList(), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); - checkHasExactlyOneAttribute(data, "reflection class descriptor object", List.of("name", "type")); - - Optional type = parseTypeOrName(data, treatAllNameEntriesAsType); - if (type.isEmpty()) { - return; - } - /* - * Classes registered using the old ("name") syntax requires elements (fields, methods, - * constructors, ...) to be registered for runtime queries, whereas the new ("type") syntax - * automatically registers all elements as queried. - */ - boolean isType = type.get().definedAsType(); - - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, isType); - TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); - if (!conditionResult.isPresent()) { - return; - } - - /* - * Even if primitives cannot be queried through Class.forName, they can be registered to - * allow getDeclaredMethods() and similar bulk queries at run time. - */ - C condition = conditionResult.get(); - TypeResult result = delegate.resolveType(condition, type.get(), true); - if (!result.isPresent()) { - handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); - return; - } - - C queryCondition = isType ? conditionResolver.alwaysTrue() : condition; - T clazz = result.get(); - delegate.registerType(conditionResult.get(), clazz); - - registerIfNecessary(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); - registerIfNecessary(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); - registerIfNecessary(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); - registerIfNecessary(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); - registerIfNecessary(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); - registerIfNecessary(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); - registerIfNecessary(data, isType, clazz, "allDeclaredClasses", () -> delegate.registerDeclaredClasses(queryCondition, clazz)); - registerIfNecessary(data, isType, clazz, "allRecordComponents", () -> delegate.registerRecordComponents(queryCondition, clazz)); - registerIfNecessary(data, isType, clazz, "allPermittedSubclasses", () -> delegate.registerPermittedSubclasses(queryCondition, clazz)); - registerIfNecessary(data, isType, clazz, "allNestMembers", () -> delegate.registerNestMembers(queryCondition, clazz)); - registerIfNecessary(data, isType, clazz, "allSigners", () -> delegate.registerSigners(queryCondition, clazz)); - registerIfNecessary(data, isType, clazz, "allPublicClasses", () -> delegate.registerPublicClasses(queryCondition, clazz)); - registerIfNecessary(data, isType, clazz, "queryAllDeclaredConstructors", () -> delegate.registerDeclaredConstructors(queryCondition, true, clazz)); - registerIfNecessary(data, isType, clazz, "queryAllPublicConstructors", () -> delegate.registerPublicConstructors(queryCondition, true, clazz)); - registerIfNecessary(data, isType, clazz, "queryAllDeclaredMethods", () -> delegate.registerDeclaredMethods(queryCondition, true, clazz)); - registerIfNecessary(data, isType, clazz, "queryAllPublicMethods", () -> delegate.registerPublicMethods(queryCondition, true, clazz)); - if (isType) { - delegate.registerDeclaredFields(queryCondition, true, clazz); - delegate.registerPublicFields(queryCondition, true, clazz); - } - registerIfNecessary(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); - MapCursor cursor = data.getEntries(); - while (cursor.advance()) { - String name = cursor.getKey(); - Object value = cursor.getValue(); - try { - switch (name) { - case "methods": - parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); - break; - case "queriedMethods": - parseMethods(condition, true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz); - break; - case "fields": - parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); - break; - } - } catch (LinkageError e) { - handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); - } - } - } + protected abstract void parseClass(EconomicMap data); - private void registerIfNecessary(EconomicMap data, boolean defaultValue, T clazz, String propertyName, Runnable register) { + protected void registerIfNotDefault(EconomicMap data, boolean defaultValue, T clazz, String propertyName, Runnable register) { if (data.containsKey(propertyName) ? asBoolean(data.get(propertyName), propertyName) : defaultValue) { try { register.run(); @@ -167,7 +84,7 @@ private void registerIfNecessary(EconomicMap data, boolean defau } } - private void parseFields(C condition, List fields, T clazz) { + protected void parseFields(C condition, List fields, T clazz) { for (Object field : fields) { parseField(condition, asMap(field, "Elements of 'fields' array must be field descriptor objects"), clazz); } @@ -187,7 +104,7 @@ private void parseField(C condition, EconomicMap data, T clazz) } } - private void parseMethods(C condition, boolean queriedOnly, List methods, T clazz) { + protected void parseMethods(C condition, boolean queriedOnly, List methods, T clazz) { for (Object method : methods) { parseMethod(condition, queriedOnly, asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); } @@ -270,7 +187,7 @@ private void handleMissingElement(String message) { handleMissingElement(message, null); } - private void handleMissingElement(String msg, Throwable cause) { + protected void handleMissingElement(String msg, Throwable cause) { if (printMissingElements) { String message = msg; if (cause != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java new file mode 100644 index 000000000000..33bd0028be6c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +class ReflectionMetadataParser extends ReflectionConfigurationParser { + private static final List OPTIONAL_REFLECT_METADATA_ATTRS = Arrays.asList(CONDITIONAL_KEY, + "allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", + "methods", "fields", "unsafeAllocated"); + + private final String combinedFileKey; + + ReflectionMetadataParser(String combinedFileKey, ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { + super(conditionResolver, delegate, strictConfiguration, printMissingElements); + this.combinedFileKey = combinedFileKey; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object reflectionJson = getFromGlobalFile(json, combinedFileKey); + if (reflectionJson != null) { + parseClassArray(asList(reflectionJson, "first level of document must be an array of class descriptors")); + } + } + + @Override + protected void parseClass(EconomicMap data) { + checkAttributes(data, "reflection class descriptor object", List.of(TYPE_KEY), OPTIONAL_REFLECT_METADATA_ATTRS); + + Optional type = parseTypeContents(data.get(TYPE_KEY)); + if (type.isEmpty()) { + return; + } + + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, true); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { + return; + } + C condition = conditionResult.get(); + + /* + * Even if primitives cannot be queried through Class.forName, they can be registered to + * allow getDeclaredMethods() and similar bulk queries at run time. + */ + TypeResult result = delegate.resolveType(condition, type.get(), true); + if (!result.isPresent()) { + handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); + return; + } + + C queryCondition = conditionResolver.alwaysTrue(); + T clazz = result.get(); + delegate.registerType(conditionResult.get(), clazz); + + delegate.registerDeclaredClasses(queryCondition, clazz); + delegate.registerRecordComponents(queryCondition, clazz); + delegate.registerPermittedSubclasses(queryCondition, clazz); + delegate.registerNestMembers(queryCondition, clazz); + delegate.registerSigners(queryCondition, clazz); + delegate.registerPublicClasses(queryCondition, clazz); + delegate.registerDeclaredConstructors(queryCondition, true, clazz); + delegate.registerPublicConstructors(queryCondition, true, clazz); + delegate.registerDeclaredMethods(queryCondition, true, clazz); + delegate.registerPublicMethods(queryCondition, true, clazz); + delegate.registerDeclaredFields(queryCondition, true, clazz); + delegate.registerPublicFields(queryCondition, true, clazz); + + registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + + MapCursor cursor = data.getEntries(); + while (cursor.advance()) { + String name = cursor.getKey(); + Object value = cursor.getValue(); + try { + switch (name) { + case "methods": + parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + break; + case "fields": + parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); + break; + } + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java index 2c5d5962ea17..01cf76743194 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,81 +32,61 @@ import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.jdk.localization.LocalizationSupport; import jdk.graal.compiler.util.json.JsonParserException; -public class ResourceConfigurationParser extends ConfigurationParser { - private final ResourcesRegistry registry; +public abstract class ResourceConfigurationParser extends ConfigurationParser { + protected final ResourcesRegistry registry; - private final ConfigurationConditionResolver conditionResolver; + protected final ConfigurationConditionResolver conditionResolver; - public ResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + public static ResourceConfigurationParser create(boolean strictMetadata, ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + if (strictMetadata) { + return new ResourceMetadataParser<>(conditionResolver, registry, strictConfiguration); + } else { + return new LegacyResourceConfigurationParser<>(conditionResolver, registry, strictConfiguration); + } + } + + protected ResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { super(strictConfiguration); this.registry = registry; this.conditionResolver = conditionResolver; } - @Override - public void parseAndRegister(Object json, URI origin) { - parseTopLevelObject(asMap(json, "first level of document must be an object")); + protected void parseBundlesObject(Object bundlesObject) { + List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); + for (Object bundle : bundles) { + parseBundle(bundle); + } } @SuppressWarnings("unchecked") - private void parseTopLevelObject(EconomicMap obj) { - Object resourcesObject = null; - Object globsObject = null; - Object bundlesObject = null; - MapCursor cursor = obj.getEntries(); - while (cursor.advance()) { - if ("resources".equals(cursor.getKey())) { - resourcesObject = cursor.getValue(); - } else if ("bundles".equals(cursor.getKey())) { - bundlesObject = cursor.getValue(); - } else if ("globs".equals(cursor.getKey())) { - globsObject = cursor.getValue(); - } - } - - if (globsObject != null) { - List globs = asList(globsObject, "Attribute 'globs' must be a list of glob patterns"); - for (Object object : globs) { - parseGlobEntry(object, registry::addGlob); + protected void parseResourcesObject(Object resourcesObject) { + if (resourcesObject instanceof EconomicMap) { // New format + EconomicMap resourcesObjectMap = (EconomicMap) resourcesObject; + checkAttributes(resourcesObjectMap, "resource descriptor object", Collections.singleton("includes"), Collections.singleton("excludes")); + Object includesObject = resourcesObjectMap.get("includes"); + Object excludesObject = resourcesObjectMap.get("excludes"); + + List includes = asList(includesObject, "Attribute 'includes' must be a list of resources"); + for (Object object : includes) { + parsePatternEntry(object, registry::addResources, "'includes' list"); } - } - - if (resourcesObject != null) { - if (resourcesObject instanceof EconomicMap) { // New format - EconomicMap resourcesObjectMap = (EconomicMap) resourcesObject; - checkAttributes(resourcesObjectMap, "resource descriptor object", Collections.singleton("includes"), Collections.singleton("excludes")); - Object includesObject = resourcesObjectMap.get("includes"); - Object excludesObject = resourcesObjectMap.get("excludes"); - List includes = asList(includesObject, "Attribute 'includes' must be a list of resources"); - for (Object object : includes) { - parsePatternEntry(object, registry::addResources, "'includes' list"); - } - - if (excludesObject != null) { - List excludes = asList(excludesObject, "Attribute 'excludes' must be a list of resources"); - for (Object object : excludes) { - parsePatternEntry(object, registry::ignoreResources, "'excludes' list"); - } - } - } else { // Old format: may be deprecated in future versions - List resources = asList(resourcesObject, "Attribute 'resources' must be a list of resources"); - for (Object object : resources) { - parsePatternEntry(object, registry::addResources, "'resources' list"); + if (excludesObject != null) { + List excludes = asList(excludesObject, "Attribute 'excludes' must be a list of resources"); + for (Object object : excludes) { + parsePatternEntry(object, registry::ignoreResources, "'excludes' list"); } } - } - if (bundlesObject != null) { - List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); - for (Object bundle : bundles) { - parseBundle(bundle); + } else { // Old format: may be deprecated in future versions + List resources = asList(resourcesObject, "Attribute 'resources' must be a list of resources"); + for (Object object : resources) { + parsePatternEntry(object, registry::addResources, "'resources' list"); } } } @@ -154,13 +133,9 @@ private static Locale parseLocale(Object input) { return locale; } - private interface GlobPatternConsumer { - void accept(T a, String b, String c); - } - private void parsePatternEntry(Object data, BiConsumer resourceRegistry, String parentType) { EconomicMap resource = asMap(data, "Elements of " + parentType + " must be a resource descriptor object"); - checkAttributes(resource, "resource and resource bundle descriptor object", Collections.singletonList("pattern"), Collections.singletonList(CONDITIONAL_KEY)); + checkAttributes(resource, "regex resource descriptor object", Collections.singletonList("pattern"), Collections.singletonList(CONDITIONAL_KEY)); TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource, false)); if (!resolvedConfigurationCondition.isPresent()) { return; @@ -171,9 +146,20 @@ private void parsePatternEntry(Object data, BiConsumer resourceRegist resourceRegistry.accept(resolvedConfigurationCondition.get(), value); } + protected void parseGlobsObject(Object globsObject) { + List globs = asList(globsObject, "Attribute 'globs' must be a list of glob patterns"); + for (Object object : globs) { + parseGlobEntry(object, registry::addGlob); + } + } + + private interface GlobPatternConsumer { + void accept(T a, String b, String c); + } + private void parseGlobEntry(Object data, GlobPatternConsumer resourceRegistry) { EconomicMap globObject = asMap(data, "Elements of 'globs' list must be a glob descriptor objects"); - checkAttributes(globObject, "resource and resource bundle descriptor object", Collections.singletonList(GLOB_KEY), List.of(CONDITIONAL_KEY, MODULE_KEY)); + checkAttributes(globObject, "glob resource descriptor object", Collections.singletonList(GLOB_KEY), List.of(CONDITIONAL_KEY, MODULE_KEY)); TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(globObject, false)); if (!resolvedConfigurationCondition.isPresent()) { return; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java new file mode 100644 index 000000000000..2e1b4720ed4a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; + +final class ResourceMetadataParser extends ResourceConfigurationParser { + ResourceMetadataParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + super(conditionResolver, registry, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object resourcesJson = getFromGlobalFile(json, RESOURCES_KEY); + if (resourcesJson != null) { + parseGlobsObject(resourcesJson); + } + Object bundlesJson = getFromGlobalFile(json, BUNDLES_KEY); + if (bundlesJson != null) { + parseBundlesObject(bundlesJson); + } + } +} 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 35722d605b20..87570bd767c9 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 @@ -25,107 +25,52 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Optional; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; import jdk.graal.compiler.util.json.JsonParserException; -public class SerializationConfigurationParser extends ConfigurationParser { +public abstract class SerializationConfigurationParser extends ConfigurationParser { public static final String CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY = "customTargetConstructorClass"; - private static final String SERIALIZATION_TYPES_KEY = "types"; - private static final String LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY = "lambdaCapturingTypes"; - private static final String PROXY_SERIALIZATION_TYPES_KEY = "proxies"; - private final ConfigurationConditionResolver conditionResolver; - private final RuntimeSerializationSupport serializationSupport; - private final ProxyConfigurationParser proxyConfigurationParser; - private final boolean treatAllNameEntriesAsType; + protected final ConfigurationConditionResolver conditionResolver; + protected final RuntimeSerializationSupport serializationSupport; - public SerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { - super(strictConfiguration); - this.serializationSupport = serializationSupport; - this.proxyConfigurationParser = new ProxyConfigurationParser<>(conditionResolver, strictConfiguration, serializationSupport::registerProxyClass); - this.conditionResolver = conditionResolver; - this.treatAllNameEntriesAsType = false; - } - - @Override - public void parseAndRegister(Object json, URI origin) { - if (json instanceof List) { - parseOldConfiguration(asList(json, "First-level of document must be an array of serialization lists")); - } else if (json instanceof EconomicMap) { - parseNewConfiguration(asMap(json, "First-level of document must be a map of serialization types")); + public static SerializationConfigurationParser create(boolean strictMetadata, ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, + boolean strictConfiguration) { + if (strictMetadata) { + return new SerializationMetadataParser<>(conditionResolver, serializationSupport, strictConfiguration); } else { - throw new JsonParserException("First-level of document must either be an array of serialization lists or a map of serialization types"); + return new LegacySerializationConfigurationParser<>(conditionResolver, serializationSupport, strictConfiguration); } } - private void parseOldConfiguration(List listOfSerializationConfigurationObjects) { - parseSerializationTypes(asList(listOfSerializationConfigurationObjects, "Second-level of document must be serialization descriptor objects"), false); - } - - private void parseNewConfiguration(EconomicMap listOfSerializationConfigurationObjects) { - if (!listOfSerializationConfigurationObjects.containsKey(SERIALIZATION_TYPES_KEY) || !listOfSerializationConfigurationObjects.containsKey(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY)) { - throw new JsonParserException("Second-level of document must be arrays of serialization descriptor objects"); - } - - parseSerializationTypes(asList(listOfSerializationConfigurationObjects.get(SERIALIZATION_TYPES_KEY), "The types property must be an array of serialization descriptor objects"), false); - parseSerializationTypes( - asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), - "The lambdaCapturingTypes property must be an array of serialization descriptor objects"), - true); - - if (listOfSerializationConfigurationObjects.containsKey(PROXY_SERIALIZATION_TYPES_KEY)) { - proxyConfigurationParser.parseAndRegister(listOfSerializationConfigurationObjects.get(PROXY_SERIALIZATION_TYPES_KEY), null); - } + public SerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(strictConfiguration); + this.serializationSupport = serializationSupport; + this.conditionResolver = conditionResolver; } - private void parseSerializationTypes(List listOfSerializationTypes, boolean lambdaCapturingTypes) { + protected void parseSerializationTypes(List listOfSerializationTypes, boolean lambdaCapturingTypes) { for (Object serializationType : listOfSerializationTypes) { parseSerializationDescriptorObject(asMap(serializationType, "Third-level of document must be serialization descriptor objects"), lambdaCapturingTypes); } } - private void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { - if (lambdaCapturingType) { - checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CONDITIONAL_KEY)); - } else { - checkAttributes(data, "serialization descriptor object", Collections.emptySet(), Arrays.asList(TYPE_KEY, NAME_KEY, CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); - checkHasExactlyOneAttribute(data, "serialization descriptor object", List.of(TYPE_KEY, NAME_KEY)); - } - - Optional targetSerializationClass = parseTypeOrName(data, treatAllNameEntriesAsType); - if (targetSerializationClass.isEmpty()) { - return; - } - - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, targetSerializationClass.get().definedAsType()); - var condition = conditionResolver.resolveCondition(unresolvedCondition); - if (!condition.isPresent()) { - return; - } + protected abstract void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType); - if (lambdaCapturingType) { - String className = ((NamedConfigurationTypeDescriptor) targetSerializationClass.get()).name(); - serializationSupport.registerLambdaCapturingClass(condition.get(), className); + protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, C condition, Object optionalCustomCtorValue) { + String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { + serializationSupport.registerWithTargetConstructorClass(condition, namedClass.name(), customTargetConstructorClass); + } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { + serializationSupport.registerProxyClass(condition, Arrays.asList(proxyClass.interfaceNames())); } else { - Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); - String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; - if (targetSerializationClass.get() instanceof NamedConfigurationTypeDescriptor namedClass) { - serializationSupport.registerWithTargetConstructorClass(condition.get(), namedClass.name(), customTargetConstructorClass); - } else if (targetSerializationClass.get() instanceof ProxyConfigurationTypeDescriptor proxyClass) { - serializationSupport.registerProxyClass(condition.get(), Arrays.asList(proxyClass.interfaceNames())); - } else { - throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); - } + throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); } } } 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 new file mode 100644 index 000000000000..9b74ed811243 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + +final class SerializationMetadataParser extends SerializationConfigurationParser { + + SerializationMetadataParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(conditionResolver, serializationSupport, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object serializationJson = getFromGlobalFile(json, SERIALIZATION_KEY); + if (serializationJson != null) { + parseSerializationTypes(asList(serializationJson, "The serialization property must be an array of serialization descriptor object"), 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)); + + Optional targetSerializationClass = parseTypeContents(data.get(TYPE_KEY)); + if (targetSerializationClass.isEmpty()) { + return; + } + + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, true); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { + return; + } + + Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); + registerType(targetSerializationClass.get(), condition.get(), optionalCustomCtorValue); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index a6eccd09c233..1594cc8bb7d5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -385,9 +385,14 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* load and parse resource configuration files */ ConfigurationConditionResolver conditionResolver = new NativeImageConditionResolver(((FeatureImpl.BeforeAnalysisAccessImpl) access).getImageClassLoader(), ClassInitializationSupport.singleton()); - ResourceConfigurationParser parser = new ResourceConfigurationParser<>(conditionResolver, ResourcesRegistry.singleton(), + + ResourceConfigurationParser parser = ResourceConfigurationParser.create(true, conditionResolver, ResourcesRegistry.singleton(), + ConfigurationFiles.Options.StrictConfiguration.getValue()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, imageClassLoader, "resource"); + + ResourceConfigurationParser legacyParser = ResourceConfigurationParser.create(false, conditionResolver, ResourcesRegistry.singleton(), ConfigurationFiles.Options.StrictConfiguration.getValue()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "resource", + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, imageClassLoader, "resource", ConfigurationFiles.Options.ResourceConfigurationFiles, ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFile.RESOURCES.getFileName()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java index d12a2d18e797..d7ba93451876 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java @@ -24,12 +24,15 @@ */ package com.oracle.svm.hosted.config; +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllNameEntriesAsType; + import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Spliterator; @@ -42,6 +45,7 @@ import org.graalvm.nativeimage.impl.ReflectionRegistry; import com.oracle.svm.core.configure.ConfigurationConditionResolver; +import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.ReflectionConfigurationParser; @@ -53,13 +57,11 @@ import jdk.graal.compiler.util.json.JsonParserException; -import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllNameEntriesAsType; - public final class ConfigurationParserUtils { - public static ReflectionConfigurationParser> create( + public static ReflectionConfigurationParser> create(String combinedFileKey, boolean strictMetadata, ConfigurationConditionResolver conditionResolver, ReflectionRegistry registry, ProxyRegistry proxyRegistry, ImageClassLoader imageClassLoader) { - return new ReflectionConfigurationParser<>(conditionResolver, + return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, conditionResolver, RegistryAdapter.create(registry, proxyRegistry, imageClassLoader), ConfigurationFiles.Options.StrictConfiguration.getValue(), ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue(), TreatAllNameEntriesAsType.getValue()); @@ -82,6 +84,10 @@ public static int parseAndRegisterConfigurations(ConfigurationParser parser, Ima return parseAndRegisterConfigurations(parser, classLoader, featureName, directoryFileName, paths, resourceValues); } + public static int parseAndRegisterConfigurationsFromCombinedFile(ConfigurationParser parser, ImageClassLoader classLoader, String featureName) { + return parseAndRegisterConfigurations(parser, classLoader, featureName, ConfigurationFile.REACHABILITY_METADATA.getFileName(), Collections.emptyList(), Collections.emptyList()); + } + public static int parseAndRegisterConfigurations(ConfigurationParser parser, ImageClassLoader classLoader, String featureName, String directoryFileName, List paths, diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 530311c86e8a..8f99771bfc97 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted.jni; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; + import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -200,8 +202,11 @@ public void afterRegistration(AfterRegistrationAccess arg) { ConfigurationConditionResolver conditionResolver = new NativeImageConditionResolver(access.getImageClassLoader(), ClassInitializationSupport.singleton()); - ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(conditionResolver, runtimeSupport, null, access.getImageClassLoader()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "JNI", + ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(JNI_KEY, true, conditionResolver, runtimeSupport, null, access.getImageClassLoader()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "JNI"); + ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, conditionResolver, runtimeSupport, null, + access.getImageClassLoader()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "JNI", ConfigurationFiles.Options.JNIConfigurationFiles, ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index e0a42e10d187..bad951b53e91 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted.reflect; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; + import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; @@ -277,8 +279,12 @@ public void duringSetup(DuringSetupAccess a) { var conditionResolver = new NativeImageConditionResolver(access.getImageClassLoader(), ClassInitializationSupport.singleton()); reflectionData.duringSetup(access.getMetaAccess(), aUniverse); ProxyRegistry proxyRegistry = ImageSingletons.lookup(ProxyRegistry.class); - ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(conditionResolver, reflectionData, proxyRegistry, access.getImageClassLoader()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "reflection", + ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(REFLECTION_KEY, true, conditionResolver, reflectionData, proxyRegistry, + access.getImageClassLoader()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "reflection"); + ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, conditionResolver, reflectionData, proxyRegistry, + access.getImageClassLoader()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "reflection", ConfigurationFiles.Options.ReflectionConfigurationFiles, ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()); 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 724952f45132..3b98142c82e3 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 @@ -128,16 +128,22 @@ public void duringSetup(DuringSetupAccess a) { SerializationDenyRegistry serializationDenyRegistry = new SerializationDenyRegistry(typeResolver); serializationBuilder = new SerializationBuilder(serializationDenyRegistry, access, typeResolver, ImageSingletons.lookup(ProxyRegistry.class)); ImageSingletons.add(RuntimeSerializationSupport.class, serializationBuilder); - SerializationConfigurationParser denyCollectorParser = new SerializationConfigurationParser<>(conditionResolver, serializationDenyRegistry, - ConfigurationFiles.Options.StrictConfiguration.getValue()); + Boolean strictConfiguration = ConfigurationFiles.Options.StrictConfiguration.getValue(); + + SerializationConfigurationParser parser = SerializationConfigurationParser.create(true, conditionResolver, serializationBuilder, + strictConfiguration); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, imageClassLoader, "serialization"); + + SerializationConfigurationParser denyCollectorParser = SerializationConfigurationParser.create(false, conditionResolver, serializationDenyRegistry, + strictConfiguration); ConfigurationParserUtils.parseAndRegisterConfigurations(denyCollectorParser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationDenyConfigurationFiles, ConfigurationFiles.Options.SerializationDenyConfigurationResources, ConfigurationFile.SERIALIZATION_DENY.getFileName()); - SerializationConfigurationParser parser = new SerializationConfigurationParser<>(conditionResolver, serializationBuilder, - ConfigurationFiles.Options.StrictConfiguration.getValue()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "serialization", + SerializationConfigurationParser legacyParser = SerializationConfigurationParser.create(false, conditionResolver, serializationBuilder, + strictConfiguration); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationConfigurationFiles, ConfigurationFiles.Options.SerializationConfigurationResources, ConfigurationFile.SERIALIZATION.getFileName());