diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 7fb0632652c6..8a38c76c0c75 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -4,7 +4,7 @@ "sourceinprojectwhitelist" : [], "groupId" : "org.graalvm.compiler", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "release" : False, "url" : "http://www.graalvm.org/", "developer" : { diff --git a/espresso/mx.espresso/suite.py b/espresso/mx.espresso/suite.py index e58af602678d..e4d92863ae2b 100644 --- a/espresso/mx.espresso/suite.py +++ b/espresso/mx.espresso/suite.py @@ -23,7 +23,7 @@ suite = { "mxversion": "6.44.0", "name": "espresso", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "release" : False, "groupId" : "org.graalvm.espresso", "url" : "https://www.graalvm.org/reference-manual/java-on-truffle/", diff --git a/regex/mx.regex/suite.py b/regex/mx.regex/suite.py index 661486dd7989..971f50415202 100644 --- a/regex/mx.regex/suite.py +++ b/regex/mx.regex/suite.py @@ -43,7 +43,7 @@ "name" : "regex", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "release" : False, "groupId" : "org.graalvm.regex", "url" : "http://www.graalvm.org/", diff --git a/sdk/mx.sdk/mx_sdk_shaded.py b/sdk/mx.sdk/mx_sdk_shaded.py index e43bd6337676..14e3adf50db3 100644 --- a/sdk/mx.sdk/mx_sdk_shaded.py +++ b/sdk/mx.sdk/mx_sdk_shaded.py @@ -315,8 +315,8 @@ def glob_match(path, pattern): """ assert isinstance(path, PurePath), path if sys.version_info[:2] >= (3, 13): - # Since Python 3.13, PurePath.match already supports '**'. - return path.match(pattern) + # Python 3.13+: PurePath.full_match already supports '**'. + return path.full_match(pattern) pathType = type(path) patternParts = pathType(pattern).parts diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index ac3f731e4c2c..323113c689c5 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -41,7 +41,7 @@ suite = { "mxversion": "6.39.0", "name" : "sdk", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "release" : False, "sourceinprojectwhitelist" : [], "url" : "https://github.com/oracle/graal", diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index c2b60e1f5d06..7f9bea5d9228 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -32,7 +32,7 @@ from glob import glob from contextlib import contextmanager from os.path import join, exists, dirname -import pipes +import shlex from argparse import ArgumentParser import fnmatch import collections @@ -585,7 +585,7 @@ def dummy_harness(test_deps, vm_launcher, vm_args): unittest_image = native_image(['-ea', '-esa'] + build_args + extra_image_args + [macro_junit + '=' + unittest_file] + svm_experimental_options(['-H:Path=' + junit_test_dir]), env=custom_env) image_pattern_replacement = unittest_image + ".exe" if mx.is_windows() else unittest_image run_args = [arg.replace('${unittest.image}', image_pattern_replacement) for arg in run_args] - mx.log('Running: ' + ' '.join(map(pipes.quote, [unittest_image] + run_args))) + mx.log('Running: ' + ' '.join(map(shlex.quote, [unittest_image] + run_args))) mx.run([unittest_image] + run_args) finally: if not preserve_image: diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index ccd338194bf4..e45afe67e1e0 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -2,7 +2,7 @@ suite = { "mxversion": "6.27.1", "name": "substratevm", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "release" : False, "url" : "https://github.com/oracle/graal/tree/master/substratevm", 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 38c5440b0feb..6c70847ea12a 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 @@ -229,8 +229,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 600b21440bb4..edb48f64a3b6 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 @@ -38,9 +38,9 @@ import org.junit.Test; import com.oracle.svm.configure.config.ResourceConfiguration; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ResourceConfigurationParser; import com.oracle.svm.core.configure.ResourcesRegistry; +import com.oracle.svm.core.util.json.JsonWriter; public class ResourceConfigurationTest { @@ -117,7 +117,7 @@ public void addClassBasedResourceBundle(ConfigurationCondition condition, String } }; - ResourceConfigurationParser rcp = new ResourceConfigurationParser(registry, true); + ResourceConfigurationParser rcp = ResourceConfigurationParser.create(false, 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 26a7c7a596da..25962c529838 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 @@ -26,10 +26,11 @@ import java.util.function.Consumer; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.configure.ConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.util.json.JsonPrintable; + public abstract class ConfigurationBase, P> implements JsonPrintable { public abstract boolean isEmpty(); @@ -68,5 +69,5 @@ public T copyAndFilter(P predicate) { return copyAnd(copy -> copy.removeIf(predicate)); } - public abstract ConfigurationParser createParser(); + public abstract ConfigurationParser createParser(boolean strictMetadata); } 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 166eb5179747..4397508d5fa2 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,6 +24,9 @@ */ 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; @@ -32,10 +35,10 @@ import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.config.conditional.ConditionalConfigurationPredicate; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.core.util.json.JsonWriter; public class ConfigurationSet { @FunctionalInterface @@ -66,7 +69,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)); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index 62a199c3a15e..095ea107b671 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -31,6 +31,8 @@ import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.core.TypeResult; +import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; +import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate; public class ParserConfigurationAdapter implements ReflectionConfigurationParserDelegate { @@ -42,114 +44,114 @@ public ParserConfigurationAdapter(TypeConfiguration configuration) { } @Override - public TypeResult resolveCondition(String typeName) { - return TypeResult.forType(typeName, ConfigurationCondition.create(typeName)); + public TypeResult resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + if (typeDescriptor instanceof NamedConfigurationTypeDescriptor namedDescriptor) { + String typeName = namedDescriptor.name(); + ConfigurationType type = configuration.get(condition, typeName); + ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName); + return TypeResult.forType(typeName, result); + } else { + return TypeResult.forException(typeDescriptor.toString(), null); + } } @Override - public TypeResult resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { - ConfigurationType type = configuration.get(condition, typeName); - ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName); - return TypeResult.forType(typeName, result); - } - - @Override - public void registerType(ConfigurationType type) { + public void registerType(ConfigurationCondition condition, ConfigurationType type) { configuration.add(type); } @Override - public void registerField(ConfigurationType type, String fieldName, boolean finalButWritable) { + public void registerField(ConfigurationCondition condition, ConfigurationType type, String fieldName, boolean finalButWritable) { type.addField(fieldName, ConfigurationMemberDeclaration.PRESENT, finalButWritable); } @Override - public boolean registerAllMethodsWithName(boolean queriedOnly, ConfigurationType type, String methodName) { + public boolean registerAllMethodsWithName(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type, String methodName) { type.addMethodsWithName(methodName, ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); return true; } @Override - public boolean registerAllConstructors(boolean queriedOnly, ConfigurationType type) { + public boolean registerAllConstructors(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.addMethodsWithName(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); return true; } @Override - public void registerUnsafeAllocated(ConfigurationType type) { + public void registerUnsafeAllocated(ConfigurationCondition condition, ConfigurationType type) { type.setUnsafeAllocated(); } @Override - public void registerMethod(boolean queriedOnly, ConfigurationType type, String methodName, List methodParameterTypes) { + public void registerMethod(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type, String methodName, List methodParameterTypes) { type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerConstructor(boolean queriedOnly, ConfigurationType type, List methodParameterTypes) { + public void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type, List methodParameterTypes) { type.addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerPublicClasses(ConfigurationType type) { + public void registerPublicClasses(ConfigurationCondition condition, ConfigurationType type) { type.setAllPublicClasses(); } @Override - public void registerDeclaredClasses(ConfigurationType type) { + public void registerDeclaredClasses(ConfigurationCondition condition, ConfigurationType type) { type.setAllDeclaredClasses(); } @Override - public void registerRecordComponents(ConfigurationType type) { + public void registerRecordComponents(ConfigurationCondition condition, ConfigurationType type) { type.setAllRecordComponents(); } @Override - public void registerPermittedSubclasses(ConfigurationType type) { + public void registerPermittedSubclasses(ConfigurationCondition condition, ConfigurationType type) { type.setAllPermittedSubclasses(); } @Override - public void registerNestMembers(ConfigurationType type) { + public void registerNestMembers(ConfigurationCondition condition, ConfigurationType type) { type.setAllNestMembers(); } @Override - public void registerSigners(ConfigurationType type) { + public void registerSigners(ConfigurationCondition condition, ConfigurationType type) { type.setAllSigners(); } @Override - public void registerPublicFields(ConfigurationType type) { + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllPublicFields(); } @Override - public void registerDeclaredFields(ConfigurationType type) { + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllDeclaredFields(); } @Override - public void registerPublicMethods(boolean queriedOnly, ConfigurationType type) { + public void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllPublicMethods(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerDeclaredMethods(boolean queriedOnly, ConfigurationType type) { + public void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllDeclaredMethods(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerPublicConstructors(boolean queriedOnly, ConfigurationType type) { + public void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllPublicConstructors(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerDeclaredConstructors(boolean queriedOnly, ConfigurationType type) { + public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllDeclaredConstructors(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } 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 40229ed4648f..87e86fa0309f 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 @@ -34,14 +34,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import com.oracle.svm.core.configure.ConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonWriter; 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.hub.PredefinedClassesSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; public final class PredefinedClassesConfiguration extends ConfigurationBase { private final Path[] classDestinationDirs; @@ -164,7 +165,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); } 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 9859a20d9e55..98715fbba05c 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 @@ -31,13 +31,14 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import com.oracle.svm.core.configure.ConfigurationParser; -import com.oracle.svm.core.configure.ProxyConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.configure.ProxyConfigurationParser; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; public final class ProxyConfiguration extends ConfigurationBase { private final Set>> interfaceLists = ConcurrentHashMap.newKeySet(); @@ -129,8 +130,9 @@ public static void printProxyInterfaces(JsonWriter writer, List interfaceLists.add(new ConditionalElement<>(cond, intfs))); } @Override 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 803fbb86fa34..b990bdca9b43 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 @@ -38,13 +38,13 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonPrinter; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.ResourceConfigurationParser; import com.oracle.svm.core.configure.ResourcesRegistry; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonPrinter; +import com.oracle.svm.core.util.json.JsonWriter; public final class ResourceConfiguration extends ConfigurationBase { @@ -249,8 +249,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { - return new ResourceConfigurationParser(new ResourceConfiguration.ParserAdapter(this), true); + public ConfigurationParser createParser(boolean strictMetadata) { + return ResourceConfigurationParser.create(strictMetadata, new ResourceConfiguration.ParserAdapter(this), true); } private static void printResourceBundle(BundleConfiguration config, JsonWriter writer) throws IOException { 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 1ed2ed4d756b..c59d78976a54 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 @@ -37,11 +37,11 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.SerializationConfigurationParser; +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.core.util.json.JsonWriter; public final class SerializationConfiguration extends ConfigurationBase implements RuntimeSerializationSupport { @@ -123,8 +123,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { - return new SerializationConfigurationParser(this, true); + public ConfigurationParser createParser(boolean strictMetadata) { + return SerializationConfigurationParser.create(strictMetadata, this, true); } private void printProxies(JsonWriter writer) throws IOException { 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 b90efed5dfdb..32d02920d90d 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 @@ -32,24 +32,28 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import com.oracle.svm.core.configure.ConfigurationParser; -import com.oracle.svm.core.configure.ReflectionConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.configure.ReflectionConfigurationParser; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; public final class TypeConfiguration extends ConfigurationBase { private final ConcurrentMap, 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 @@ -142,8 +146,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { - return new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(this), true, false); + public ConfigurationParser createParser(boolean strictMetadata) { + return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, new ParserConfigurationAdapter(this), true, false); } @Override 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 e7ee09dd8ccb..cb56f8c7c52d 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 @@ -33,10 +33,10 @@ import org.graalvm.util.json.JSONParserException; import com.oracle.svm.configure.config.ConfigurationSet; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.core.util.json.JsonWriter; public class PartialConfigurationWithOrigins extends ConfigurationParser implements JsonPrintable { private static final ConfigurationSet emptyConfigurationSet = new ConfigurationSet(); @@ -169,7 +169,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/ConfigurationConditionResolver.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java new file mode 100644 index 000000000000..f63e5947d040 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, 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 org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +public interface ConfigurationConditionResolver { + + static ConfigurationConditionResolver identityResolver() { + return new ConfigurationConditionResolver() { + @Override + public TypeResult resolveCondition(ConfigurationCondition unresolvedCondition) { + return TypeResult.forType(unresolvedCondition.getTypeName(), unresolvedCondition); + } + + @Override + public ConfigurationCondition alwaysTrue() { + return ConfigurationCondition.alwaysTrue(); + } + }; + } + + TypeResult resolveCondition(ConfigurationCondition unresolvedCondition); + + ConfigurationCondition alwaysTrue(); +} 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..8ec71e14ba5a 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,34 @@ */ 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); + /* Combined file */ + REACHABILITY_METADATA("reachability-metadata", null, true, true), + /* Main metadata categories (order matters) */ + REFLECTION("reflect", REFLECTION_KEY, true, false), + RESOURCES("resource", RESOURCES_KEY, true, false), + SERIALIZATION("serialization", SERIALIZATION_KEY, true, false), + JNI("jni", JNI_KEY, true, false), + /* Deprecated metadata categories */ + DYNAMIC_PROXY("proxy", null, true, false), + PREDEFINED_CLASSES_NAME("predefined-classes", null, true, false), + /* Non-metadata categories */ + FOREIGN("foreign", null, false, false), + SERIALIZATION_DENY("serialization-deny", null, false, 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 +60,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 +84,7 @@ public String getFileName(String suffix) { } public boolean canBeGeneratedByAgent() { - return canAgentGenerate; + return canAgentGenerate && !combinedFile; } public static ConfigurationFile getByName(String name) { @@ -82,6 +101,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/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index c78a2ea1d5ee..d32581732677 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -96,6 +96,11 @@ public static final class Options { @Option(help = "Resources describing program elements to be made accessible via JNI (see JNIConfigurationFiles).", type = OptionType.User)// public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "Resources describing reachability metadata needed for the program " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json", type = OptionType.User)// + public static final HostedOptionKey ReachabilityMetadataResources = new HostedOptionKey<>( + LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "Files describing stubs allowing foreign calls.", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey ForeignConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); 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 49b77297caf5..9333a6955ab2 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -25,6 +25,7 @@ package com.oracle.svm.core.configure; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -37,6 +38,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import org.graalvm.collections.EconomicMap; @@ -61,6 +63,18 @@ public static InputStream openStream(URI uri) throws IOException { public static final String CONDITIONAL_KEY = "condition"; public static final String TYPE_REACHABLE_KEY = "typeReachable"; + public static final String TYPE_REACHED_KEY = "typeReached"; + 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<>(); private final boolean strictSchema; @@ -71,6 +85,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 + */ } } @@ -84,6 +103,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) { @@ -127,6 +152,23 @@ protected void checkAttributes(EconomicMap map, String type, Col } } + public static void checkHasExactlyOneAttribute(EconomicMap map, String type, Collection alternativeAttributes) { + boolean attributeFound = false; + for (String key : map.getKeys()) { + if (alternativeAttributes.contains(key)) { + if (attributeFound) { + String message = "Exactly one of [" + String.join(", ", alternativeAttributes) + "] must be set in " + type; + throw new JSONParserException(message); + } + attributeFound = true; + } + } + if (!attributeFound) { + String message = "Exactly one of [" + String.join(", ", alternativeAttributes) + "] must be set in " + type; + throw new JSONParserException(message); + } + } + /** * Used to warn about schema errors in configuration files. Should never be used if the type is * missing. @@ -135,7 +177,7 @@ protected void checkAttributes(EconomicMap map, String type, Col */ protected void warnOrFailOnSchemaError(String message) { if (strictSchema) { - throw new JSONParserException(message); + failOnSchemaError(message); } else { LogUtils.warning(message); } @@ -180,18 +222,89 @@ protected static long asLong(Object value, String propertyName) { throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); } - protected ConfigurationCondition parseCondition(EconomicMap data) { + private static boolean alreadyWarned = false; + + protected ConfigurationCondition parseCondition(EconomicMap data, boolean runtimeCondition) { Object conditionData = data.get(CONDITIONAL_KEY); if (conditionData != null) { EconomicMap conditionObject = asMap(conditionData, "Attribute 'condition' must be an object"); - Object conditionType = conditionObject.get(TYPE_REACHABLE_KEY); - if (conditionType instanceof String) { - return ConfigurationCondition.create((String) conditionType); - } else { - warnOrFailOnSchemaError("'" + TYPE_REACHABLE_KEY + "' should be of type string"); + if (conditionObject.containsKey(TYPE_REACHABLE_KEY) && conditionObject.containsKey(TYPE_REACHED_KEY)) { + failOnSchemaError("condition can not have both '" + TYPE_REACHED_KEY + "' and '" + TYPE_REACHABLE_KEY + "' set."); + } else if (conditionObject.isEmpty()) { + failOnSchemaError("condition can not be empty"); + } + + if (conditionObject.containsKey(TYPE_REACHED_KEY)) { + if (!runtimeCondition) { + failOnSchemaError("'" + TYPE_REACHED_KEY + "' condition cannot be used in older schemas. Please migrate the file to the latest schema."); + } + Object object = conditionObject.get(TYPE_REACHED_KEY); + var condition = parseTypeContents(object); + if (condition.isPresent()) { + if (!alreadyWarned) { + LogUtils.warning("Found typeReached condition in JSON configuration files. " + + "The typeReached condition is not supported by this version of GraalVM and will be considered as always true. " + + "Please consider upgrading to the latest GraalVM version to be able to use all the latest features."); + alreadyWarned = true; + } + return ConfigurationCondition.alwaysTrue(); + } + } else if (conditionObject.containsKey(TYPE_REACHABLE_KEY)) { + if (runtimeCondition) { + failOnSchemaError("'" + TYPE_REACHABLE_KEY + "' condition can not be used with the latest schema. Please use '" + TYPE_REACHED_KEY + "'."); + } + Object object = conditionObject.get(TYPE_REACHABLE_KEY); + var condition = parseTypeContents(object); + if (condition.isPresent()) { + String className = ((NamedConfigurationTypeDescriptor) condition.get()).name(); + return ConfigurationCondition.create(className); + } } } + /* + * Ensure forward-compatibility with condition types added in the future + */ return ConfigurationCondition.alwaysTrue(); } + private static JSONParserException failOnSchemaError(String message) { + throw new JSONParserException(message); + } + + protected record TypeDescriptorWithOrigin(ConfigurationTypeDescriptor typeDescriptor, boolean definedAsType) { + } + + protected static Optional parseName(EconomicMap data, boolean treatAllNameEntriesAsType) { + Object name = data.get(NAME_KEY); + if (name != null) { + NamedConfigurationTypeDescriptor typeDescriptor = new NamedConfigurationTypeDescriptor(asString(name)); + return Optional.of(new TypeDescriptorWithOrigin(typeDescriptor, treatAllNameEntriesAsType)); + } else { + throw failOnSchemaError("must have type or name specified for an element"); + } + } + + protected static Optional parseTypeContents(Object typeObject) { + if (typeObject instanceof String stringValue) { + return Optional.of(new NamedConfigurationTypeDescriptor(stringValue)); + } else { + EconomicMap type = asMap(typeObject, "type descriptor should be a string or object"); + if (type.containsKey(PROXY_KEY)) { + checkHasExactlyOneAttribute(type, "type descriptor object", Set.of(PROXY_KEY)); + return Optional.of(getProxyDescriptor(type.get(PROXY_KEY))); + } + /* + * 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(); + } + } + + private static ProxyConfigurationTypeDescriptor getProxyDescriptor(Object proxyObject) { + List proxyInterfaces = asList(proxyObject, "proxy interface content should be an interface list"); + List proxyInterfaceNames = proxyInterfaces.stream().map(obj -> asString(obj, "proxy")).toList(); + return new ProxyConfigurationTypeDescriptor(proxyInterfaceNames); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..49e04564f2d4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java @@ -0,0 +1,80 @@ +/* + * 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.util.Collection; + +import org.graalvm.nativeimage.ImageInfo; + +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.util.LogUtils; + +import jdk.vm.ci.meta.MetaUtil; + +/** + * Provides a representation of a Java type based on String type names. This is used to parse types + * in configuration files. The supported types are: + * + *
    + *
  • Named types: regular Java types described by their fully qualified name.
  • + *
+ */ +public interface ConfigurationTypeDescriptor extends Comparable, JsonPrintable { + static String canonicalizeTypeName(String typeName) { + if (typeName == null) { + return null; + } + String name = typeName; + if (name.indexOf('[') != -1) { + /* accept "int[][]", "java.lang.String[]" */ + name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); + } + return name; + } + + enum Kind { + NAMED, + PROXY + } + + Kind getDescriptorType(); + + @Override + String toString(); + + /** + * Returns the qualified names of all named Java types (excluding proxy classes, lambda classes + * and similar anonymous classes) required for this type descriptor to properly describe its + * type. This is used to filter configurations based on a String-based class filter. + */ + Collection getAllQualifiedJavaNames(); + + static String checkQualifiedJavaName(String javaName) { + if (ImageInfo.inImageBuildtimeCode() && !(javaName.indexOf('/') == -1 || javaName.indexOf('/') > javaName.lastIndexOf('.'))) { + LogUtils.warning("Type descriptor requires qualified Java name, not internal representation: %s", javaName); + } + return canonicalizeTypeName(javaName); + } +} 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..064e99e9017e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java @@ -0,0 +1,142 @@ +/* + * 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.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +final class LegacyReflectionConfigurationParser extends ReflectionConfigurationParser { + + private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("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", List.of(NAME_KEY), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); + + Optional type = parseName(data, treatAllNameEntriesAsType); + if (type.isEmpty()) { + return; + } + ConfigurationTypeDescriptor typeDescriptor = type.get().typeDescriptor(); + /* + * 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(); + + ConfigurationCondition 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. + */ + ConfigurationCondition condition = conditionResult.get(); + TypeResult result = delegate.resolveType(condition, typeDescriptor, true); + if (!result.isPresent()) { + handleMissingElement("Could not resolve " + typeDescriptor + " for reflection configuration.", result.getException()); + return; + } + + ConfigurationCondition 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..525b43cc1d49 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.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.Collections; +import java.util.List; +import java.util.function.BiConsumer; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +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")); + } + + @Override + protected ConfigurationCondition parseCondition(EconomicMap data) { + return parseCondition(data, false); + } + + 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); + } + } + + @SuppressWarnings("unchecked") + private 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 (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"); + } + } + } + + private void parsePatternEntry(Object data, BiConsumer resourceRegistry, String parentType) { + EconomicMap resource = asMap(data, "Elements of " + parentType + " must be a resource descriptor object"); + checkAttributes(resource, "regex resource descriptor object", Collections.singletonList("pattern"), Collections.singletonList(CONDITIONAL_KEY)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource, false)); + if (!resolvedConfigurationCondition.isPresent()) { + return; + } + + Object valueObject = resource.get("pattern"); + String value = asString(valueObject, "pattern"); + resourceRegistry.accept(resolvedConfigurationCondition.get(), value); + } +} 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..b94f83b3812e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java @@ -0,0 +1,105 @@ +/* + * 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 org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; +import org.graalvm.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(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.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); + } + + NamedConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); + ConfigurationCondition unresolvedCondition = parseCondition(data, false); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { + return; + } + + if (lambdaCapturingType) { + String className = targetSerializationClass.name(); + serializationSupport.registerLambdaCapturingClass(condition.get(), className); + } else { + Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); + String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + serializationSupport.registerWithTargetConstructorClass(condition.get(), targetSerializationClass.name(), customTargetConstructorClass); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..86b33152bf47 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.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.io.IOException; +import java.util.Collection; +import java.util.Collections; + +import com.oracle.svm.core.util.json.JsonWriter; + +public record NamedConfigurationTypeDescriptor(String name) implements ConfigurationTypeDescriptor { + + public NamedConfigurationTypeDescriptor(String name) { + this.name = ConfigurationTypeDescriptor.checkQualifiedJavaName(name); + } + + @Override + public Kind getDescriptorType() { + return Kind.NAMED; + } + + @Override + public String toString() { + return name; + } + + @Override + public Collection getAllQualifiedJavaNames() { + return Collections.singleton(name); + } + + @Override + public int compareTo(ConfigurationTypeDescriptor other) { + if (other instanceof NamedConfigurationTypeDescriptor namedOther) { + return name.compareTo(namedOther.name); + } else { + return getDescriptorType().compareTo(other.getDescriptorType()); + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.quote(name); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java index d910374f1e0f..816cd15b9224 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java @@ -27,24 +27,29 @@ import java.net.URI; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.util.json.JSONParserException; +import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; /** * Parses JSON describing lists of interfaces and register them in the {@link DynamicProxyRegistry}. */ public final class ProxyConfigurationParser extends ConfigurationParser { - private final Consumer>> interfaceListConsumer; - public ProxyConfigurationParser(Consumer>> interfaceListConsumer, boolean strictConfiguration) { + private final ConfigurationConditionResolver conditionResolver; + + private final BiConsumer> proxyConfigConsumer; + + public ProxyConfigurationParser(boolean strictConfiguration, BiConsumer> proxyConfigConsumer) { super(strictConfiguration); - this.interfaceListConsumer = interfaceListConsumer; + this.proxyConfigConsumer = proxyConfigConsumer; + this.conditionResolver = ConfigurationConditionResolver.identityResolver(); } @Override @@ -58,7 +63,7 @@ private void parseTopLevelArray(List proxyConfiguration) { for (Object proxyConfigurationObject : proxyConfiguration) { if (proxyConfigurationObject instanceof List) { foundInterfaceLists = true; - parseInterfaceList(ConfigurationCondition.alwaysTrue(), asList(proxyConfigurationObject, "")); + parseInterfaceList(conditionResolver.alwaysTrue(), asList(proxyConfigurationObject, "")); } else if (proxyConfigurationObject instanceof EconomicMap) { foundProxyConfigurationObjects = true; parseWithConditionalConfig(asMap(proxyConfigurationObject, "")); @@ -75,7 +80,7 @@ private void parseInterfaceList(ConfigurationCondition condition, List data) List interfaces = data.stream().map(ConfigurationParser::asString).collect(Collectors.toList()); try { - interfaceListConsumer.accept(new ConditionalElement<>(condition, interfaces)); + proxyConfigConsumer.accept(condition, interfaces); } catch (Exception e) { throw new JSONParserException(e.toString()); } @@ -83,7 +88,10 @@ private void parseInterfaceList(ConfigurationCondition condition, List data) private void parseWithConditionalConfig(EconomicMap proxyConfigObject) { checkAttributes(proxyConfigObject, "proxy descriptor object", Collections.singleton("interfaces"), Collections.singletonList(CONDITIONAL_KEY)); - ConfigurationCondition condition = parseCondition(proxyConfigObject); - parseInterfaceList(condition, asList(proxyConfigObject.get("interfaces"), "The interfaces property must be an array of fully qualified interface names")); + ConfigurationCondition condition = parseCondition(proxyConfigObject, false); + TypeResult resolvedCondition = conditionResolver.resolveCondition(condition); + if (resolvedCondition.isPresent()) { + parseInterfaceList(resolvedCondition.get(), asList(proxyConfigObject.get("interfaces"), "The interfaces property must be an array of fully qualified interface names")); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..a085ae2ab252 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java @@ -0,0 +1,73 @@ +/* + * 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.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.oracle.svm.core.reflect.proxy.DynamicProxySupport; +import com.oracle.svm.core.util.json.JsonPrinter; +import com.oracle.svm.core.util.json.JsonWriter; + +public record ProxyConfigurationTypeDescriptor(List interfaceNames) implements ConfigurationTypeDescriptor { + + public ProxyConfigurationTypeDescriptor(List interfaceNames) { + this.interfaceNames = interfaceNames.stream().map(ConfigurationTypeDescriptor::checkQualifiedJavaName).toList(); + } + + @Override + public Kind getDescriptorType() { + return Kind.PROXY; + } + + @Override + public String toString() { + return DynamicProxySupport.proxyTypeDescriptor(interfaceNames.toArray(String[]::new)); + } + + @Override + public Collection getAllQualifiedJavaNames() { + return interfaceNames; + } + + @Override + public int compareTo(ConfigurationTypeDescriptor other) { + if (other instanceof ProxyConfigurationTypeDescriptor proxyOther) { + return Arrays.compare(interfaceNames.toArray(String[]::new), proxyOther.interfaceNames.toArray(String[]::new)); + } else { + return getDescriptorType().compareTo(other.getDescriptorType()); + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.appendObjectStart(); + writer.quote("proxy").appendFieldSeparator(); + JsonPrinter.printCollection(writer, interfaceNames, null, (String p, JsonWriter w) -> w.quote(p)); + writer.appendObjectEnd(); + } +} 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 166a67838b48..202b6c3c2669 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -32,7 +31,6 @@ import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.util.json.JSONParserException; @@ -43,182 +41,61 @@ * 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 ReflectionConfigurationParserDelegate delegate; - private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("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; - public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { - this(delegate, true, false); - } - - public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, boolean printMissingElements) { + public ReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { super(strictConfiguration); + this.conditionResolver = conditionResolver; this.printMissingElements = printMissingElements; this.delegate = delegate; } - @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, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { + if (strictMetadata) { + return new ReflectionMetadataParser<>(combinedFileKey, ConfigurationConditionResolver.identityResolver(), delegate, strictConfiguration, printMissingElements); + } else { + return new LegacyReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), delegate, strictConfiguration, printMissingElements, false); + } } - 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.singleton("name"), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); - - Object classObject = data.get("name"); - String className = asString(classObject, "name"); + protected abstract void parseClass(EconomicMap data); - TypeResult conditionResult = delegate.resolveCondition(parseCondition(data).getTypeName()); - if (!conditionResult.isPresent()) { - return; - } - ConfigurationCondition 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, className, true); - if (!result.isPresent()) { - handleMissingElement("Could not resolve class " + className + " for reflection configuration.", result.getException()); - return; - } - T clazz = result.get(); - delegate.registerType(clazz); - - MapCursor cursor = data.getEntries(); - while (cursor.advance()) { - String name = cursor.getKey(); - Object value = cursor.getValue(); + protected void registerIfNotDefault(EconomicMap data, boolean defaultValue, T clazz, String propertyName, Runnable register) { + if (data.containsKey(propertyName) ? asBoolean(data.get(propertyName), propertyName) : defaultValue) { try { - switch (name) { - case "allDeclaredConstructors": - if (asBoolean(value, "allDeclaredConstructors")) { - delegate.registerDeclaredConstructors(false, clazz); - } - break; - case "allPublicConstructors": - if (asBoolean(value, "allPublicConstructors")) { - delegate.registerPublicConstructors(false, clazz); - } - break; - case "allDeclaredMethods": - if (asBoolean(value, "allDeclaredMethods")) { - delegate.registerDeclaredMethods(false, clazz); - } - break; - case "allPublicMethods": - if (asBoolean(value, "allPublicMethods")) { - delegate.registerPublicMethods(false, clazz); - } - break; - case "allDeclaredFields": - if (asBoolean(value, "allDeclaredFields")) { - delegate.registerDeclaredFields(clazz); - } - break; - case "allPublicFields": - if (asBoolean(value, "allPublicFields")) { - delegate.registerPublicFields(clazz); - } - break; - case "allDeclaredClasses": - if (asBoolean(value, "allDeclaredClasses")) { - delegate.registerDeclaredClasses(clazz); - } - break; - case "allRecordComponents": - if (asBoolean(value, "allRecordComponents")) { - delegate.registerRecordComponents(clazz); - } - break; - case "allPermittedSubclasses": - if (asBoolean(value, "allPermittedSubclasses")) { - delegate.registerPermittedSubclasses(clazz); - } - break; - case "allNestMembers": - if (asBoolean(value, "allNestMembers")) { - delegate.registerNestMembers(clazz); - } - break; - case "allSigners": - if (asBoolean(value, "allSigners")) { - delegate.registerSigners(clazz); - } - break; - case "allPublicClasses": - if (asBoolean(value, "allPublicClasses")) { - delegate.registerPublicClasses(clazz); - } - break; - case "queryAllDeclaredConstructors": - if (asBoolean(value, "queryAllDeclaredConstructors")) { - delegate.registerDeclaredConstructors(true, clazz); - } - break; - case "queryAllPublicConstructors": - if (asBoolean(value, "queryAllPublicConstructors")) { - delegate.registerPublicConstructors(true, clazz); - } - break; - case "queryAllDeclaredMethods": - if (asBoolean(value, "queryAllDeclaredMethods")) { - delegate.registerDeclaredMethods(true, clazz); - } - break; - case "queryAllPublicMethods": - if (asBoolean(value, "queryAllPublicMethods")) { - delegate.registerPublicMethods(true, clazz); - } - break; - case "unsafeAllocated": - if (asBoolean(value, "unsafeAllocated")) { - delegate.registerUnsafeAllocated(clazz); - } - break; - case "methods": - parseMethods(false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); - break; - case "queriedMethods": - parseMethods(true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz); - break; - case "fields": - parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); - break; - } + register.run(); } catch (LinkageError e) { - handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + propertyName + " for reflection.", e); } } } - private void parseFields(List fields, T clazz) { + protected void parseFields(ConfigurationCondition condition, List fields, T clazz) { for (Object field : fields) { - parseField(asMap(field, "Elements of 'fields' array must be field descriptor objects"), clazz); + parseField(condition, asMap(field, "Elements of 'fields' array must be field descriptor objects"), clazz); } } - private void parseField(EconomicMap data, T clazz) { + private void parseField(ConfigurationCondition condition, EconomicMap data, T clazz) { checkAttributes(data, "reflection field descriptor object", Collections.singleton("name"), Arrays.asList("allowWrite", "allowUnsafeAccess")); String fieldName = asString(data.get("name"), "name"); boolean allowWrite = data.containsKey("allowWrite") && asBoolean(data.get("allowWrite"), "allowWrite"); try { - delegate.registerField(clazz, fieldName, allowWrite); + delegate.registerField(condition, clazz, fieldName, allowWrite); } catch (NoSuchFieldException e) { handleMissingElement("Field " + formatField(clazz, fieldName) + " not found."); } catch (LinkageError e) { @@ -226,13 +103,13 @@ private void parseField(EconomicMap data, T clazz) { } } - private void parseMethods(boolean queriedOnly, List methods, T clazz) { + protected void parseMethods(ConfigurationCondition condition, boolean queriedOnly, List methods, T clazz) { for (Object method : methods) { - parseMethod(queriedOnly, asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); + parseMethod(condition, queriedOnly, asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); } } - private void parseMethod(boolean queriedOnly, EconomicMap data, T clazz) { + private void parseMethod(ConfigurationCondition condition, boolean queriedOnly, EconomicMap data, T clazz) { checkAttributes(data, "reflection method descriptor object", Collections.singleton("name"), Collections.singleton("parameterTypes")); String methodName = asString(data.get("name"), "name"); List methodParameterTypes = null; @@ -248,9 +125,9 @@ private void parseMethod(boolean queriedOnly, EconomicMap data, if (methodParameterTypes != null) { try { if (isConstructor) { - delegate.registerConstructor(queriedOnly, clazz, methodParameterTypes); + delegate.registerConstructor(condition, queriedOnly, clazz, methodParameterTypes); } else { - delegate.registerMethod(queriedOnly, clazz, methodName, methodParameterTypes); + delegate.registerMethod(condition, queriedOnly, clazz, methodName, methodParameterTypes); } } catch (NoSuchMethodException e) { handleMissingElement("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found."); @@ -261,9 +138,9 @@ private void parseMethod(boolean queriedOnly, EconomicMap data, try { boolean found; if (isConstructor) { - found = delegate.registerAllConstructors(queriedOnly, clazz); + found = delegate.registerAllConstructors(condition, queriedOnly, clazz); } else { - found = delegate.registerAllMethodsWithName(queriedOnly, clazz, methodName); + found = delegate.registerAllMethodsWithName(condition, queriedOnly, clazz, methodName); } if (!found) { throw new JSONParserException("Method " + formatMethod(clazz, methodName) + " not found"); @@ -278,7 +155,7 @@ private List parseMethodParameters(T clazz, String methodName, List t List result = new ArrayList<>(); for (Object type : types) { String typeName = asString(type, "types"); - TypeResult typeResult = delegate.resolveType(ConfigurationCondition.alwaysTrue(), typeName, true); + TypeResult typeResult = delegate.resolveType(conditionResolver.alwaysTrue(), new NamedConfigurationTypeDescriptor(typeName), true); if (!typeResult.isPresent()) { handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException()); return null; @@ -309,7 +186,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/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 7cbce8f012f5..417080a2f19f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -32,47 +32,45 @@ public interface ReflectionConfigurationParserDelegate { - TypeResult resolveCondition(String typeName); + TypeResult resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives); - TypeResult resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives); + void registerType(ConfigurationCondition condition, T type); - void registerType(T type); + void registerPublicClasses(ConfigurationCondition condition, T type); - void registerPublicClasses(T type); + void registerDeclaredClasses(ConfigurationCondition condition, T type); - void registerDeclaredClasses(T type); + void registerRecordComponents(ConfigurationCondition condition, T type); - void registerRecordComponents(T type); + void registerPermittedSubclasses(ConfigurationCondition condition, T type); - void registerPermittedSubclasses(T type); + void registerNestMembers(ConfigurationCondition condition, T type); - void registerNestMembers(T type); + void registerSigners(ConfigurationCondition condition, T type); - void registerSigners(T type); + void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicFields(T type); + void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredFields(T type); + void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicMethods(boolean queriedOnly, T type); + void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredMethods(boolean queriedOnly, T type); + void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicConstructors(boolean queriedOnly, T type); + void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredConstructors(boolean queriedOnly, T type); + void registerField(ConfigurationCondition condition, T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; - void registerField(T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; + boolean registerAllMethodsWithName(ConfigurationCondition condition, boolean queriedOnly, T type, String methodName); - boolean registerAllMethodsWithName(boolean queriedOnly, T type, String methodName); + void registerMethod(ConfigurationCondition condition, boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; - void registerMethod(boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; + void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; - void registerConstructor(boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; + boolean registerAllConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - boolean registerAllConstructors(boolean queriedOnly, T type); - - void registerUnsafeAllocated(T clazz); + void registerUnsafeAllocated(ConfigurationCondition condition, T clazz); String getTypeName(T type); 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..7b4fa47a4533 --- /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.ConfigurationCondition; + +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; + } + + ConfigurationCondition unresolvedCondition = parseCondition(data, true); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { + return; + } + ConfigurationCondition 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; + } + + ConfigurationCondition 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 5ebcc0d4e463..237d9e964c10 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,76 +24,45 @@ */ 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.Locale; -import java.util.function.BiConsumer; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.util.json.JSONParserException; +import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.jdk.localization.LocalizationSupport; -public class ResourceConfigurationParser extends ConfigurationParser { - private final ResourcesRegistry registry; +public abstract class ResourceConfigurationParser extends ConfigurationParser { + protected final ResourcesRegistry registry; - public ResourceConfigurationParser(ResourcesRegistry registry, boolean strictConfiguration) { + protected final ConfigurationConditionResolver conditionResolver; + + public static ResourceConfigurationParser create(boolean strictMetadata, ResourcesRegistry registry, boolean strictConfiguration) { + if (strictMetadata) { + return new ResourceMetadataParser(ConfigurationConditionResolver.identityResolver(), registry, strictConfiguration); + } else { + return new LegacyResourceConfigurationParser(ConfigurationConditionResolver.identityResolver(), 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 abstract ConfigurationCondition parseCondition(EconomicMap data); - @SuppressWarnings("unchecked") - private void parseTopLevelObject(EconomicMap obj) { - Object resourcesObject = 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(); - } - } - 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) { - parseStringEntry(object, "pattern", registry::addResources, "resource descriptor object", "'includes' list"); - } - - if (excludesObject != null) { - List excludes = asList(excludesObject, "Attribute 'excludes' must be a list of resources"); - for (Object object : excludes) { - parseStringEntry(object, "pattern", registry::ignoreResources, "resource descriptor object", "'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) { - parseStringEntry(object, "pattern", registry::addResources, "resource descriptor object", "'resources' list"); - } - } - } - if (bundlesObject != null) { - List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); - for (Object bundle : bundles) { - parseBundle(bundle); - } + protected void parseBundlesObject(Object bundlesObject) { + List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); + for (Object bundle : bundles) { + parseBundle(bundle); } } @@ -101,7 +70,10 @@ private void parseBundle(Object bundle) { EconomicMap resource = asMap(bundle, "Elements of 'bundles' list must be a bundle descriptor object"); checkAttributes(resource, "bundle descriptor object", Collections.singletonList("name"), Arrays.asList("locales", "classNames", "condition")); String basename = asString(resource.get("name")); - ConfigurationCondition condition = parseCondition(resource); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource)); + if (!resolvedConfigurationCondition.isPresent()) { + return; + } Object locales = resource.get("locales"); if (locales != null) { List asList = asList(locales, "Attribute 'locales' must be a list of locales") @@ -109,7 +81,7 @@ private void parseBundle(Object bundle) { .map(ResourceConfigurationParser::parseLocale) .collect(Collectors.toList()); if (!asList.isEmpty()) { - registry.addResourceBundles(condition, basename, asList); + registry.addResourceBundles(resolvedConfigurationCondition.get(), basename, asList); } } @@ -118,12 +90,12 @@ private void parseBundle(Object bundle) { List asList = asList(classNames, "Attribute 'classNames' must be a list of classes"); for (Object o : asList) { String className = asString(o); - registry.addClassBasedResourceBundle(condition, basename, className); + registry.addClassBasedResourceBundle(resolvedConfigurationCondition.get(), basename, className); } } if (locales == null && classNames == null) { /* If nothing more precise is specified, register in every included locale */ - registry.addResourceBundles(condition, basename); + registry.addResourceBundles(resolvedConfigurationCondition.get(), basename); } } @@ -136,12 +108,106 @@ private static Locale parseLocale(Object input) { return locale; } - private void parseStringEntry(Object data, String valueKey, BiConsumer resourceRegistry, String expectedType, String parentType) { - EconomicMap resource = asMap(data, "Elements of " + parentType + " must be a " + expectedType); - checkAttributes(resource, "resource and resource bundle descriptor object", Collections.singletonList(valueKey), Collections.singletonList(CONDITIONAL_KEY)); - ConfigurationCondition condition = parseCondition(resource); - Object valueObject = resource.get(valueKey); - String value = asString(valueObject, valueKey); - resourceRegistry.accept(condition, value); + public static String globToRegex(String module, String glob) { + return (module == null || module.isEmpty() ? "" : module + ":") + globToRegex(glob); + } + + private static String globToRegex(String glob) { + /* this char will trigger last wildcard dump if the glob ends with the wildcard */ + String tmpGlob = glob + '#'; + StringBuilder sb = new StringBuilder(); + + int quoteStartIndex = 0; + Wildcard previousWildcard = Wildcard.START; + for (int i = 0; i < tmpGlob.length(); i++) { + char c = tmpGlob.charAt(i); + Wildcard currentWildcard = previousWildcard.next(c); + + boolean wildcardStart = previousWildcard == Wildcard.START && currentWildcard != Wildcard.START; + if (wildcardStart && quoteStartIndex != i) { + /* start of the new wildcard => quote previous content */ + sb.append(Pattern.quote(tmpGlob.substring(quoteStartIndex, i))); + } + + boolean consecutiveWildcards = previousWildcard == Wildcard.DOUBLE_STAR_SLASH && currentWildcard != Wildcard.START; + boolean wildcardEnd = previousWildcard != Wildcard.START && currentWildcard == Wildcard.START; + if (wildcardEnd || consecutiveWildcards) { + /* end of the wildcard => append regex and move start of next quote after it */ + sb.append(previousWildcard.regex); + quoteStartIndex = i; + } + previousWildcard = currentWildcard; + } + /* remove the last char we added artificially */ + tmpGlob = tmpGlob.substring(0, tmpGlob.length() - 1); + if (quoteStartIndex < tmpGlob.length()) { + sb.append(Pattern.quote(tmpGlob.substring(quoteStartIndex))); + } + return sb.toString(); + } + + /** + * This enum acts like a state machine that helps to identify glob wildcards. + */ + private enum Wildcard { + START("") { + @Override + public Wildcard next(char c) { + return c == '*' ? STAR : START; + } + }, + STAR("[^/]*") { + @Override + public Wildcard next(char c) { + return c == '*' ? DOUBLE_STAR : START; + } + }, + DOUBLE_STAR(".*") { + @Override + public Wildcard next(char c) { + return c == '/' ? DOUBLE_STAR_SLASH : START; + } + }, + DOUBLE_STAR_SLASH("([^/]*(/|$))*") { + @Override + public Wildcard next(char c) { + return c == '*' ? STAR : START; + } + }; + + final String regex; + + Wildcard(String val) { + regex = val; + } + + public abstract Wildcard next(char c); + } + + protected void parseGlobsObject(Object globsObject) { + List globs = asList(globsObject, "Attribute 'globs' must be a list of glob patterns"); + for (Object object : globs) { + parseGlobEntry(object, (condition, module, resource) -> registry.addResources(condition, globToRegex(module, resource))); + } + } + + 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, "glob resource descriptor object", Collections.singletonList(GLOB_KEY), List.of(CONDITIONAL_KEY, MODULE_KEY)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(globObject)); + if (!resolvedConfigurationCondition.isPresent()) { + return; + } + + Object moduleObject = globObject.get(MODULE_KEY); + String module = asNullableString(moduleObject, MODULE_KEY); + + Object valueObject = globObject.get(GLOB_KEY); + String value = asString(valueObject, GLOB_KEY); + resourceRegistry.accept(resolvedConfigurationCondition.get(), module, value); } } 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..caa09ce98cf3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java @@ -0,0 +1,53 @@ +/* + * 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.nativeimage.impl.ConfigurationCondition; + +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); + } + } + + @Override + protected ConfigurationCondition parseCondition(EconomicMap data) { + return parseCondition(data, true); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java index df941c65f45c..323feb31e897 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java @@ -27,11 +27,17 @@ import java.util.Collection; import java.util.Locale; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeResourceSupport; public interface ResourcesRegistry extends RuntimeResourceSupport { + @SuppressWarnings("unchecked") + static ResourcesRegistry singleton() { + return ImageSingletons.lookup(ResourcesRegistry.class); + } + /** * @deprecated Use {@link RuntimeResourceSupport#addResources(ConfigurationCondition, String)} * instead. 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 ff497052314b..1f26a9e072ac 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,9 +25,6 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.graalvm.collections.EconomicMap; @@ -35,76 +32,43 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import org.graalvm.util.json.JSONParserException; -public class SerializationConfigurationParser extends ConfigurationParser { +public abstract class SerializationConfigurationParser extends ConfigurationParser { - public static final String NAME_KEY = "name"; 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 RuntimeSerializationSupport serializationSupport; - private final ProxyConfigurationParser proxyConfigurationParser; - public SerializationConfigurationParser(RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { - super(strictConfiguration); - this.serializationSupport = serializationSupport; - this.proxyConfigurationParser = new ProxyConfigurationParser( - (conditionalElement) -> serializationSupport.registerProxyClass(conditionalElement.getCondition(), conditionalElement.getElement()), strictConfiguration); - } + protected final ConfigurationConditionResolver conditionResolver; + protected final RuntimeSerializationSupport serializationSupport; - @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, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + if (strictMetadata) { + return new SerializationMetadataParser<>(ConfigurationConditionResolver.identityResolver(), 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(ConfigurationConditionResolver.identityResolver(), 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.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); - } - - ConfigurationCondition unresolvedCondition = parseCondition(data); - String targetSerializationClass = asString(data.get(NAME_KEY)); + protected abstract void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType); - if (lambdaCapturingType) { - serializationSupport.registerLambdaCapturingClass(unresolvedCondition, targetSerializationClass); + protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, ConfigurationCondition 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, proxyClass.interfaceNames()); } else { - Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); - String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; - serializationSupport.registerWithTargetConstructorClass(unresolvedCondition, targetSerializationClass, customTargetConstructorClass); + 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..86d81982df34 --- /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.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; + +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; + } + + ConfigurationCondition 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.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt index cf0217313abe..a4841995909f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt @@ -1,4 +1,5 @@ One or several (comma-separated) paths to JSON files that specify lists of interfaces that define Java proxy classes. + The JSON structure is described in the following schema: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/proxy-config-schema-v1.0.0.json diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt index aaf1027fdb4b..ff4c16a974f6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt @@ -1,4 +1,5 @@ One or several (comma-separated) paths to JSON files that specify which program elements should be made available via reflection. + The JSON object schema is described at: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.0.0.json diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt index e199d657abc5..aecd04f45479 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt @@ -1,4 +1,5 @@ One or several (comma-separated) paths to JSON files that specify lists of serialization configurations. + The structure is described in the following schema: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/serialization-config-schema-v1.0.0.json diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java index d57bd01e0b52..e5749235490e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java @@ -35,5 +35,5 @@ public interface DynamicProxyRegistry extends RuntimeProxyCreationSupport { boolean isProxyClass(Class clazz); @Platforms(Platform.HOSTED_ONLY.class) - Class createProxyClassForSerialization(Class... interfaces); + Class getProxyClassHosted(Class... interfaces); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java index 5f28bed354b9..773038bc8e8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java @@ -28,9 +28,11 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.util.concurrent.ConcurrentHashMap; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @@ -39,6 +41,11 @@ @TargetClass(value = MethodHandles.class, innerClass = "Lookup") final class Target_java_lang_invoke_MethodHandles_Lookup { + // Checkstyle: stop + @Delete // + static ConcurrentHashMap LOOKASIDE_TABLE; + // Checkstyle: resume + @SuppressWarnings("static-method") @Substitute public Class defineClass(@SuppressWarnings("unused") byte[] bytes) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java index 8ca8222d8ccd..305ec0c30eb9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java @@ -54,6 +54,17 @@ public static void forField(Class declaringClass, String fieldName) { report(exception); } + public static MissingReflectionRegistrationError errorForQueriedOnlyField(Field field) { + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("read or write field", field.toString()), + field.getClass(), field.getDeclaringClass(), field.getName(), null); + report(exception); + /* + * If report doesn't throw, we throw the exception anyway since this is a Native + * Image-specific error that is unrecoverable in any case. + */ + return exception; + } + public static void forMethod(Class declaringClass, String methodName, Class[] paramTypes) { StringJoiner paramTypeNames = new StringJoiner(", ", "(", ")"); if (paramTypes != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java index b1a3d5f075af..8961f347adbf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java @@ -145,7 +145,7 @@ private Object createProxyClass(Class[] interfaces) { @Override @Platforms(Platform.HOSTED_ONLY.class) - public Class createProxyClassForSerialization(Class... interfaces) { + public Class getProxyClassHosted(Class... interfaces) { final Class[] intfs = interfaces.clone(); return createProxyClassFromImplementedInterfaces(intfs); } @@ -244,4 +244,8 @@ private synchronized boolean isHostedProxyClass(Class clazz) { public static Class getJdkProxyClass(ClassLoader loader, Class... interfaces) { return java.lang.reflect.Proxy.getProxyClass(loader, interfaces); } + + public static String proxyTypeDescriptor(String... interfaceNames) { + return "Proxy[" + String.join(", ", interfaceNames) + "]"; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java index 1fc5303ac94c..98ed7c9b61ee 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java @@ -31,7 +31,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; @TargetClass(className = "jdk.internal.misc.Unsafe") @SuppressWarnings({"static-method"}) @@ -80,13 +80,10 @@ static long getFieldOffset(Target_java_lang_reflect_Field field) { throw new NullPointerException(); } int offset = field.root == null ? field.offset : field.root.offset; - if (offset > 0) { - return offset; + if (offset <= 0) { + throw MissingReflectionRegistrationUtils.errorForQueriedOnlyField(SubstrateUtil.cast(field, Field.class)); } - throw VMError.unsupportedFeature("The offset of " + field + " is accessed without the field being first registered as unsafe accessed. " + - "Please register the field as unsafe accessed. You can do so with a reflection configuration that " + - "contains an entry for the field with the attribute \"allowUnsafeAccess\": true. Such a configuration " + - "file can be generated for you. Read BuildConfiguration.md and Reflection.md for details."); + return offset; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java index 8af9e717078f..33e2a65289db 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -53,7 +53,9 @@ protected void registerConditionalConfiguration(ConfigurationCondition condition public void flushConditionalConfiguration(Feature.BeforeAnalysisAccess b) { for (Map.Entry> reachabilityEntry : pendingReachabilityHandlers.entrySet()) { TypeResult> typeResult = ((FeatureImpl.BeforeAnalysisAccessImpl) b).getImageClassLoader().findClass(reachabilityEntry.getKey()); - b.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), typeResult.get()); + if (typeResult.isPresent()) { + b.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), typeResult.get()); + } } pendingReachabilityHandlers.clear(); } 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 5ce7637a13ac..ee51025bdb02 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 @@ -213,10 +213,14 @@ private static ResourcesRegistryImpl resourceRegistryImpl() { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - ResourceConfigurationParser parser = new ResourceConfigurationParser(ImageSingletons.lookup(ResourcesRegistry.class), ConfigurationFiles.Options.StrictConfiguration.getValue()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "resource", - ConfigurationFiles.Options.ResourceConfigurationFiles, ConfigurationFiles.Options.ResourceConfigurationResources, - ConfigurationFile.RESOURCES.getFileName()); + ResourceConfigurationParser parser = ResourceConfigurationParser.create(true, ResourcesRegistry.singleton(), + ConfigurationFiles.Options.StrictConfiguration.getValue()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, imageClassLoader, "resource"); + + ResourceConfigurationParser legacyParser = ResourceConfigurationParser.create(false, ResourcesRegistry.singleton(), + ConfigurationFiles.Options.StrictConfiguration.getValue()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, imageClassLoader, "resource", ConfigurationFiles.Options.ResourceConfigurationFiles, + ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFile.RESOURCES.getFileName()); resourcePatternWorkSet.addAll(Options.IncludeResources.getValue().values()); excludedResourcePatterns.addAll(Options.ExcludeResources.getValue().values()); 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 fde6041471ee..98e01b52b720 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 @@ -30,6 +30,7 @@ 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; @@ -41,7 +42,7 @@ import org.graalvm.nativeimage.impl.ReflectionRegistry; import org.graalvm.util.json.JSONParserException; -import com.oracle.svm.core.configure.ConditionalElement; +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; @@ -52,10 +53,9 @@ public final class ConfigurationParserUtils { - public static ReflectionConfigurationParser>> create(ReflectionRegistry registry, ImageClassLoader imageClassLoader) { - return new ReflectionConfigurationParser<>(RegistryAdapter.create(registry, imageClassLoader), - ConfigurationFiles.Options.StrictConfiguration.getValue(), - ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue()); + public static ReflectionConfigurationParser> create(String combinedFileKey, boolean strictMetadata, ReflectionRegistry registry, ImageClassLoader imageClassLoader) { + return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, RegistryAdapter.create(registry, imageClassLoader), + ConfigurationFiles.Options.StrictConfiguration.getValue(), ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue()); } /** @@ -75,6 +75,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/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index 6b763eb4e8bd..c7bdb4043af5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -32,8 +32,9 @@ import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.svm.core.TypeResult; -import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; public class ReflectionRegistryAdapter extends RegistryAdapter { private final RuntimeReflectionSupport reflectionSupport; @@ -44,86 +45,86 @@ public class ReflectionRegistryAdapter extends RegistryAdapter { } @Override - public TypeResult>> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { - TypeResult>> result = super.resolveType(condition, typeName, allowPrimitives); + public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + TypeResult> result = super.resolveType(condition, typeDescriptor, allowPrimitives); if (!result.isPresent()) { Throwable classLookupException = result.getException(); if (classLookupException instanceof LinkageError) { - reflectionSupport.registerClassLookupException(condition, typeName, classLookupException); + reflectionSupport.registerClassLookupException(condition, typeDescriptor.toString(), classLookupException); } else if (throwMissingRegistrationErrors() && classLookupException instanceof ClassNotFoundException) { - reflectionSupport.registerClassLookup(condition, typeName); + reflectionSupport.registerClassLookup(condition, typeDescriptor.toString()); } } return result; } @Override - public void registerPublicClasses(ConditionalElement> type) { - reflectionSupport.registerAllClassesQuery(type.getCondition(), type.getElement()); + public void registerPublicClasses(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllClassesQuery(condition, type); } @Override - public void registerDeclaredClasses(ConditionalElement> type) { - reflectionSupport.registerAllDeclaredClassesQuery(type.getCondition(), type.getElement()); + public void registerDeclaredClasses(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllDeclaredClassesQuery(condition, type); } @Override - public void registerRecordComponents(ConditionalElement> type) { - reflectionSupport.registerAllRecordComponentsQuery(type.getCondition(), type.getElement()); + public void registerRecordComponents(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllRecordComponentsQuery(condition, type); } @Override - public void registerPermittedSubclasses(ConditionalElement> type) { - reflectionSupport.registerAllPermittedSubclassesQuery(type.getCondition(), type.getElement()); + public void registerPermittedSubclasses(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllPermittedSubclassesQuery(condition, type); } @Override - public void registerNestMembers(ConditionalElement> type) { - reflectionSupport.registerAllNestMembersQuery(type.getCondition(), type.getElement()); + public void registerNestMembers(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllNestMembersQuery(condition, type); } @Override - public void registerSigners(ConditionalElement> type) { - reflectionSupport.registerAllSignersQuery(type.getCondition(), type.getElement()); + public void registerSigners(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllSignersQuery(condition, type); } @Override - public void registerPublicFields(ConditionalElement> type) { - reflectionSupport.registerAllFieldsQuery(type.getCondition(), type.getElement()); + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + ((ReflectionDataBuilder) reflectionSupport).registerAllFieldsQuery(condition, queriedOnly, type); } @Override - public void registerDeclaredFields(ConditionalElement> type) { - reflectionSupport.registerAllDeclaredFieldsQuery(type.getCondition(), type.getElement()); + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + ((ReflectionDataBuilder) reflectionSupport).registerAllDeclaredFieldsQuery(condition, queriedOnly, type); } @Override - public void registerPublicMethods(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllMethodsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllMethodsQuery(condition, queriedOnly, type); } @Override - public void registerDeclaredMethods(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllDeclaredMethodsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllDeclaredMethodsQuery(condition, queriedOnly, type); } @Override - public void registerPublicConstructors(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllConstructorsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllConstructorsQuery(condition, queriedOnly, type); } @Override - public void registerDeclaredConstructors(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllDeclaredConstructorsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllDeclaredConstructorsQuery(condition, queriedOnly, type); } @Override - public void registerField(ConditionalElement> type, String fieldName, boolean allowWrite) throws NoSuchFieldException { + public void registerField(ConfigurationCondition condition, Class type, String fieldName, boolean allowWrite) throws NoSuchFieldException { try { - super.registerField(type, fieldName, allowWrite); + super.registerField(condition, type, fieldName, allowWrite); } catch (NoSuchFieldException e) { if (throwMissingRegistrationErrors()) { - reflectionSupport.registerFieldLookup(type.getCondition(), type.getElement(), fieldName); + reflectionSupport.registerFieldLookup(condition, type, fieldName); } else { throw e; } @@ -131,12 +132,12 @@ public void registerField(ConditionalElement> type, String fieldName, b } @Override - public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerMethod(ConfigurationCondition condition, boolean queriedOnly, Class type, String methodName, List> methodParameterTypes) throws NoSuchMethodException { try { - super.registerMethod(queriedOnly, type, methodName, methodParameterTypes); + super.registerMethod(condition, queriedOnly, type, methodName, methodParameterTypes); } catch (NoSuchMethodException e) { if (throwMissingRegistrationErrors()) { - reflectionSupport.registerMethodLookup(type.getCondition(), type.getElement(), methodName, getParameterTypes(methodParameterTypes)); + reflectionSupport.registerMethodLookup(condition, type, methodName, getParameterTypes(methodParameterTypes)); } else { throw e; } @@ -144,12 +145,12 @@ public void registerMethod(boolean queriedOnly, ConditionalElement> typ } @Override - public void registerConstructor(boolean queriedOnly, ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, Class type, List> methodParameterTypes) throws NoSuchMethodException { try { - super.registerConstructor(queriedOnly, type, methodParameterTypes); + super.registerConstructor(condition, queriedOnly, type, methodParameterTypes); } catch (NoSuchMethodException e) { if (throwMissingRegistrationErrors()) { - reflectionSupport.registerConstructorLookup(type.getCondition(), type.getElement(), getParameterTypes(methodParameterTypes)); + reflectionSupport.registerConstructorLookup(condition, type, getParameterTypes(methodParameterTypes)); } else { throw e; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java index 1725a1cc7752..73a12c2d8384 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java @@ -27,21 +27,25 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.List; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.ReflectionRegistry; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.svm.core.TypeResult; -import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; +import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor; +import com.oracle.svm.core.configure.ProxyConfigurationTypeDescriptor; import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate; +import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.util.ClassUtil; -import jdk.vm.ci.meta.MetaUtil; - -public class RegistryAdapter implements ReflectionConfigurationParserDelegate>> { +public class RegistryAdapter implements ReflectionConfigurationParserDelegate> { private final ReflectionRegistry registry; private final ImageClassLoader classLoader; @@ -59,102 +63,116 @@ public static RegistryAdapter create(ReflectionRegistry registry, ImageClassLoad } @Override - public void registerType(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement()); - } - - @Override - public TypeResult resolveCondition(String typeName) { - String canonicalizedName = canonicalizeTypeName(typeName); - TypeResult> clazz = classLoader.findClass(canonicalizedName); - return clazz.map(Class::getTypeName) - .map(ConfigurationCondition::create); + public void registerType(ConfigurationCondition condition, Class type) { + registry.register(condition, type); } @Override - public TypeResult>> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { - String name = canonicalizeTypeName(typeName); - TypeResult> clazz = classLoader.findClass(name, allowPrimitives); - return clazz.map(c -> new ConditionalElement<>(condition, c)); + public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + switch (typeDescriptor.getDescriptorType()) { + case NAMED -> { + NamedConfigurationTypeDescriptor namedDescriptor = (NamedConfigurationTypeDescriptor) typeDescriptor; + return classLoader.findClass(namedDescriptor.name(), allowPrimitives); + } + case PROXY -> { + return resolveProxyType((ProxyConfigurationTypeDescriptor) typeDescriptor); + } + default -> { + throw VMError.shouldNotReachHere("Unknown type descriptor kind: %s", typeDescriptor.getDescriptorType()); + } + } } - private static String canonicalizeTypeName(String typeName) { - String name = typeName; - if (name.indexOf('[') != -1) { - /* accept "int[][]", "java.lang.String[]" */ - name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); + private TypeResult> resolveProxyType(ProxyConfigurationTypeDescriptor typeDescriptor) { + String typeName = typeDescriptor.toString(); + List>> interfaceResults = typeDescriptor.interfaceNames().stream().map(name -> { + NamedConfigurationTypeDescriptor typeDescriptor1 = new NamedConfigurationTypeDescriptor(name); + return classLoader.findClass(typeDescriptor1.name(), false); + }).toList(); + List> interfaces = new ArrayList<>(); + for (TypeResult> intf : interfaceResults) { + if (!intf.isPresent()) { + return TypeResult.forException(typeName, intf.getException()); + } + interfaces.add(intf.get()); + } + try { + DynamicProxyRegistry proxyRegistry = ImageSingletons.lookup(DynamicProxyRegistry.class); + Class proxyClass = proxyRegistry.getProxyClassHosted(interfaces.toArray(Class[]::new)); + return TypeResult.forType(typeName, proxyClass); + } catch (Throwable t) { + return TypeResult.forException(typeName, t); } - return name; } @Override - public void registerPublicClasses(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getClasses()); + public void registerPublicClasses(ConfigurationCondition condition, Class type) { + registry.register(condition, type.getClasses()); } @Override - public void registerDeclaredClasses(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getDeclaredClasses()); + public void registerDeclaredClasses(ConfigurationCondition condition, Class type) { + registry.register(condition, type.getDeclaredClasses()); } @Override - public void registerRecordComponents(ConditionalElement> type) { + public void registerRecordComponents(ConfigurationCondition condition, Class type) { } @Override - public void registerPermittedSubclasses(ConditionalElement> type) { + public void registerPermittedSubclasses(ConfigurationCondition condition, Class type) { } @Override - public void registerNestMembers(ConditionalElement> type) { + public void registerNestMembers(ConfigurationCondition condition, Class type) { } @Override - public void registerSigners(ConditionalElement> type) { + public void registerSigners(ConfigurationCondition condition, Class type) { } @Override - public void registerPublicFields(ConditionalElement> type) { - registry.register(type.getCondition(), false, type.getElement().getFields()); + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, false, type.getFields()); } @Override - public void registerDeclaredFields(ConditionalElement> type) { - registry.register(type.getCondition(), false, type.getElement().getDeclaredFields()); + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, false, type.getDeclaredFields()); } @Override - public void registerPublicMethods(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getMethods()); + public void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getMethods()); } @Override - public void registerDeclaredMethods(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredMethods()); + public void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getDeclaredMethods()); } @Override - public void registerPublicConstructors(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getConstructors()); + public void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getConstructors()); } @Override - public void registerDeclaredConstructors(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructors()); + public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getDeclaredConstructors()); } @Override - public void registerField(ConditionalElement> type, String fieldName, boolean allowWrite) throws NoSuchFieldException { - registry.register(type.getCondition(), allowWrite, type.getElement().getDeclaredField(fieldName)); + public void registerField(ConfigurationCondition condition, Class type, String fieldName, boolean allowWrite) throws NoSuchFieldException { + registry.register(condition, allowWrite, type.getDeclaredField(fieldName)); } @Override - public boolean registerAllMethodsWithName(boolean queriedOnly, ConditionalElement> type, String methodName) { + public boolean registerAllMethodsWithName(ConfigurationCondition condition, boolean queriedOnly, Class type, String methodName) { boolean found = false; - Executable[] methods = type.getElement().getDeclaredMethods(); + Executable[] methods = type.getDeclaredMethods(); for (Executable method : methods) { if (method.getName().equals(methodName)) { - registerExecutable(type.getCondition(), queriedOnly, method); + registerExecutable(condition, queriedOnly, method); found = true; } } @@ -162,17 +180,16 @@ public boolean registerAllMethodsWithName(boolean queriedOnly, ConditionalElemen } @Override - public boolean registerAllConstructors(boolean queriedOnly, ConditionalElement> type) { - Executable[] methods = type.getElement().getDeclaredConstructors(); - registerExecutable(type.getCondition(), queriedOnly, methods); + public boolean registerAllConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + Executable[] methods = type.getDeclaredConstructors(); + registerExecutable(condition, queriedOnly, methods); return methods.length > 0; } @Override - public void registerUnsafeAllocated(ConditionalElement> clazz) { - Class type = clazz.getElement(); + public void registerUnsafeAllocated(ConfigurationCondition condition, Class type) { if (!type.isArray() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { - registry.register(clazz.getCondition(), true, clazz.getElement()); + registry.register(condition, true, type); /* * Ignore otherwise as the implementation of allocateInstance will anyhow throw an * exception. @@ -181,11 +198,11 @@ public void registerUnsafeAllocated(ConditionalElement> clazz) { } @Override - public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerMethod(ConfigurationCondition condition, boolean queriedOnly, Class type, String methodName, List> methodParameterTypes) throws NoSuchMethodException { Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); Method method; try { - method = type.getElement().getDeclaredMethod(methodName, parameterTypesArray); + method = type.getDeclaredMethod(methodName, parameterTypesArray); } catch (NoClassDefFoundError e) { /* * getDeclaredMethod() builds a set of all the declared methods, which can fail when a @@ -196,24 +213,22 @@ public void registerMethod(boolean queriedOnly, ConditionalElement> typ * precisely because the application used getMethod() instead of getDeclaredMethod(). */ try { - method = type.getElement().getMethod(methodName, parameterTypesArray); + method = type.getMethod(methodName, parameterTypesArray); } catch (Throwable ignored) { throw e; } } - registerExecutable(type.getCondition(), queriedOnly, method); + registerExecutable(condition, queriedOnly, method); } @Override - public void registerConstructor(boolean queriedOnly, ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, Class type, List> methodParameterTypes) throws NoSuchMethodException { Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); - registerExecutable(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructor(parameterTypesArray)); + registerExecutable(condition, queriedOnly, type.getDeclaredConstructor(parameterTypesArray)); } - static Class[] getParameterTypes(List>> methodParameterTypes) { - return methodParameterTypes.stream() - .map(ConditionalElement::getElement) - .toArray(Class[]::new); + static Class[] getParameterTypes(List> methodParameterTypes) { + return methodParameterTypes.toArray(Class[]::new); } private void registerExecutable(ConfigurationCondition condition, boolean queriedOnly, Executable... executable) { @@ -221,12 +236,12 @@ private void registerExecutable(ConfigurationCondition condition, boolean querie } @Override - public String getTypeName(ConditionalElement> type) { - return type.getElement().getTypeName(); + public String getTypeName(Class type) { + return type.getTypeName(); } @Override - public String getSimpleName(ConditionalElement> type) { - return ClassUtil.getUnqualifiedName(type.getElement()); + public String getSimpleName(Class type) { + return ClassUtil.getUnqualifiedName(type); } } 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 37bf159f74b7..328a65577029 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; @@ -61,7 +63,6 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.config.ObjectLayout; -import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ReflectionConfigurationParser; @@ -192,9 +193,13 @@ public void afterRegistration(AfterRegistrationAccess arg) { runtimeSupport = new JNIRuntimeAccessibilitySupportImpl(); ImageSingletons.add(RuntimeJNIAccessSupport.class, runtimeSupport); - ReflectionConfigurationParser>> parser = ConfigurationParserUtils.create(runtimeSupport, access.getImageClassLoader()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "JNI", - ConfigurationFiles.Options.JNIConfigurationFiles, ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()); + ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(JNI_KEY, true, runtimeSupport, + access.getImageClassLoader()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "JNI"); + ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, runtimeSupport, + access.getImageClassLoader()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "JNI", ConfigurationFiles.Options.JNIConfigurationFiles, + ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()); } private class JNIRuntimeAccessibilitySupportImpl extends ConditionalConfigurationRegistry @@ -212,7 +217,9 @@ public void register(ConfigurationCondition condition, boolean unsafeAllocated, public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods) { requireNonNull(methods, "methods"); abortIfSealed(); - registerConditionalConfiguration(condition, () -> newMethods.addAll(Arrays.asList(methods))); + if (!queriedOnly) { + registerConditionalConfiguration(condition, () -> newMethods.addAll(Arrays.asList(methods))); + } } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 0c7148455340..933700269eec 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -414,26 +414,30 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { requireNonNull(fields, "field"); checkNotSealed(); - registerInternal(condition, fields); + registerInternal(condition, false, fields); } - private void registerInternal(ConfigurationCondition condition, Field... fields) { + private void registerInternal(ConfigurationCondition condition, boolean queriedOnly, Field... fields) { register(analysisUniverse -> registerConditionalConfiguration(condition, () -> { for (Field field : fields) { - analysisUniverse.getBigbang().postTask(debug -> registerField(field)); + analysisUniverse.getBigbang().postTask(debug -> registerField(queriedOnly, field)); } })); } @Override public void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz) { + registerAllFieldsQuery(condition, false, clazz); + } + + public void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { checkNotSealed(); for (Class current = clazz; current != null; current = current.getSuperclass()) { final Class currentLambda = current; registerConditionalConfiguration(condition, () -> setQueryFlag(currentLambda, ALL_FIELDS_FLAG)); } try { - registerInternal(condition, clazz.getFields()); + registerInternal(condition, queriedOnly, clazz.getFields()); } catch (LinkageError e) { /* Ignore the error */ } @@ -441,23 +445,27 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, Class cl @Override public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz) { + registerAllDeclaredFieldsQuery(condition, false, clazz); + } + + public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { checkNotSealed(); registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG)); try { - registerInternal(condition, clazz.getDeclaredFields()); + registerInternal(condition, queriedOnly, clazz.getDeclaredFields()); } catch (LinkageError e) { /* Ignore the error */ } } - private void registerField(Field reflectField) { + private void registerField(boolean queriedOnly, Field reflectField) { if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { return; } AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); if (registeredFields.put(analysisField, reflectField) == null) { - registerTypesForField(analysisField, reflectField); + registerTypesForField(analysisField, reflectField, true); AnalysisType declaringClass = analysisField.getDeclaringClass(); /* @@ -472,13 +480,21 @@ private void registerField(Field reflectField) { processAnnotationField(reflectField); } } + + /* + * We need to run this even if the method has already been registered, in case it was only + * registered as queried. + */ + if (!queriedOnly) { + registerTypesForField(analysisField, reflectField, false); + } } @Override public void registerFieldLookup(ConfigurationCondition condition, Class declaringClass, String fieldName) { checkNotSealed(); try { - registerInternal(condition, declaringClass.getDeclaredField(fieldName)); + registerInternal(condition, false, declaringClass.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { registerConditionalConfiguration(condition, () -> negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName)); } @@ -645,13 +661,15 @@ private Object[] getEnclosingMethodInfo(Class clazz) { } } - private void registerTypesForField(AnalysisField analysisField, Field reflectField) { - /* - * Reflection accessors use Unsafe, so ensure that all reflectively accessible fields are - * registered as unsafe-accessible, whether they have been explicitly registered or their - * Field object is reachable in the image heap. - */ - analysisField.registerAsUnsafeAccessed("is registered for reflection."); + private void registerTypesForField(AnalysisField analysisField, Field reflectField, boolean queriedOnly) { + if (!queriedOnly) { + /* + * Reflection accessors use Unsafe, so ensure that all reflectively accessible fields + * are registered as unsafe-accessible, whether they have been explicitly registered or + * their Field object is reachable in the image heap. + */ + analysisField.registerAsUnsafeAccessed("is registered for reflection."); + } /* * The generic signature is parsed at run time, so we need to make all the types necessary @@ -996,7 +1014,7 @@ public void registerHeapReflectionField(Field reflectField, ScanReason reason) { assert !sealed; AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); if (heapFields.put(analysisField, reflectField) == null && !SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { - registerTypesForField(analysisField, reflectField); + registerTypesForField(analysisField, reflectField, false); if (analysisField.getDeclaringClass().isAnnotation()) { processAnnotationField(reflectField); } 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 372d1e533162..ddf7d4ce88e9 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; @@ -52,7 +54,6 @@ import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Delete; -import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ReflectionConfigurationParser; @@ -261,10 +262,13 @@ public void duringSetup(DuringSetupAccess a) { aUniverse = access.getUniverse(); reflectionData.duringSetup(access.getMetaAccess(), aUniverse); - ReflectionConfigurationParser>> parser = ConfigurationParserUtils.create(reflectionData, access.getImageClassLoader()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "reflection", - ConfigurationFiles.Options.ReflectionConfigurationFiles, ConfigurationFiles.Options.ReflectionConfigurationResources, - ConfigurationFile.REFLECTION.getFileName()); + ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(REFLECTION_KEY, true, reflectionData, + access.getImageClassLoader()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "reflection"); + ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, reflectionData, + access.getImageClassLoader()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "reflection", + ConfigurationFiles.Options.ReflectionConfigurationFiles, ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()); loader = access.getImageClassLoader(); annotationSubstitutions = ((Inflation) access.getBigBang()).getAnnotationSubstitutionProcessor(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java index 3caa69ae79f8..f311da09d87d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java @@ -32,6 +32,7 @@ import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.impl.RuntimeProxyCreationSupport; +import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ProxyConfigurationParser; @@ -68,7 +69,8 @@ public void duringSetup(DuringSetupAccess a) { ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("resource configuration", imageClassLoader); ProxyRegistry proxyRegistry = new ProxyRegistry(typeResolver, dynamicProxySupport, imageClassLoader); ImageSingletons.add(ProxyRegistry.class, proxyRegistry); - ProxyConfigurationParser parser = new ProxyConfigurationParser(proxyRegistry, ConfigurationFiles.Options.StrictConfiguration.getValue()); + ProxyConfigurationParser parser = new ProxyConfigurationParser(ConfigurationFiles.Options.StrictConfiguration.getValue(), + (cond, intfs) -> proxyRegistry.accept(new ConditionalElement<>(cond, intfs))); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "dynamic proxy", ConfigurationFiles.Options.DynamicProxyConfigurationFiles, ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFile.DYNAMIC_PROXY.getFileName()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java index 82794c408f11..086bb379b188 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java @@ -59,7 +59,7 @@ public void accept(ConditionalElement> proxies) { public Class createProxyClassForSerialization(ConditionalElement> proxies) { Class[] interfaces = checkIfInterfacesAreValid(proxies); if (interfaces != null) { - return dynamicProxySupport.createProxyClassForSerialization(interfaces); + return dynamicProxySupport.getProxyClassHosted(interfaces); } return null; 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 32a4813ca802..2e9eecc2a000 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 @@ -122,14 +122,23 @@ 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(serializationDenyRegistry, ConfigurationFiles.Options.StrictConfiguration.getValue()); + + Boolean strictConfiguration = ConfigurationFiles.Options.StrictConfiguration.getValue(); + + SerializationConfigurationParser parser = SerializationConfigurationParser.create(true, serializationBuilder, + strictConfiguration); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, imageClassLoader, "serialization"); + + SerializationConfigurationParser denyCollectorParser = SerializationConfigurationParser.create(false, serializationDenyRegistry, + strictConfiguration); ConfigurationParserUtils.parseAndRegisterConfigurations(denyCollectorParser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationDenyConfigurationFiles, ConfigurationFiles.Options.SerializationDenyConfigurationResources, ConfigurationFile.SERIALIZATION_DENY.getFileName()); - SerializationConfigurationParser parser = new SerializationConfigurationParser(serializationBuilder, ConfigurationFiles.Options.StrictConfiguration.getValue()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "serialization", + SerializationConfigurationParser legacyParser = SerializationConfigurationParser.create(false, serializationBuilder, + strictConfiguration); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationConfigurationFiles, ConfigurationFiles.Options.SerializationConfigurationResources, ConfigurationFile.SERIALIZATION.getFileName()); diff --git a/sulong/mx.sulong/mx_sulong.py b/sulong/mx.sulong/mx_sulong.py index 5851ca8e9d6a..6dced01c5ebf 100644 --- a/sulong/mx.sulong/mx_sulong.py +++ b/sulong/mx.sulong/mx_sulong.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2023, Oracle and/or its affiliates. +# Copyright (c) 2016, 2024, Oracle and/or its affiliates. # # All rights reserved. # @@ -29,7 +29,7 @@ # import sys import os -import pipes +import shlex import tempfile from os.path import join import shutil @@ -166,7 +166,7 @@ def get_jacoco_setting(): def _subst_get_jvm_args(dep): java = mx.get_jdk().java main_class = mx.distribution(dep).mainClass - jvm_args = [pipes.quote(arg) for arg in mx.get_runtime_jvm_args([dep])] + jvm_args = [shlex.quote(arg) for arg in mx.get_runtime_jvm_args([dep])] cmd = [java] + jvm_args + [main_class] return " ".join(cmd) diff --git a/sulong/mx.sulong/mx_sulong_suite_constituents.py b/sulong/mx.sulong/mx_sulong_suite_constituents.py index 45a59e943ee4..4c0479451076 100644 --- a/sulong/mx.sulong/mx_sulong_suite_constituents.py +++ b/sulong/mx.sulong/mx_sulong_suite_constituents.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2023, Oracle and/or its affiliates. +# Copyright (c) 2016, 2024, Oracle and/or its affiliates. # # All rights reserved. # @@ -32,7 +32,7 @@ import abc import fnmatch -import pipes +import shlex import mx import mx_cmake @@ -418,7 +418,7 @@ def clean(self, forBuild=False): def contents(self, tool, exe): # platform support all_params = '%*' if mx.is_windows() else '"$@"' - _quote = _quote_windows if mx.is_windows() else pipes.quote + _quote = _quote_windows if mx.is_windows() else shlex.quote # build command line java = mx.get_jdk().java classpath_deps = [dep for dep in self.subject.buildDependencies if isinstance(dep, mx.ClasspathDependency)] diff --git a/tools/mx.tools/suite.py b/tools/mx.tools/suite.py index 211c143b5fc7..e517a24205c4 100644 --- a/tools/mx.tools/suite.py +++ b/tools/mx.tools/suite.py @@ -26,7 +26,7 @@ "defaultLicense" : "GPLv2-CPE", "groupId" : "org.graalvm.tools", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "release" : False, "url" : "http://openjdk.java.net/projects/graal", "developer" : { diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index ef59a25ef839..cfcb596b5460 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -41,7 +41,7 @@ suite = { "mxversion": "6.39.0", "name" : "truffle", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "release" : False, "groupId" : "org.graalvm.truffle", "sourceinprojectwhitelist" : [], diff --git a/vm/mx.vm/suite.py b/vm/mx.vm/suite.py index e747f8bad3de..6715749c1161 100644 --- a/vm/mx.vm/suite.py +++ b/vm/mx.vm/suite.py @@ -1,6 +1,6 @@ suite = { "name": "vm", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "mxversion": "6.41.0", "release" : False, "groupId" : "org.graalvm", diff --git a/wasm/mx.wasm/suite.py b/wasm/mx.wasm/suite.py index a015f4ee425a..933b37572c6b 100644 --- a/wasm/mx.wasm/suite.py +++ b/wasm/mx.wasm/suite.py @@ -42,7 +42,7 @@ "mxversion": "6.41.0", "name" : "wasm", "groupId" : "org.graalvm.wasm", - "version" : "23.1.5.0", + "version" : "23.1.6.0", "versionConflictResolution" : "latest", "url" : "http://graalvm.org/", "developer" : {