From bbba6e1a6acdf55beb857f1442a6b321bd5a1c3f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 29 Apr 2024 19:42:53 +0200 Subject: [PATCH 1/8] Use JVMCI for @Delete classes (cherry picked from commit 30ae9d3c8dce269e1bcb9f1a090fddfff7d8eea4) --- .../AnnotationSubstitutionProcessor.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java index 8f35f4f73891..687344c79623 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java @@ -635,23 +635,35 @@ private static boolean hasDefaultValue(Field annotatedField) { private void handleDeletedClass(Class originalClass, Delete deleteAnnotation) { if (NativeImageOptions.ReportUnsupportedElementsAtRuntime.getValue()) { + ResolvedJavaType type = metaAccess.lookupJavaType(originalClass); + + try { + type.link(); + } catch (LinkageError ignored) { + /* + * Ignore any linking errors. A type that cannot be linked doesn't need elements + * replaced: it will simply fail at runtime with the same linkage error before + * reaching those elements. + */ + return; + } + /* * We register all methods and fields as deleted. That still allows usage of the type in * type checks. */ - for (Executable m : originalClass.getDeclaredMethods()) { - ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m); + for (ResolvedJavaMethod method : type.getDeclaredMethods()) { registerAsDeleted(null, method, deleteAnnotation); } - for (Executable m : originalClass.getDeclaredConstructors()) { - ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m); - registerAsDeleted(null, method, deleteAnnotation); + for (ResolvedJavaMethod constructor : type.getDeclaredConstructors()) { + registerAsDeleted(null, constructor, deleteAnnotation); } - for (Field f : originalClass.getDeclaredFields()) { - ResolvedJavaField field = metaAccess.lookupJavaField(f); - registerAsDeleted(null, field, deleteAnnotation); + for (ResolvedJavaField f : type.getInstanceFields(false)) { + registerAsDeleted(null, f, deleteAnnotation); + } + for (ResolvedJavaField f : type.getStaticFields()) { + registerAsDeleted(null, f, deleteAnnotation); } - } else { deleteAnnotations.put(metaAccess.lookupJavaType(originalClass), deleteAnnotation); } From 3a097bf0534c5d3471acbb3354404697a12283cb Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 27 Aug 2024 14:36:49 +0200 Subject: [PATCH 2/8] Replace configuration parser with master version --- .../configure/ConditionalRuntimeValue.java | 69 ++++++ .../ConfigurationConditionResolver.java | 50 +++++ .../svm/core/configure/ConfigurationFile.java | 45 ++-- .../core/configure/ConfigurationFiles.java | 109 +++++++--- .../core/configure/ConfigurationParser.java | 146 +++++++++++-- .../ConfigurationTypeDescriptor.java | 80 +++++++ .../LegacyReflectionConfigurationParser.java | 142 +++++++++++++ .../LegacyResourceConfigurationParser.java | 67 ++++++ ...egacySerializationConfigurationParser.java | 112 ++++++++++ .../NamedConfigurationTypeDescriptor.java | 67 ++++++ .../configure/ProxyConfigurationParser.java | 40 ++-- .../ProxyConfigurationTypeDescriptor.java | 74 +++++++ .../ReflectionConfigurationParser.java | 196 ++++------------- ...ReflectionConfigurationParserDelegate.java | 48 ++--- .../configure/ReflectionMetadataParser.java | 128 ++++++++++++ .../ResourceConfigurationParser.java | 144 +++++++------ .../configure/ResourceMetadataParser.java | 45 ++++ .../svm/core/configure/ResourcesRegistry.java | 43 +--- .../core/configure/RuntimeConditionSet.java | 197 ++++++++++++++++++ .../SerializationConfigurationParser.java | 81 ++----- .../SerializationMetadataParser.java | 67 ++++++ .../doc-files/ProxyConfigurationFilesHelp.txt | 1 + .../ReflectionConfigurationFilesHelp.txt | 3 +- .../SerializationConfigurationFilesHelp.txt | 3 +- 24 files changed, 1541 insertions(+), 416 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java new file mode 100644 index 000000000000..d78533653924 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java @@ -0,0 +1,69 @@ +/* + * 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 org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +/** + * A image-heap stored {@link ConditionalRuntimeValue#value} that is guarded by run-time computed + * {@link ConditionalRuntimeValue#conditions}. + *

+ * {@link ConditionalRuntimeValue#conditions} are stored as an array to save space in the image + * heap. This is subject to further optimizations. + * + * @param type of the stored value. + */ +public final class ConditionalRuntimeValue { + RuntimeConditionSet conditions; + volatile T value; + + public ConditionalRuntimeValue(RuntimeConditionSet conditions, T value) { + this.conditions = conditions; + this.value = value; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public T getValueUnconditionally() { + return value; + } + + public RuntimeConditionSet getConditions() { + return conditions; + } + + public T getValue() { + if (conditions.satisfied()) { + return value; + } else { + return null; + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void updateValue(T newValue) { + this.value = newValue; + } +} 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..bfbba32b23cc --- /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.UnresolvedConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +public interface ConfigurationConditionResolver { + + static ConfigurationConditionResolver identityResolver() { + return new ConfigurationConditionResolver<>() { + @Override + public TypeResult resolveCondition(UnresolvedConfigurationCondition unresolvedCondition) { + return TypeResult.forType(unresolvedCondition.getTypeName(), unresolvedCondition); + } + + @Override + public UnresolvedConfigurationCondition alwaysTrue() { + return UnresolvedConfigurationCondition.alwaysTrue(); + } + }; + } + + TypeResult resolveCondition(UnresolvedConfigurationCondition unresolvedCondition); + + T 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..53bfa1a703ea 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 @@ -34,15 +34,16 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.graalvm.compiler.options.Option; -import org.graalvm.compiler.options.OptionStability; -import org.graalvm.compiler.options.OptionType; - +import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.HostedOptionKey; -import com.oracle.svm.core.option.LocatableMultiOptionValue; +import com.oracle.svm.core.option.OptionMigrationMessage; import com.oracle.svm.core.util.UserError; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionStability; +import jdk.graal.compiler.options.OptionType; + /** * Gathers configuration files from specified directories without having to provide each * configuration file individually. @@ -50,70 +51,118 @@ public final class ConfigurationFiles { public static final class Options { + @Option(help = "Directories directly containing configuration files for dynamic features at runtime.", type = OptionType.User, stability = OptionStability.STABLE)// @BundleMember(role = BundleMember.Role.Input)// - static final HostedOptionKey ConfigurationFileDirectories = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + static final HostedOptionKey ConfigurationFileDirectories = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resource path above configuration resources for dynamic features at runtime.", type = OptionType.User)// - public static final HostedOptionKey ConfigurationResourceRoots = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ConfigurationResourceRoots = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @OptionMigrationMessage("Use a reflect-config.json in your META-INF/native-image// directory instead.")// @Option(help = "file:doc-files/ReflectionConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey ReflectionConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey ReflectionConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made available for reflection (see ReflectionConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey ReflectionConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ReflectionConfigurationResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = "file:doc-files/ProxyConfigurationFilesHelp.txt", type = OptionType.User)// + @OptionMigrationMessage("Use a proxy-config.json in your META-INF/native-image// directory instead.")// + @Option(help = "file:doc-files/ProxyConfigurationFilesHelp.txt", type = OptionType.User, deprecated = true)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey DynamicProxyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - + public static final HostedOptionKey DynamicProxyConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + @Option(help = "Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", type = OptionType.User, deprecated = true, // + deprecationMessage = "This can be caused by a proxy-config.json file in your META-INF directory. " + + "Consider including proxy configuration in the reflection section of reachability-metadata.md instead.")// + public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + + @OptionMigrationMessage("Use a serialization-config.json in your META-INF/native-image// directory instead.")// @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made available for serialization (see SerializationConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @OptionMigrationMessage("Use a serialization-config.json in your META-INF/native-image// directory instead.")// @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey SerializationDenyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationDenyConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements that must not be made available for serialization.", type = OptionType.User)// - public static final HostedOptionKey SerializationDenyConfigurationResources = new HostedOptionKey<>( - LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationDenyConfigurationResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @OptionMigrationMessage("Use a resource-config.json in your META-INF/native-image// directory instead.")// @Option(help = "Files describing Java resources to be included in the image according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing Java resources to be included in the image according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)// - public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @Option(help = "Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", type = OptionType.User)// + @OptionMigrationMessage("Use a jni-config.json in your META-INF/native-image// directory instead.")// + @Option(help = "Files describing program elements to be made accessible via JNI according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @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()); + public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + @Option(help = "Resources describing program elements to be made accessible via JNI according to the schema at " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json", type = OptionType.User)// + public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + + @Option(help = "Resources describing reachability metadata needed for the program " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json", type = OptionType.User)// + public static final HostedOptionKey ReachabilityMetadataResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.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()); + public static final HostedOptionKey ForeignConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing stubs allowing foreign calls.", type = OptionType.User)// - public static final HostedOptionKey ForeignResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ForeignResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @OptionMigrationMessage("Use a predefined-classes-config.json in your META-INF/native-image// directory instead.")// @Option(help = "Files describing predefined classes that can be loaded at runtime according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing predefined classes that can be loaded at runtime according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)// - public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>( - LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>( + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "When configuration files do not match their schema, abort the image build instead of emitting a warning.")// public static final HostedOptionKey StrictConfiguration = new HostedOptionKey<>(false); + @Option(help = "Testing flag: the 'typeReachable' condition is treated as typeReached so the semantics of programs can change.")// + public static final HostedOptionKey TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: the 'name' is treated as 'type' in reflection configuration.")// + public static final HostedOptionKey TreatAllNameEntriesAsType = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: the 'typeReached' condition is always satisfied however it prints the stack trace where it would not be satisfied.")// + public static final HostedOptionKey TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: print 'typeReached' conditions that are used on interfaces without default methods at build time.")// + public static final HostedOptionKey TrackTypeReachedOnInterfaces = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")// + public static final HostedOptionKey TreatAllUserSpaceTypesAsTrackedForTypeReached = new HostedOptionKey<>(false); + @Option(help = "Warn when reflection and JNI configuration files have elements that could not be found on the classpath or modulepath.", type = OptionType.Expert)// public static final HostedOptionKey WarnAboutMissingReflectionOrJNIMetadataElements = new HostedOptionKey<>(false); } 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..cc28760d0899 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 @@ -24,7 +24,12 @@ */ package com.oracle.svm.core.configure; +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllTypeReachableConditionsAsTypeReached; +import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHABLE_KEY; +import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHED_KEY; + import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -37,18 +42,20 @@ 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; -import org.graalvm.nativeimage.impl.ConfigurationCondition; -import org.graalvm.util.json.JSONParser; -import org.graalvm.util.json.JSONParserException; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.jdk.JavaNetSubstitutions; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; +import jdk.graal.compiler.util.json.JsonParser; +import jdk.graal.compiler.util.json.JsonParserException; + public abstract class ConfigurationParser { public static InputStream openStream(URI uri) throws IOException { URL url = uri.toURL(); @@ -60,7 +67,15 @@ 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 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"; private final Map> seenUnknownAttributesByType = new HashMap<>(); private final boolean strictSchema; @@ -70,7 +85,12 @@ protected ConfigurationParser(boolean strictConfiguration) { public void parseAndRegister(URI uri) throws IOException { try (Reader reader = openReader(uri)) { - parseAndRegister(new JSONParser(reader).parse(), 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 + */ } } @@ -79,17 +99,23 @@ protected static BufferedReader openReader(URI uri) throws IOException { } public void parseAndRegister(Reader reader) throws IOException { - parseAndRegister(new JSONParser(reader).parse(), null); + parseAndRegister(new JsonParser(reader).parse(), null); } 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) { return (List) data; } - throw new JSONParserException(errorMessage); + throw new JsonParserException(errorMessage); } @SuppressWarnings("unchecked") @@ -97,7 +123,7 @@ public static EconomicMap asMap(Object data, String errorMessage if (data instanceof EconomicMap) { return (EconomicMap) data; } - throw new JSONParserException(errorMessage); + throw new JsonParserException(errorMessage); } protected void checkAttributes(EconomicMap map, String type, Collection requiredAttrs, Collection optionalAttrs) { @@ -106,7 +132,7 @@ protected void checkAttributes(EconomicMap map, String type, Col unseenRequired.remove(key); } if (!unseenRequired.isEmpty()) { - throw new JSONParserException("Missing attribute(s) [" + String.join(", ", unseenRequired) + "] in " + type); + throw new JsonParserException("Missing attribute(s) [" + String.join(", ", unseenRequired) + "] in " + type); } Set unknownAttributes = new HashSet<>(); for (String key : map.getKeys()) { @@ -127,6 +153,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 +178,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); } @@ -149,14 +192,14 @@ public static String asString(Object value) { if (value instanceof String) { return (String) value; } - throw new JSONParserException("Invalid string value \"" + value + "\"."); + throw new JsonParserException("Invalid string value \"" + value + "\"."); } protected static String asString(Object value, String propertyName) { if (value instanceof String) { return (String) value; } - throw new JSONParserException("Invalid string value \"" + value + "\" for element '" + propertyName + "'"); + throw new JsonParserException("Invalid string value \"" + value + "\" for element '" + propertyName + "'"); } protected static String asNullableString(Object value, String propertyName) { @@ -167,7 +210,7 @@ protected static boolean asBoolean(Object value, String propertyName) { if (value instanceof Boolean) { return (boolean) value; } - throw new JSONParserException("Invalid boolean value '" + value + "' for element '" + propertyName + "'"); + throw new JsonParserException("Invalid boolean value '" + value + "' for element '" + propertyName + "'"); } protected static long asLong(Object value, String propertyName) { @@ -177,21 +220,80 @@ protected static long asLong(Object value, String propertyName) { if (value instanceof Integer) { return (int) value; } - throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); + throw new JsonParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); } - protected ConfigurationCondition parseCondition(EconomicMap data) { + protected UnresolvedConfigurationCondition 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."); + } + + 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()) { + String className = ((NamedConfigurationTypeDescriptor) condition.get()).name(); + return UnresolvedConfigurationCondition.create(className, true); + } + } else if (conditionObject.containsKey(TYPE_REACHABLE_KEY)) { + if (runtimeCondition && !TreatAllTypeReachableConditionsAsTypeReached.getValue()) { + 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 UnresolvedConfigurationCondition.create(className, TreatAllTypeReachableConditionsAsTypeReached.getValue()); + } + } + } + return UnresolvedConfigurationCondition.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(); } - return ConfigurationCondition.alwaysTrue(); } + 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..38f6a240ef80 --- /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.util.LogUtils; + +import jdk.graal.compiler.util.json.JsonPrintable; +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..a123418dc670 --- /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.UnresolvedConfigurationCondition; + +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(); + + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, isType); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { + return; + } + + /* + * Even if primitives cannot be queried through Class.forName, they can be registered to + * allow getDeclaredMethods() and similar bulk queries at run time. + */ + C condition = conditionResult.get(); + TypeResult result = delegate.resolveType(condition, typeDescriptor, true); + if (!result.isPresent()) { + handleMissingElement("Could not resolve " + typeDescriptor + " for reflection configuration.", result.getException()); + return; + } + + C queryCondition = isType ? conditionResolver.alwaysTrue() : condition; + T clazz = result.get(); + delegate.registerType(conditionResult.get(), clazz); + + registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); + registerIfNotDefault(data, isType, clazz, "allDeclaredClasses", () -> delegate.registerDeclaredClasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allRecordComponents", () -> delegate.registerRecordComponents(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allPermittedSubclasses", () -> delegate.registerPermittedSubclasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allNestMembers", () -> delegate.registerNestMembers(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allSigners", () -> delegate.registerSigners(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allPublicClasses", () -> delegate.registerPublicClasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllDeclaredConstructors", () -> delegate.registerDeclaredConstructors(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllPublicConstructors", () -> delegate.registerPublicConstructors(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllDeclaredMethods", () -> delegate.registerDeclaredMethods(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllPublicMethods", () -> delegate.registerPublicMethods(queryCondition, true, clazz)); + if (isType) { + /* + * Fields cannot be registered as queried only by the user, we register them + * unconditionally if the class is registered via "type". + */ + delegate.registerDeclaredFields(queryCondition, true, clazz); + delegate.registerPublicFields(queryCondition, true, clazz); + } + registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + MapCursor cursor = data.getEntries(); + while (cursor.advance()) { + String name = cursor.getKey(); + Object value = cursor.getValue(); + try { + switch (name) { + case "methods": + parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + break; + case "queriedMethods": + parseMethods(condition, true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz); + break; + case "fields": + parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); + break; + } + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java new file mode 100644 index 000000000000..0f680cb44899 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; + +final class LegacyResourceConfigurationParser extends ResourceConfigurationParser { + LegacyResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + super(conditionResolver, registry, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + parseTopLevelObject(asMap(json, "first level of document must be an object")); + } + + private void parseTopLevelObject(EconomicMap obj) { + Object resourcesObject = null; + Object bundlesObject = null; + Object globsObject = null; + MapCursor cursor = obj.getEntries(); + while (cursor.advance()) { + if (RESOURCES_KEY.equals(cursor.getKey())) { + resourcesObject = cursor.getValue(); + } else if (BUNDLES_KEY.equals(cursor.getKey())) { + bundlesObject = cursor.getValue(); + } else if (GLOBS_KEY.equals(cursor.getKey())) { + globsObject = cursor.getValue(); + } + } + + if (resourcesObject != null) { + parseResourcesObject(resourcesObject); + } + if (bundlesObject != null) { + parseBundlesObject(bundlesObject); + } + if (globsObject != null) { + parseGlobsObject(globsObject); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java new file mode 100644 index 000000000000..46c92f8b389c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java @@ -0,0 +1,112 @@ +/* + * 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.RuntimeSerializationSupport; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + +import jdk.graal.compiler.util.json.JsonParserException; + +final class LegacySerializationConfigurationParser extends SerializationConfigurationParser { + + private static final String SERIALIZATION_TYPES_KEY = "types"; + private static final String LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY = "lambdaCapturingTypes"; + private static final String PROXY_SERIALIZATION_TYPES_KEY = "proxies"; + + private final ProxyConfigurationParser proxyConfigurationParser; + + LegacySerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(conditionResolver, serializationSupport, strictConfiguration); + this.proxyConfigurationParser = new ProxyConfigurationParser<>(conditionResolver, strictConfiguration, serializationSupport::registerProxyClass); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + if (json instanceof List) { + parseOldConfiguration(asList(json, "First-level of document must be an array of serialization lists")); + } else if (json instanceof EconomicMap) { + parseNewConfiguration(asMap(json, "First-level of document must be a map of serialization types")); + } else { + throw new JsonParserException("First-level of document must either be an array of serialization lists or a map of serialization types"); + } + } + + private void parseOldConfiguration(List listOfSerializationConfigurationObjects) { + parseSerializationTypes(asList(listOfSerializationConfigurationObjects, "Second-level of document must be serialization descriptor objects"), false); + } + + private void parseNewConfiguration(EconomicMap listOfSerializationConfigurationObjects) { + if (!listOfSerializationConfigurationObjects.containsKey(SERIALIZATION_TYPES_KEY) || !listOfSerializationConfigurationObjects.containsKey(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY)) { + throw new JsonParserException("Second-level of document must be arrays of serialization descriptor objects"); + } + + parseSerializationTypes(asList(listOfSerializationConfigurationObjects.get(SERIALIZATION_TYPES_KEY), "The types property must be an array of serialization descriptor objects"), false); + parseSerializationTypes( + asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), + "The lambdaCapturingTypes property must be an array of serialization descriptor objects"), + true); + + if (listOfSerializationConfigurationObjects.containsKey(PROXY_SERIALIZATION_TYPES_KEY)) { + proxyConfigurationParser.parseAndRegister(listOfSerializationConfigurationObjects.get(PROXY_SERIALIZATION_TYPES_KEY), null); + } + } + + @Override + protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { + if (lambdaCapturingType) { + checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CONDITIONAL_KEY)); + } else { + checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); + } + + ConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, false); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { + return; + } + + if (lambdaCapturingType) { + String className = ((NamedConfigurationTypeDescriptor) targetSerializationClass).name(); + serializationSupport.registerLambdaCapturingClass(condition.get(), className); + } else { + Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); + String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { + serializationSupport.registerWithTargetConstructorClass(condition.get(), namedClass.name(), customTargetConstructorClass); + } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { + serializationSupport.registerProxyClass(condition.get(), proxyClass.interfaceNames()); + } else { + throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..5a4985834819 --- /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 jdk.graal.compiler.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..048db3ef2e0f 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,31 @@ 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 org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; +import jdk.graal.compiler.util.json.JsonParserException; + /** * Parses JSON describing lists of interfaces and register them in the {@link DynamicProxyRegistry}. */ -public final class ProxyConfigurationParser extends ConfigurationParser { - private final Consumer>> interfaceListConsumer; +public final class ProxyConfigurationParser extends ConfigurationParser { + + private final ConfigurationConditionResolver conditionResolver; - public ProxyConfigurationParser(Consumer>> interfaceListConsumer, boolean strictConfiguration) { + private final BiConsumer> proxyConfigConsumer; + + public ProxyConfigurationParser(ConfigurationConditionResolver conditionResolver, boolean strictConfiguration, + BiConsumer> proxyConfigConsumer) { super(strictConfiguration); - this.interfaceListConsumer = interfaceListConsumer; + this.proxyConfigConsumer = proxyConfigConsumer; + this.conditionResolver = conditionResolver; } @Override @@ -58,32 +65,35 @@ 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, "")); } else { - throw new JSONParserException("Second-level must be composed of either interface lists or proxy configuration objects"); + throw new JsonParserException("Second-level must be composed of either interface lists or proxy configuration objects"); } if (foundInterfaceLists && foundProxyConfigurationObjects) { - throw new JSONParserException("Second-level can only be populated of either interface lists or proxy configuration objects, but these cannot be mixed"); + throw new JsonParserException("Second-level can only be populated of either interface lists or proxy configuration objects, but these cannot be mixed"); } } } - private void parseInterfaceList(ConfigurationCondition condition, List data) { + private void parseInterfaceList(C 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()); + throw new JsonParserException(e.toString()); } } 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")); + UnresolvedConfigurationCondition 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..c60f589852c9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java @@ -0,0 +1,74 @@ +/* + * 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 jdk.graal.compiler.util.json.JsonPrinter; +import jdk.graal.compiler.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..349f586ff5f7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -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,193 +31,72 @@ 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.util.LogUtils; +import jdk.graal.compiler.util.json.JsonParserException; + /** * 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, + ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, + boolean strictConfiguration, boolean printMissingElements, boolean treatAllEntriesAsType) { + if (strictMetadata) { + return new ReflectionMetadataParser<>(combinedFileKey, conditionResolver, delegate, strictConfiguration, printMissingElements); + } else { + return new LegacyReflectionConfigurationParser<>(conditionResolver, delegate, strictConfiguration, printMissingElements, treatAllEntriesAsType); + } } - private void parseClassArray(List classes) { + protected void parseClassArray(List classes) { for (Object clazz : classes) { parseClass(asMap(clazz, "second level of document must be class descriptor objects")); } } - private void parseClass(EconomicMap data) { - checkAttributes(data, "reflection class descriptor object", Collections.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(C 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(C 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 +104,13 @@ private void parseField(EconomicMap data, T clazz) { } } - private void parseMethods(boolean queriedOnly, List methods, T clazz) { + protected void parseMethods(C 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(C 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 +126,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,12 +139,12 @@ 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"); + throw new JsonParserException("Method " + formatMethod(clazz, methodName) + " not found"); } } catch (LinkageError e) { handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", e); @@ -278,7 +156,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 +187,7 @@ private void handleMissingElement(String message) { handleMissingElement(message, null); } - private void handleMissingElement(String msg, Throwable cause) { + protected void handleMissingElement(String msg, Throwable cause) { if (printMissingElements) { String message = msg; if (cause != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 7cbce8f012f5..748262afabb7 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -26,53 +26,49 @@ import java.util.List; -import org.graalvm.nativeimage.impl.ConfigurationCondition; - import com.oracle.svm.core.TypeResult; -public interface ReflectionConfigurationParserDelegate { - - TypeResult resolveCondition(String typeName); +public interface ReflectionConfigurationParserDelegate { - TypeResult resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives); + TypeResult resolveType(C condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives); - void registerType(T type); + void registerType(C condition, T type); - void registerPublicClasses(T type); + void registerPublicClasses(C condition, T type); - void registerDeclaredClasses(T type); + void registerDeclaredClasses(C condition, T type); - void registerRecordComponents(T type); + void registerRecordComponents(C condition, T type); - void registerPermittedSubclasses(T type); + void registerPermittedSubclasses(C condition, T type); - void registerNestMembers(T type); + void registerNestMembers(C condition, T type); - void registerSigners(T type); + void registerSigners(C condition, T type); - void registerPublicFields(T type); + void registerPublicFields(C condition, boolean queriedOnly, T type); - void registerDeclaredFields(T type); + void registerDeclaredFields(C condition, boolean queriedOnly, T type); - void registerPublicMethods(boolean queriedOnly, T type); + void registerPublicMethods(C condition, boolean queriedOnly, T type); - void registerDeclaredMethods(boolean queriedOnly, T type); + void registerDeclaredMethods(C condition, boolean queriedOnly, T type); - void registerPublicConstructors(boolean queriedOnly, T type); + void registerPublicConstructors(C condition, boolean queriedOnly, T type); - void registerDeclaredConstructors(boolean queriedOnly, T type); + void registerDeclaredConstructors(C condition, boolean queriedOnly, T type); - void registerField(T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; + void registerField(C condition, T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; - boolean registerAllMethodsWithName(boolean queriedOnly, T type, String methodName); + boolean registerAllMethodsWithName(C condition, boolean queriedOnly, T type, String methodName); - void registerMethod(boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; + void registerMethod(C condition, boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; - void registerConstructor(boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; + void registerConstructor(C condition, boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; - boolean registerAllConstructors(boolean queriedOnly, T type); + boolean registerAllConstructors(C condition, boolean queriedOnly, T type); - void registerUnsafeAllocated(T clazz); + void registerUnsafeAllocated(C 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..33bd0028be6c --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +class ReflectionMetadataParser extends ReflectionConfigurationParser { + private static final List OPTIONAL_REFLECT_METADATA_ATTRS = Arrays.asList(CONDITIONAL_KEY, + "allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", + "methods", "fields", "unsafeAllocated"); + + private final String combinedFileKey; + + ReflectionMetadataParser(String combinedFileKey, ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { + super(conditionResolver, delegate, strictConfiguration, printMissingElements); + this.combinedFileKey = combinedFileKey; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object reflectionJson = getFromGlobalFile(json, combinedFileKey); + if (reflectionJson != null) { + parseClassArray(asList(reflectionJson, "first level of document must be an array of class descriptors")); + } + } + + @Override + protected void parseClass(EconomicMap data) { + checkAttributes(data, "reflection class descriptor object", List.of(TYPE_KEY), OPTIONAL_REFLECT_METADATA_ATTRS); + + Optional type = parseTypeContents(data.get(TYPE_KEY)); + if (type.isEmpty()) { + return; + } + + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, true); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { + return; + } + C condition = conditionResult.get(); + + /* + * Even if primitives cannot be queried through Class.forName, they can be registered to + * allow getDeclaredMethods() and similar bulk queries at run time. + */ + TypeResult result = delegate.resolveType(condition, type.get(), true); + if (!result.isPresent()) { + handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); + return; + } + + C queryCondition = conditionResolver.alwaysTrue(); + T clazz = result.get(); + delegate.registerType(conditionResult.get(), clazz); + + delegate.registerDeclaredClasses(queryCondition, clazz); + delegate.registerRecordComponents(queryCondition, clazz); + delegate.registerPermittedSubclasses(queryCondition, clazz); + delegate.registerNestMembers(queryCondition, clazz); + delegate.registerSigners(queryCondition, clazz); + delegate.registerPublicClasses(queryCondition, clazz); + delegate.registerDeclaredConstructors(queryCondition, true, clazz); + delegate.registerPublicConstructors(queryCondition, true, clazz); + delegate.registerDeclaredMethods(queryCondition, true, clazz); + delegate.registerPublicMethods(queryCondition, true, clazz); + delegate.registerDeclaredFields(queryCondition, true, clazz); + delegate.registerPublicFields(queryCondition, true, clazz); + + registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + + MapCursor cursor = data.getEntries(); + while (cursor.advance()) { + String name = cursor.getKey(); + Object value = cursor.getValue(); + try { + switch (name) { + case "methods": + parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + break; + case "fields": + parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); + break; + } + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java index 5ebcc0d4e463..8242237d6a6c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,66 +32,61 @@ 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; +import jdk.graal.compiler.util.json.JsonParserException; - public ResourceConfigurationParser(ResourcesRegistry registry, boolean strictConfiguration) { +public abstract class ResourceConfigurationParser extends ConfigurationParser { + protected final ResourcesRegistry registry; + + protected final ConfigurationConditionResolver conditionResolver; + + public static ResourceConfigurationParser create(boolean strictMetadata, ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + if (strictMetadata) { + return new ResourceMetadataParser<>(conditionResolver, registry, strictConfiguration); + } else { + return new LegacyResourceConfigurationParser<>(conditionResolver, registry, strictConfiguration); + } + } + + protected ResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { super(strictConfiguration); this.registry = registry; + this.conditionResolver = conditionResolver; } - @Override - public void parseAndRegister(Object json, URI origin) { - parseTopLevelObject(asMap(json, "first level of document must be an object")); + protected void parseBundlesObject(Object bundlesObject) { + List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); + for (Object bundle : bundles) { + parseBundle(bundle); + } } @SuppressWarnings("unchecked") - private void parseTopLevelObject(EconomicMap obj) { - Object resourcesObject = null; - Object 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(); + protected void parseResourcesObject(Object resourcesObject) { + if (resourcesObject instanceof EconomicMap) { // New format + EconomicMap resourcesObjectMap = (EconomicMap) resourcesObject; + checkAttributes(resourcesObjectMap, "resource descriptor object", Collections.singleton("includes"), Collections.singleton("excludes")); + Object includesObject = resourcesObjectMap.get("includes"); + Object excludesObject = resourcesObjectMap.get("excludes"); + + List includes = asList(includesObject, "Attribute 'includes' must be a list of resources"); + for (Object object : includes) { + parsePatternEntry(object, registry::addResources, "'includes' list"); } - } - if (resourcesObject != null) { - if (resourcesObject instanceof EconomicMap) { // New format - EconomicMap resourcesObjectMap = (EconomicMap) resourcesObject; - checkAttributes(resourcesObjectMap, "resource descriptor object", Collections.singleton("includes"), Collections.singleton("excludes")); - Object includesObject = resourcesObjectMap.get("includes"); - Object excludesObject = resourcesObjectMap.get("excludes"); - - List includes = asList(includesObject, "Attribute 'includes' must be a list of resources"); - for (Object object : includes) { - 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 (excludesObject != null) { + List excludes = asList(excludesObject, "Attribute 'excludes' must be a list of resources"); + for (Object object : excludes) { + parsePatternEntry(object, registry::ignoreResources, "'excludes' list"); } } - } - if (bundlesObject != null) { - List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); - for (Object bundle : bundles) { - parseBundle(bundle); + } else { // Old format: may be deprecated in future versions + List resources = asList(resourcesObject, "Attribute 'resources' must be a list of resources"); + for (Object object : resources) { + parsePatternEntry(object, registry::addResources, "'resources' list"); } } } @@ -101,7 +95,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, false)); + 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 +106,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 +115,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); } } @@ -131,17 +128,48 @@ private static Locale parseLocale(Object input) { String localeTag = asString(input); Locale locale = LocalizationSupport.parseLocaleFromTag(localeTag); if (locale == null) { - throw new JSONParserException(localeTag + " is not a valid locale tag"); + throw new JsonParserException(localeTag + " is not a valid locale tag"); } 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); + 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); + } + + protected void parseGlobsObject(Object globsObject) { + List globs = asList(globsObject, "Attribute 'globs' must be a list of glob patterns"); + for (Object object : globs) { + parseGlobEntry(object, registry::addGlob); + } + } + + private interface GlobPatternConsumer { + void accept(T a, String b, String c); + } + + private void parseGlobEntry(Object data, GlobPatternConsumer resourceRegistry) { + EconomicMap globObject = asMap(data, "Elements of 'globs' list must be a glob descriptor objects"); + checkAttributes(globObject, "glob resource descriptor object", Collections.singletonList(GLOB_KEY), List.of(CONDITIONAL_KEY, MODULE_KEY)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(globObject, false)); + if (!resolvedConfigurationCondition.isPresent()) { + return; + } + + 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..2e1b4720ed4a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; + +final class ResourceMetadataParser extends ResourceConfigurationParser { + ResourceMetadataParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + super(conditionResolver, registry, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object resourcesJson = getFromGlobalFile(json, RESOURCES_KEY); + if (resourcesJson != null) { + parseGlobsObject(resourcesJson); + } + Object bundlesJson = getFromGlobalFile(json, BUNDLES_KEY); + if (bundlesJson != null) { + parseBundlesObject(bundlesJson); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java index df941c65f45c..89ce92951e1b 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,41 +27,18 @@ 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 { +public interface ResourcesRegistry extends RuntimeResourceSupport { - /** - * @deprecated Use {@link RuntimeResourceSupport#addResources(ConfigurationCondition, String)} - * instead. - */ - @Deprecated - default void addResources(String pattern) { - addResources(ConfigurationCondition.alwaysTrue(), pattern); - } - - /** - * @deprecated Use - * {@link RuntimeResourceSupport#ignoreResources(ConfigurationCondition, String)} - * instead. - */ - @Deprecated - default void ignoreResources(String pattern) { - ignoreResources(ConfigurationCondition.alwaysTrue(), pattern); - } - - /** - * @deprecated Use - * {@link RuntimeResourceSupport#addResourceBundles(ConfigurationCondition, String)} - * instead. - */ - @Deprecated - default void addResourceBundles(String name) { - addResourceBundles(ConfigurationCondition.alwaysTrue(), name); + @SuppressWarnings("unchecked") + static ResourcesRegistry singleton() { + return ImageSingletons.lookup(ResourcesRegistry.class); } - void addClassBasedResourceBundle(ConfigurationCondition condition, String basename, String className); + void addClassBasedResourceBundle(C condition, String basename, String className); /** * Although the interface-methods below are already defined in the super-interface @@ -69,14 +46,14 @@ default void addResourceBundles(String name) { * reflectively. */ @Override - void addResources(ConfigurationCondition condition, String pattern); + void addResources(C condition, String pattern); @Override - void ignoreResources(ConfigurationCondition condition, String pattern); + void ignoreResources(C condition, String pattern); @Override - void addResourceBundles(ConfigurationCondition condition, String name); + void addResourceBundles(C condition, String name); @Override - void addResourceBundles(ConfigurationCondition condition, String basename, Collection locales); + void addResourceBundles(C condition, String basename, Collection locales); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java new file mode 100644 index 000000000000..d403da2152bc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java @@ -0,0 +1,197 @@ +/* + * 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 static com.oracle.svm.core.configure.ConfigurationFiles.Options.TrackUnsatisfiedTypeReachedConditions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.LogUtils; + +/** + * Represents a group of {@link #conditions} that guard a value. The conditions are encoded + *

+ * If any of the {@link #conditions} is satisfied then the whole set becomes also + * {@link #satisfied}. {@link RuntimeConditionSet}s can be created at build time + * {@link #createHosted(ConfigurationCondition)} and stored to the image heap, or it can be encoded + * ({@link #getTypesForEncoding()} and later decoded at run time ({@link #createDecoded(Object[])}. + * The current implementation does not cache {@link #conditions}, although this will be implemented + * in the future (GR-49526) + */ +public class RuntimeConditionSet { + + private Object[] conditions; + private boolean satisfied; + + @Platforms(Platform.HOSTED_ONLY.class) + public static RuntimeConditionSet emptySet() { + return new RuntimeConditionSet(new Object[0]); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static RuntimeConditionSet createHosted(ConfigurationCondition condition) { + var conditionSet = new RuntimeConditionSet(new Object[0]); + conditionSet.addCondition(condition); + return conditionSet; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public synchronized void addCondition(ConfigurationCondition cnd) { + VMError.guarantee(cnd.isRuntimeChecked(), "Only runtime conditions can be added to the ConditionalRuntimeValue."); + if (satisfied) { + return; + } else if (cnd.isAlwaysTrue()) { + conditions = null; + satisfied = true; + return; + } + + Object newRuntimeCondition = createRuntimeCondition(cnd); + Set existingConditions = conditions == null ? new HashSet<>() : new HashSet<>(Arrays.asList(conditions)); + existingConditions.add(newRuntimeCondition); + setConditions(existingConditions.toArray()); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public Set> getTypesForEncoding() { + if (conditions == null) { + return Set.of(); + } else { + Set> types = new HashSet<>(); + for (Object condition : conditions) { + types.addAll(getTypesForEncoding(condition)); + } + return types; + } + } + + public static RuntimeConditionSet unmodifiableEmptySet() { + return UnmodifiableRuntimeConditionSet.UNMODIFIABLE_EMPTY_SET; + } + + public static RuntimeConditionSet createDecoded(Object[] conditions) { + return new RuntimeConditionSet(conditions); + } + + /** + * Checks if any of the conditions has been satisfied. It caches the value in satisfied. This + * code can be concurrently executed, however there are no concurrency primitives used. The + * implementation relies on the fact that checking if a condition is satisfied is an idempotent + * operation. + * + * @return true if any of the elements is satisfied. + */ + public boolean satisfied() { + var result = false; + if (satisfied) { + result = true; + } else { + final var localConditions = conditions; + if (localConditions == null) { + result = true; + } else { + for (Object condition : localConditions) { + if (isSatisfied(condition)) { + conditions = null; + satisfied = result = true; + break; + } + } + } + } + + if (TrackUnsatisfiedTypeReachedConditions.getValue() && !result) { + LogUtils.info("Unsatisfied runtime conditions reachable at build-time: " + Arrays.toString(conditions)); + new Exception().printStackTrace(System.out); + return true; + } + + return result; + } + + @Override + public String toString() { + String conditionsString = this.conditions == null ? "[]" : Arrays.toString(this.conditions); + return conditionsString + " = " + satisfied; + } + + private RuntimeConditionSet(Object[] conditions) { + setConditions(conditions); + } + + private void setConditions(Object[] conditions) { + if (conditions.length == 0) { + this.conditions = null; + } else { + this.conditions = conditions; + } + satisfied = false; + } + + private static Object createRuntimeCondition(ConfigurationCondition cnd) { + if (cnd.isAlwaysTrue() || !cnd.isRuntimeChecked()) { + throw VMError.shouldNotReachHere("We should never create run-time conditions from conditions that are always true at build time. Condition: " + cnd); + } + return cnd.getType(); + } + + private static boolean isSatisfied(Object condition) { + if (condition instanceof Class typeReachedCondition) { + return DynamicHub.fromClass(typeReachedCondition).isReached(); + } else { + throw VMError.shouldNotReachHere("Only typeReached condition is supported."); + } + } + + private static Set> getTypesForEncoding(Object condition) { + if (condition instanceof Class res) { + return Set.of(res); + } else { + throw VMError.shouldNotReachHere("Only typeReached condition is supported."); + } + } + + public static final class UnmodifiableRuntimeConditionSet extends RuntimeConditionSet { + private static final RuntimeConditionSet UNMODIFIABLE_EMPTY_SET = new UnmodifiableRuntimeConditionSet(new Object[0]); + + private UnmodifiableRuntimeConditionSet(Object[] conditions) { + super(conditions); + } + + @Override + public synchronized void addCondition(ConfigurationCondition cnd) { + throw new UnsupportedOperationException("Can't add conditions to an unmodifiable set of conditions."); + } + } +} 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..e34d651a3030 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,86 +25,51 @@ */ 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; -public class SerializationConfigurationParser extends ConfigurationParser { +import jdk.graal.compiler.util.json.JsonParserException; + +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, ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, + boolean strictConfiguration) { + if (strictMetadata) { + return new SerializationMetadataParser<>(conditionResolver, serializationSupport, strictConfiguration); } else { - throw new JSONParserException("First-level of document must either be an array of serialization lists or a map of serialization types"); + return new LegacySerializationConfigurationParser<>(conditionResolver, serializationSupport, strictConfiguration); } } - private void parseOldConfiguration(List listOfSerializationConfigurationObjects) { - parseSerializationTypes(asList(listOfSerializationConfigurationObjects, "Second-level of document must be serialization descriptor objects"), false); - } - - private void parseNewConfiguration(EconomicMap listOfSerializationConfigurationObjects) { - if (!listOfSerializationConfigurationObjects.containsKey(SERIALIZATION_TYPES_KEY) || !listOfSerializationConfigurationObjects.containsKey(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY)) { - throw new JSONParserException("Second-level of document must be arrays of serialization descriptor objects"); - } - - parseSerializationTypes(asList(listOfSerializationConfigurationObjects.get(SERIALIZATION_TYPES_KEY), "The types property must be an array of serialization descriptor objects"), false); - parseSerializationTypes( - asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), - "The lambdaCapturingTypes property must be an array of serialization descriptor objects"), - true); - - if (listOfSerializationConfigurationObjects.containsKey(PROXY_SERIALIZATION_TYPES_KEY)) { - proxyConfigurationParser.parseAndRegister(listOfSerializationConfigurationObjects.get(PROXY_SERIALIZATION_TYPES_KEY), null); - } + public SerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(strictConfiguration); + this.serializationSupport = serializationSupport; + this.conditionResolver = conditionResolver; } - private void parseSerializationTypes(List listOfSerializationTypes, boolean lambdaCapturingTypes) { + protected void parseSerializationTypes(List listOfSerializationTypes, boolean lambdaCapturingTypes) { for (Object serializationType : listOfSerializationTypes) { parseSerializationDescriptorObject(asMap(serializationType, "Third-level of document must be serialization descriptor objects"), lambdaCapturingTypes); } } - private void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { - if (lambdaCapturingType) { - checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CONDITIONAL_KEY)); - } else { - checkAttributes(data, "serialization descriptor object", Collections.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, C condition, Object optionalCustomCtorValue) { + String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { + serializationSupport.registerWithTargetConstructorClass(condition, namedClass.name(), customTargetConstructorClass); + } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { + serializationSupport.registerProxyClass(condition, 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..9b74ed811243 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; +import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; + +final class SerializationMetadataParser extends SerializationConfigurationParser { + + SerializationMetadataParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(conditionResolver, serializationSupport, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object serializationJson = getFromGlobalFile(json, SERIALIZATION_KEY); + if (serializationJson != null) { + parseSerializationTypes(asList(serializationJson, "The serialization property must be an array of serialization descriptor object"), false); + } + } + + @Override + protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { + checkAttributes(data, "serialization descriptor object", List.of(TYPE_KEY), List.of(CONDITIONAL_KEY, CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)); + + Optional targetSerializationClass = parseTypeContents(data.get(TYPE_KEY)); + if (targetSerializationClass.isEmpty()) { + return; + } + + UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, true); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { + return; + } + + Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); + registerType(targetSerializationClass.get(), condition.get(), optionalCustomCtorValue); + } +} diff --git a/substratevm/src/com.oracle.svm.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..cc20fd1e7d7f 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,7 +1,8 @@ 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 + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json Example: 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..e606e1ede89c 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,7 +1,8 @@ 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 + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/serialization-config-schema-v1.1.0.json Example: From 9e2c6e7629eeb2a50550be6dea17ec81a2dd09ff Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 27 Aug 2024 14:41:50 +0200 Subject: [PATCH 3/8] Adapt configuration parser to 23.1 --- .../test/config/OmitPreviousConfigTests.java | 4 +- .../config/ResourceConfigurationTest.java | 4 +- .../svm/configure/ConfigurationBase.java | 7 +- .../config/ConfigurationFileCollection.java | 70 +++++-- .../configure/config/ConfigurationSet.java | 9 +- .../config/ParserConfigurationAdapter.java | 58 +++--- .../PredefinedClassesConfiguration.java | 8 +- .../configure/config/ProxyConfiguration.java | 12 +- .../config/ResourceConfiguration.java | 8 +- .../config/SerializationConfiguration.java | 8 +- .../configure/config/TypeConfiguration.java | 16 +- .../PartialConfigurationWithOrigins.java | 6 +- .../configure/ConditionalRuntimeValue.java | 69 ------ .../ConfigurationConditionResolver.java | 18 +- .../core/configure/ConfigurationFiles.java | 108 +++------- .../core/configure/ConfigurationParser.java | 65 +++--- .../ConfigurationTypeDescriptor.java | 2 +- .../LegacyReflectionConfigurationParser.java | 14 +- .../LegacyResourceConfigurationParser.java | 55 ++++- ...egacySerializationConfigurationParser.java | 31 ++- .../NamedConfigurationTypeDescriptor.java | 2 +- .../configure/ProxyConfigurationParser.java | 28 ++- .../ProxyConfigurationTypeDescriptor.java | 5 +- .../ReflectionConfigurationParser.java | 31 ++- ...ReflectionConfigurationParserDelegate.java | 46 ++-- .../configure/ReflectionMetadataParser.java | 14 +- .../ResourceConfigurationParser.java | 140 ++++++++----- .../configure/ResourceMetadataParser.java | 12 +- .../svm/core/configure/ResourcesRegistry.java | 43 +++- .../core/configure/RuntimeConditionSet.java | 197 ------------------ .../SerializationConfigurationParser.java | 23 +- .../SerializationMetadataParser.java | 8 +- .../ReflectionConfigurationFilesHelp.txt | 2 +- .../SerializationConfigurationFilesHelp.txt | 2 +- .../core/jdk/proxy/DynamicProxyRegistry.java | 2 +- ...java_lang_invoke_MethodHandles_Lookup.java | 7 + .../reflect/proxy/DynamicProxySupport.java | 6 +- .../ConditionalConfigurationRegistry.java | 4 +- .../oracle/svm/hosted/ResourcesFeature.java | 12 +- .../config/ConfigurationParserUtils.java | 14 +- .../config/ReflectionRegistryAdapter.java | 80 +++---- .../svm/hosted/config/RegistryAdapter.java | 153 ++++++++------ .../svm/hosted/jni/JNIAccessFeature.java | 17 +- .../svm/hosted/reflect/ReflectionFeature.java | 14 +- .../reflect/proxy/DynamicProxyFeature.java | 4 +- .../hosted/reflect/proxy/ProxyRegistry.java | 2 +- .../serialize/SerializationFeature.java | 15 +- 47 files changed, 693 insertions(+), 762 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java 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/ConditionalRuntimeValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java deleted file mode 100644 index d78533653924..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -/** - * A image-heap stored {@link ConditionalRuntimeValue#value} that is guarded by run-time computed - * {@link ConditionalRuntimeValue#conditions}. - *

- * {@link ConditionalRuntimeValue#conditions} are stored as an array to save space in the image - * heap. This is subject to further optimizations. - * - * @param type of the stored value. - */ -public final class ConditionalRuntimeValue { - RuntimeConditionSet conditions; - volatile T value; - - public ConditionalRuntimeValue(RuntimeConditionSet conditions, T value) { - this.conditions = conditions; - this.value = value; - } - - @Platforms(Platform.HOSTED_ONLY.class) - public T getValueUnconditionally() { - return value; - } - - public RuntimeConditionSet getConditions() { - return conditions; - } - - public T getValue() { - if (conditions.satisfied()) { - return value; - } else { - return null; - } - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void updateValue(T newValue) { - this.value = newValue; - } -} 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 index bfbba32b23cc..f63e5947d040 100644 --- 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 @@ -24,27 +24,27 @@ */ package com.oracle.svm.core.configure; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.TypeResult; -public interface ConfigurationConditionResolver { +public interface ConfigurationConditionResolver { - static ConfigurationConditionResolver identityResolver() { - return new ConfigurationConditionResolver<>() { + static ConfigurationConditionResolver identityResolver() { + return new ConfigurationConditionResolver() { @Override - public TypeResult resolveCondition(UnresolvedConfigurationCondition unresolvedCondition) { + public TypeResult resolveCondition(ConfigurationCondition unresolvedCondition) { return TypeResult.forType(unresolvedCondition.getTypeName(), unresolvedCondition); } @Override - public UnresolvedConfigurationCondition alwaysTrue() { - return UnresolvedConfigurationCondition.alwaysTrue(); + public ConfigurationCondition alwaysTrue() { + return ConfigurationCondition.alwaysTrue(); } }; } - TypeResult resolveCondition(UnresolvedConfigurationCondition unresolvedCondition); + TypeResult resolveCondition(ConfigurationCondition unresolvedCondition); - T alwaysTrue(); + ConfigurationCondition alwaysTrue(); } 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 53bfa1a703ea..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 @@ -34,16 +34,15 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionStability; +import org.graalvm.compiler.options.OptionType; + import com.oracle.svm.core.option.BundleMember; import com.oracle.svm.core.option.HostedOptionKey; -import com.oracle.svm.core.option.OptionMigrationMessage; +import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.util.UserError; -import jdk.graal.compiler.options.Option; -import jdk.graal.compiler.options.OptionStability; -import jdk.graal.compiler.options.OptionType; - /** * Gathers configuration files from specified directories without having to provide each * configuration file individually. @@ -51,118 +50,75 @@ public final class ConfigurationFiles { public static final class Options { - @Option(help = "Directories directly containing configuration files for dynamic features at runtime.", type = OptionType.User, stability = OptionStability.STABLE)// @BundleMember(role = BundleMember.Role.Input)// - static final HostedOptionKey ConfigurationFileDirectories = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + static final HostedOptionKey ConfigurationFileDirectories = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resource path above configuration resources for dynamic features at runtime.", type = OptionType.User)// - public static final HostedOptionKey ConfigurationResourceRoots = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ConfigurationResourceRoots = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @OptionMigrationMessage("Use a reflect-config.json in your META-INF/native-image// directory instead.")// @Option(help = "file:doc-files/ReflectionConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey ReflectionConfigurationFiles = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey ReflectionConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made available for reflection (see ReflectionConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey ReflectionConfigurationResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ReflectionConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @OptionMigrationMessage("Use a proxy-config.json in your META-INF/native-image// directory instead.")// - @Option(help = "file:doc-files/ProxyConfigurationFilesHelp.txt", type = OptionType.User, deprecated = true)// + @Option(help = "file:doc-files/ProxyConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey DynamicProxyConfigurationFiles = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", type = OptionType.User, deprecated = true, // - deprecationMessage = "This can be caused by a proxy-config.json file in your META-INF directory. " + - "Consider including proxy configuration in the reflection section of reachability-metadata.md instead.")// - public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - - @OptionMigrationMessage("Use a serialization-config.json in your META-INF/native-image// directory instead.")// + public static final HostedOptionKey DynamicProxyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + @Option(help = "Resources describing program elements to be made available for reflection (see ProxyConfigurationFiles).", type = OptionType.User)// + public static final HostedOptionKey DynamicProxyConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements to be made available for serialization (see SerializationConfigurationFiles).", type = OptionType.User)// - public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @OptionMigrationMessage("Use a serialization-config.json in your META-INF/native-image// directory instead.")// @Option(help = "file:doc-files/SerializationConfigurationFilesHelp.txt", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey SerializationDenyConfigurationFiles = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationDenyConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing program elements that must not be made available for serialization.", type = OptionType.User)// - public static final HostedOptionKey SerializationDenyConfigurationResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey SerializationDenyConfigurationResources = new HostedOptionKey<>( + LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @OptionMigrationMessage("Use a resource-config.json in your META-INF/native-image// directory instead.")// @Option(help = "Files describing Java resources to be included in the image according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey ResourceConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing Java resources to be included in the image according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/resource-config-schema-v1.0.0.json", type = OptionType.User)// - public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @OptionMigrationMessage("Use a jni-config.json in your META-INF/native-image// directory instead.")// - @Option(help = "Files describing program elements to be made accessible via JNI according to the schema at " + - "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json", type = OptionType.User)// + @Option(help = "Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing program elements to be made accessible via JNI according to the schema at " + - "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/jni-config-schema-v1.1.0.json", type = OptionType.User)// - public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey JNIConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + @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<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + 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<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey ForeignConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing stubs allowing foreign calls.", type = OptionType.User)// - public static final HostedOptionKey ForeignResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey ForeignResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - @OptionMigrationMessage("Use a predefined-classes-config.json in your META-INF/native-image// directory instead.")// @Option(help = "Files describing predefined classes that can be loaded at runtime according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// - public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Resources describing predefined classes that can be loaded at runtime according to the schema at " + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/predefined-classes-config-schema-v1.0.0.json", type = OptionType.User)// - public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>( + LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); @Option(help = "When configuration files do not match their schema, abort the image build instead of emitting a warning.")// public static final HostedOptionKey StrictConfiguration = new HostedOptionKey<>(false); - @Option(help = "Testing flag: the 'typeReachable' condition is treated as typeReached so the semantics of programs can change.")// - public static final HostedOptionKey TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false); - - @Option(help = "Testing flag: the 'name' is treated as 'type' in reflection configuration.")// - public static final HostedOptionKey TreatAllNameEntriesAsType = new HostedOptionKey<>(false); - - @Option(help = "Testing flag: the 'typeReached' condition is always satisfied however it prints the stack trace where it would not be satisfied.")// - public static final HostedOptionKey TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false); - - @Option(help = "Testing flag: print 'typeReached' conditions that are used on interfaces without default methods at build time.")// - public static final HostedOptionKey TrackTypeReachedOnInterfaces = new HostedOptionKey<>(false); - - @Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")// - public static final HostedOptionKey TreatAllUserSpaceTypesAsTrackedForTypeReached = new HostedOptionKey<>(false); - @Option(help = "Warn when reflection and JNI configuration files have elements that could not be found on the classpath or modulepath.", type = OptionType.Expert)// public static final HostedOptionKey WarnAboutMissingReflectionOrJNIMetadataElements = new HostedOptionKey<>(false); } 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 cc28760d0899..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 @@ -24,10 +24,6 @@ */ package com.oracle.svm.core.configure; -import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllTypeReachableConditionsAsTypeReached; -import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHABLE_KEY; -import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHED_KEY; - import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; @@ -46,16 +42,15 @@ import java.util.Set; import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.util.json.JSONParser; +import org.graalvm.util.json.JSONParserException; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.jdk.JavaNetSubstitutions; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; -import jdk.graal.compiler.util.json.JsonParser; -import jdk.graal.compiler.util.json.JsonParserException; - public abstract class ConfigurationParser { public static InputStream openStream(URI uri) throws IOException { URL url = uri.toURL(); @@ -67,6 +62,8 @@ 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"; @@ -76,6 +73,8 @@ public static InputStream openStream(URI uri) throws IOException { 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; @@ -85,7 +84,7 @@ protected ConfigurationParser(boolean strictConfiguration) { public void parseAndRegister(URI uri) throws IOException { try (Reader reader = openReader(uri)) { - parseAndRegister(new JsonParser(reader).parse(), uri); + parseAndRegister(new JSONParser(reader).parse(), uri); } catch (FileNotFoundException e) { /* * Ignore: *-config.json files can be missing when reachability-metadata.json is @@ -99,7 +98,7 @@ protected static BufferedReader openReader(URI uri) throws IOException { } public void parseAndRegister(Reader reader) throws IOException { - parseAndRegister(new JsonParser(reader).parse(), null); + parseAndRegister(new JSONParser(reader).parse(), null); } public abstract void parseAndRegister(Object json, URI origin) throws IOException; @@ -115,7 +114,7 @@ public static List asList(Object data, String errorMessage) { if (data instanceof List) { return (List) data; } - throw new JsonParserException(errorMessage); + throw new JSONParserException(errorMessage); } @SuppressWarnings("unchecked") @@ -123,7 +122,7 @@ public static EconomicMap asMap(Object data, String errorMessage if (data instanceof EconomicMap) { return (EconomicMap) data; } - throw new JsonParserException(errorMessage); + throw new JSONParserException(errorMessage); } protected void checkAttributes(EconomicMap map, String type, Collection requiredAttrs, Collection optionalAttrs) { @@ -132,7 +131,7 @@ protected void checkAttributes(EconomicMap map, String type, Col unseenRequired.remove(key); } if (!unseenRequired.isEmpty()) { - throw new JsonParserException("Missing attribute(s) [" + String.join(", ", unseenRequired) + "] in " + type); + throw new JSONParserException("Missing attribute(s) [" + String.join(", ", unseenRequired) + "] in " + type); } Set unknownAttributes = new HashSet<>(); for (String key : map.getKeys()) { @@ -159,14 +158,14 @@ public static void checkHasExactlyOneAttribute(EconomicMap map, if (alternativeAttributes.contains(key)) { if (attributeFound) { String message = "Exactly one of [" + String.join(", ", alternativeAttributes) + "] must be set in " + type; - throw new JsonParserException(message); + 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); + throw new JSONParserException(message); } } @@ -192,14 +191,14 @@ public static String asString(Object value) { if (value instanceof String) { return (String) value; } - throw new JsonParserException("Invalid string value \"" + value + "\"."); + throw new JSONParserException("Invalid string value \"" + value + "\"."); } protected static String asString(Object value, String propertyName) { if (value instanceof String) { return (String) value; } - throw new JsonParserException("Invalid string value \"" + value + "\" for element '" + propertyName + "'"); + throw new JSONParserException("Invalid string value \"" + value + "\" for element '" + propertyName + "'"); } protected static String asNullableString(Object value, String propertyName) { @@ -210,7 +209,7 @@ protected static boolean asBoolean(Object value, String propertyName) { if (value instanceof Boolean) { return (boolean) value; } - throw new JsonParserException("Invalid boolean value '" + value + "' for element '" + propertyName + "'"); + throw new JSONParserException("Invalid boolean value '" + value + "' for element '" + propertyName + "'"); } protected static long asLong(Object value, String propertyName) { @@ -220,15 +219,19 @@ protected static long asLong(Object value, String propertyName) { if (value instanceof Integer) { return (int) value; } - throw new JsonParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); + throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); } - protected UnresolvedConfigurationCondition parseCondition(EconomicMap data, boolean runtimeCondition) { + 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"); 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)) { @@ -238,26 +241,34 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap extends ReflectionConfigurationParser { +final class LegacyReflectionConfigurationParser extends ReflectionConfigurationParser { private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", @@ -45,7 +45,7 @@ final class LegacyReflectionConfigurationParser extends ReflectionConfigur private final boolean treatAllNameEntriesAsType; - LegacyReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + LegacyReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, boolean printMissingElements, boolean treatAllNameEntriesAsType) { super(conditionResolver, delegate, strictConfiguration, printMissingElements); this.treatAllNameEntriesAsType = treatAllNameEntriesAsType; @@ -72,8 +72,8 @@ protected void parseClass(EconomicMap data) { */ boolean isType = type.get().definedAsType(); - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, isType); - TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + ConfigurationCondition unresolvedCondition = parseCondition(data, isType); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); if (!conditionResult.isPresent()) { return; } @@ -82,14 +82,14 @@ protected void parseClass(EconomicMap data) { * Even if primitives cannot be queried through Class.forName, they can be registered to * allow getDeclaredMethods() and similar bulk queries at run time. */ - C condition = conditionResult.get(); + 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; } - C queryCondition = isType ? conditionResolver.alwaysTrue() : condition; + ConfigurationCondition queryCondition = isType ? conditionResolver.alwaysTrue() : condition; T clazz = result.get(); delegate.registerType(conditionResult.get(), clazz); 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 index 0f680cb44899..525b43cc1d49 100644 --- 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 @@ -25,12 +25,18 @@ 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; -final class LegacyResourceConfigurationParser extends ResourceConfigurationParser { - LegacyResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { +import com.oracle.svm.core.TypeResult; + +final class LegacyResourceConfigurationParser extends ResourceConfigurationParser { + LegacyResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { super(conditionResolver, registry, strictConfiguration); } @@ -39,6 +45,11 @@ 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; @@ -64,4 +75,44 @@ private void parseTopLevelObject(EconomicMap obj) { 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 index 46c92f8b389c..b94f83b3812e 100644 --- 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 @@ -30,22 +30,21 @@ import java.util.List; import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import org.graalvm.util.json.JSONParserException; -import jdk.graal.compiler.util.json.JsonParserException; - -final class LegacySerializationConfigurationParser extends SerializationConfigurationParser { +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; + private final ProxyConfigurationParser proxyConfigurationParser; - LegacySerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + LegacySerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { super(conditionResolver, serializationSupport, strictConfiguration); - this.proxyConfigurationParser = new ProxyConfigurationParser<>(conditionResolver, strictConfiguration, serializationSupport::registerProxyClass); + this.proxyConfigurationParser = new ProxyConfigurationParser(strictConfiguration, serializationSupport::registerProxyClass); } @Override @@ -55,7 +54,7 @@ public void parseAndRegister(Object json, URI origin) { } 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"); + throw new JSONParserException("First-level of document must either be an array of serialization lists or a map of serialization types"); } } @@ -65,7 +64,7 @@ private void parseOldConfiguration(List listOfSerializationConfiguration 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"); + 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); @@ -87,26 +86,20 @@ protected void parseSerializationDescriptorObject(EconomicMap da checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); } - ConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, false); + 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 = ((NamedConfigurationTypeDescriptor) targetSerializationClass).name(); + 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; - if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { - serializationSupport.registerWithTargetConstructorClass(condition.get(), namedClass.name(), customTargetConstructorClass); - } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { - serializationSupport.registerProxyClass(condition.get(), proxyClass.interfaceNames()); - } else { - throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); - } + 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 index 5a4985834819..86b33152bf47 100644 --- 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 @@ -28,7 +28,7 @@ import java.util.Collection; import java.util.Collections; -import jdk.graal.compiler.util.json.JsonWriter; +import com.oracle.svm.core.util.json.JsonWriter; public record NamedConfigurationTypeDescriptor(String name) implements ConfigurationTypeDescriptor { 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 048db3ef2e0f..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 @@ -31,27 +31,25 @@ import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +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; -import jdk.graal.compiler.util.json.JsonParserException; - /** * Parses JSON describing lists of interfaces and register them in the {@link DynamicProxyRegistry}. */ -public final class ProxyConfigurationParser extends ConfigurationParser { +public final class ProxyConfigurationParser extends ConfigurationParser { - private final ConfigurationConditionResolver conditionResolver; + private final ConfigurationConditionResolver conditionResolver; - private final BiConsumer> proxyConfigConsumer; + private final BiConsumer> proxyConfigConsumer; - public ProxyConfigurationParser(ConfigurationConditionResolver conditionResolver, boolean strictConfiguration, - BiConsumer> proxyConfigConsumer) { + public ProxyConfigurationParser(boolean strictConfiguration, BiConsumer> proxyConfigConsumer) { super(strictConfiguration); this.proxyConfigConsumer = proxyConfigConsumer; - this.conditionResolver = conditionResolver; + this.conditionResolver = ConfigurationConditionResolver.identityResolver(); } @Override @@ -70,28 +68,28 @@ private void parseTopLevelArray(List proxyConfiguration) { foundProxyConfigurationObjects = true; parseWithConditionalConfig(asMap(proxyConfigurationObject, "")); } else { - throw new JsonParserException("Second-level must be composed of either interface lists or proxy configuration objects"); + throw new JSONParserException("Second-level must be composed of either interface lists or proxy configuration objects"); } if (foundInterfaceLists && foundProxyConfigurationObjects) { - throw new JsonParserException("Second-level can only be populated of either interface lists or proxy configuration objects, but these cannot be mixed"); + throw new JSONParserException("Second-level can only be populated of either interface lists or proxy configuration objects, but these cannot be mixed"); } } } - private void parseInterfaceList(C condition, List data) { + private void parseInterfaceList(ConfigurationCondition condition, List data) { List interfaces = data.stream().map(ConfigurationParser::asString).collect(Collectors.toList()); try { proxyConfigConsumer.accept(condition, interfaces); } catch (Exception e) { - throw new JsonParserException(e.toString()); + throw new JSONParserException(e.toString()); } } private void parseWithConditionalConfig(EconomicMap proxyConfigObject) { checkAttributes(proxyConfigObject, "proxy descriptor object", Collections.singleton("interfaces"), Collections.singletonList(CONDITIONAL_KEY)); - UnresolvedConfigurationCondition condition = parseCondition(proxyConfigObject, false); - TypeResult resolvedCondition = conditionResolver.resolveCondition(condition); + 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 index c60f589852c9..a085ae2ab252 100644 --- 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 @@ -30,9 +30,8 @@ import java.util.List; import com.oracle.svm.core.reflect.proxy.DynamicProxySupport; - -import jdk.graal.compiler.util.json.JsonPrinter; -import jdk.graal.compiler.util.json.JsonWriter; +import com.oracle.svm.core.util.json.JsonPrinter; +import com.oracle.svm.core.util.json.JsonWriter; public record ProxyConfigurationTypeDescriptor(List interfaceNames) implements ConfigurationTypeDescriptor { 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 349f586ff5f7..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 @@ -31,24 +31,24 @@ 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.util.LogUtils; -import jdk.graal.compiler.util.json.JsonParserException; - /** * Parses JSON describing classes, methods and fields and delegates their registration to a * {@link ReflectionConfigurationParserDelegate}. */ -public abstract class ReflectionConfigurationParser extends ConfigurationParser { +public abstract class ReflectionConfigurationParser extends ConfigurationParser { private static final String CONSTRUCTOR_NAME = ""; - protected final ConfigurationConditionResolver conditionResolver; - protected final ReflectionConfigurationParserDelegate delegate; + protected final ConfigurationConditionResolver conditionResolver; + protected final ReflectionConfigurationParserDelegate delegate; private final boolean printMissingElements; - public ReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + public ReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, boolean printMissingElements) { super(strictConfiguration); this.conditionResolver = conditionResolver; @@ -56,13 +56,12 @@ public ReflectionConfigurationParser(ConfigurationConditionResolver condition this.delegate = delegate; } - public static ReflectionConfigurationParser create(String combinedFileKey, boolean strictMetadata, - ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, - boolean strictConfiguration, boolean printMissingElements, boolean treatAllEntriesAsType) { + public static ReflectionConfigurationParser create(String combinedFileKey, boolean strictMetadata, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { if (strictMetadata) { - return new ReflectionMetadataParser<>(combinedFileKey, conditionResolver, delegate, strictConfiguration, printMissingElements); + return new ReflectionMetadataParser<>(combinedFileKey, ConfigurationConditionResolver.identityResolver(), delegate, strictConfiguration, printMissingElements); } else { - return new LegacyReflectionConfigurationParser<>(conditionResolver, delegate, strictConfiguration, printMissingElements, treatAllEntriesAsType); + return new LegacyReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), delegate, strictConfiguration, printMissingElements, false); } } @@ -84,13 +83,13 @@ protected void registerIfNotDefault(EconomicMap data, boolean de } } - protected void parseFields(C condition, List fields, T clazz) { + protected void parseFields(ConfigurationCondition condition, List fields, T clazz) { for (Object field : fields) { parseField(condition, asMap(field, "Elements of 'fields' array must be field descriptor objects"), clazz); } } - private void parseField(C condition, 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"); @@ -104,13 +103,13 @@ private void parseField(C condition, EconomicMap data, T clazz) } } - protected void parseMethods(C condition, boolean queriedOnly, List methods, T clazz) { + protected void parseMethods(ConfigurationCondition condition, boolean queriedOnly, List methods, T clazz) { for (Object method : methods) { parseMethod(condition, queriedOnly, asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); } } - private void parseMethod(C condition, 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; @@ -144,7 +143,7 @@ private void parseMethod(C condition, boolean queriedOnly, EconomicMap { +public interface ReflectionConfigurationParserDelegate { - TypeResult resolveType(C condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives); + TypeResult resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives); - void registerType(C condition, T type); + void registerType(ConfigurationCondition condition, T type); - void registerPublicClasses(C condition, T type); + void registerPublicClasses(ConfigurationCondition condition, T type); - void registerDeclaredClasses(C condition, T type); + void registerDeclaredClasses(ConfigurationCondition condition, T type); - void registerRecordComponents(C condition, T type); + void registerRecordComponents(ConfigurationCondition condition, T type); - void registerPermittedSubclasses(C condition, T type); + void registerPermittedSubclasses(ConfigurationCondition condition, T type); - void registerNestMembers(C condition, T type); + void registerNestMembers(ConfigurationCondition condition, T type); - void registerSigners(C condition, T type); + void registerSigners(ConfigurationCondition condition, T type); - void registerPublicFields(C condition, boolean queriedOnly, T type); + void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredFields(C condition, boolean queriedOnly, T type); + void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicMethods(C condition, boolean queriedOnly, T type); + void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredMethods(C condition, boolean queriedOnly, T type); + void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicConstructors(C condition, boolean queriedOnly, T type); + void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredConstructors(C condition, boolean queriedOnly, T type); + void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerField(C condition, T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; + void registerField(ConfigurationCondition condition, T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; - boolean registerAllMethodsWithName(C condition, boolean queriedOnly, T type, String methodName); + boolean registerAllMethodsWithName(ConfigurationCondition condition, boolean queriedOnly, T type, String methodName); - void registerMethod(C condition, boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; + void registerMethod(ConfigurationCondition condition, boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; - void registerConstructor(C condition, boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; + void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; - boolean registerAllConstructors(C condition, boolean queriedOnly, T type); + boolean registerAllConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerUnsafeAllocated(C condition, 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 index 33bd0028be6c..7b4fa47a4533 100644 --- 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 @@ -31,18 +31,18 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.collections.MapCursor; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.TypeResult; -class ReflectionMetadataParser extends ReflectionConfigurationParser { +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, + ReflectionMetadataParser(String combinedFileKey, ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, boolean printMissingElements) { super(conditionResolver, delegate, strictConfiguration, printMissingElements); this.combinedFileKey = combinedFileKey; @@ -65,12 +65,12 @@ protected void parseClass(EconomicMap data) { return; } - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, true); - TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + ConfigurationCondition unresolvedCondition = parseCondition(data, true); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); if (!conditionResult.isPresent()) { return; } - C condition = conditionResult.get(); + ConfigurationCondition condition = conditionResult.get(); /* * Even if primitives cannot be queried through Class.forName, they can be registered to @@ -82,7 +82,7 @@ protected void parseClass(EconomicMap data) { return; } - C queryCondition = conditionResolver.alwaysTrue(); + ConfigurationCondition queryCondition = conditionResolver.alwaysTrue(); T clazz = result.get(); delegate.registerType(conditionResult.get(), clazz); 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 8242237d6a6c..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 @@ -28,35 +28,37 @@ 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.nativeimage.impl.ConfigurationCondition; +import org.graalvm.util.json.JSONParserException; import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.jdk.localization.LocalizationSupport; -import jdk.graal.compiler.util.json.JsonParserException; +public abstract class ResourceConfigurationParser extends ConfigurationParser { + protected final ResourcesRegistry registry; -public abstract class ResourceConfigurationParser extends ConfigurationParser { - protected final ResourcesRegistry registry; + protected final ConfigurationConditionResolver conditionResolver; - protected final ConfigurationConditionResolver conditionResolver; - - public static ResourceConfigurationParser create(boolean strictMetadata, ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + public static ResourceConfigurationParser create(boolean strictMetadata, ResourcesRegistry registry, boolean strictConfiguration) { if (strictMetadata) { - return new ResourceMetadataParser<>(conditionResolver, registry, strictConfiguration); + return new ResourceMetadataParser(ConfigurationConditionResolver.identityResolver(), registry, strictConfiguration); } else { - return new LegacyResourceConfigurationParser<>(conditionResolver, registry, strictConfiguration); + return new LegacyResourceConfigurationParser(ConfigurationConditionResolver.identityResolver(), registry, strictConfiguration); } } - protected ResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + protected ResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { super(strictConfiguration); this.registry = registry; this.conditionResolver = conditionResolver; } + protected abstract ConfigurationCondition parseCondition(EconomicMap data); + protected void parseBundlesObject(Object bundlesObject) { List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); for (Object bundle : bundles) { @@ -64,38 +66,11 @@ protected void parseBundlesObject(Object bundlesObject) { } } - @SuppressWarnings("unchecked") - protected void parseResourcesObject(Object resourcesObject) { - if (resourcesObject instanceof EconomicMap) { // New format - EconomicMap resourcesObjectMap = (EconomicMap) resourcesObject; - checkAttributes(resourcesObjectMap, "resource descriptor object", Collections.singleton("includes"), Collections.singleton("excludes")); - Object includesObject = resourcesObjectMap.get("includes"); - Object excludesObject = resourcesObjectMap.get("excludes"); - - List includes = asList(includesObject, "Attribute 'includes' must be a list of resources"); - for (Object object : includes) { - parsePatternEntry(object, registry::addResources, "'includes' list"); - } - - if (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 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")); - TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource, false)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource)); if (!resolvedConfigurationCondition.isPresent()) { return; } @@ -128,28 +103,91 @@ private static Locale parseLocale(Object input) { String localeTag = asString(input); Locale locale = LocalizationSupport.parseLocaleFromTag(localeTag); if (locale == null) { - throw new JsonParserException(localeTag + " is not a valid locale tag"); + throw new JSONParserException(localeTag + " is not a valid locale tag"); } return locale; } - 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; + 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; } - Object valueObject = resource.get("pattern"); - String value = asString(valueObject, "pattern"); - resourceRegistry.accept(resolvedConfigurationCondition.get(), value); + 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, registry::addGlob); + parseGlobEntry(object, (condition, module, resource) -> registry.addResources(condition, globToRegex(module, resource))); } } @@ -157,10 +195,10 @@ private interface GlobPatternConsumer { void accept(T a, String b, String c); } - private void parseGlobEntry(Object data, GlobPatternConsumer resourceRegistry) { + 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, false)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(globObject)); if (!resolvedConfigurationCondition.isPresent()) { return; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java index 2e1b4720ed4a..caa09ce98cf3 100644 --- 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 @@ -26,8 +26,11 @@ import java.net.URI; -final class ResourceMetadataParser extends ResourceConfigurationParser { - ResourceMetadataParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { +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); } @@ -42,4 +45,9 @@ public void parseAndRegister(Object json, URI origin) { 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 89ce92951e1b..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 @@ -31,14 +31,43 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeResourceSupport; -public interface ResourcesRegistry extends RuntimeResourceSupport { +public interface ResourcesRegistry extends RuntimeResourceSupport { @SuppressWarnings("unchecked") - static ResourcesRegistry singleton() { + static ResourcesRegistry singleton() { return ImageSingletons.lookup(ResourcesRegistry.class); } - void addClassBasedResourceBundle(C condition, String basename, String className); + /** + * @deprecated Use {@link RuntimeResourceSupport#addResources(ConfigurationCondition, String)} + * instead. + */ + @Deprecated + default void addResources(String pattern) { + addResources(ConfigurationCondition.alwaysTrue(), pattern); + } + + /** + * @deprecated Use + * {@link RuntimeResourceSupport#ignoreResources(ConfigurationCondition, String)} + * instead. + */ + @Deprecated + default void ignoreResources(String pattern) { + ignoreResources(ConfigurationCondition.alwaysTrue(), pattern); + } + + /** + * @deprecated Use + * {@link RuntimeResourceSupport#addResourceBundles(ConfigurationCondition, String)} + * instead. + */ + @Deprecated + default void addResourceBundles(String name) { + addResourceBundles(ConfigurationCondition.alwaysTrue(), name); + } + + void addClassBasedResourceBundle(ConfigurationCondition condition, String basename, String className); /** * Although the interface-methods below are already defined in the super-interface @@ -46,14 +75,14 @@ static ResourcesRegistry singleton() { * reflectively. */ @Override - void addResources(C condition, String pattern); + void addResources(ConfigurationCondition condition, String pattern); @Override - void ignoreResources(C condition, String pattern); + void ignoreResources(ConfigurationCondition condition, String pattern); @Override - void addResourceBundles(C condition, String name); + void addResourceBundles(ConfigurationCondition condition, String name); @Override - void addResourceBundles(C condition, String basename, Collection locales); + void addResourceBundles(ConfigurationCondition condition, String basename, Collection locales); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java deleted file mode 100644 index d403da2152bc..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/RuntimeConditionSet.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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 static com.oracle.svm.core.configure.ConfigurationFiles.Options.TrackUnsatisfiedTypeReachedConditions; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.impl.ConfigurationCondition; - -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.LogUtils; - -/** - * Represents a group of {@link #conditions} that guard a value. The conditions are encoded - *

- * If any of the {@link #conditions} is satisfied then the whole set becomes also - * {@link #satisfied}. {@link RuntimeConditionSet}s can be created at build time - * {@link #createHosted(ConfigurationCondition)} and stored to the image heap, or it can be encoded - * ({@link #getTypesForEncoding()} and later decoded at run time ({@link #createDecoded(Object[])}. - * The current implementation does not cache {@link #conditions}, although this will be implemented - * in the future (GR-49526) - */ -public class RuntimeConditionSet { - - private Object[] conditions; - private boolean satisfied; - - @Platforms(Platform.HOSTED_ONLY.class) - public static RuntimeConditionSet emptySet() { - return new RuntimeConditionSet(new Object[0]); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public static RuntimeConditionSet createHosted(ConfigurationCondition condition) { - var conditionSet = new RuntimeConditionSet(new Object[0]); - conditionSet.addCondition(condition); - return conditionSet; - } - - @Platforms(Platform.HOSTED_ONLY.class) - public synchronized void addCondition(ConfigurationCondition cnd) { - VMError.guarantee(cnd.isRuntimeChecked(), "Only runtime conditions can be added to the ConditionalRuntimeValue."); - if (satisfied) { - return; - } else if (cnd.isAlwaysTrue()) { - conditions = null; - satisfied = true; - return; - } - - Object newRuntimeCondition = createRuntimeCondition(cnd); - Set existingConditions = conditions == null ? new HashSet<>() : new HashSet<>(Arrays.asList(conditions)); - existingConditions.add(newRuntimeCondition); - setConditions(existingConditions.toArray()); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public Set> getTypesForEncoding() { - if (conditions == null) { - return Set.of(); - } else { - Set> types = new HashSet<>(); - for (Object condition : conditions) { - types.addAll(getTypesForEncoding(condition)); - } - return types; - } - } - - public static RuntimeConditionSet unmodifiableEmptySet() { - return UnmodifiableRuntimeConditionSet.UNMODIFIABLE_EMPTY_SET; - } - - public static RuntimeConditionSet createDecoded(Object[] conditions) { - return new RuntimeConditionSet(conditions); - } - - /** - * Checks if any of the conditions has been satisfied. It caches the value in satisfied. This - * code can be concurrently executed, however there are no concurrency primitives used. The - * implementation relies on the fact that checking if a condition is satisfied is an idempotent - * operation. - * - * @return true if any of the elements is satisfied. - */ - public boolean satisfied() { - var result = false; - if (satisfied) { - result = true; - } else { - final var localConditions = conditions; - if (localConditions == null) { - result = true; - } else { - for (Object condition : localConditions) { - if (isSatisfied(condition)) { - conditions = null; - satisfied = result = true; - break; - } - } - } - } - - if (TrackUnsatisfiedTypeReachedConditions.getValue() && !result) { - LogUtils.info("Unsatisfied runtime conditions reachable at build-time: " + Arrays.toString(conditions)); - new Exception().printStackTrace(System.out); - return true; - } - - return result; - } - - @Override - public String toString() { - String conditionsString = this.conditions == null ? "[]" : Arrays.toString(this.conditions); - return conditionsString + " = " + satisfied; - } - - private RuntimeConditionSet(Object[] conditions) { - setConditions(conditions); - } - - private void setConditions(Object[] conditions) { - if (conditions.length == 0) { - this.conditions = null; - } else { - this.conditions = conditions; - } - satisfied = false; - } - - private static Object createRuntimeCondition(ConfigurationCondition cnd) { - if (cnd.isAlwaysTrue() || !cnd.isRuntimeChecked()) { - throw VMError.shouldNotReachHere("We should never create run-time conditions from conditions that are always true at build time. Condition: " + cnd); - } - return cnd.getType(); - } - - private static boolean isSatisfied(Object condition) { - if (condition instanceof Class typeReachedCondition) { - return DynamicHub.fromClass(typeReachedCondition).isReached(); - } else { - throw VMError.shouldNotReachHere("Only typeReached condition is supported."); - } - } - - private static Set> getTypesForEncoding(Object condition) { - if (condition instanceof Class res) { - return Set.of(res); - } else { - throw VMError.shouldNotReachHere("Only typeReached condition is supported."); - } - } - - public static final class UnmodifiableRuntimeConditionSet extends RuntimeConditionSet { - private static final RuntimeConditionSet UNMODIFIABLE_EMPTY_SET = new UnmodifiableRuntimeConditionSet(new Object[0]); - - private UnmodifiableRuntimeConditionSet(Object[] conditions) { - super(conditions); - } - - @Override - public synchronized void addCondition(ConfigurationCondition cnd) { - throw new UnsupportedOperationException("Can't add conditions to an unmodifiable set of conditions."); - } - } -} 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 e34d651a3030..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 @@ -28,27 +28,26 @@ 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; -import jdk.graal.compiler.util.json.JsonParserException; - -public abstract class SerializationConfigurationParser extends ConfigurationParser { +public abstract class SerializationConfigurationParser extends ConfigurationParser { public static final String CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY = "customTargetConstructorClass"; - protected final ConfigurationConditionResolver conditionResolver; - protected final RuntimeSerializationSupport serializationSupport; + protected final ConfigurationConditionResolver conditionResolver; + protected final RuntimeSerializationSupport serializationSupport; - public static SerializationConfigurationParser create(boolean strictMetadata, ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, - boolean strictConfiguration) { + public static SerializationConfigurationParser create(boolean strictMetadata, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { if (strictMetadata) { - return new SerializationMetadataParser<>(conditionResolver, serializationSupport, strictConfiguration); + return new SerializationMetadataParser<>(ConfigurationConditionResolver.identityResolver(), serializationSupport, strictConfiguration); } else { - return new LegacySerializationConfigurationParser<>(conditionResolver, serializationSupport, strictConfiguration); + return new LegacySerializationConfigurationParser(ConfigurationConditionResolver.identityResolver(), serializationSupport, strictConfiguration); } } - public SerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + public SerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { super(strictConfiguration); this.serializationSupport = serializationSupport; this.conditionResolver = conditionResolver; @@ -62,14 +61,14 @@ protected void parseSerializationTypes(List listOfSerializationTypes, bo protected abstract void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType); - protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, C condition, Object optionalCustomCtorValue) { + 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 { - throw new JsonParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); + throw new JSONParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java index 9b74ed811243..86d81982df34 100644 --- 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 @@ -29,12 +29,12 @@ import java.util.Optional; import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; -import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition; -final class SerializationMetadataParser extends SerializationConfigurationParser { +final class SerializationMetadataParser extends SerializationConfigurationParser { - SerializationMetadataParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + SerializationMetadataParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { super(conditionResolver, serializationSupport, strictConfiguration); } @@ -55,7 +55,7 @@ protected void parseSerializationDescriptorObject(EconomicMap da return; } - UnresolvedConfigurationCondition unresolvedCondition = parseCondition(data, true); + ConfigurationCondition unresolvedCondition = parseCondition(data, true); var condition = conditionResolver.resolveCondition(unresolvedCondition); if (!condition.isPresent()) { return; 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 cc20fd1e7d7f..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 @@ -2,7 +2,7 @@ One or several (comma-separated) paths to JSON files that specify which program The JSON object schema is described at: - https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.0.0.json Example: 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 e606e1ede89c..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 @@ -2,7 +2,7 @@ One or several (comma-separated) paths to JSON files that specify lists of seria 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.1.0.json + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/serialization-config-schema-v1.0.0.json Example: 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/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.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..1d815eb71c0e 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,7 +32,7 @@ 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; public class ReflectionRegistryAdapter extends RegistryAdapter { @@ -44,86 +44,90 @@ 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) { + if (!queriedOnly) { + reflectionSupport.registerAllFieldsQuery(condition, type); + } } @Override - public void registerDeclaredFields(ConditionalElement> type) { - reflectionSupport.registerAllDeclaredFieldsQuery(type.getCondition(), type.getElement()); + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + if (!queriedOnly) { + reflectionSupport.registerAllDeclaredFieldsQuery(condition, 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 +135,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 +148,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/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()); From 595cd903422c7b69a8432335467dccc3eace506b Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Thu, 30 Nov 2023 11:35:47 +0100 Subject: [PATCH 4/8] Backport GR-50432: Allow fields to be registered for reflection without being made reachable --- .../MissingReflectionRegistrationUtils.java | 11 ++++ ...t_jdk_internal_misc_Unsafe_Reflection.java | 11 ++-- .../config/ReflectionRegistryAdapter.java | 9 ++-- .../hosted/reflect/ReflectionDataBuilder.java | 50 +++++++++++++------ 4 files changed, 52 insertions(+), 29 deletions(-) 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/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/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index 1d815eb71c0e..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 @@ -34,6 +34,7 @@ import com.oracle.svm.core.TypeResult; 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; @@ -89,16 +90,12 @@ public void registerSigners(ConfigurationCondition condition, Class type) { @Override public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { - if (!queriedOnly) { - reflectionSupport.registerAllFieldsQuery(condition, type); - } + ((ReflectionDataBuilder) reflectionSupport).registerAllFieldsQuery(condition, queriedOnly, type); } @Override public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { - if (!queriedOnly) { - reflectionSupport.registerAllDeclaredFieldsQuery(condition, type); - } + ((ReflectionDataBuilder) reflectionSupport).registerAllDeclaredFieldsQuery(condition, queriedOnly, type); } @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); } From 53be74330f0f64dd707e9c383f3f4f54e759c280 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Thu, 17 Oct 2024 18:47:31 +0300 Subject: [PATCH 5/8] Mark suite files for 23.1.5 release --- compiler/mx.compiler/suite.py | 2 +- espresso/mx.espresso/suite.py | 2 +- regex/mx.regex/suite.py | 2 +- sdk/mx.sdk/suite.py | 2 +- substratevm/mx.substratevm/suite.py | 2 +- tools/mx.tools/suite.py | 2 +- truffle/mx.truffle/suite.py | 2 +- vm/mx.vm/suite.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index afefef8fbc99..654d7924de06 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -5,7 +5,7 @@ "groupId" : "org.graalvm.compiler", "version" : "23.1.5", - "release" : False, + "release" : True, "url" : "http://www.graalvm.org/", "developer" : { "name" : "GraalVM Development", diff --git a/espresso/mx.espresso/suite.py b/espresso/mx.espresso/suite.py index 15b839216aa2..95bb3b734544 100644 --- a/espresso/mx.espresso/suite.py +++ b/espresso/mx.espresso/suite.py @@ -24,7 +24,7 @@ "mxversion": "6.44.0", "name": "espresso", "version" : "23.1.5", - "release" : False, + "release" : True, "groupId" : "org.graalvm.espresso", "url" : "https://www.graalvm.org/reference-manual/java-on-truffle/", "developer" : { diff --git a/regex/mx.regex/suite.py b/regex/mx.regex/suite.py index 18b63f812a86..866154707c69 100644 --- a/regex/mx.regex/suite.py +++ b/regex/mx.regex/suite.py @@ -44,7 +44,7 @@ "name" : "regex", "version" : "23.1.5", - "release" : False, + "release" : True, "groupId" : "org.graalvm.regex", "url" : "http://www.graalvm.org/", "developer" : { diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index bdc521a137c3..1cad3a2e0da2 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -42,7 +42,7 @@ "mxversion": "6.39.0", "name" : "sdk", "version" : "23.1.5", - "release" : False, + "release" : True, "sourceinprojectwhitelist" : [], "url" : "https://github.com/oracle/graal", "groupId" : "org.graalvm.sdk", diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 405da9538582..860c641d5d36 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -3,7 +3,7 @@ "mxversion": "6.27.1", "name": "substratevm", "version" : "23.1.5", - "release" : False, + "release" : True, "url" : "https://github.com/oracle/graal/tree/master/substratevm", "groupId" : "org.graalvm.nativeimage", diff --git a/tools/mx.tools/suite.py b/tools/mx.tools/suite.py index d2ab31125dca..387787a5a965 100644 --- a/tools/mx.tools/suite.py +++ b/tools/mx.tools/suite.py @@ -27,7 +27,7 @@ "groupId" : "org.graalvm.tools", "version" : "23.1.5", - "release" : False, + "release" : True, "url" : "http://openjdk.java.net/projects/graal", "developer" : { "name" : "GraalVM Development", diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index 7f1c7ca62bea..a6788b65eab3 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -42,7 +42,7 @@ "mxversion": "6.39.0", "name" : "truffle", "version" : "23.1.5", - "release" : False, + "release" : True, "groupId" : "org.graalvm.truffle", "sourceinprojectwhitelist" : [], "url" : "http://openjdk.java.net/projects/graal", diff --git a/vm/mx.vm/suite.py b/vm/mx.vm/suite.py index 3e1226aef96a..95a25579c904 100644 --- a/vm/mx.vm/suite.py +++ b/vm/mx.vm/suite.py @@ -2,7 +2,7 @@ "name": "vm", "version" : "23.1.5", "mxversion": "6.41.0", - "release" : False, + "release" : True, "groupId" : "org.graalvm", "url" : "http://www.graalvm.org/", From c1a1444809d2fc243a3ed7d5ea4a92a55b302d8b Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Thu, 17 Oct 2024 19:00:12 +0300 Subject: [PATCH 6/8] New dev cycle GraalVM Community 23.1.6 --- compiler/mx.compiler/suite.py | 4 ++-- espresso/mx.espresso/suite.py | 4 ++-- regex/mx.regex/suite.py | 4 ++-- sdk/mx.sdk/suite.py | 4 ++-- substratevm/mx.substratevm/suite.py | 4 ++-- tools/mx.tools/suite.py | 4 ++-- truffle/mx.truffle/suite.py | 4 ++-- vm/mx.vm/suite.py | 4 ++-- wasm/mx.wasm/suite.py | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index 654d7924de06..cd48add0b659 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -4,8 +4,8 @@ "sourceinprojectwhitelist" : [], "groupId" : "org.graalvm.compiler", - "version" : "23.1.5", - "release" : True, + "version" : "23.1.6", + "release" : False, "url" : "http://www.graalvm.org/", "developer" : { "name" : "GraalVM Development", diff --git a/espresso/mx.espresso/suite.py b/espresso/mx.espresso/suite.py index 95bb3b734544..c8741c878758 100644 --- a/espresso/mx.espresso/suite.py +++ b/espresso/mx.espresso/suite.py @@ -23,8 +23,8 @@ suite = { "mxversion": "6.44.0", "name": "espresso", - "version" : "23.1.5", - "release" : True, + "version" : "23.1.6", + "release" : False, "groupId" : "org.graalvm.espresso", "url" : "https://www.graalvm.org/reference-manual/java-on-truffle/", "developer" : { diff --git a/regex/mx.regex/suite.py b/regex/mx.regex/suite.py index 866154707c69..597f6b371f31 100644 --- a/regex/mx.regex/suite.py +++ b/regex/mx.regex/suite.py @@ -43,8 +43,8 @@ "name" : "regex", - "version" : "23.1.5", - "release" : True, + "version" : "23.1.6", + "release" : False, "groupId" : "org.graalvm.regex", "url" : "http://www.graalvm.org/", "developer" : { diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index 1cad3a2e0da2..3aa20eeb20c4 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -41,8 +41,8 @@ suite = { "mxversion": "6.39.0", "name" : "sdk", - "version" : "23.1.5", - "release" : True, + "version" : "23.1.6", + "release" : False, "sourceinprojectwhitelist" : [], "url" : "https://github.com/oracle/graal", "groupId" : "org.graalvm.sdk", diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 860c641d5d36..c5b92ef5f4c4 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -2,8 +2,8 @@ suite = { "mxversion": "6.27.1", "name": "substratevm", - "version" : "23.1.5", - "release" : True, + "version" : "23.1.6", + "release" : False, "url" : "https://github.com/oracle/graal/tree/master/substratevm", "groupId" : "org.graalvm.nativeimage", diff --git a/tools/mx.tools/suite.py b/tools/mx.tools/suite.py index 387787a5a965..a93016b6968c 100644 --- a/tools/mx.tools/suite.py +++ b/tools/mx.tools/suite.py @@ -26,8 +26,8 @@ "defaultLicense" : "GPLv2-CPE", "groupId" : "org.graalvm.tools", - "version" : "23.1.5", - "release" : True, + "version" : "23.1.6", + "release" : False, "url" : "http://openjdk.java.net/projects/graal", "developer" : { "name" : "GraalVM Development", diff --git a/truffle/mx.truffle/suite.py b/truffle/mx.truffle/suite.py index a6788b65eab3..6c5dbf905191 100644 --- a/truffle/mx.truffle/suite.py +++ b/truffle/mx.truffle/suite.py @@ -41,8 +41,8 @@ suite = { "mxversion": "6.39.0", "name" : "truffle", - "version" : "23.1.5", - "release" : True, + "version" : "23.1.6", + "release" : False, "groupId" : "org.graalvm.truffle", "sourceinprojectwhitelist" : [], "url" : "http://openjdk.java.net/projects/graal", diff --git a/vm/mx.vm/suite.py b/vm/mx.vm/suite.py index 95a25579c904..b708569bc592 100644 --- a/vm/mx.vm/suite.py +++ b/vm/mx.vm/suite.py @@ -1,8 +1,8 @@ suite = { "name": "vm", - "version" : "23.1.5", + "version" : "23.1.6", "mxversion": "6.41.0", - "release" : True, + "release" : False, "groupId" : "org.graalvm", "url" : "http://www.graalvm.org/", diff --git a/wasm/mx.wasm/suite.py b/wasm/mx.wasm/suite.py index 1dab86c25de2..8d0061dc940a 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", + "version" : "23.1.6", "versionConflictResolution" : "latest", "url" : "http://graalvm.org/", "developer" : { From 8353bb3e347e6ee74d92f4f537554a68bb7b377c Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 10 Oct 2024 17:45:02 +0200 Subject: [PATCH 7/8] Replace pipes.quote with shlex.quote. The pipes module has been removed in Python 3.13. (cherry picked from commit 52d898d8bb6db8728e371d94fc5f87235f1ac202) --- substratevm/mx.substratevm/mx_substratevm.py | 4 ++-- sulong/mx.sulong/mx_sulong.py | 6 +++--- sulong/mx.sulong/mx_sulong_suite_constituents.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) 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/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)] From 4a4b5995ec9efa4192e3eac6f6920b376f5c016f Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 10 Oct 2024 18:05:54 +0200 Subject: [PATCH 8/8] Fix glob_match on Python 3.13+. (cherry picked from commit 1b0be9e9d1dc550f9199553d2c8ab79f11f1d873) --- sdk/mx.sdk/mx_sdk_shaded.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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