From a07147befea835fce04774fdf4303f72b2df6865 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 27 Aug 2024 15:19:05 +0200 Subject: [PATCH 1/4] Replace configuration parser with master version --- .../configure/ConditionalRuntimeValue.java | 69 ++++++ .../ConfigurationConditionResolver.java | 50 ++++ .../svm/core/configure/ConfigurationFile.java | 44 +++- .../core/configure/ConfigurationFiles.java | 135 ++++++++--- .../core/configure/ConfigurationParser.java | 165 ++++++++++--- .../ConfigurationTypeDescriptor.java | 80 +++++++ .../LegacyReflectionConfigurationParser.java | 142 +++++++++++ .../LegacyResourceConfigurationParser.java | 67 ++++++ ...egacySerializationConfigurationParser.java | 112 +++++++++ .../NamedConfigurationTypeDescriptor.java | 67 ++++++ .../configure/ProxyConfigurationParser.java | 42 ++-- .../ProxyConfigurationTypeDescriptor.java | 74 ++++++ .../ReflectionConfigurationParser.java | 225 +++++------------- ...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 | 82 ++----- .../SerializationMetadataParser.java | 67 ++++++ .../doc-files/ProxyConfigurationFilesHelp.txt | 27 ++- .../ReflectionConfigurationFilesHelp.txt | 29 +-- .../SerializationConfigurationFilesHelp.txt | 31 ++- 24 files changed, 1643 insertions(+), 470 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 049680e8e383..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,20 +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), - 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"; @@ -46,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) { @@ -64,7 +84,7 @@ public String getFileName(String suffix) { } public boolean canBeGeneratedByAgent() { - return canAgentGenerate; + return canAgentGenerate && !combinedFile; } public static ConfigurationFile getByName(String name) { @@ -81,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 be86da7c13a3..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,14 +34,16 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.graalvm.compiler.options.Option; -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. @@ -49,59 +51,120 @@ public final class ConfigurationFiles { public static final class Options { - @Option(help = "Directories directly containing configuration files for dynamic features at runtime.", type = OptionType.User)// + + @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()); - @Option(help = "Files describing Java resources to be included in the image.", type = OptionType.User)// + @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()); - @Option(help = "Resources describing Java resources to be included in the image.", type = OptionType.User)// - public static final HostedOptionKey ResourceConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - - @Option(help = "Files describing program elements to be made accessible via JNI (for syntax, see ReflectionConfigurationFiles)", type = OptionType.User)// + 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<>( + AccumulatingLocatableMultiOptionValue.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)// @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()); - - @Option(help = "Files describing predefined classes that can be loaded at runtime.", type = OptionType.User)// + 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 PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); - @Option(help = "Resources describing predefined classes that can be loaded at runtime.", type = OptionType.User)// - public static final HostedOptionKey PredefinedClassesConfigurationResources = new HostedOptionKey<>( - LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); - - @Option(help = "Causes unknown attributes in configuration objects to abort the image build instead of emitting a warning.")// + 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<>( + 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<>( + 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<>( + 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); } public static List findConfigurationFiles(String fileName) { 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 682a3c358345..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,16 +42,19 @@ 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 { @@ -59,17 +67,30 @@ 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 strictConfiguration; + private final boolean strictSchema; protected ConfigurationParser(boolean strictConfiguration) { - this.strictConfiguration = strictConfiguration; + this.strictSchema = 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 + */ } } @@ -78,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") @@ -96,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) { @@ -105,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()) { @@ -120,17 +147,40 @@ protected void checkAttributes(EconomicMap map, String type, Col if (unknownAttributes.size() > 0) { String message = "Unknown attribute(s) [" + String.join(", ", unknownAttributes) + "] in " + type; - warnOrFail(message); + warnOrFailOnSchemaError(message); Set unknownAttributesForType = seenUnknownAttributesByType.computeIfAbsent(type, key -> new HashSet<>()); unknownAttributesForType.addAll(unknownAttributes); } } - protected void warnOrFail(String message) { - if (strictConfiguration) { - throw new JSONParserException(message); + 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. + * + * @param message message to be displayed. + */ + protected void warnOrFailOnSchemaError(String message) { + if (strictSchema) { + failOnSchemaError(message); } else { - System.err.println("Warning: " + message); + LogUtils.warning(message); } } @@ -142,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) { @@ -160,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) { @@ -170,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 { - warnOrFail("'" + 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 31969b23c9a5..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,29 +27,36 @@ 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 public void parseAndRegister(Object json, URI origin) { - parseTopLevelArray(asList(json, "first level of document must be an array of interface lists")); + parseTopLevelArray(asList(json, "First-level of document must be an array of interface lists")); } private void parseTopLevelArray(List proxyConfiguration) { @@ -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"), "\"interfaces\" 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 c39b13dc41ac..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,204 +31,86 @@ 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"); - - public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { - this(delegate, true); - } + protected final ConfigurationConditionResolver conditionResolver; + protected final ReflectionConfigurationParserDelegate delegate; + private final boolean printMissingElements; - public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration) { + 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()) { - handleError("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) { - handleError("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) { - handleError("Field " + formatField(clazz, fieldName) + " not found."); + handleMissingElement("Field " + formatField(clazz, fieldName) + " not found."); } catch (LinkageError e) { - handleError("Could not register field " + formatField(clazz, fieldName) + " for reflection.", e); + handleMissingElement("Could not register field " + formatField(clazz, fieldName) + " for reflection.", e); } } - 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; @@ -245,28 +126,28 @@ 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) { - handleError("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found."); + handleMissingElement("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found."); } catch (LinkageError e) { - handleError("Could not register method " + formatMethod(clazz, methodName, methodParameterTypes) + " for reflection.", e); + handleMissingElement("Could not register method " + formatMethod(clazz, methodName, methodParameterTypes) + " for reflection.", e); } } else { 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) { - handleError("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", e); + handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", e); } } } @@ -275,9 +156,9 @@ 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()) { - handleError("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException()); + handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException()); return null; } result.add(typeResult.get()); @@ -302,15 +183,17 @@ private String formatMethod(T clazz, String methodName, List paramTypes) { return delegate.getTypeName(clazz) + '.' + methodName + '(' + parameterTypeNames + ')'; } - private static void handleError(String message) { - handleError(message, null); + private void handleMissingElement(String message) { + handleMissingElement(message, null); } - private static void handleError(String msg, Throwable cause) { - String message = msg; - if (cause != null) { - message += " Reason: " + formatError(cause) + '.'; + protected void handleMissingElement(String msg, Throwable cause) { + if (printMissingElements) { + String message = msg; + if (cause != null) { + message += " Reason: " + formatError(cause) + '.'; + } + LogUtils.warning(message); } - System.err.println("Warning: " + message); } } 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 5f3adf32c7e8..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,85 +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), "types must be an array of serialization descriptor objects"), false); - parseSerializationTypes( - asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), "lambdaCapturingTypes 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); + 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 6c2c517abc0a..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,10 +1,27 @@ One or several (comma-separated) paths to JSON files that specify lists of interfaces that define Java proxy classes. -The structure is an array of arrays of fully qualified interface names. -Example: +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 + +An example file contents follows: [ - ["java.lang.AutoCloseable", "java.util.Comparator"], - ["java.util.Comparator"], - ["java.util.List"] + { + "condition" : { + "typeReachable" : "org.example.CallingClass" + }, + "interfaces" : [ + "java.lang.AutoCloseable", + "java.util.Comparator" + ] + }, + { + "condition" : { + "typeReachable" : "org.example.CallingClass" + }, + "interfaces" : [ + "java.util.Comparator" + ] + } ] 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 41f3e0ffc505..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,27 +1,16 @@ 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: - { - String name; // fully qualified class name - boolean allDeclaredConstructors; // include all declared constructors, see Class.getDeclaredConstructors() - boolean allPublicConstructors; // include all public constructors, see Class.getConstructors() - boolean allDeclaredMethods; // include all declared methods, see Class.getDeclaredMethods() - boolean allPublicMethods; // include all public methods, see Class.getMethods() - boolean allDeclaredFields; // include all declared fields, see Class.getDeclaredFields() - boolean allPublicFields; // include all public fields, see Class.getFields() - { - String name; // method name - String[] parameterTypes; // parameter types (optional, use if ambiguous) - }[] methods; - { - String name; // field name - }[] fields; - }[]; +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 Example: [ { + "condition" : { + "typeReachable" : "org.example.CallingClass" + }, "name" : "java.lang.Class", "allDeclaredConstructors" : "true", "allPublicConstructors" : "true", @@ -29,6 +18,9 @@ Example: "allPublicMethods" : "true" }, { + "condition" : { + "typeReachable" : "org.example.CallingClass" + }, "name" : "java.lang.String", "fields" : [ { "name" : "value" }, @@ -42,6 +34,9 @@ Example: ] }, { + "condition" : { + "typeReachable" : "org.example.CallingClass" + }, "name" : "java.lang.String$CaseInsensitiveComparator", "methods" : [ { "name" : "compare" } 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 cbcccd03d9ca..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,23 +1,32 @@ One or several (comma-separated) paths to JSON files that specify lists of serialization configurations. -The structure is an array of elements specifying the target serialization/deserialization class. + +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 Example: [ { - "condition":{"typeReachable":"app.DataSerializer"}, - "name":"java.util.ArrayList" + "condition" : { + "typeReachable" : "app.DataSerializer" + }, + "name" : "java.util.ArrayList" } ] For deserializing lambda classes, the capturing class of the lambda needs to be specified in a separate section of the configuration file, for example: [ - "types": [ - {"name":"java.lang.Object"} + "types" : [ + { + "name" : "java.lang.Object" + } ], - "lambdaCapturingTypes": [ - {"name":"java.util.Comparator"} + "lambdaCapturingTypes" : [ + { + "name" : "java.util.Comparator" + } ] ] @@ -35,8 +44,10 @@ serialization-config.json: [ { - "condition":{"typeReachable":"org.apache.spark.SparkContext"}, - "name":"org.apache.spark.SparkContext$$anonfun$hadoopFile$1", - "customTargetConstructorClass":"java.lang.Object" + "condition" : { + "typeReachable" : "org.apache.spark.SparkContext" + }, + "name" : "org.apache.spark.SparkContext$$anonfun$hadoopFile$1", + "customTargetConstructorClass" : "java.lang.Object" } ] From 4f7c6992c4581186882b9f96c9f36e8f34810133 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 27 Aug 2024 15:19:36 +0200 Subject: [PATCH 2/4] Adapt configuration parser to 23.0 --- .../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 | 4 +- .../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 | 14 +- .../core/configure/RuntimeConditionSet.java | 197 ------------------ .../SerializationConfigurationParser.java | 23 +- .../SerializationMetadataParser.java | 8 +- .../core/jdk/proxy/DynamicProxyRegistry.java | 2 +- .../reflect/proxy/DynamicProxySupport.java | 6 +- .../ConditionalConfigurationRegistry.java | 4 +- .../oracle/svm/hosted/ResourcesFeature.java | 12 +- .../config/ConfigurationParserUtils.java | 13 +- .../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 +- 44 files changed, 656 insertions(+), 760 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 23e3ecbe7068..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); + 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..703cb39745fe 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 GLOB_KEY = "glob"; + public static final String MODULE_KEY = "module"; 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 javaName.lastIndexOf('.'))) { - LogUtils.warning("Type descriptor requires qualified Java name, not internal representation: %s", javaName); + LogUtils.warning("Type descriptor requires qualified Java name, not internal representation: " + 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 index a123418dc670..064e99e9017e 100644 --- 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 @@ -31,11 +31,11 @@ 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; -final class LegacyReflectionConfigurationParser 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..28fe44e2e39b 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") + 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 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..541b0ea34e90 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,11 +26,19 @@ 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); } + @Override + protected ConfigurationCondition parseCondition(EconomicMap data) { + return parseCondition(data, true); + } + @Override public void parseAndRegister(Object json, URI origin) { Object resourcesJson = getFromGlobalFile(json, RESOURCES_KEY); 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..310f25d43b07 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,14 @@ 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); + void addClassBasedResourceBundle(ConfigurationCondition condition, String basename, String className); /** * Although the interface-methods below are already defined in the super-interface @@ -46,14 +46,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/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/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java index 0d6f588b5763..9bc58c413a3f 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 @@ -140,7 +140,7 @@ public void addProxyClass(Class... interfaces) { } @Override - public Class createProxyClassForSerialization(Class... interfaces) { + public Class getProxyClassHosted(Class... interfaces) { final Class[] intfs = interfaces.clone(); return createProxyClassFromImplementedInterfaces(intfs); @@ -234,4 +234,8 @@ public boolean isProxyClass(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 dab90726dfbf..1f9ef65a20e5 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 @@ -50,7 +50,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 45d3860b6e26..40cadb81eeb5 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 @@ -214,10 +214,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 55e89f7fda2c..4b8327434873 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 @@ -32,6 +32,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; @@ -43,7 +44,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; @@ -55,9 +56,9 @@ public final class ConfigurationParserUtils { - public static ReflectionConfigurationParser>> create(ReflectionRegistry registry, ImageClassLoader imageClassLoader) { - return new ReflectionConfigurationParser<>(RegistryAdapter.create(registry, imageClassLoader), - ConfigurationFiles.Options.StrictConfiguration.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(), false); } /** @@ -78,6 +79,10 @@ public static int parseAndRegisterConfigurations(ConfigurationParser parser, Ima return parseAndRegisterConfigurations(parser, classLoader, featureName, configFilesOptionName, configResourcesOption.getName(), directoryFileName, paths, resourceValues); } + public static int parseAndRegisterConfigurationsFromCombinedFile(ConfigurationParser parser, ImageClassLoader classLoader, String featureName) { + return parseAndRegisterConfigurations(parser, classLoader, featureName, null, null, ConfigurationFile.REACHABILITY_METADATA.getFileName(), Collections.emptyList(), Collections.emptyList()); + } + public static int parseAndRegisterConfigurations(ConfigurationParser parser, ImageClassLoader classLoader, String featureName, String configFilesOptionName, String configResourcesOptionName, 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 700961f041e4..b20066a2b563 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 3e14166ce5ba..1fe4bfa18e72 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; @@ -60,7 +62,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; @@ -191,9 +192,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 @@ -209,7 +214,9 @@ public void register(ConfigurationCondition condition, boolean unsafeAllocated, @Override public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... 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 2036e72bcaf3..619ad30ee265 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 f41d82e818c8..4ed0d645a9fb 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 3289575876b9..fc627ff74c0d 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 @@ -58,7 +58,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 2ac851332dfb..00143171ac40 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 c20ab96a10b0f6f235b7ef04b7e19e5bf7692f00 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Thu, 30 Nov 2023 11:35:47 +0100 Subject: [PATCH 3/4] 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 79fc40515374..8472b1d29b07 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 @@ -81,6 +81,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(", ", "(", ")"); for (Class paramType : paramTypes) { 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 b20066a2b563..f2fc9630e2b7 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 52cd6679b95a..5c1048263237 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 @@ -398,26 +398,30 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { 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 */ } @@ -425,23 +429,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(); /* @@ -456,13 +464,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)); } @@ -629,13 +645,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 @@ -981,7 +999,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 65c5381c23e91af0ff5db4e30a718623453300d0 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Thu, 12 Sep 2024 11:21:49 +0200 Subject: [PATCH 4/4] Run master Github workflows --- .github/actions/build-graalvm/action.yml | 66 ++++++++++ .github/workflows/cdt-inspect.yml | 92 +++++++++++++ .github/workflows/main.yml | 133 +++++++++++++++---- .github/workflows/micronaut.yml | 89 +++++++++++++ .github/workflows/quarkus.yml | 113 +++++++++------- .github/workflows/reachability-metadata.yml | 139 ++++++++++++++++++++ .github/workflows/spring.yml | 81 ++++++++++++ 7 files changed, 636 insertions(+), 77 deletions(-) create mode 100644 .github/actions/build-graalvm/action.yml create mode 100644 .github/workflows/cdt-inspect.yml create mode 100644 .github/workflows/micronaut.yml create mode 100644 .github/workflows/reachability-metadata.yml create mode 100644 .github/workflows/spring.yml diff --git a/.github/actions/build-graalvm/action.yml b/.github/actions/build-graalvm/action.yml new file mode 100644 index 000000000000..05f7c57917f3 --- /dev/null +++ b/.github/actions/build-graalvm/action.yml @@ -0,0 +1,66 @@ +name: Build GraalVM JDK +description: 'Build GraalVM JDK and set up environment for testing' + +inputs: + native-images: + description: 'Internal GraalVM native images to build' + required: false + default: 'native-image' + components: + description: 'Internal GraalVM components to build' + required: false + default: 'Native Image' + java-version: + description: 'Java version to use' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Set up environment variables + shell: bash + run: | + echo "GRAALVM_HOME=${{ github.workspace }}/graalvm" >> ${GITHUB_ENV} + echo "LABSJDK_HOME=${{ github.workspace }}/labsjdk" >> ${GITHUB_ENV} + echo "MX_GIT_CACHE=refcache" >> ${GITHUB_ENV} + echo "MX_PATH=${{ github.workspace }}/mx" >> ${GITHUB_ENV} + echo "MX_PYTHON=python3.8" >> ${GITHUB_ENV} + echo "MX_VERSION=$(jq -r '.mx_version' common.json)" >> ${GITHUB_ENV} + # Workaround testsuite locale issue + echo "LANG=en_US.UTF-8" >> ${GITHUB_ENV} + - name: Checkout graalvm/mx + uses: actions/checkout@v4 + with: + repository: graalvm/mx + ref: ${{ env.MX_VERSION }} + path: ${{ env.MX_PATH }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + - name: Update mx cache + uses: actions/cache@v4 + with: + path: ~/.mx + key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} + restore-keys: ${{ runner.os }}-mx- + - name: Fetch LabsJDK + shell: bash + run: | + mkdir jdk-dl + ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-17 --to jdk-dl --alias ${LABSJDK_HOME} + - name: Build GraalVM JDK + shell: bash + run: | + cd substratevm + ${MX_PATH}/mx --java-home=${LABSJDK_HOME} --native-images="${{ inputs.native-images }}" --components="${{ inputs.components }}" build + ln -s $(${MX_PATH}/mx --java-home=${LABSJDK_HOME} --native-images="${{ inputs.native-images }}" --components="${{ inputs.components }}" graalvm-home) ${GRAALVM_HOME} + ${GRAALVM_HOME}/bin/native-image --version + - name: Set up JAVA_HOME + if: ${{ inputs.java-version }} != '' + uses: actions/setup-java@v4 + with: + distribution: 'oracle' + java-version: '${{ inputs.java-version }}' + \ No newline at end of file diff --git a/.github/workflows/cdt-inspect.yml b/.github/workflows/cdt-inspect.yml new file mode 100644 index 000000000000..9ea51c7c49de --- /dev/null +++ b/.github/workflows/cdt-inspect.yml @@ -0,0 +1,92 @@ +# +# Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Intergation test of CDT with Inspector backend. +name: Weekly CDT Inspector + +on: + schedule: + - cron: "30 2 * * 2,5" # Tuesday and Friday at 2:30 + +env: + JAVA_HOME: ${{ github.workspace }}/jdk + JDK_VERSION: "latest" + MX_PATH: ${{ github.workspace }}/mx + SE_SKIP_DRIVER_IN_PATH: "true" + +jobs: + build: + + runs-on: ubuntu-latest + + if: github.repository == 'oracle/graal' + + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + with: + path: ${{ github.workspace }}/graal + - name: Checkout oracle/graaljs + uses: actions/checkout@v4 + with: + repository: oracle/graaljs + sparse-checkout: | + graal-js + path: ${{ github.workspace }}/js + - name: Checkout graalvm/mx + uses: actions/checkout@v4 + with: + repository: graalvm/mx.git + ref: master + path: ${{ env.MX_PATH }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + - name: Fetch LabsJDK + run: | + mkdir jdk-dl + ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-${JDK_VERSION} --to jdk-dl --alias ${JAVA_HOME} + - run: | + cd ${{ github.workspace }}/graal/vm + ${MX_PATH}/mx --dy /tools,graal-js build + cd tests/gh_workflows/CDTInspectorTest + mvn -q compile + mvn -q exec:exec -Dtestargs="${{ github.workspace }}/graal/sdk/latest_graalvm_home/bin/js scripts/StepTest.js" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bc8adcd413cb..67e6f60be515 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,3 +1,43 @@ +# +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# name: GraalVM Gate on: @@ -37,72 +77,70 @@ concurrency: env: JAVA_HOME: ${{ github.workspace }}/jdk - JDT: builtin LANG: en_US.UTF-8 MX_GIT_CACHE: refcache MX_PATH: ${{ github.workspace }}/mx MX_PYTHON: python3.8 + # Enforce experimental option checking in CI (GR-47922) + NATIVE_IMAGE_EXPERIMENTAL_OPTIONS_ARE_FATAL: "true" permissions: contents: read # to fetch code (actions/checkout) jobs: - build-graalvm: + build-graalvm-linux: name: /${{ matrix.env.PRIMARY }} ${{ matrix.env.GATE_TAGS }} JDK${{ matrix.env.JDK_VERSION }} runs-on: ubuntu-20.04 + timeout-minutes: 60 strategy: fail-fast: false matrix: include: # /compiler - env: - JDK_VERSION: "20" + JDK_VERSION: "21" GATE_TAGS: "style,fullbuild,test" PRIMARY: "compiler" - env: - JDK_VERSION: "20" + JDK_VERSION: "latest" GATE_TAGS: "build,bootstraplite" PRIMARY: "compiler" # /espresso - env: - JDK_VERSION: "20" + JDK_VERSION: "21" GATE_TAGS: "style,fullbuild" PRIMARY: "espresso" # /substratevm - env: - JDK_VERSION: "20" + JDK_VERSION: "21" GATE_TAGS: "style,fullbuild" PRIMARY: "substratevm" - env: - JDK_VERSION: "20" - GATE_TAGS: "build,test,helloworld" + JDK_VERSION: "latest" + GATE_TAGS: "build,helloworld,native_unittests" PRIMARY: "substratevm" PIP_PACKAGES: "jsonschema==4.6.1" - env: - JDK_VERSION: "20" - GATE_TAGS: "build,helloworld_debug" - PRIMARY: "substratevm" - - env: - JDK_VERSION: "20" + JDK_VERSION: "latest" GATE_TAGS: "build,debuginfotest" PRIMARY: "substratevm" - env: - JDK_VERSION: "20" + JDK_VERSION: "latest" GATE_TAGS: "hellomodule" PRIMARY: "substratevm" # /sulong - env: - JDK_VERSION: "20" + JDK_VERSION: "21" GATE_TAGS: "style,fullbuild,sulongBasic" PRIMARY: "sulong" # /truffle - env: - JDK_VERSION: "20" + JDK_VERSION: "21" GATE_TAGS: "" # Truffle does not use tags PRIMARY: "truffle" # /vm - env: - JDK_VERSION: "20" + JDK_VERSION: "latest" GATE_TAGS: "build,sulong" GATE_OPTS: "--no-warning-as-error" PRIMARY: "vm" @@ -111,7 +149,7 @@ jobs: DISABLE_POLYGLOT: true DISABLE_LIBPOLYGLOT: true - env: - JDK_VERSION: "20" + JDK_VERSION: "latest" GATE_TAGS: "build" GATE_OPTS: "--no-warning-as-error" PRIMARY: "vm" @@ -119,29 +157,29 @@ jobs: NATIVE_IMAGES: "lib:jvmcicompiler,native-image,lib:native-image-agent,lib:native-image-diagnostics-agent,polyglot" WITHOUT_VCS: true env: + JDT: builtin # Compile with ECJ (and javac) as part of gate runs tagged with 'fullbuild' MX_RUNS_DEBUG: ${{ contains(matrix.env.GATE_TAGS, 'debug') || matrix.env.GATE_TAGS == '' }} MX_RUNS_STYLE: ${{ contains(matrix.env.GATE_TAGS, 'style') || matrix.env.GATE_TAGS == '' }} steps: - name: Checkout oracle/graal - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} # Lock ref to current branch to avoid fetching others fetch-depth: "${{ env.MX_RUNS_STYLE && '0' || '1' }}" # The style gate needs the full commit history for checking copyright years - name: Determine mx version run: echo "MX_VERSION=$(jq -r '.mx_version' common.json)" >> ${GITHUB_ENV} - name: Checkout graalvm/mx - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: graalvm/mx.git ref: ${{ env.MX_VERSION }} - fetch-depth: 1 path: ${{ env.MX_PATH }} - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' - name: Update mx cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.mx key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} @@ -161,9 +199,7 @@ jobs: if: ${{ env.MX_RUNS_STYLE == 'true' }} run: | sudo apt install python3-pip python-setuptools - sudo pip install ninja_syntax$(jq -r '.pip.ninja_syntax' common.json) - sudo pip install lazy-object-proxy$(jq -r '.pip["lazy-object-proxy"]' common.json) - sudo pip install pylint$(jq -r '.pip.pylint' common.json) + sudo pip install $(jq -r '[.pip | to_entries[] | join("")] | join(" ")' common.json) - name: Install additional pip packages if: ${{ matrix.env.PIP_PACKAGES != '' }} run: ${MX_PYTHON} -m pip install ${{ matrix.env.PIP_PACKAGES }} @@ -187,3 +223,48 @@ jobs: env: ${{ matrix.env }} run: ${MX_PATH}/mx --primary-suite-path ${PRIMARY} --java-home=${JAVA_HOME} gate --strict-mode ${{ matrix.env.GATE_OPTS }} if: ${{ matrix.env.GATE_TAGS == '' }} + build-graalvm-windows: + name: /substratevm on Windows + runs-on: windows-2022 + timeout-minutes: 60 + env: + MX_PYTHON: 'python' + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} # Lock ref to current branch to avoid fetching others + - name: Determine mx version + shell: bash + run: echo "MX_VERSION=$(jq -r '.mx_version' common.json)" >> ${GITHUB_ENV} + - name: Checkout graalvm/mx + uses: actions/checkout@v4 + with: + repository: graalvm/mx.git + ref: ${{ env.MX_VERSION }} + path: ${{ env.MX_PATH }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + - name: Fetch LabsJDK + shell: bash + run: | + mkdir jdk-dl + ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-latest --to jdk-dl --alias ${JAVA_HOME} + - name: Build GraalVM via cmd.exe + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 + call ${{ env.MX_PATH }}\mx.cmd -p substratevm --native-images=native-image --components="Native Image" build + call ${{ env.MX_PATH }}\mx.cmd -p substratevm --native-images=native-image --components="Native Image" graalvm-home > graalvm-home-with-forward-slashes.txt + set /p GRAALVM_HOME=>%GITHUB_PATH% + echo GRAALVM_HOME=%GRAALVM_HOME%>>%GITHUB_ENV% + - name: Test GraalVM + run: | + native-image --version + native-image -m jdk.httpserver + diff --git a/.github/workflows/micronaut.yml b/.github/workflows/micronaut.yml new file mode 100644 index 000000000000..4077f457eb3a --- /dev/null +++ b/.github/workflows/micronaut.yml @@ -0,0 +1,89 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +name: Nightly Micronaut Tests + +on: + push: + paths: + - '.github/workflows/micronaut.yml' + pull_request: + paths: + - '.github/workflows/micronaut.yml' + schedule: + - cron: '0 2 * * *' + workflow_dispatch: + +env: + MICRONAUT_CORE_PATH: ${{ github.workspace }}/micronaut-core + MICRONAUT_JAVA_VERSION: 21 + # Enforce experimental option checking in CI (GR-47922) + NATIVE_IMAGE_EXPERIMENTAL_OPTIONS_ARE_FATAL: 'true' + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build-graalvm-and-micronaut: + name: Native Tests + runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm + with: + java-version: ${{ env.MICRONAUT_JAVA_VERSION }} + - name: Run nativeTest in Micronaut launch project + run: | + curl --fail --silent --location --retry 3 --max-time 10 --output demo.zip --request GET 'https://launch.micronaut.io/create/default/com.example.demo?lang=JAVA&build=GRADLE&test=JUNIT&javaVersion=JDK_${{ env.MICRONAUT_JAVA_VERSION }}' + unzip demo.zip + cd demo + ./gradlew nativeTest + - name: Checkout micronaut-projects/micronaut-core + uses: actions/checkout@v4 + with: + repository: micronaut-projects/micronaut-core + path: ${{ env.MICRONAUT_CORE_PATH }} + - name: Run nativeTest in micronaut-core + run: | + cd ${{ env.MICRONAUT_CORE_PATH }} + ./gradlew nativeTest diff --git a/.github/workflows/quarkus.yml b/.github/workflows/quarkus.yml index b14e5f27504d..289551edb574 100644 --- a/.github/workflows/quarkus.yml +++ b/.github/workflows/quarkus.yml @@ -1,3 +1,43 @@ +# +# Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# name: Nightly Quarkus Tests on: @@ -9,19 +49,15 @@ on: - '.github/workflows/quarkus.yml' schedule: - cron: '0 3 * * *' + workflow_dispatch: env: COMMON_MAVEN_ARGS: "-e -B --settings .github/mvn-settings.xml --fail-at-end" DB_NAME: hibernate_orm_test DB_PASSWORD: hibernate_orm_test DB_USER: hibernate_orm_test - GRAALVM_HOME: ${{ github.workspace }}/graalvm - LABSJDK_HOME: ${{ github.workspace }}/jdk - LANG: en_US.UTF-8 # Workaround testsuite locale issue - MX_GIT_CACHE: refcache - MX_PATH: ${{ github.workspace }}/mx - MX_PYTHON: python3.8 NATIVE_TEST_MAVEN_ARGS: "-Dtest-containers -Dstart-containers -Dquarkus.native.native-image-xmx=5g -Dnative -Dnative.surefire.skip -Dformat.skip -Dno-descriptor-tests install -DskipDocs -Dquarkus.native.container-build=false" + QUARKUS_JAVA_VERSION: 17 # Use Java 17 to build Quarkus as that's the lowest supported JDK version currently QUARKUS_PATH: ${{ github.workspace }}/quarkus permissions: {} @@ -32,24 +68,16 @@ jobs: name: Nightly Quarkus and GraalVM build runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') outputs: matrix: ${{ steps.read.outputs.matrix }} steps: - name: Checkout oracle/graal - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm with: - fetch-depth: 1 - - name: Checkout graalvm/mx - uses: actions/checkout@v3 - with: - repository: graalvm/mx.git - fetch-depth: 1 - ref: master - path: ${{ env.MX_PATH }} - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.8' + java-version: ${{ env.QUARKUS_JAVA_VERSION }} - name: Get latest Quarkus release run: | export QUARKUS_VERSION=main #$(curl https://repo1.maven.org/maven2/io/quarkus/quarkus-bom/maven-metadata.xml | awk -F"[<>]" '/latest/ {print $3}') @@ -57,34 +85,17 @@ jobs: curl --output quarkus.tgz -sL https://api.github.com/repos/quarkusio/quarkus/tarball/$QUARKUS_VERSION mkdir ${QUARKUS_PATH} tar xf quarkus.tgz -C ${QUARKUS_PATH} --strip-components=1 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - uses: actions/cache@v3 - with: - path: ~/.mx - key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} - restore-keys: | - ${{ runner.os }}-mx- - - name: Fetch LabsJDK - run: | - mkdir jdk-dl - ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-20 --to jdk-dl --alias ${LABSJDK_HOME} - - name: Build graalvm native-image - run: | - export JAVA_HOME=${LABSJDK_HOME} - cd substratevm - ${MX_PATH}/mx --native=native-image,lib:jvmcicompiler --components="Native Image,LibGraal" build - mv $(${MX_PATH}/mx --native=native-image,lib:jvmcicompiler --components="Native Image,LibGraal" graalvm-home) ${GRAALVM_HOME} - ${GRAALVM_HOME}/bin/native-image --version - - name: Tar GraalVM + - name: Tar GraalVM JDK shell: bash - run: tar -czvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) $(basename ${GRAALVM_HOME}) - - name: Persist GraalVM build - uses: actions/upload-artifact@v1 + run: tar -czvhf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) $(basename ${GRAALVM_HOME}) + - name: Persist GraalVM JDK build + uses: actions/upload-artifact@v4 with: name: graalvm path: graalvm.tgz @@ -102,7 +113,7 @@ jobs: shell: bash run: tar -czvf maven-repo.tgz -C ~ .m2/repository - name: Persist Maven Repo - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: maven-repo path: maven-repo.tgz @@ -111,6 +122,8 @@ jobs: name: Native Tests - ${{matrix.category}} needs: build-quarkus-and-graalvm runs-on: ubuntu-latest + env: + GRAALVM_HOME: ${{ github.workspace }}/graalvm # identical to the one in ./.github/actions/build-graalvm # Ignore the following YAML Schema error timeout-minutes: ${{matrix.timeout}} strategy: @@ -118,13 +131,13 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.build-quarkus-and-graalvm.outputs.matrix) }} steps: - - name: Download GraalVM build + - name: Download GraalVM JDK build if: startsWith(matrix.os-name, 'ubuntu') - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: graalvm path: . - - name: Extract GraalVM build + - name: Extract GraalVM JDK build if: startsWith(matrix.os-name, 'ubuntu') shell: bash run: tar -xzvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) @@ -141,7 +154,7 @@ jobs: run: ${QUARKUS_PATH}/.github/ci-prerequisites.sh - name: Download Maven Repo if: startsWith(matrix.os-name, 'ubuntu') - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: maven-repo path: . @@ -149,17 +162,15 @@ jobs: if: startsWith(matrix.os-name, 'ubuntu') shell: bash run: tar -xzf maven-repo.tgz -C ~ - - uses: graalvm/setup-graalvm@v1 + - uses: actions/setup-java@v4 with: - version: 'latest' + distribution: 'oracle' java-version: '17' - github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build with Maven if: startsWith(matrix.os-name, 'ubuntu') env: TEST_MODULES: ${{matrix.test-modules}} run: | - export GRAALVM_HOME=${{ github.workspace }}/graalvm cd ${QUARKUS_PATH} ${GRAALVM_HOME}/bin/native-image --version ./mvnw $COMMON_MAVEN_ARGS -f integration-tests -pl "$TEST_MODULES" $NATIVE_TEST_MAVEN_ARGS @@ -168,7 +179,7 @@ jobs: shell: bash run: find . -type d -name '*-reports' -o -wholename '*/build/reports/tests/functionalTest' | tar -czf test-reports.tgz -T - - name: Upload failure Archive (if maven failed) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 if: failure() with: name: test-reports-native-${{matrix.category}} diff --git a/.github/workflows/reachability-metadata.yml b/.github/workflows/reachability-metadata.yml new file mode 100644 index 000000000000..b19d621f5103 --- /dev/null +++ b/.github/workflows/reachability-metadata.yml @@ -0,0 +1,139 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +name: Weekly Reachability Metadata Tests + +on: + push: + paths: + - '.github/workflows/reachability-metadata.yml' + pull_request: + paths: + - '.github/workflows/reachability-metadata.yml' + schedule: + - cron: '0 1 * * 1' + workflow_dispatch: + +env: + REACHABILITY_METADATA_PATH: ${{ github.workspace }}/graalvm-reachability-metadata + MINIMUM_METADATA_JAVA_VERSION: 17 + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build-graalvm-and-populate-matrix: + name: Build GraalVM and populate matrix + runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm + with: + native-images: 'native-image,native-image-configure,lib:native-image-agent' + components: 'Native Image,Native Image Configure Tool' + java-version: ${{ env.MINIMUM_METADATA_JAVA_VERSION }} + - name: Tar GraalVM JDK + shell: bash + run: tar -czvhf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) $(basename ${GRAALVM_HOME}) + - name: Persist GraalVM JDK build + uses: actions/upload-artifact@v4 + with: + name: graalvm + path: graalvm.tgz + - name: Checkout oracle/graalvm-reachability-metadata + uses: actions/checkout@v4 + with: + repository: oracle/graalvm-reachability-metadata + path: ${{ env.REACHABILITY_METADATA_PATH }} + - name: "Populate matrix" + id: set-matrix + run: | + cd ${{ env.REACHABILITY_METADATA_PATH }} + ./gradlew generateMatrixMatchingCoordinates -Pcoordinates=all + + test-all-metadata: + name: ${{ matrix.coordinates }} + runs-on: ubuntu-latest + env: + GRAALVM_HOME: ${{ github.workspace }}/graalvm # identical to the one in ./.github/actions/build-graalvm + timeout-minutes: 20 + needs: build-graalvm-and-populate-matrix + strategy: + fail-fast: false + matrix: + coordinates: ${{fromJson(needs.build-graalvm-and-populate-matrix.outputs.matrix).coordinates}} + steps: + - name: "Checkout oracle/graalvm-reachability-metadata" + uses: actions/checkout@v4 + with: + repository: oracle/graalvm-reachability-metadata + - name: Download GraalVM JDK build + uses: actions/download-artifact@v4 + with: + name: graalvm + path: . + - name: Extract GraalVM JDK build + run: tar -xzvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) + - name: "Setup JAVA_HOME" + uses: actions/setup-java@v4 + with: + distribution: 'oracle' + java-version: ${{ env.MINIMUM_METADATA_JAVA_VERSION }} + - name: "Pull allowed docker images" + run: | + ./gradlew pullAllowedDockerImages --coordinates=${{ matrix.coordinates }} + - name: "Disable docker" + run: | + sudo apt-get install openbsd-inetd + sudo bash -c "cat ./.github/workflows/discard-port.conf >> /etc/inetd.conf" + sudo systemctl start inetd + sudo mkdir /etc/systemd/system/docker.service.d + sudo bash -c "cat ./.github/workflows/dockerd.service > /etc/systemd/system/docker.service.d/http-proxy.conf" + sudo systemctl daemon-reload + sudo systemctl restart docker + - name: "Run '${{ matrix.coordinates }}' tests" + run: | + ./gradlew test -Pcoordinates=${{ matrix.coordinates }} + \ No newline at end of file diff --git a/.github/workflows/spring.yml b/.github/workflows/spring.yml new file mode 100644 index 000000000000..3d2c0a6efa58 --- /dev/null +++ b/.github/workflows/spring.yml @@ -0,0 +1,81 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +name: Nightly Spring Tests + +on: + push: + paths: + - '.github/workflows/spring.yml' + pull_request: + paths: + - '.github/workflows/spring.yml' + schedule: + - cron: '0 4 * * *' + workflow_dispatch: + +env: + SPRING_PETCLINIC_PATH: ${{ github.workspace }}/spring-petclinic + SPRING_JAVA_VERSION: 21 + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build-graalvm-and-spring: + name: Native Tests + runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm + with: + java-version: ${{ env.SPRING_JAVA_VERSION }} + - name: Checkout spring-projects/spring-petclinic + uses: actions/checkout@v4 + with: + repository: spring-projects/spring-petclinic + path: ${{ env.SPRING_PETCLINIC_PATH }} + - name: Run nativeTest in spring-petclinic + run: | + cd ${{ env.SPRING_PETCLINIC_PATH }} + ./gradlew nativeTest