From 2168e314b0e97adad7c88a59f75044bc64c521ab Mon Sep 17 00:00:00 2001 From: Aleksandar Gradinac Date: Thu, 17 Mar 2022 16:13:42 +0100 Subject: [PATCH] Support conditional configuration generation from native-image-configure. --- substratevm/mx.substratevm/mx_substratevm.py | 52 ++++- .../oracle/svm/agent/NativeImageAgent.java | 126 +++++----- ...ditionalConfigurationPartialRunWriter.java | 64 ++++++ .../ConditionalConfigurationWriter.java | 214 ++--------------- .../agent/configwithorigins/ClassInfo.java | 5 +- .../ConfigurationWithOrigins.java | 91 -------- .../ConfigurationWithOriginsTracer.java | 26 ++- .../ConfigurationWithOriginsWriter.java | 11 +- .../MethodInfoRecordKeeper.java | 1 + .../ConfigurationGenerator.java | 3 +- .../ConfigurationVerifier.java | 4 +- .../PartialConfigurationGenerator.java | 52 +++++ .../svm/configure/ConfigurationBase.java | 5 +- .../svm/configure/ConfigurationTool.java | 98 +++++++- .../config/ConfigurationFileCollection.java | 16 +- .../configure/config/ConfigurationSet.java | 32 +-- .../config/ParserConfigurationAdapter.java | 2 +- .../PredefinedClassesConfiguration.java | 6 + .../configure/config/ProxyConfiguration.java | 7 + .../config/ResourceConfiguration.java | 7 + .../config/SerializationConfiguration.java | 7 + .../configure/config/TypeConfiguration.java | 7 + .../ConditionalConfigurationComputer.java | 215 ++++++++++++++++++ .../ConditionalConfigurationPredicate.java | 11 +- ...HumanReadableConfigurationWithOrigins.java | 2 +- .../config/conditional}/MethodCallNode.java | 37 ++- .../config/conditional}/MethodInfo.java | 22 +- .../conditional/MethodInfoRepository.java | 43 ++++ .../PartialConfigurationWithOrigins.java | 172 ++++++++++++++ .../filters/FilterConfigurationParser.java | 6 +- .../svm/configure/trace/AccessAdvisor.java | 1 + .../svm/core/configure/ConfigurationFile.java | 21 ++ .../core/configure/ConfigurationParser.java | 9 +- .../PredefinedClassesConfigurationParser.java | 25 +- .../configure/ProxyConfigurationParser.java | 8 +- .../ReflectionConfigurationParser.java | 8 +- .../ResourceConfigurationParser.java | 12 +- .../SerializationConfigurationParser.java | 10 +- .../svm/truffle/tck/WhiteListParser.java | 8 +- 39 files changed, 958 insertions(+), 488 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationPartialRunWriter.java delete mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOrigins.java create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/PartialConfigurationGenerator.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java rename substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/{ => conditional}/ConditionalConfigurationPredicate.java (83%) rename substratevm/src/{com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins => com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional}/HumanReadableConfigurationWithOrigins.java (98%) rename substratevm/src/{com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins => com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional}/MethodCallNode.java (80%) rename substratevm/src/{com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins => com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional}/MethodInfo.java (82%) create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodInfoRepository.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index b75bfcd2b7ec..81a0c7d40375 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -495,9 +495,6 @@ def native_unittests_task(extra_build_args=None): def conditional_config_task(native_image): agent_path = build_native_image_agent(native_image) - config_dir = join(svmbuild_dir(), 'cond-config-test-config') - if exists(config_dir): - mx.rmtree(config_dir) conditional_config_filter_path = join(svmbuild_dir(), 'conditional-config-filter.json') with open(conditional_config_filter_path, 'w') as conditional_config_filter: conditional_config_filter.write( @@ -509,11 +506,56 @@ def conditional_config_task(native_image): } ''' ) - agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-config-filter-file=' + conditional_config_filter_path] + + run_agent_conditional_config_test(agent_path, conditional_config_filter_path) + + run_nic_conditional_config_test(agent_path, conditional_config_filter_path) + + +def run_nic_conditional_config_test(agent_path, conditional_config_filter_path): + test_cases = [ + "createConfigPartOne", + "createConfigPartTwo", + "createConfigPartThree" + ] + config_directories = [] + nic_test_dir = join(svmbuild_dir(), 'nic-cond-config-test') + if exists(nic_test_dir): + mx.rmtree(nic_test_dir) + for test_case in test_cases: + config_dir = join(nic_test_dir, test_case) + config_directories.append(config_dir) + + agent_opts = ['config-output-dir=' + config_dir, + 'experimental-conditional-config-part'] + jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts), + '-Dcom.oracle.svm.configure.test.conditionalconfig.PartialConfigurationGenerator.enabled=true', + 'com.oracle.svm.configure.test.conditionalconfig.PartialConfigurationGenerator#' + test_case]) + config_output_dir = join(nic_test_dir, 'config-output') + nic_exe = mx.cmd_suffix(join(mx.JDKConfig(home=mx_sdk_vm_impl.graalvm_output()).home, 'bin', 'native-image-configure')) + nic_command = [nic_exe, 'create-conditional'] \ + + ['--user-code-filter=' + conditional_config_filter_path] \ + + ['--input-dir=' + config_dir for config_dir in config_directories] \ + + ['--output-dir=' + config_output_dir] + mx.run(nic_command) + jvm_unittest( + ['-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.configpath=' + config_output_dir, + "-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.enabled=true", + 'com.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier']) + + +def run_agent_conditional_config_test(agent_path, conditional_config_filter_path): + config_dir = join(svmbuild_dir(), 'cond-config-test-config') + if exists(config_dir): + mx.rmtree(config_dir) + + agent_opts = ['config-output-dir=' + config_dir, + 'experimental-conditional-config-filter-file=' + conditional_config_filter_path] + # This run generates the configuration from different test cases jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts), '-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator.enabled=true', 'com.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator']) - + # This run verifies that the generated configuration matches the expected one jvm_unittest(['-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.configpath=' + config_dir, "-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.enabled=true", 'com.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier']) diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index 05890dffe578..dd77274025f8 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -50,6 +50,8 @@ import java.util.function.Supplier; import java.util.regex.Pattern; +import com.oracle.svm.agent.conditionalconfig.ConditionalConfigurationPartialRunWriter; +import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsTracer; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.hosted.Feature; @@ -65,7 +67,7 @@ import com.oracle.svm.agent.tracing.TraceFileWriter; import com.oracle.svm.agent.tracing.core.Tracer; import com.oracle.svm.agent.tracing.core.TracingResultWriter; -import com.oracle.svm.configure.config.ConditionalConfigurationPredicate; +import com.oracle.svm.configure.config.conditional.ConditionalConfigurationPredicate; import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.filters.ComplexFilter; @@ -105,6 +107,18 @@ private static String getTokenValue(String token) { return token.substring(token.indexOf('=') + 1); } + private static boolean getBooleanTokenValue(String token) { + int equalsIndex = token.indexOf('='); + if (equalsIndex == -1) { + return true; + } + return Boolean.parseBoolean(token.substring(equalsIndex + 1)); + } + + private static boolean isBooleanOption(String token, String option) { + return token.equals(option) || token.startsWith(option + "="); + } + @Override protected int getRequiredJvmtiVersion() { return JvmtiInterface.JVMTI_VERSION_1_2; @@ -133,6 +147,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean configurationWithOrigins = false; List conditionalConfigUserPackageFilterFiles = new ArrayList<>(); List conditionalConfigClassNameFilterFiles = new ArrayList<>(); + boolean conditionalConfigPartialRun = false; int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds boolean trackReflectionMetadata = true; @@ -156,42 +171,31 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c String omittedConfigDir = getTokenValue(token); omittedConfigDir = transformPath(omittedConfigDir); omittedConfigs.addDirectory(Paths.get(omittedConfigDir)); - } else if (token.equals("experimental-omit-config-from-classpath")) { - experimentalOmitClasspathConfig = true; - } else if (token.startsWith("experimental-omit-config-from-classpath=")) { - experimentalOmitClasspathConfig = Boolean.parseBoolean(getTokenValue(token)); + } else if (isBooleanOption(token, "experimental-omit-config-from-classpath")) { + experimentalOmitClasspathConfig = getBooleanTokenValue(token); } else if (token.startsWith("restrict-all-dir") || token.equals("restrict") || token.startsWith("restrict=")) { warn("restrict mode is no longer supported, ignoring option: " + token); } else if (token.equals("no-builtin-caller-filter")) { builtinCallerFilter = false; - } else if (token.startsWith("builtin-caller-filter=")) { - builtinCallerFilter = Boolean.parseBoolean(getTokenValue(token)); + } else if (isBooleanOption(token, "builtin-caller-filter")) { + builtinCallerFilter = getBooleanTokenValue(token); } else if (token.equals("no-builtin-heuristic-filter")) { builtinHeuristicFilter = false; - } else if (token.startsWith("builtin-heuristic-filter=")) { - builtinHeuristicFilter = Boolean.parseBoolean(getTokenValue(token)); - } else if (token.equals("no-filter")) { // legacy - builtinCallerFilter = false; - builtinHeuristicFilter = false; - } else if (token.startsWith("no-filter=")) { // legacy - builtinCallerFilter = !Boolean.parseBoolean(getTokenValue(token)); + } else if (isBooleanOption(token, "builtin-heuristic-filter")) { + builtinHeuristicFilter = getBooleanTokenValue(token); + } else if (isBooleanOption(token, "no-filter")) { // legacy + builtinCallerFilter = !getBooleanTokenValue(token); builtinHeuristicFilter = builtinCallerFilter; } else if (token.startsWith("caller-filter-file=")) { callerFilterFiles.add(getTokenValue(token)); } else if (token.startsWith("access-filter-file=")) { accessFilterFiles.add(getTokenValue(token)); - } else if (token.equals("experimental-class-loader-support")) { - experimentalClassLoaderSupport = true; - } else if (token.startsWith("experimental-class-loader-support=")) { - experimentalClassLoaderSupport = Boolean.parseBoolean(getTokenValue(token)); - } else if (token.equals("experimental-class-define-support")) { - experimentalClassDefineSupport = true; - } else if (token.startsWith("experimental-class-define-support=")) { - experimentalClassDefineSupport = Boolean.parseBoolean(getTokenValue(token)); - } else if (token.equals("experimental-unsafe-allocation-support")) { - experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token)); - } else if (token.startsWith("experimental-unsafe-allocation-support=")) { - experimentalUnsafeAllocationSupport = Boolean.parseBoolean(getTokenValue(token)); + } else if (isBooleanOption(token, "experimental-class-loader-support")) { + experimentalClassLoaderSupport = getBooleanTokenValue(token); + } else if (isBooleanOption(token, "experimental-class-define-support")) { + experimentalClassDefineSupport = getBooleanTokenValue(token); + } else if (isBooleanOption(token, "experimental-unsafe-allocation-support")) { + experimentalUnsafeAllocationSupport = getBooleanTokenValue(token); } else if (token.startsWith("config-write-period-secs=")) { configWritePeriod = parseIntegerOrNegative(getTokenValue(token)); if (configWritePeriod <= 0) { @@ -202,20 +206,18 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c if (configWritePeriodInitialDelay < 0) { return usage(1, "config-write-initial-delay-secs must be an integer greater or equal to 0"); } - } else if (token.equals("build")) { - build = true; - } else if (token.startsWith("build=")) { - build = Boolean.parseBoolean(getTokenValue(token)); - } else if (token.equals("experimental-configuration-with-origins")) { - configurationWithOrigins = true; + } else if (isBooleanOption(token, "build")) { + build = getBooleanTokenValue(token); + } else if (isBooleanOption(token, "experimental-configuration-with-origins")) { + configurationWithOrigins = getBooleanTokenValue(token); } else if (token.startsWith("experimental-conditional-config-filter-file=")) { conditionalConfigUserPackageFilterFiles.add(getTokenValue(token)); } else if (token.startsWith("conditional-config-class-filter-file=")) { conditionalConfigClassNameFilterFiles.add(getTokenValue(token)); - } else if (token.equals("track-reflection-metadata")) { - trackReflectionMetadata = true; - } else if (token.startsWith("track-reflection-metadata=")) { - trackReflectionMetadata = Boolean.parseBoolean(getTokenValue(token)); + } else if (isBooleanOption(token, "experimental-conditional-config-part")) { + conditionalConfigPartialRun = getBooleanTokenValue(token); + } else if (isBooleanOption(token, "track-reflection-metadata")) { + trackReflectionMetadata = getBooleanTokenValue(token); } else { return usage(1, "unknown option: '" + token + "'."); } @@ -264,7 +266,12 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } } - boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigUserPackageFilterFiles.isEmpty(); + if (!conditionalConfigUserPackageFilterFiles.isEmpty() && conditionalConfigPartialRun) { + return error(6, "The agent can generate conditional configuration either for the current run or in the partial mode but not both at the same time."); + } + + boolean isConditionalConfigurationRun = !conditionalConfigUserPackageFilterFiles.isEmpty() || conditionalConfigPartialRun; + boolean shouldTraceOriginInformation = configurationWithOrigins || isConditionalConfigurationRun; final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(shouldTraceOriginInformation); final Supplier interceptedStateSupplier = shouldTraceOriginInformation ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() : OnDemandJavaStackAccess.stackAccessSupplier(); @@ -309,29 +316,34 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c shouldExcludeClassesWithHash = omittedConfiguration.getPredefinedClassesConfiguration()::containsClassWithHash; } - if (configurationWithOrigins) { - ConfigurationWithOriginsWriter writer = new ConfigurationWithOriginsWriter(processor, recordKeeper); - tracer = writer; - tracingResultWriter = writer; - } else if (!conditionalConfigUserPackageFilterFiles.isEmpty()) { - ComplexFilter userCodeFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); - if (!parseFilterFiles(userCodeFilter, conditionalConfigUserPackageFilterFiles)) { - return 2; - } - ComplexFilter classNameFilter; - if (!conditionalConfigClassNameFilterFiles.isEmpty()) { - classNameFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); - if (!parseFilterFiles(classNameFilter, conditionalConfigClassNameFilterFiles)) { - return 3; + if (shouldTraceOriginInformation) { + ConfigurationWithOriginsTracer configWithOriginsTracer = new ConfigurationWithOriginsTracer(processor, recordKeeper); + tracer = configWithOriginsTracer; + + if (isConditionalConfigurationRun) { + if (conditionalConfigPartialRun) { + tracingResultWriter = new ConditionalConfigurationPartialRunWriter(configWithOriginsTracer); + } else { + ComplexFilter userCodeFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); + if (!parseFilterFiles(userCodeFilter, conditionalConfigUserPackageFilterFiles)) { + return 2; + } + ComplexFilter classNameFilter; + if (!conditionalConfigClassNameFilterFiles.isEmpty()) { + classNameFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); + if (!parseFilterFiles(classNameFilter, conditionalConfigClassNameFilterFiles)) { + return 3; + } + } else { + classNameFilter = new ComplexFilter(HierarchyFilterNode.createInclusiveRoot()); + } + + ConditionalConfigurationPredicate predicate = new ConditionalConfigurationPredicate(classNameFilter); + tracingResultWriter = new ConditionalConfigurationWriter(configWithOriginsTracer, userCodeFilter, predicate); } } else { - classNameFilter = new ComplexFilter(HierarchyFilterNode.createInclusiveRoot()); + tracingResultWriter = new ConfigurationWithOriginsWriter(configWithOriginsTracer); } - - ConditionalConfigurationPredicate predicate = new ConditionalConfigurationPredicate(classNameFilter); - ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(processor, recordKeeper, userCodeFilter, predicate); - tracer = writer; - tracingResultWriter = writer; } else { Path[] predefinedClassDestDirs = {Files.createDirectories(configOutputDirPath.resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR))}; Function handler = e -> { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationPartialRunWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationPartialRunWriter.java new file mode 100644 index 000000000000..a1da0ee6b1e5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationPartialRunWriter.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, 2022, 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.agent.conditionalconfig; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsTracer; +import com.oracle.svm.agent.tracing.core.TracingResultWriter; +import com.oracle.svm.configure.config.conditional.PartialConfigurationWithOrigins; +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.ConfigurationFile; + +public class ConditionalConfigurationPartialRunWriter implements TracingResultWriter { + + private final ConfigurationWithOriginsTracer tracer; + + public ConditionalConfigurationPartialRunWriter(ConfigurationWithOriginsTracer tracer) { + this.tracer = tracer; + } + + @Override + public boolean supportsPeriodicTraceWriting() { + return false; + } + + @Override + public boolean supportsOnUnloadTraceWriting() { + return true; + } + + @Override + public List writeToDirectory(Path directoryPath) throws IOException { + Path resolvedPath = directoryPath.resolve(ConfigurationFile.PARTIAL_CONFIGURATION_WITH_ORIGINS); + try (JsonWriter writer = new JsonWriter(resolvedPath)) { + new PartialConfigurationWithOrigins(tracer.getRootNode(), null).printJson(writer); + } + return Collections.singletonList(resolvedPath); + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java index 5fc1e969d5ed..91d290f6f1f4 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java @@ -26,219 +26,34 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.oracle.svm.agent.configwithorigins.HumanReadableConfigurationWithOrigins; -import com.oracle.svm.configure.filters.ComplexFilter; -import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsTracer; -import com.oracle.svm.agent.configwithorigins.MethodCallNode; -import com.oracle.svm.agent.configwithorigins.MethodInfo; -import com.oracle.svm.agent.configwithorigins.MethodInfoRecordKeeper; +import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsWriter; import com.oracle.svm.agent.tracing.core.TracingResultWriter; -import com.oracle.svm.configure.config.ConditionalConfigurationPredicate; import com.oracle.svm.configure.config.ConfigurationSet; -import com.oracle.svm.configure.config.PredefinedClassesConfiguration; -import com.oracle.svm.configure.config.ProxyConfiguration; -import com.oracle.svm.configure.config.ResourceConfiguration; -import com.oracle.svm.configure.config.SerializationConfiguration; -import com.oracle.svm.configure.config.TypeConfiguration; -import com.oracle.svm.configure.trace.TraceProcessor; +import com.oracle.svm.configure.config.conditional.ConditionalConfigurationComputer; +import com.oracle.svm.configure.config.conditional.ConditionalConfigurationPredicate; +import com.oracle.svm.configure.config.conditional.MethodCallNode; +import com.oracle.svm.configure.filters.ComplexFilter; /** * Outputs configuration augmented with reachability conditions. * * This writer leverages the configuration origin information to deduce conditions for the - * configuration. See {@link #createConditionalConfiguration()} + * configuration. See {@link ConditionalConfigurationComputer} */ -public class ConditionalConfigurationWriter extends ConfigurationWithOriginsTracer implements TracingResultWriter { +public class ConditionalConfigurationWriter implements TracingResultWriter { + private final ConfigurationWithOriginsTracer tracer; private final ComplexFilter userCodeFilter; - private ConfigurationSet configurationContainer = new ConfigurationSet(); private final ConditionalConfigurationPredicate predicate; - public ConditionalConfigurationWriter(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper, ComplexFilter userCodeFilter, ConditionalConfigurationPredicate predicate) { - super(processor, methodInfoRecordKeeper); + public ConditionalConfigurationWriter(ConfigurationWithOriginsTracer tracer, ComplexFilter userCodeFilter, ConditionalConfigurationPredicate predicate) { + this.tracer = tracer; this.userCodeFilter = userCodeFilter; this.predicate = predicate; } - private Map> mapMethodsToCallNodes() { - /* Create a map that maps each method to the call nodes of that method in the call graph. */ - Map> methodCallNodes = new HashMap<>(); - ConfigurationSet emptyConfigurationSet = new ConfigurationSet(); - rootNode.visitPostOrder(node -> { - if (node.configuration == null) { - node.configuration = emptyConfigurationSet; - } - List callNodes = methodCallNodes.computeIfAbsent(node.methodInfo, info -> new ArrayList<>()); - callNodes.add(node); - }); - - return methodCallNodes; - } - - /* This code is only ever executed by one thread. */ - @SuppressWarnings("NonAtomicOperationOnVolatileField") - private static Set maybePropagateConfiguration(List callNodes) { - /* - * Iterate over a given method's call nodes and try to find the common config across all - * calls of that method. Then, for each call node of the given method: 1. Set the common - * config as the configuration of that node 2. Find the parent call node of that node 3. Add - * the config difference between the previous config and the common config of that node to - * the parent node config - */ - - /* Only one call of this method happened. Keep everything as it is. */ - if (callNodes.size() <= 1) { - return Collections.emptySet(); - } - - /* Find configuration present in every call of this method */ - ConfigurationSet commonConfig = findCommonConfigurationForMethod(callNodes); - - /* - * For each call, determine the configuration unique to that call and see if any such - * configuration exists - */ - List newNodeConfiguration = new ArrayList<>(); - boolean hasNonEmptyNode = false; - for (MethodCallNode node : callNodes) { - ConfigurationSet callParentConfig = node.configuration.copyAndSubtract(commonConfig); - if (!callParentConfig.isEmpty()) { - hasNonEmptyNode = true; - } - newNodeConfiguration.add(callParentConfig); - } - - /* All remaining configuration is common to each node, no need to propagate anything. */ - if (!hasNonEmptyNode) { - return Collections.emptySet(); - } - - Set affectedNodes = new HashSet<>(); - for (int i = 0; i < callNodes.size(); i++) { - MethodCallNode node = callNodes.get(i); - ConfigurationSet uniqueNodeConfig = newNodeConfiguration.get(i); - node.configuration = new ConfigurationSet(commonConfig); - node.parent.configuration = node.parent.configuration.copyAndMerge(uniqueNodeConfig); - affectedNodes.add(node.parent.methodInfo); - } - - return affectedNodes; - } - - private static ConfigurationSet findCommonConfigurationForMethod(List callNodes) { - ConfigurationSet config = null; - for (MethodCallNode node : callNodes) { - if (config == null) { - config = node.configuration; - } else { - config = config.copyAndIntersectWith(node.configuration); - } - } - return config; - } - - private void createConditionalConfiguration() { - Map> methodCallNodes = mapMethodsToCallNodes(); - - propagateConfiguration(methodCallNodes); - - deduceConditionalConfiguration(methodCallNodes); - } - - private void deduceConditionalConfiguration(Map> methodCallNodes) { - /* - * Once the configuration has been propagated, iterate over all call nodes and use each call - * node as the condition for that call node's config. - */ - MethodCallNode rootCallNode = methodCallNodes.remove(null).get(0); - - for (List value : methodCallNodes.values()) { - for (MethodCallNode node : value) { - String className = node.methodInfo.getJavaDeclaringClassName(); - ConfigurationCondition condition = ConfigurationCondition.create(className); - - addConfigurationWithCondition(node.configuration, condition); - } - } - - addConfigurationWithCondition(rootCallNode.configuration, ConfigurationCondition.alwaysTrue()); - - filterConfiguration(); - } - - private void addConfigurationWithCondition(ConfigurationSet nodeConfig, ConfigurationCondition condition) { - TypeConfiguration reflectionConfig = nodeConfig.getReflectionConfiguration(); - configurationContainer.getReflectionConfiguration().mergeConditional(condition, reflectionConfig); - - TypeConfiguration jniConfig = nodeConfig.getJniConfiguration(); - configurationContainer.getJniConfiguration().mergeConditional(condition, jniConfig); - - ResourceConfiguration resourceConfiguration = nodeConfig.getResourceConfiguration(); - configurationContainer.getResourceConfiguration().mergeConditional(condition, resourceConfiguration); - - ProxyConfiguration proxyConfiguration = nodeConfig.getProxyConfiguration(); - configurationContainer.getProxyConfiguration().mergeConditional(condition, proxyConfiguration); - - SerializationConfiguration serializationConfiguration = nodeConfig.getSerializationConfiguration(); - configurationContainer.getSerializationConfiguration().mergeConditional(condition, serializationConfiguration); - - PredefinedClassesConfiguration predefinedClassesConfiguration = nodeConfig.getPredefinedClassesConfiguration(); - configurationContainer.getPredefinedClassesConfiguration().mergeConditional(condition, predefinedClassesConfiguration); - } - - private void filterConfiguration() { - configurationContainer = configurationContainer.filter(predicate); - } - - private static void propagateConfiguration(Map> methodCallNodes) { - /* - * Iteratively propagate configuration from children to parent calls until an iteration - * doesn't produce any changes. - */ - - Set methodsToHandle = methodCallNodes.keySet(); - while (methodsToHandle.size() != 0) { - Set nextIterationMethodsToHandle = new HashSet<>(); - for (List callNodes : methodCallNodes.values()) { - Set affectedMethods = maybePropagateConfiguration(callNodes); - nextIterationMethodsToHandle.addAll(affectedMethods); - } - methodsToHandle = nextIterationMethodsToHandle; - } - } - - private boolean methodOriginatesFromApplicationPackage(MethodInfo methodInfo) { - return userCodeFilter.includes(methodInfo.getJavaDeclaringClassName()); - } - - @Override - protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { - /* Keep only the classes from the selected package names on the stack trace. */ - int dest = 0; - for (int i = 0; i < stackTrace.length; ++i) { - if (methodOriginatesFromApplicationPackage(stackTrace[i])) { - stackTrace[dest++] = stackTrace[i]; - } - } - - /* No classes on this trace originated from user code */ - if (dest == 0) { - return null; - } - - return Arrays.copyOfRange(stackTrace, 0, dest); - } - @Override public boolean supportsOnUnloadTraceWriting() { return true; @@ -251,11 +66,10 @@ public boolean supportsPeriodicTraceWriting() { @Override public List writeToDirectory(Path directoryPath) throws IOException { - List writtenFiles; - writtenFiles = ConfigurationSet.writeConfiguration(configurationFile -> directoryPath.resolve(configurationFile.getFileName("-origins.txt")), - configurationFile -> new HumanReadableConfigurationWithOrigins(rootNode, configurationFile)); - createConditionalConfiguration(); - writtenFiles.addAll(configurationContainer.writeConfiguration(configurationFile -> directoryPath.resolve(configurationFile.getFileName()))); + MethodCallNode rootNode = tracer.getRootNode(); + List writtenFiles = new ConfigurationWithOriginsWriter(tracer).writeToDirectory(directoryPath); + ConfigurationSet config = new ConditionalConfigurationComputer(rootNode, userCodeFilter, predicate).computeConditionalConfiguration(); + writtenFiles.addAll(config.writeConfiguration(configurationFile -> directoryPath.resolve(configurationFile.getFileName()))); return writtenFiles; } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java index a527a86e183c..a879226cb885 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java @@ -31,6 +31,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import com.oracle.svm.configure.config.conditional.MethodInfo; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.word.WordFactory; @@ -50,7 +51,7 @@ class ClassInfo { this.nameAndSignatureToMethodInfoMap = new ConcurrentHashMap<>(); } - MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) throws Support.WrongPhaseException { + public MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) throws Support.WrongPhaseException { JNIMethodId jMethodId = WordFactory.pointer(rawJMethodIdValue); CCharPointerPointer methodNamePtr = StackValue.get(CCharPointerPointer.class); @@ -61,7 +62,7 @@ MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) throws Support.WrongPh String methodSignature = MethodInfoRecordKeeper.getJavaStringAndFreeNativeString(methodSignaturePtr.read()); String methodNameAndSignature = combineMethodNameAndSignature(methodName, methodSignature); - nameAndSignatureToMethodInfoMap.computeIfAbsent(methodNameAndSignature, nameAndSignature -> new MethodInfo(methodName, methodSignature, this)); + nameAndSignatureToMethodInfoMap.computeIfAbsent(methodNameAndSignature, nameAndSignature -> new MethodInfo(methodName, methodSignature, className)); return nameAndSignatureToMethodInfoMap.get(methodNameAndSignature); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOrigins.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOrigins.java deleted file mode 100644 index 0b1bdd39e2c9..000000000000 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOrigins.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2021, 2022, 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.agent.configwithorigins; - -import java.io.IOException; -import java.util.Set; - -import com.oracle.svm.configure.config.ConfigurationSet; -import com.oracle.svm.configure.json.JsonPrintable; -import com.oracle.svm.configure.json.JsonWriter; -import com.oracle.svm.core.configure.ConfigurationFile; - -public class ConfigurationWithOrigins implements JsonPrintable { - - private final MethodCallNode root; - private final ConfigurationFile configFile; - private static final ConfigurationSet emptyConfigurationSet = new ConfigurationSet(); - - public ConfigurationWithOrigins(MethodCallNode root, ConfigurationFile configFile) { - this.root = root; - this.configFile = configFile; - } - - @Override - public void printJson(JsonWriter writer) throws IOException { - writer.append("[").newline() - .append("{").indent().newline() - .quote("configuration-with-origins").append(": ["); - printJson(root, writer, root.getNodesWithNonEmptyConfig(configFile)); - writer.newline() - .append("]").unindent().newline(); - - writer.quote("configuration-without-origins").append(": ").indent(); - ConfigurationSet rootConfiguration = root.configuration != null ? root.configuration : emptyConfigurationSet; - rootConfiguration.getConfiguration(configFile).printJson(writer); - writer.append("}").newline() - .append("]"); - } - - private void printJson(MethodCallNode node, JsonWriter writer, Set nodesWithNonEmptyConfig) throws IOException { - writer.append("{").indent().newline(); - - writer.quote("method").append(": ").quote(node.methodInfo).append(",").newline(); - - writer.quote("methods").append(": ["); - boolean first = true; - for (MethodCallNode methodCallNode : node.calledMethods.values()) { - if (!nodesWithNonEmptyConfig.contains(methodCallNode)) { - continue; - } - if (first) { - first = false; - } else { - writer.append(","); - } - writer.newline(); - printJson(methodCallNode, writer, nodesWithNonEmptyConfig); - } - writer.newline().append("],").newline(); - - writer.quote("config").append(": "); - ConfigurationSet configuration = node.configuration != null ? node.configuration : emptyConfigurationSet; - configuration.getConfiguration(configFile).printJson(writer); - - writer.unindent().newline(); - writer.append("}"); - - } -} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsTracer.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsTracer.java index 68d81512e975..f912b695cb3b 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsTracer.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsTracer.java @@ -28,6 +28,8 @@ import com.oracle.svm.agent.tracing.ConfigurationResultWriter; import com.oracle.svm.agent.tracing.core.Tracer; +import com.oracle.svm.configure.config.conditional.MethodCallNode; +import com.oracle.svm.configure.config.conditional.MethodInfo; import com.oracle.svm.configure.trace.TraceProcessor; import com.oracle.svm.jni.nativeapi.JNIMethodId; @@ -39,7 +41,7 @@ * resulting from the trace events from that method. When writing configuration files, the call tree * is written node by node, once per configuration file. */ -public abstract class ConfigurationWithOriginsTracer extends Tracer { +public final class ConfigurationWithOriginsTracer extends Tracer { protected final TraceProcessor processor; protected final MethodCallNode rootNode; @@ -63,27 +65,27 @@ public void traceEntry(Map entry) { Map transformedEntry = ConfigurationResultWriter.arraysToLists(entry); if (stackTrace == null) { - rootNode.traceEntry(processor, transformedEntry); + traceEntry(rootNode, transformedEntry); } else { - stackTrace = filterStackTrace(stackTrace); - if (stackTrace != null) { - dispatchTraceEntry(stackTrace, entry); - } + dispatchTraceEntry(stackTrace, transformedEntry); } } } - public void dispatchTraceEntry(MethodInfo[] stackTrace, Map entry) { + public MethodCallNode getRootNode() { + return rootNode; + } + + private void dispatchTraceEntry(MethodInfo[] stackTrace, Map entry) { MethodCallNode currentNode = rootNode; for (int i = stackTrace.length - 1; i >= 0; i--) { MethodInfo nextMethodInfo = stackTrace[i]; - MethodCallNode current = currentNode; - currentNode = currentNode.calledMethods.computeIfAbsent(nextMethodInfo, key -> new MethodCallNode(key, current)); + currentNode = currentNode.getOrCreateChild(nextMethodInfo); } - currentNode.traceEntry(processor, entry); + traceEntry(currentNode, entry); } - protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { - return stackTrace; + private void traceEntry(MethodCallNode node, Map entry) { + processor.processEntry(entry, node.getConfiguration()); } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java index 510c137cd992..8a90cd7c6f60 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java @@ -30,14 +30,15 @@ import com.oracle.svm.agent.tracing.core.TracingResultWriter; import com.oracle.svm.configure.config.ConfigurationSet; -import com.oracle.svm.configure.trace.TraceProcessor; +import com.oracle.svm.configure.config.conditional.HumanReadableConfigurationWithOrigins; -public class ConfigurationWithOriginsWriter extends ConfigurationWithOriginsTracer implements TracingResultWriter { +public class ConfigurationWithOriginsWriter implements TracingResultWriter { public static final String CONFIG_WITH_ORIGINS_SUFFIX = "-origins.txt"; + private final ConfigurationWithOriginsTracer tracer; - public ConfigurationWithOriginsWriter(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper) { - super(processor, methodInfoRecordKeeper); + public ConfigurationWithOriginsWriter(ConfigurationWithOriginsTracer tracer) { + this.tracer = tracer; } @Override @@ -53,6 +54,6 @@ public boolean supportsOnUnloadTraceWriting() { @Override public List writeToDirectory(Path directoryPath) throws IOException { return ConfigurationSet.writeConfiguration(configFile -> directoryPath.resolve(configFile.getFileName(CONFIG_WITH_ORIGINS_SUFFIX)), - configFile -> new HumanReadableConfigurationWithOrigins(rootNode, configFile)); + configFile -> new HumanReadableConfigurationWithOrigins(tracer.getRootNode(), configFile)); } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfoRecordKeeper.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfoRecordKeeper.java index 84d3bdfc66c4..18790282a118 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfoRecordKeeper.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfoRecordKeeper.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.oracle.svm.configure.config.conditional.MethodInfo; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java index 8194d0392de3..c96455a9164e 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java @@ -33,8 +33,7 @@ public class ConfigurationGenerator { @Test public void createTestConfig() { - String enabledProperty = System.getProperty(ConfigurationGenerator.class.getName() + ".enabled"); - if (!Boolean.parseBoolean(enabledProperty)) { + if (!Boolean.getBoolean(ConfigurationGenerator.class.getName() + ".enabled")) { return; } NoPropagationNecessary.runTest(); diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java index 3f836be78ed4..50b78945a021 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java @@ -68,8 +68,8 @@ public void testConfig() throws Exception { private static String getConfigurationJSON(ConfigurationSet config) throws IOException { StringWriter sw = new StringWriter(); try (JsonWriter writer = new JsonWriter(sw)) { - for (ConfigurationFile file : ConfigurationFile.values()) { - if (file.canBeGeneratedByAgent() && !config.getConfiguration(file).isEmpty()) { + for (ConfigurationFile file : ConfigurationFile.agentGeneratedFiles()) { + if (!config.getConfiguration(file).isEmpty()) { sw.append("\n").append(file.getName()).append("\n"); config.getConfiguration(file).printJson(writer); } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/PartialConfigurationGenerator.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/PartialConfigurationGenerator.java new file mode 100644 index 000000000000..25893c3f3122 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/PartialConfigurationGenerator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, 2022, 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.configure.test.conditionalconfig; + +import org.junit.Test; + +public class PartialConfigurationGenerator { + + private static void runIfEnabled(Runnable runnable) { + if (!Boolean.getBoolean(PartialConfigurationGenerator.class.getName() + ".enabled")) { + return; + } + runnable.run(); + } + + @Test + public void createConfigPartOne() { + runIfEnabled(NoPropagationNecessary::runTest); + } + + @Test + public void createConfigPartTwo() { + runIfEnabled(PropagateToParent::runTest); + } + + @Test + public void createConfigPartThree() { + runIfEnabled(PropagateButLeaveCommonConfiguration::runTest); + } +} 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 f1828e9a3b28..99de5aa92c44 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 @@ -27,6 +27,7 @@ import java.util.function.Consumer; import com.oracle.svm.configure.json.JsonPrintable; +import com.oracle.svm.core.configure.ConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; public abstract class ConfigurationBase, P> implements JsonPrintable { @@ -37,7 +38,7 @@ public abstract class ConfigurationBase, P> im protected abstract void merge(T other); - protected abstract void mergeConditional(ConfigurationCondition condition, T other); + public abstract void mergeConditional(ConfigurationCondition condition, T other); protected abstract void subtract(T other); @@ -66,4 +67,6 @@ public T copyAndIntersect(T other) { public T copyAndFilter(P predicate) { return copyAnd(copy -> copy.removeIf(predicate)); } + + public abstract ConfigurationParser createParser(); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java index ccfeae60add0..5d6e841674b8 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java @@ -47,6 +47,11 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import com.oracle.svm.configure.config.conditional.ConditionalConfigurationComputer; +import com.oracle.svm.configure.config.conditional.ConditionalConfigurationPredicate; +import com.oracle.svm.configure.config.conditional.MethodCallNode; +import com.oracle.svm.configure.config.conditional.MethodInfoRepository; +import com.oracle.svm.configure.config.conditional.PartialConfigurationWithOrigins; import org.graalvm.nativeimage.ImageInfo; import com.oracle.svm.configure.config.ConfigurationFileCollection; @@ -97,6 +102,9 @@ public static void main(String[] args) { case "process-trace": // legacy generate(argsIter, true); break; + case "generate-conditional": + createConditionalConfig(argsIter); + break; case "generate-filters": generateFilterRules(argsIter); break; @@ -166,12 +174,7 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA inputCollection.addDirectory(requirePath(current, value)); break; case "--output-dir": - Path directory = requirePath(current, value); - if (!Files.exists(directory)) { - Files.createDirectory(directory); - } else if (!Files.isDirectory(directory)) { - throw new NoSuchFileException(value); - } + Path directory = getOrCreateDirectory(current, value); outputCollection.addDirectory(directory); break; @@ -342,6 +345,89 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA } } + private static Path getOrCreateDirectory(String current, String value) throws IOException { + Path directory = requirePath(current, value); + if (!Files.exists(directory)) { + Files.createDirectory(directory); + } else if (!Files.isDirectory(directory)) { + throw new NoSuchFileException(value); + } + return directory; + } + + private static void createConditionalConfig(Iterator argsIter) throws IOException { + Set configInputPaths = new HashSet<>(); + Set configOutputPaths = new HashSet<>(); + URI userCodeFilterUri = null; + Set classNameFiltersUri = new HashSet<>(); + while (argsIter.hasNext()) { + String opt = argsIter.next(); + String[] parts = opt.split("="); + if (parts.length != 2) { + throw new UsageException("Invalid option " + opt); + } + String option = parts[0]; + String value = parts[1]; + switch (option) { + case "--input-dir": + Path dir = requirePath(option, value); + if (!Files.isDirectory(dir)) { + throw new UsageException("Path is not a directory: " + dir); + } + Path configPath = dir.resolve(ConfigurationFile.PARTIAL_CONFIGURATION_WITH_ORIGINS); + if (!Files.isRegularFile(configPath)) { + throw new UsageException("Cannot find partial configuration file at " + configPath); + } + configInputPaths.add(configPath.toUri()); + break; + case "--output-dir": + Path outputDir = getOrCreateDirectory(option, value); + configOutputPaths.add(outputDir.toUri()); + break; + case "--user-code-filter": + Path userCodeFilter = requirePath(option, value); + if (!Files.isRegularFile(userCodeFilter)) { + throw new UsageException("Cannot find user code filter file at " + userCodeFilter); + } + userCodeFilterUri = userCodeFilter.toUri(); + break; + case "--class-name-filter": + Path classNameFilter = requirePath(option, value); + if (!Files.isRegularFile(classNameFilter)) { + throw new UsageException("Cannot find user code filter file at " + classNameFilter); + } + classNameFiltersUri.add(classNameFilter.toUri()); + break; + default: + throw new UsageException("Unknown option: " + option); + } + } + + ComplexFilter userCodeFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); + new FilterConfigurationParser(userCodeFilter).parseAndRegister(userCodeFilterUri); + + ComplexFilter classNameFilter; + if (classNameFiltersUri.isEmpty()) { + classNameFilter = new ComplexFilter(HierarchyFilterNode.createInclusiveRoot()); + } else { + classNameFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); + for (URI classNameFilterUri : classNameFiltersUri) { + new FilterConfigurationParser(classNameFilter).parseAndRegister(classNameFilterUri); + } + } + + MethodCallNode rootNode = MethodCallNode.createRoot(); + MethodInfoRepository registry = new MethodInfoRepository(); + for (URI inputUri : configInputPaths) { + new PartialConfigurationWithOrigins(rootNode, registry).parseAndRegister(inputUri); + } + + ConfigurationSet configSet = new ConditionalConfigurationComputer(rootNode, userCodeFilter, new ConditionalConfigurationPredicate(classNameFilter)).computeConditionalConfiguration(); + for (URI outputUri : configOutputPaths) { + configSet.writeConfiguration(file -> Path.of(outputUri).resolve(file.getFileName())); + } + } + private static void failIfAgentLockFilesPresent(ConfigurationFileCollection... collections) { Set paths = null; for (ConfigurationFileCollection coll : collections) { 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 2f4e1df09b84..fbc031bc8402 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 @@ -28,7 +28,6 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -39,11 +38,6 @@ 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.configure.ProxyConfigurationParser; -import com.oracle.svm.core.configure.ReflectionConfigurationParser; -import com.oracle.svm.core.configure.ResourceConfigurationParser; -import com.oracle.svm.core.configure.SerializationConfigurationParser; public class ConfigurationFileCollection { public static final Function FAIL_ON_EXCEPTION = e -> e; @@ -128,26 +122,26 @@ public TypeConfiguration loadReflectConfig(Function exce public ProxyConfiguration loadProxyConfig(Function exceptionHandler) throws Exception { ProxyConfiguration proxyConfiguration = new ProxyConfiguration(); - loadConfig(proxyConfigPaths, new ProxyConfigurationParser(types -> proxyConfiguration.add(types.getCondition(), new ArrayList<>(types.getElement())), true), exceptionHandler); + loadConfig(proxyConfigPaths, proxyConfiguration.createParser(), exceptionHandler); return proxyConfiguration; } public PredefinedClassesConfiguration loadPredefinedClassesConfig(Path[] classDestinationDirs, Predicate shouldExcludeClassesWithHash, Function exceptionHandler) throws Exception { PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(classDestinationDirs, shouldExcludeClassesWithHash); - loadConfig(predefinedClassesConfigPaths, new PredefinedClassesConfigurationParser(predefinedClassesConfiguration::add, true), exceptionHandler); + loadConfig(predefinedClassesConfigPaths, predefinedClassesConfiguration.createParser(), exceptionHandler); return predefinedClassesConfiguration; } public ResourceConfiguration loadResourceConfig(Function exceptionHandler) throws Exception { ResourceConfiguration resourceConfiguration = new ResourceConfiguration(); - loadConfig(resourceConfigPaths, new ResourceConfigurationParser(new ResourceConfiguration.ParserAdapter(resourceConfiguration), true), exceptionHandler); + loadConfig(resourceConfigPaths, resourceConfiguration.createParser(), exceptionHandler); return resourceConfiguration; } public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); - loadConfig(serializationConfigPaths, new SerializationConfigurationParser(serializationConfiguration, true), exceptionHandler); + loadConfig(serializationConfigPaths, serializationConfiguration.createParser(), exceptionHandler); return serializationConfiguration; } @@ -160,7 +154,7 @@ public ConfigurationSet loadConfigurationSet(Function ex private static TypeConfiguration loadTypeConfig(Collection uris, Function exceptionHandler) throws Exception { TypeConfiguration configuration = new TypeConfiguration(); - loadConfig(uris, new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(configuration)), exceptionHandler); + loadConfig(uris, configuration.createParser(), 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 7e1261de6386..a5895a415dfd 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 @@ -31,6 +31,7 @@ import java.util.function.Function; import com.oracle.svm.configure.ConfigurationBase; +import com.oracle.svm.configure.config.conditional.ConditionalConfigurationPredicate; import com.oracle.svm.configure.json.JsonPrintable; import com.oracle.svm.configure.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; @@ -125,20 +126,21 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { return predefinedClassesConfiguration; } - public ConfigurationBase getConfiguration(ConfigurationFile configurationFile) { + @SuppressWarnings("unchecked") + public > T getConfiguration(ConfigurationFile configurationFile) { switch (configurationFile) { case DYNAMIC_PROXY: - return proxyConfiguration; + return (T) proxyConfiguration; case RESOURCES: - return resourceConfiguration; + return (T) resourceConfiguration; case JNI: - return jniConfiguration; + return (T) jniConfiguration; case REFLECTION: - return reflectionConfiguration; + return (T) reflectionConfiguration; case SERIALIZATION: - return serializationConfiguration; + return (T) serializationConfiguration; case PREDEFINED_CLASSES_NAME: - return predefinedClassesConfiguration; + return (T) predefinedClassesConfiguration; default: throw VMError.shouldNotReachHere("Unsupported configuration in configuration container: " + configurationFile); } @@ -146,15 +148,13 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { public static List writeConfiguration(Function configFilePathResolver, Function configSupplier) throws IOException { List writtenFiles = new ArrayList<>(); - for (ConfigurationFile configFile : ConfigurationFile.values()) { - if (configFile.canBeGeneratedByAgent()) { - Path path = configFilePathResolver.apply(configFile); - writtenFiles.add(path); - JsonWriter writer = new JsonWriter(path); - configSupplier.apply(configFile).printJson(writer); - writer.newline(); - writer.close(); - } + for (ConfigurationFile configFile : ConfigurationFile.agentGeneratedFiles()) { + Path path = configFilePathResolver.apply(configFile); + writtenFiles.add(path); + JsonWriter writer = new JsonWriter(path); + configSupplier.apply(configFile).printJson(writer); + writer.newline(); + writer.close(); } return writtenFiles; } 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 7908b4cd7226..f4e9055669e1 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 @@ -37,7 +37,7 @@ public class ParserConfigurationAdapter implements ReflectionConfigurationParser private final TypeConfiguration configuration; - ParserConfigurationAdapter(TypeConfiguration configuration) { + public ParserConfigurationAdapter(TypeConfiguration configuration) { this.configuration = configuration; } 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 5146db2c05f6..b8f7f7234f51 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,6 +34,7 @@ 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; @@ -162,6 +163,11 @@ public void printJson(JsonWriter writer) throws IOException { writer.unindent().newline().append(']').newline(); } + @Override + public ConfigurationParser createParser() { + return new PredefinedClassesConfigurationParser(this::add, true); + } + @Override public boolean isEmpty() { return classes.isEmpty(); 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 8434f36188a6..f5c3a867843d 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,6 +31,8 @@ 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; @@ -122,6 +124,11 @@ public void printJson(JsonWriter writer) throws IOException { writer.append(']'); } + @Override + public ConfigurationParser createParser() { + return new ProxyConfigurationParser(interfaceLists::add, true); + } + @Override public boolean isEmpty() { return interfaceLists.isEmpty(); 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 3be0e9f304d7..b0845a4db275 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 @@ -35,6 +35,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.configure.ResourceConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; @@ -240,6 +242,11 @@ public void printJson(JsonWriter writer) throws IOException { writer.unindent().newline().append('}'); } + @Override + public ConfigurationParser createParser() { + return new ResourceConfigurationParser(new ResourceConfiguration.ParserAdapter(this), true); + } + private static void printResourceBundle(BundleConfiguration config, JsonWriter writer) throws IOException { writer.append('{').indent().newline(); ConfigurationConditionPrintable.printConditionAttribute(config.condition, writer); 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 3d019e5f5949..a9c0a49d7dd0 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 @@ -33,6 +33,8 @@ import java.util.concurrent.ConcurrentHashMap; import com.oracle.svm.configure.json.JsonPrintable; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.configure.SerializationConfigurationParser; import org.graalvm.compiler.java.LambdaUtils; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; @@ -109,6 +111,11 @@ public void printJson(JsonWriter writer) throws IOException { writer.append('}'); } + @Override + public ConfigurationParser createParser() { + return new SerializationConfigurationParser(this, true); + } + private static void printSerializationClasses(JsonWriter writer, String types, List serializationConfigurationTypes) throws IOException { writer.quote(types).append(":"); writer.append('['); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java index 19e3ed6ae671..028fff2b5543 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,6 +32,8 @@ 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; @@ -139,6 +141,11 @@ public void printJson(JsonWriter writer) throws IOException { writer.newline().append(']'); } + @Override + public ConfigurationParser createParser() { + return new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(this), true); + } + @Override public boolean isEmpty() { return types.isEmpty(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java new file mode 100644 index 000000000000..85d2b3568483 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2022, 2022, 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.configure.config.conditional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.configure.ConfigurationBase; +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.filters.ComplexFilter; +import com.oracle.svm.core.configure.ConfigurationFile; + +public class ConditionalConfigurationComputer { + + private final MethodCallNode rootNode; + private final ComplexFilter userCodeFilter; + private final ConditionalConfigurationPredicate configurationFilter; + + public ConditionalConfigurationComputer(MethodCallNode rootNode, ComplexFilter userCodeFilter, ConditionalConfigurationPredicate configurationFilter) { + this.rootNode = rootNode; + this.userCodeFilter = userCodeFilter; + this.configurationFilter = configurationFilter; + } + + public ConfigurationSet computeConditionalConfiguration() { + retainOnlyUserCodeMethodCallNodes(); + + Map> methodCallNodes = mapMethodsToCallNodes(); + + propagateConfiguration(methodCallNodes); + + return deduceConditionalConfiguration(methodCallNodes); + } + + private void retainOnlyUserCodeMethodCallNodes() { + retainOnlyUserCodeMethodCallNodes(rootNode); + } + + private void retainOnlyUserCodeMethodCallNodes(MethodCallNode node) { + /* Make a copy as the map can change under our feet */ + List calledMethods = new ArrayList<>(node.calledMethods.values()); + for (MethodCallNode child : calledMethods) { + retainOnlyUserCodeMethodCallNodes(child); + } + + node.calledMethods.values().removeIf(child -> { + if (!userCodeFilter.includes(child.methodInfo.getJavaDeclaringClassName())) { + child.parent.mergeSubTree(child, child.parent != rootNode); + return true; + } + return false; + }); + } + + private Map> mapMethodsToCallNodes() { + /* Create a map that maps each method to the call nodes of that method in the call graph. */ + Map> methodCallNodes = new HashMap<>(); + ConfigurationSet emptyConfigurationSet = new ConfigurationSet(); + rootNode.visitPostOrder(node -> { + if (node.configuration == null) { + node.configuration = emptyConfigurationSet; + } + List callNodes = methodCallNodes.computeIfAbsent(node.methodInfo, info -> new ArrayList<>()); + callNodes.add(node); + }); + + return methodCallNodes; + } + /* This code is only ever executed by one thread. */ + + @SuppressWarnings("NonAtomicOperationOnVolatileField") + private static Set maybePropagateConfiguration(List callNodes) { + /* + * Iterate over a given method's call nodes and try to find the common config across all + * calls of that method. Then, for each call node of the given method: 1. Set the common + * config as the configuration of that node 2. Find the parent call node of that node 3. Add + * the config difference between the previous config and the common config of that node to + * the parent node config + */ + + /* Only one call of this method happened. Keep everything as it is. */ + if (callNodes.size() <= 1) { + return Collections.emptySet(); + } + + /* Find configuration present in every call of this method */ + ConfigurationSet commonConfig = findCommonConfigurationForMethod(callNodes); + + /* + * For each call, determine the configuration unique to that call and see if any such + * configuration exists + */ + List newNodeConfiguration = new ArrayList<>(); + boolean hasNonEmptyNode = false; + for (MethodCallNode node : callNodes) { + ConfigurationSet callParentConfig = node.configuration.copyAndSubtract(commonConfig); + if (!callParentConfig.isEmpty()) { + hasNonEmptyNode = true; + } + newNodeConfiguration.add(callParentConfig); + } + + /* All remaining configuration is common to each node, no need to propagate anything. */ + if (!hasNonEmptyNode) { + return Collections.emptySet(); + } + + Set affectedNodes = new HashSet<>(); + for (int i = 0; i < callNodes.size(); i++) { + MethodCallNode node = callNodes.get(i); + ConfigurationSet uniqueNodeConfig = newNodeConfiguration.get(i); + node.configuration = new ConfigurationSet(commonConfig); + node.parent.configuration = node.parent.configuration.copyAndMerge(uniqueNodeConfig); + affectedNodes.add(node.parent.methodInfo); + } + + return affectedNodes; + } + + private static ConfigurationSet findCommonConfigurationForMethod(List callNodes) { + ConfigurationSet config = null; + for (MethodCallNode node : callNodes) { + if (config == null) { + config = node.configuration; + } else { + config = config.copyAndIntersectWith(node.configuration); + } + } + return config; + } + + private ConfigurationSet deduceConditionalConfiguration(Map> methodCallNodes) { + ConfigurationSet configurationSet = new ConfigurationSet(); + + /* + * Once the configuration has been propagated, iterate over all call nodes and use each call + * node as the condition for that call node's config. + */ + MethodCallNode rootCallNode = methodCallNodes.remove(null).get(0); + + for (List value : methodCallNodes.values()) { + for (MethodCallNode node : value) { + String className = node.methodInfo.getJavaDeclaringClassName(); + ConfigurationCondition condition = ConfigurationCondition.create(className); + + addConfigurationWithCondition(configurationSet, node.configuration, condition); + } + } + + addConfigurationWithCondition(configurationSet, rootCallNode.configuration, ConfigurationCondition.alwaysTrue()); + + return configurationSet.filter(configurationFilter); + } + + /* Force the compiler to believe us we're referring to the same type. */ + private static > void mergeWithCondition(ConfigurationSet destConfigSet, ConfigurationSet srcConfigSet, ConfigurationCondition condition, + ConfigurationFile configType) { + T destConfig = destConfigSet.getConfiguration(configType); + T srcConfig = srcConfigSet.getConfiguration(configType); + destConfig.mergeConditional(condition, srcConfig); + } + + private static void addConfigurationWithCondition(ConfigurationSet destConfigSet, ConfigurationSet srcConfigSet, ConfigurationCondition condition) { + for (ConfigurationFile configType : ConfigurationFile.agentGeneratedFiles()) { + mergeWithCondition(destConfigSet, srcConfigSet, condition, configType); + } + } + + private static void propagateConfiguration(Map> methodCallNodes) { + /* + * Iteratively propagate configuration from children to parent calls until an iteration + * doesn't produce any changes. + */ + + Set methodsToHandle = methodCallNodes.keySet(); + while (methodsToHandle.size() != 0) { + Set nextIterationMethodsToHandle = new HashSet<>(); + for (List callNodes : methodCallNodes.values()) { + Set affectedMethods = maybePropagateConfiguration(callNodes); + nextIterationMethodsToHandle.addAll(affectedMethods); + } + methodsToHandle = nextIterationMethodsToHandle; + } + } + +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java similarity index 83% rename from substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java rename to substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java index a4b9c2fa1ea8..7362854a4623 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java @@ -22,11 +22,20 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.configure.config; +package com.oracle.svm.configure.config.conditional; import java.util.List; import java.util.regex.Pattern; +import com.oracle.svm.configure.config.ConfigurationPredefinedClass; +import com.oracle.svm.configure.config.ConfigurationType; +import com.oracle.svm.configure.config.PredefinedClassesConfiguration; +import com.oracle.svm.configure.config.ProxyConfiguration; +import com.oracle.svm.configure.config.ResourceConfiguration; +import com.oracle.svm.configure.config.SerializationConfiguration; +import com.oracle.svm.configure.config.SerializationConfigurationLambdaCapturingType; +import com.oracle.svm.configure.config.SerializationConfigurationType; +import com.oracle.svm.configure.config.TypeConfiguration; import com.oracle.svm.configure.filters.ComplexFilter; import com.oracle.svm.core.configure.ConditionalElement; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/HumanReadableConfigurationWithOrigins.java similarity index 98% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java rename to substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/HumanReadableConfigurationWithOrigins.java index a0dc0219f65d..7d643c0f5515 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/HumanReadableConfigurationWithOrigins.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.agent.configwithorigins; +package com.oracle.svm.configure.config.conditional; import java.io.IOException; import java.io.StringWriter; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodCallNode.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodCallNode.java similarity index 80% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodCallNode.java rename to substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodCallNode.java index 23b46ade80f9..e7a8161c81e4 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodCallNode.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodCallNode.java @@ -22,11 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.agent.configwithorigins; - -import com.oracle.svm.configure.config.ConfigurationSet; -import com.oracle.svm.configure.trace.TraceProcessor; -import com.oracle.svm.core.configure.ConfigurationFile; +package com.oracle.svm.configure.config.conditional; import java.util.HashSet; import java.util.Map; @@ -35,6 +31,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.core.configure.ConfigurationFile; + public final class MethodCallNode { public final MethodInfo methodInfo; @@ -42,14 +41,18 @@ public final class MethodCallNode { public final Map calledMethods; public volatile ConfigurationSet configuration; - MethodCallNode(MethodInfo methodInfo, MethodCallNode parent) { + private MethodCallNode(MethodInfo methodInfo, MethodCallNode parent) { this.methodInfo = methodInfo; this.parent = parent; this.calledMethods = new ConcurrentHashMap<>(); this.configuration = null; } - static MethodCallNode createRoot() { + public MethodCallNode getOrCreateChild(MethodInfo info) { + return calledMethods.computeIfAbsent(info, key -> new MethodCallNode(key, this)); + } + + public static MethodCallNode createRoot() { return new MethodCallNode(null, null); } @@ -73,11 +76,27 @@ public Set getNodesWithNonEmptyConfig(ConfigurationFile configFi return nodesWithNonEmptyConfig; } + public void mergeSubTree(MethodCallNode other, boolean mergeConfig) { + if (mergeConfig) { + configuration = getConfiguration().copyAndMerge(other.getConfiguration()); + } + for (MethodCallNode child : other.calledMethods.values()) { + calledMethods.compute(child.methodInfo, (key, value) -> { + if (value == null) { + return child; + } else { + value.mergeSubTree(child, true); + return value; + } + }); + } + } + public boolean hasConfig(ConfigurationFile configFile) { return configuration != null && !configuration.getConfiguration(configFile).isEmpty(); } - void traceEntry(TraceProcessor processor, Map entry) { + public ConfigurationSet getConfiguration() { if (configuration == null) { synchronized (this) { if (configuration == null) { @@ -85,7 +104,7 @@ void traceEntry(TraceProcessor processor, Map entry) { } } } - processor.processEntry(entry, configuration); + return configuration; } public void visitPostOrder(Consumer methodCallNodeConsumer) { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodInfo.java similarity index 82% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java rename to substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodInfo.java index 1806988cf35c..60f5c9a96a14 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodInfo.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.agent.configwithorigins; +package com.oracle.svm.configure.config.conditional; import java.util.Objects; @@ -32,12 +32,12 @@ public class MethodInfo { private final String name; private final String signature; - private final ClassInfo declaringClass; + private final String declaringClassName; - MethodInfo(String name, String signature, ClassInfo declaringClass) { + public MethodInfo(String name, String signature, String declaringClassName) { this.name = name; this.signature = signature; - this.declaringClass = declaringClass; + this.declaringClassName = declaringClassName; } public String getJavaMethodNameAndSignature() { @@ -58,7 +58,15 @@ public String getJavaMethodNameAndSignature() { } public String getJavaDeclaringClassName() { - return declaringClass.className; + return declaringClassName; + } + + public String getName() { + return name; + } + + public String getSignature() { + return signature; } @Override @@ -70,12 +78,12 @@ public boolean equals(Object o) { return false; } MethodInfo that = (MethodInfo) o; - return name.equals(that.name) && signature.equals(that.signature) && declaringClass.equals(that.declaringClass); + return name.equals(that.name) && signature.equals(that.signature) && declaringClassName.equals(that.declaringClassName); } @Override public int hashCode() { - return Objects.hash(name, signature, declaringClass); + return Objects.hash(name, signature, declaringClassName); } @Override diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodInfoRepository.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodInfoRepository.java new file mode 100644 index 000000000000..825f1a90b4d9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/MethodInfoRepository.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021, 2022, 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.configure.config.conditional; + +import java.util.HashMap; +import java.util.Map; + +/** + * A cache used to avoid creating multiple {@link MethodInfo} objects for the same method when + * parsing a call tree from multiple sources. + */ +public class MethodInfoRepository { + + private final Map cache = new HashMap<>(); + + public MethodInfo getOrAdd(String className, String methodName, String signature) { + return cache.computeIfAbsent(new MethodInfo(methodName, signature, className), key -> key); + } + +} 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 new file mode 100644 index 000000000000..189409c1e197 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2021, 2022, 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.configure.config.conditional; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.json.JsonPrintable; +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.util.json.JSONParserException; + +public class PartialConfigurationWithOrigins extends ConfigurationParser implements JsonPrintable { + private static final ConfigurationSet emptyConfigurationSet = new ConfigurationSet(); + + private final MethodCallNode root; + private final MethodInfoRepository methodInfoRegistry; + + public PartialConfigurationWithOrigins(MethodCallNode root, MethodInfoRepository methodInfoRegistry) { + super(true); + this.root = root; + this.methodInfoRegistry = methodInfoRegistry; + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + + writer.append("{").indent().newline() + .quote("configuration-with-origins").append(": "); + printJson(root, writer); + writer.unindent().newline(); + writer.append("}").newline(); + } + + private void printJson(MethodCallNode node, JsonWriter writer) throws IOException { + writer.append("{").indent().newline(); + + String className = node.methodInfo != null ? node.methodInfo.getJavaDeclaringClassName() : ""; + String methodName = node.methodInfo != null ? node.methodInfo.getName() : ""; + String signature = node.methodInfo != null ? node.methodInfo.getSignature() : "()V"; + writer.quote("class").append(": ").quote(className).append(",").newline(); + writer.quote("method").append(": ").quote(methodName).append(",").newline(); + writer.quote("signature").append(": ").quote(signature).append(",").newline(); + + writer.quote("methods").append(": ["); + boolean first = true; + for (MethodCallNode methodCallNode : node.calledMethods.values()) { + if (first) { + first = false; + } else { + writer.append(","); + } + writer.newline(); + printJson(methodCallNode, writer); + } + writer.newline().append("]"); + ConfigurationSet configSet = node.configuration == null ? emptyConfigurationSet : node.configuration; + if (!configSet.isEmpty()) { + writer.append(",").newline(); + } + + printConfigurationSet(writer, configSet); + + writer.unindent().newline(); + writer.append("}"); + + } + + @Override + public void parseAndRegister(Object json, URI origin) throws IOException { + Map topObject = asMap(json, "Top level of document must be an object"); + Object originsObject = topObject.get("configuration-with-origins"); + if (originsObject == null) { + throw new JSONParserException("Top level object must have a 'configuration-with-origins' property."); + } + Map rootMethod = asMap(originsObject, "'configuration-with-origins' must be an object"); + parseMethodEntry(null, rootMethod, origin); + } + + private static String getStringProperty(Map json, String property) { + Object prop = json.get(property); + if (prop == null) { + throw new JSONParserException("Missing property '" + property + "'"); + } + return asString(prop); + } + + private void parseMethodEntry(MethodCallNode parent, Map methodJson, URI origin) throws IOException { + /* Check if we are parsing the root node */ + MethodCallNode target; + if (parent == null) { + target = root; + } else { + String className = getStringProperty(methodJson, "class"); + String methodName = getStringProperty(methodJson, "method"); + String signature = getStringProperty(methodJson, "signature"); + MethodInfo info = methodInfoRegistry.getOrAdd(className, methodName, signature); + target = parent.getOrCreateChild(info); + } + + Object config = methodJson.get("config"); + if (config != null) { + Map configJson = asMap(config, "'config' must be an object"); + parseConfigurationSet(configJson, target.getConfiguration(), origin); + } + + Object methods = methodJson.get("methods"); + List methodsList = asList(methods, "'methods' must be a list"); + for (Object methodObject : methodsList) { + Map method = asMap(methodObject, "'methods' must contain objects"); + parseMethodEntry(target, method, origin); + } + } + + private static void printConfigurationSet(JsonWriter writer, ConfigurationSet configurationSet) throws IOException { + if (!configurationSet.isEmpty()) { + writer.quote("config").append(": {").indent().newline(); + boolean first = true; + for (ConfigurationFile file : ConfigurationFile.agentGeneratedFiles()) { + if (!configurationSet.getConfiguration(file).isEmpty()) { + if (first) { + first = false; + } else { + writer.append(",").newline(); + } + + writer.quote(file.getName()).append(": "); + configurationSet.getConfiguration(file).printJson(writer); + } + } + writer.unindent().newline() + .append("}"); + } + } + + private static void parseConfigurationSet(Map configJson, ConfigurationSet configurationSet, URI origin) throws IOException { + for (Map.Entry entry : configJson.entrySet()) { + String configName = entry.getKey(); + ConfigurationFile configType = ConfigurationFile.getByName(configName); + if (configType == null) { + throw new JSONParserException("Invalid configuration type: " + configName); + } + configurationSet.getConfiguration(configType).createParser().parseAndRegister(entry.getValue(), origin); + } + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/FilterConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/FilterConfigurationParser.java index 6a4e409076ba..65e396df45a3 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/FilterConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/FilterConfigurationParser.java @@ -25,13 +25,12 @@ package com.oracle.svm.configure.filters; import java.io.IOException; -import java.io.Reader; +import java.net.URI; import java.util.Map; import java.util.function.BiConsumer; import com.oracle.svm.configure.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationParser; -import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; public class FilterConfigurationParser extends ConfigurationParser { @@ -93,8 +92,7 @@ static void printEntry(JsonWriter writer, boolean[] isFirstRule, ConfigurationFi } @Override - public void parseAndRegister(Reader reader) throws IOException { - Object json = new JSONParser(reader).parse(); + public void parseAndRegister(Object json, URI origin) { filter.parseFromJson(asMap(json, "First level of document must be an object")); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java index c594694d4be6..2ad7cd371486 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java @@ -104,6 +104,7 @@ public final class AccessAdvisor { accessWithoutCallerFilter = HierarchyFilterNode.createInclusiveRoot(); // in addition to // accessFilter + accessWithoutCallerFilter.addOrGetChildren("jdk.vm.ci.**", ConfigurationFilter.Inclusion.Exclude); accessWithoutCallerFilter.addOrGetChildren("[Ljava.lang.String;", ConfigurationFilter.Inclusion.Exclude); // ^ String[]: for command-line argument arrays created before Java main method is called 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 1517388032c4..049680e8e383 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,6 +24,8 @@ */ package com.oracle.svm.core.configure; +import java.util.Arrays; + public enum ConfigurationFile { DYNAMIC_PROXY("proxy", true), RESOURCES("resource", true), @@ -40,6 +42,9 @@ public enum ConfigurationFile { public static final String LOCK_FILE_NAME = ".lock"; public static final String PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR = "agent-extracted-predefined-classes"; public static final String PREDEFINED_CLASSES_AGENT_EXTRACTED_NAME_SUFFIX = ".classdata"; + public static final String PARTIAL_CONFIGURATION_WITH_ORIGINS = "partial-config-with-origins.json"; + + private static final ConfigurationFile[] agentGeneratedFiles = computeAgentGeneratedFiles(); ConfigurationFile(String name, boolean canAgentGenerate) { this.name = name; @@ -62,4 +67,20 @@ public boolean canBeGeneratedByAgent() { return canAgentGenerate; } + public static ConfigurationFile getByName(String name) { + for (ConfigurationFile file : values()) { + if (file.getName().equals(name)) { + return file; + } + } + return null; + } + + public static ConfigurationFile[] agentGeneratedFiles() { + return agentGeneratedFiles; + } + + private static ConfigurationFile[] computeAgentGeneratedFiles() { + return Arrays.stream(values()).filter(ConfigurationFile::canBeGeneratedByAgent).toArray(ConfigurationFile[]::new); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index 7ac92675a997..5451f1556d2b 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 @@ -39,6 +39,7 @@ import java.util.Map; import java.util.Set; +import com.oracle.svm.core.util.json.JSONParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.SubstrateUtil; @@ -67,7 +68,7 @@ protected ConfigurationParser(boolean strictConfiguration) { public void parseAndRegister(URI uri) throws IOException { try (Reader reader = openReader(uri)) { - parseAndRegister(reader); + parseAndRegister(new JSONParser(reader).parse(), uri); } } @@ -75,7 +76,11 @@ protected static BufferedReader openReader(URI uri) throws IOException { return new BufferedReader(new InputStreamReader(openStream(uri))); } - public abstract void parseAndRegister(Reader reader) throws IOException; + public void parseAndRegister(Reader reader) throws IOException { + parseAndRegister(new JSONParser(reader).parse(), null); + } + + public abstract void parseAndRegister(Object json, URI origin) throws IOException; @SuppressWarnings("unchecked") public static List asList(Object data, String errorMessage) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/PredefinedClassesConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/PredefinedClassesConfigurationParser.java index db45f4d35b16..ee82e24682f0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/PredefinedClassesConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/PredefinedClassesConfigurationParser.java @@ -27,14 +27,12 @@ import java.io.IOException; import java.io.InputStream; -import java.io.Reader; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; import java.util.Map; -import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; public class PredefinedClassesConfigurationParser extends ConfigurationParser { @@ -49,18 +47,6 @@ public PredefinedClassesConfigurationParser(PredefinedClassesRegistry registry, this.registry = registry; } - @Override - public void parseAndRegister(Reader reader) throws IOException { - parseAndRegister(reader, null); - } - - @Override - public void parseAndRegister(URI uri) throws IOException { - try (Reader reader = openReader(uri)) { - parseAndRegister(reader, resolveBaseUri(uri)); - } - } - private static URI resolveBaseUri(URI original) throws IOException { String directory = ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR + "/"; /* @@ -106,12 +92,11 @@ private static URI resolveClassdataFile(URI baseUri, String providedHash) throws return baseUri.resolve(fileName); } - private void parseAndRegister(Reader reader, URI baseUri) throws IOException { - JSONParser parser = new JSONParser(reader); - Object json = parser.parse(); - - for (Object origin : asList(json, "first level of document must be an array of predefined class origin objects")) { - parseOrigin(baseUri, asMap(origin, "second level of document must be a predefined class origin object")); + @Override + public void parseAndRegister(Object json, URI origin) throws IOException { + URI baseUri = origin == null ? null : resolveBaseUri(origin); + for (Object classDataOrigin : asList(json, "first level of document must be an array of predefined class origin objects")) { + parseOrigin(baseUri, asMap(classDataOrigin, "second level of document must be a predefined class origin object")); } } 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 bb1ce4912412..2cc8c2b6322f 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 @@ -24,8 +24,7 @@ */ package com.oracle.svm.core.configure; -import java.io.IOException; -import java.io.Reader; +import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; @@ -35,7 +34,6 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; -import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; /** @@ -50,9 +48,7 @@ public ProxyConfigurationParser(Consumer>> inter } @Override - public void parseAndRegister(Reader reader) throws IOException { - JSONParser parser = new JSONParser(reader); - Object json = parser.parse(); + public void parseAndRegister(Object json, URI origin) { parseTopLevelArray(asList(json, "first level of document must be an array of interface lists")); } 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 a7f1f9d09075..404872679a3d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -24,8 +24,7 @@ */ package com.oracle.svm.core.configure; -import java.io.IOException; -import java.io.Reader; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -36,7 +35,6 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.TypeResult; -import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; /** @@ -62,9 +60,7 @@ public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate de } @Override - public void parseAndRegister(Reader reader) throws IOException { - JSONParser parser = new JSONParser(reader); - Object json = parser.parse(); + public void parseAndRegister(Object json, URI origin) { parseClassArray(asList(json, "first level of document must be an array of class descriptors")); } 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 929fe5b38edc..d4a72889f1c3 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,20 +24,18 @@ */ package com.oracle.svm.core.configure; -import java.io.IOException; -import java.io.Reader; -import java.util.Collections; +import java.net.URI; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; import java.util.function.BiConsumer; +import java.util.stream.Collectors; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.core.jdk.localization.LocalizationSupport; -import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; public class ResourceConfigurationParser extends ConfigurationParser { @@ -49,9 +47,7 @@ public ResourceConfigurationParser(ResourcesRegistry registry, boolean strictCon } @Override - public void parseAndRegister(Reader reader) throws IOException { - JSONParser parser = new JSONParser(reader); - Object json = parser.parse(); + public void parseAndRegister(Object json, URI origin) { parseTopLevelObject(asMap(json, "first level of document must be an object")); } 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 2024bcace8d3..a933c2b41eaf 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,18 +25,16 @@ */ package com.oracle.svm.core.configure; -import java.io.IOException; -import java.io.Reader; +import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import com.oracle.svm.core.util.json.JSONParserException; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; -import com.oracle.svm.core.util.json.JSONParser; +import com.oracle.svm.core.util.json.JSONParserException; public class SerializationConfigurationParser extends ConfigurationParser { @@ -53,9 +51,7 @@ public SerializationConfigurationParser(RuntimeSerializationSupport serializatio } @Override - public void parseAndRegister(Reader reader) throws IOException { - JSONParser parser = new JSONParser(reader); - Object json = parser.parse(); + 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 Map) { diff --git a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java index 60f96b640a1f..e51e449259f3 100644 --- a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java +++ b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java @@ -24,8 +24,7 @@ */ package com.oracle.svm.truffle.tck; -import java.io.IOException; -import java.io.Reader; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -43,7 +42,6 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.configure.ConfigurationParser; -import com.oracle.svm.core.util.json.JSONParser; import com.oracle.svm.core.util.json.JSONParserException; import com.oracle.svm.hosted.ImageClassLoader; @@ -74,12 +72,10 @@ Set getLoadedWhiteList() { } @Override - public void parseAndRegister(Reader reader) throws IOException { + public void parseAndRegister(Object json, URI origin) { if (whiteList == null) { whiteList = new HashSet<>(); } - JSONParser parser = new JSONParser(reader); - Object json = parser.parse(); parseClassArray(castList(json, "First level of document must be an array of class descriptors")); }