diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 9240a530d81a..b75bfcd2b7ec 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -121,6 +121,13 @@ def is_musl_supported(): return False +def build_native_image_agent(native_image): + agentfile = mx_subst.path_substitutions.substitute('') + agentname = agentfile.rsplit('.', 1)[0] # remove platform-specific file extension + native_image(['--macro:native-image-agent-library', '-H:Name=' + agentname, '-H:Path=' + svmbuild_dir()]) + return svmbuild_dir() + '/' + agentfile + + class GraalVMConfig(collections.namedtuple('GraalVMConfig', 'primary_suite_dir, dynamicimports, disable_libpolyglot, force_bash_launchers, skip_libraries, exclude_components, native_images')): @classmethod def build(cls, primary_suite_dir=None, dynamicimports=None, disable_libpolyglot=True, force_bash_launchers=True, skip_libraries=True, @@ -218,7 +225,8 @@ def __getattr__(self, name): 'benchmarktest', "nativeimagehelp", 'muslcbuild', - 'hellomodule' + 'hellomodule', + 'condconfig', ]) def vm_native_image_path(config=None): @@ -380,6 +388,11 @@ def svm_gate_body(args, tasks): with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image: native_unittests_task() + with Task('conditional configuration tests', tasks, tags=[GraalTags.condconfig]) as t: + if t: + with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image: + conditional_config_task(native_image) + with Task('native unittests quickbuild', tasks, tags=[GraalTags.test_quickbuild]) as t: if t: with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image: @@ -480,6 +493,32 @@ def native_unittests_task(extra_build_args=None): native_unittest(['--builder-on-modulepath', '--build-args', _native_unittest_features] + additional_build_args) +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( +''' +{ + "rules": [ + {"includeClasses": "com.oracle.svm.configure.test.conditionalconfig.**"} + ] +} +''' + ) + agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-config-filter-file=' + conditional_config_filter_path] + jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts), + '-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator.enabled=true', + 'com.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator']) + + 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']) + + def javac_image_command(javac_path): return [join(javac_path, 'javac'), '-proc:none'] + ( # We need to set java.home as com.sun.tools.javac.file.Locations. can't handle `null`. @@ -572,6 +611,10 @@ def _native_unittest(native_image, cmdline_args): _native_junit(native_image, unittest_args, unmask(pargs.build_args), unmask(pargs.run_args), blacklist, whitelist, pargs.preserve_image, builder_on_modulepath) +def jvm_unittest(args): + return mx_unittest.unittest(['--suite', 'substratevm'] + args) + + def js_image_test(jslib, bench_location, name, warmup_iterations, iterations, timeout=None, bin_args=None): bin_args = bin_args if bin_args is not None else [] jsruncmd = [get_js_launcher(jslib)] + bin_args + [join(bench_location, 'harness.js'), '--', join(bench_location, name + '.js'), 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 7063e1b52b74..2141bc9df29a 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 @@ -54,9 +54,10 @@ import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.svm.agent.conditionalconfig.ConditionalConfigurationWriter; +import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsWriter; +import com.oracle.svm.agent.configwithorigins.MethodInfoRecordKeeper; import com.oracle.svm.agent.ignoredconfig.AgentMetaInfProcessor; -import com.oracle.svm.agent.predicatedconfig.ConfigurationWithOriginsResultWriter; -import com.oracle.svm.agent.predicatedconfig.MethodInfoRecordKeeper; import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess; import com.oracle.svm.agent.stackaccess.InterceptedState; import com.oracle.svm.agent.stackaccess.OnDemandJavaStackAccess; @@ -64,9 +65,13 @@ 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.ConfigurationFileCollection; import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.filters.ComplexFilter; +import com.oracle.svm.configure.filters.ConfigurationFilter; import com.oracle.svm.configure.filters.FilterConfigurationParser; -import com.oracle.svm.configure.filters.RuleNode; +import com.oracle.svm.configure.filters.HierarchyFilterNode; import com.oracle.svm.configure.trace.AccessAdvisor; import com.oracle.svm.configure.trace.TraceProcessor; import com.oracle.svm.core.SubstrateUtil; @@ -114,8 +119,8 @@ protected JNIHandleSet constructJavaHandles(JNIEnvironment env) { protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, String options) { String traceOutputFile = null; String configOutputDir = null; - ConfigurationSet mergeConfigs = new ConfigurationSet(); - ConfigurationSet omittedConfigs = new ConfigurationSet(); + ConfigurationFileCollection mergeConfigs = new ConfigurationFileCollection(); + ConfigurationFileCollection omittedConfigs = new ConfigurationFileCollection(); boolean builtinCallerFilter = true; boolean builtinHeuristicFilter = true; List callerFilterFiles = new ArrayList<>(); @@ -125,6 +130,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean experimentalOmitClasspathConfig = false; boolean build = false; boolean configurationWithOrigins = false; + List conditionalConfigUserPackageFilterFiles = new ArrayList<>(); + List conditionalConfigClassNameFilterFiles = new ArrayList<>(); int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds boolean trackReflectionMetadata = true; @@ -196,6 +203,10 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c build = Boolean.parseBoolean(getTokenValue(token)); } else if (token.equals("experimental-configuration-with-origins")) { configurationWithOrigins = true; + } 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=")) { @@ -210,6 +221,10 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c inform("no output/build options provided, tracking dynamic accesses and writing configuration to directory: " + configOutputDir); } + if (configurationWithOrigins && !conditionalConfigUserPackageFilterFiles.isEmpty()) { + return error(5, "The agent can only be used in either the configuration with origins mode or the predefined classes mode."); + } + if (configurationWithOrigins && !mergeConfigs.isEmpty()) { configurationWithOrigins = false; inform("using configuration with origins with configuration merging is currently unsupported. Disabling configuration with origins mode."); @@ -219,30 +234,34 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c warn("using experimental configuration with origins mode. Note that native-image cannot process these files, and this flag may change or be removed without a warning!"); } - RuleNode callerFilter = null; + ComplexFilter callerFilter = null; + HierarchyFilterNode callerFilterHierarchyFilterNode = null; if (!builtinCallerFilter) { - callerFilter = RuleNode.createRoot(); - callerFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); + callerFilterHierarchyFilterNode = HierarchyFilterNode.createInclusiveRoot(); + callerFilter = new ComplexFilter(callerFilterHierarchyFilterNode); } + if (!callerFilterFiles.isEmpty()) { - if (callerFilter == null) { - callerFilter = AccessAdvisor.copyBuiltinCallerFilterTree(); + if (callerFilterHierarchyFilterNode == null) { + callerFilterHierarchyFilterNode = AccessAdvisor.copyBuiltinCallerFilterTree(); + callerFilter = new ComplexFilter(callerFilterHierarchyFilterNode); } if (!parseFilterFiles(callerFilter, callerFilterFiles)) { return 1; } } - RuleNode accessFilter = null; + ComplexFilter accessFilter = null; if (!accessFilterFiles.isEmpty()) { - accessFilter = AccessAdvisor.copyBuiltinAccessFilterTree(); + accessFilter = new ComplexFilter(AccessAdvisor.copyBuiltinAccessFilterTree()); if (!parseFilterFiles(accessFilter, accessFilterFiles)) { return 1; } } - final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(configurationWithOrigins); - final Supplier interceptedStateSupplier = configurationWithOrigins ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() + boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigUserPackageFilterFiles.isEmpty(); + final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(shouldTraceOriginInformation); + final Supplier interceptedStateSupplier = shouldTraceOriginInformation ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() : OnDemandJavaStackAccess.stackAccessSupplier(); if (configOutputDir != null) { @@ -273,21 +292,39 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c ignoreConfigFromClasspath(jvmti, omittedConfigs); } AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter); - TraceProcessor omittedConfigProcessor = null; + TraceProcessor processor = new TraceProcessor(advisor); + ConfigurationSet omittedConfiguration = new ConfigurationSet(); Predicate shouldExcludeClassesWithHash = null; if (!omittedConfigs.isEmpty()) { Function ignore = e -> { warn("Failed to load omitted config: " + e); return null; }; - omittedConfigProcessor = new TraceProcessor(advisor, omittedConfigs.loadJniConfig(ignore), omittedConfigs.loadReflectConfig(ignore), - omittedConfigs.loadProxyConfig(ignore), omittedConfigs.loadResourceConfig(ignore), omittedConfigs.loadSerializationConfig(ignore), - omittedConfigs.loadPredefinedClassesConfig(null, null, ignore), null); - shouldExcludeClassesWithHash = omittedConfigProcessor.getPredefinedClassesConfiguration()::containsClassWithHash; + omittedConfiguration = omittedConfigs.loadConfigurationSet(ignore, null, null); + shouldExcludeClassesWithHash = omittedConfiguration.getPredefinedClassesConfiguration()::containsClassWithHash; } if (configurationWithOrigins) { - ConfigurationWithOriginsResultWriter writer = new ConfigurationWithOriginsResultWriter(advisor, recordKeeper); + 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; + } + } else { + classNameFilter = new ComplexFilter(HierarchyFilterNode.createInclusiveRoot()); + } + + ConditionalConfigurationPredicate predicate = new ConditionalConfigurationPredicate(classNameFilter); + ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(processor, recordKeeper, userCodeFilter, predicate); tracer = writer; tracingResultWriter = writer; } else { @@ -302,10 +339,9 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } return e; // rethrow }; - TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler), - mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler), - mergeConfigs.loadPredefinedClassesConfig(predefinedClassDestDirs, shouldExcludeClassesWithHash, handler), omittedConfigProcessor); - ConfigurationResultWriter writer = new ConfigurationResultWriter(processor); + + ConfigurationSet configuration = mergeConfigs.loadConfigurationSet(handler, predefinedClassDestDirs, shouldExcludeClassesWithHash); + ConfigurationResultWriter writer = new ConfigurationResultWriter(processor, configuration, omittedConfiguration); tracer = writer; tracingResultWriter = writer; } @@ -368,7 +404,7 @@ private static T usage(T result, String message) { return result; } - private static AccessAdvisor createAccessAdvisor(boolean builtinHeuristicFilter, RuleNode callerFilter, RuleNode accessFilter) { + private static AccessAdvisor createAccessAdvisor(boolean builtinHeuristicFilter, ConfigurationFilter callerFilter, ConfigurationFilter accessFilter) { AccessAdvisor advisor = new AccessAdvisor(); advisor.setHeuristicsEnabled(builtinHeuristicFilter); if (callerFilter != null) { @@ -388,7 +424,7 @@ private static int parseIntegerOrNegative(String number) { } } - private static boolean parseFilterFiles(RuleNode filter, List filterFiles) { + private static boolean parseFilterFiles(ComplexFilter filter, List filterFiles) { for (String path : filterFiles) { try { new FilterConfigurationParser(filter).parseAndRegister(Paths.get(path).toUri()); @@ -396,7 +432,7 @@ private static boolean parseFilterFiles(RuleNode filter, List filterFile return error(false, "cannot parse filter file " + path + ": " + e); } } - filter.removeRedundantNodes(); + filter.getHierarchyFilterNode().removeRedundantNodes(); return true; } @@ -422,7 +458,7 @@ private void setupExecutorServiceForPeriodicConfigurationCapture(int writePeriod initialDelay, writePeriod, TimeUnit.SECONDS); } - private static void ignoreConfigFromClasspath(JvmtiEnv jvmti, ConfigurationSet ignoredConfigSet) { + private static void ignoreConfigFromClasspath(JvmtiEnv jvmti, ConfigurationFileCollection ignoredConfigCollection) { String classpath = Support.getSystemProperty(jvmti, "java.class.path"); String sep = Support.getSystemProperty(jvmti, "path.separator"); if (sep == null) { @@ -436,7 +472,7 @@ private static void ignoreConfigFromClasspath(JvmtiEnv jvmti, ConfigurationSet i } } - AgentMetaInfProcessor processor = new AgentMetaInfProcessor(ignoredConfigSet); + AgentMetaInfProcessor processor = new AgentMetaInfProcessor(ignoredConfigCollection); for (String cpEntry : classpath.split(sep)) { try { NativeImageMetaInfWalker.walkMetaInfForCPEntry(Paths.get(cpEntry), processor); 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 new file mode 100644 index 000000000000..5fc1e969d5ed --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2021, 2021, 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.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.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; + +/** + * Outputs configuration augmented with reachability conditions. + * + * This writer leverages the configuration origin information to deduce conditions for the + * configuration. See {@link #createConditionalConfiguration()} + */ +public class ConditionalConfigurationWriter extends ConfigurationWithOriginsTracer implements TracingResultWriter { + 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); + 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; + } + + @Override + public boolean supportsPeriodicTraceWriting() { + return false; + } + + @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()))); + return writtenFiles; + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ClassInfo.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java similarity index 90% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ClassInfo.java rename to substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java index 8291780f3753..a527a86e183c 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ClassInfo.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java @@ -22,21 +22,23 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.agent.predicatedconfig; +package com.oracle.svm.agent.configwithorigins; -import com.oracle.svm.jni.nativeapi.JNIMethodId; -import com.oracle.svm.jvmtiagentbase.Support; -import jdk.vm.ci.meta.MetaUtil; -import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.c.type.CCharPointerPointer; -import org.graalvm.word.WordFactory; +import static com.oracle.svm.jvmtiagentbase.Support.checkPhase; +import static org.graalvm.word.WordFactory.nullPointer; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import static com.oracle.svm.jvmtiagentbase.Support.check; -import static org.graalvm.word.WordFactory.nullPointer; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.type.CCharPointerPointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.jni.nativeapi.JNIMethodId; +import com.oracle.svm.jvmtiagentbase.Support; + +import jdk.vm.ci.meta.MetaUtil; class ClassInfo { @@ -48,13 +50,13 @@ class ClassInfo { this.nameAndSignatureToMethodInfoMap = new ConcurrentHashMap<>(); } - MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) { + MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) throws Support.WrongPhaseException { JNIMethodId jMethodId = WordFactory.pointer(rawJMethodIdValue); CCharPointerPointer methodNamePtr = StackValue.get(CCharPointerPointer.class); CCharPointerPointer methodSignaturePtr = StackValue.get(CCharPointerPointer.class); - check(Support.jvmtiFunctions().GetMethodName().invoke(Support.jvmtiEnv(), jMethodId, methodNamePtr, methodSignaturePtr, nullPointer())); + checkPhase(Support.jvmtiFunctions().GetMethodName().invoke(Support.jvmtiEnv(), jMethodId, methodNamePtr, methodSignaturePtr, nullPointer())); String methodName = MethodInfoRecordKeeper.getJavaStringAndFreeNativeString(methodNamePtr.read()); String methodSignature = MethodInfoRecordKeeper.getJavaStringAndFreeNativeString(methodSignaturePtr.read()); String methodNameAndSignature = combineMethodNameAndSignature(methodName, methodSignature); 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 new file mode 100644 index 000000000000..0b1bdd39e2c9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOrigins.java @@ -0,0 +1,91 @@ +/* + * 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 new file mode 100644 index 000000000000..68d81512e975 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsTracer.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021, 2021, 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.util.Map; + +import com.oracle.svm.agent.tracing.ConfigurationResultWriter; +import com.oracle.svm.agent.tracing.core.Tracer; +import com.oracle.svm.configure.trace.TraceProcessor; +import com.oracle.svm.jni.nativeapi.JNIMethodId; + +/** + * Processes trace events that carry origin information. + * + * All trace entries (events) passed to this tracer are accompanied by a full call stack. Using that + * information we construct a call tree where each call node maintains its own configuration set + * 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 { + + protected final TraceProcessor processor; + protected final MethodCallNode rootNode; + protected final MethodInfoRecordKeeper methodInfoRecordKeeper; + + public ConfigurationWithOriginsTracer(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper) { + this.processor = processor; + this.rootNode = MethodCallNode.createRoot(); + this.methodInfoRecordKeeper = methodInfoRecordKeeper; + } + + @Override + public void traceEntry(Map entry) { + String tracer = (String) entry.get("tracer"); + if (tracer.equals("meta")) { + processor.processEntry(entry, null); + } else { + assert entry.containsKey("stack_trace"); + JNIMethodId[] rawStackTrace = (JNIMethodId[]) entry.remove("stack_trace"); + MethodInfo[] stackTrace = methodInfoRecordKeeper.getStackTraceInfo(rawStackTrace); + Map transformedEntry = ConfigurationResultWriter.arraysToLists(entry); + + if (stackTrace == null) { + rootNode.traceEntry(processor, transformedEntry); + } else { + stackTrace = filterStackTrace(stackTrace); + if (stackTrace != null) { + dispatchTraceEntry(stackTrace, entry); + } + } + } + } + + public 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.traceEntry(processor, entry); + } + + protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { + return stackTrace; + } +} 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 new file mode 100644 index 000000000000..510c137cd992 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021, 2021, 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.nio.file.Path; +import java.util.List; + +import com.oracle.svm.agent.tracing.core.TracingResultWriter; +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.trace.TraceProcessor; + +public class ConfigurationWithOriginsWriter extends ConfigurationWithOriginsTracer implements TracingResultWriter { + + public static final String CONFIG_WITH_ORIGINS_SUFFIX = "-origins.txt"; + + public ConfigurationWithOriginsWriter(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper) { + super(processor, methodInfoRecordKeeper); + } + + @Override + public boolean supportsPeriodicTraceWriting() { + return false; + } + + @Override + public boolean supportsOnUnloadTraceWriting() { + return true; + } + + @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)); + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java new file mode 100644 index 000000000000..a0dc0219f65d --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java @@ -0,0 +1,98 @@ +/* + * 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.io.StringWriter; +import java.util.List; +import java.util.Set; + +import com.oracle.svm.configure.json.JsonPrintable; +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.ConfigurationFile; + +public class HumanReadableConfigurationWithOrigins implements JsonPrintable { + + static final String CONNECTING_INDENT = "\u2502 "; // "| " + static final String EMPTY_INDENT = " "; + static final String CHILD = "\u251c\u2500\u2500 "; // "|-- " + static final String LAST_CHILD = "\u2514\u2500\u2500 "; // "`-- " + + private final MethodCallNode root; + private final ConfigurationFile configFile; + private final Set nodesWithNonEmptyConfig; + + public HumanReadableConfigurationWithOrigins(MethodCallNode root, ConfigurationFile configFile) { + this.root = root; + this.configFile = configFile; + this.nodesWithNonEmptyConfig = root.getNodesWithNonEmptyConfig(configFile); + } + + private MethodCallNode findLastChildWithNonEmptyConfig(List nodes) { + MethodCallNode child = null; + for (MethodCallNode node : nodes) { + if (nodesWithNonEmptyConfig.contains(node)) { + child = node; + } + } + return child; + } + + private void printNode(JsonWriter writer, String prefix, MethodCallNode node) throws IOException { + writer.append(prefix).append(node.methodInfo.toString()); + if (node.hasConfig(configFile)) { + writer.append(" - "); + StringWriter sw = new StringWriter(); + node.configuration.getConfiguration(configFile).printJson(new JsonWriter(sw)); + writer.append(sw.toString().replace("\n", " ")); + } + writer.newline(); + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.append("root").newline(); + + List nodes = List.copyOf(root.calledMethods.values()); + MethodCallNode lastChild = findLastChildWithNonEmptyConfig(nodes); + for (MethodCallNode node : nodes) { + if (nodesWithNonEmptyConfig.contains(node)) { + printNode(writer, node == lastChild ? LAST_CHILD : CHILD, node); + printChildNodes(writer, node == lastChild ? EMPTY_INDENT : CONNECTING_INDENT, node); + } + } + } + + private void printChildNodes(JsonWriter writer, String prefix, MethodCallNode node) throws IOException { + List nodes = List.copyOf(node.calledMethods.values()); + MethodCallNode lastChild = findLastChildWithNonEmptyConfig(nodes); + for (MethodCallNode child : node.calledMethods.values()) { + if (nodesWithNonEmptyConfig.contains(child)) { + printNode(writer, prefix + (child == lastChild ? LAST_CHILD : CHILD), child); + printChildNodes(writer, prefix + (child == lastChild ? EMPTY_INDENT : CONNECTING_INDENT), child); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodCallNode.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodCallNode.java new file mode 100644 index 000000000000..23b46ade80f9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodCallNode.java @@ -0,0 +1,114 @@ +/* + * 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.configwithorigins; + +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.trace.TraceProcessor; +import com.oracle.svm.core.configure.ConfigurationFile; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +public final class MethodCallNode { + + public final MethodInfo methodInfo; + public final MethodCallNode parent; + public final Map calledMethods; + public volatile ConfigurationSet configuration; + + MethodCallNode(MethodInfo methodInfo, MethodCallNode parent) { + this.methodInfo = methodInfo; + this.parent = parent; + this.calledMethods = new ConcurrentHashMap<>(); + this.configuration = null; + } + + static MethodCallNode createRoot() { + return new MethodCallNode(null, null); + } + + public Set getNodesWithNonEmptyConfig(ConfigurationFile configFile) { + Set nodesWithNonEmptyConfig = new HashSet<>(); + /* + * Recursively construct a set of nodes with non empty config. These nodes will eventually + * be printed. A node should be printed if it or any of it's children have configuration. + */ + visitPostOrder(node -> { + /* Nodes with configuration are always included */ + if (node.hasConfig(configFile)) { + nodesWithNonEmptyConfig.add(node); + } + /* If a node is already included, also include its parent */ + if (nodesWithNonEmptyConfig.contains(node) && node.parent != null) { + nodesWithNonEmptyConfig.add(node.parent); + } + }); + + return nodesWithNonEmptyConfig; + } + + public boolean hasConfig(ConfigurationFile configFile) { + return configuration != null && !configuration.getConfiguration(configFile).isEmpty(); + } + + void traceEntry(TraceProcessor processor, Map entry) { + if (configuration == null) { + synchronized (this) { + if (configuration == null) { + configuration = new ConfigurationSet(); + } + } + } + processor.processEntry(entry, configuration); + } + + public void visitPostOrder(Consumer methodCallNodeConsumer) { + for (MethodCallNode node : calledMethods.values()) { + node.visitPostOrder(methodCallNodeConsumer); + } + methodCallNodeConsumer.accept(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MethodCallNode that = (MethodCallNode) o; + return methodInfo.equals(that.methodInfo) && Objects.equals(parent, that.parent) && calledMethods.equals(that.calledMethods); + } + + @Override + public int hashCode() { + return Objects.hash(methodInfo, parent); + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/MethodInfo.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java similarity index 92% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/MethodInfo.java rename to substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java index afbeeead0bff..1806988cf35c 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/MethodInfo.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/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.predicatedconfig; +package com.oracle.svm.agent.configwithorigins; import java.util.Objects; @@ -43,7 +43,7 @@ public class MethodInfo { public String getJavaMethodNameAndSignature() { String[] parameterTypes = SignatureUtil.toParameterTypes(signature); StringBuilder sb = new StringBuilder(name); - sb.append(" ("); + sb.append("("); boolean first = false; for (String parameterType : parameterTypes) { if (!first) { @@ -77,4 +77,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(name, signature, declaringClass); } + + @Override + public String toString() { + return getJavaDeclaringClassName() + "#" + getJavaMethodNameAndSignature(); + } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/MethodInfoRecordKeeper.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfoRecordKeeper.java similarity index 84% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/MethodInfoRecordKeeper.java rename to substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfoRecordKeeper.java index 8cab3459fa69..84d3bdfc66c4 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/MethodInfoRecordKeeper.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfoRecordKeeper.java @@ -22,9 +22,9 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.agent.predicatedconfig; +package com.oracle.svm.agent.configwithorigins; -import static com.oracle.svm.jvmtiagentbase.Support.check; +import static com.oracle.svm.jvmtiagentbase.Support.checkPhase; import static org.graalvm.word.WordFactory.nullPointer; import java.util.Map; @@ -57,11 +57,18 @@ public MethodInfoRecordKeeper(boolean shouldTrackMethodInfo) { } public MethodInfo[] getStackTraceInfo(JNIMethodId[] stackTrace) { - MethodInfo[] methodInfoTrace = new MethodInfo[stackTrace.length]; - for (int i = 0; i < stackTrace.length; ++i) { - methodInfoTrace[i] = getMethodInfo(stackTrace[i].rawValue()); + if (stackTrace == null) { + return null; + } + try { + MethodInfo[] methodInfoTrace = new MethodInfo[stackTrace.length]; + for (int i = 0; i < stackTrace.length; ++i) { + methodInfoTrace[i] = getMethodInfo(stackTrace[i].rawValue()); + } + return methodInfoTrace; + } catch (Support.WrongPhaseException e) { + return null; } - return methodInfoTrace; } /** @@ -70,7 +77,7 @@ public MethodInfo[] getStackTraceInfo(JNIMethodId[] stackTrace) { * @param rawJMethodIdValue Raw jMethodId value. * @return MethodInfo object that uniquely describes the given method. */ - private MethodInfo getMethodInfo(long rawJMethodIdValue) { + private MethodInfo getMethodInfo(long rawJMethodIdValue) throws Support.WrongPhaseException { assert shouldTrackMethodInfo; if (jMethodIdToMethodInfoMap.containsKey(rawJMethodIdValue)) { return jMethodIdToMethodInfoMap.get(rawJMethodIdValue); @@ -88,7 +95,7 @@ private MethodInfo getMethodInfo(long rawJMethodIdValue) { * @param rawJMethodIdValue Raw jMethodId value. * @return MethodInfo object that uniquely describes the given method. */ - private MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) { + private MethodInfo findOrCreateMethodInfo(long rawJMethodIdValue) throws Support.WrongPhaseException { String declaringClassSignature = getMethodDeclaringClassSignature(rawJMethodIdValue); ClassInfo classInfo = findOrCreateClassInfo(declaringClassSignature); MethodInfo methodInfo = classInfo.findOrCreateMethodInfo(rawJMethodIdValue); @@ -102,12 +109,12 @@ static String getJavaStringAndFreeNativeString(CCharPointer nativeString) { return javaString; } - private static String getMethodDeclaringClassSignature(long rawJMethodIdValue) { + private static String getMethodDeclaringClassSignature(long rawJMethodIdValue) throws Support.WrongPhaseException { JNIMethodId jMethodId = WordFactory.pointer(rawJMethodIdValue); JNIObjectHandle declaringClass = Support.getMethodDeclaringClass(jMethodId); CCharPointerPointer signaturePointer = StackValue.get(CCharPointerPointer.class); - check(Support.jvmtiFunctions().GetClassSignature().invoke(Support.jvmtiEnv(), declaringClass, signaturePointer, nullPointer())); + checkPhase(Support.jvmtiFunctions().GetClassSignature().invoke(Support.jvmtiEnv(), declaringClass, signaturePointer, nullPointer())); return getJavaStringAndFreeNativeString(signaturePointer.read()); } @@ -115,5 +122,4 @@ private ClassInfo findOrCreateClassInfo(String classSignature) { classSignatureToClassInfoMap.computeIfAbsent(classSignature, ClassInfo::new); return classSignatureToClassInfoMap.get(classSignature); } - } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ignoredconfig/AgentMetaInfProcessor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ignoredconfig/AgentMetaInfProcessor.java index e2d288865fd8..a933ce11a556 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ignoredconfig/AgentMetaInfProcessor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ignoredconfig/AgentMetaInfProcessor.java @@ -26,21 +26,21 @@ import java.nio.file.Path; -import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.driver.metainf.MetaInfFileType; import com.oracle.svm.driver.metainf.NativeImageMetaInfResourceProcessor; public class AgentMetaInfProcessor implements NativeImageMetaInfResourceProcessor { - private ConfigurationSet ignoredConfigSet; + private final ConfigurationFileCollection ignoredConfigFileCollection; - public AgentMetaInfProcessor(ConfigurationSet ignoredConfigSet) { - this.ignoredConfigSet = ignoredConfigSet; + public AgentMetaInfProcessor(ConfigurationFileCollection ignoredConfigFileCollection) { + this.ignoredConfigFileCollection = ignoredConfigFileCollection; } @Override public void processMetaInfResource(Path classpathEntry, Path resourceRoot, Path resourcePath, MetaInfFileType type) throws Exception { - ignoredConfigSet.addDirectory(resourcePath.getParent()); + ignoredConfigFileCollection.addDirectory(resourcePath.getParent()); } @Override diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConfigurationWithOriginsResultWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConfigurationWithOriginsResultWriter.java deleted file mode 100644 index be8807dd39d4..000000000000 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConfigurationWithOriginsResultWriter.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (c) 2021, 2021, 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.predicatedconfig; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import com.oracle.svm.agent.tracing.ConfigurationResultWriter; -import com.oracle.svm.agent.tracing.core.Tracer; -import com.oracle.svm.agent.tracing.core.TracingResultWriter; -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.json.JsonWriter; -import com.oracle.svm.configure.trace.AccessAdvisor; -import com.oracle.svm.configure.trace.TraceProcessor; -import com.oracle.svm.core.configure.ConfigurationFile; -import com.oracle.svm.jni.nativeapi.JNIMethodId; - -/** - * Processes trace events that carry origin information. - * - * All trace entries (events) passed to this tracer are accompanied by a full call stack. Using that - * information we construct a call tree where each call node maintains its own configuration set - * 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 class ConfigurationWithOriginsResultWriter extends Tracer implements TracingResultWriter { - - private final AccessAdvisor advisor; - private final MethodCallNode rootNode; - private final MethodInfoRecordKeeper methodInfoRecordKeeper; - - public ConfigurationWithOriginsResultWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper) { - this.advisor = advisor; - this.rootNode = MethodCallNode.createRoot(); - this.methodInfoRecordKeeper = methodInfoRecordKeeper; - } - - @Override - public void traceEntry(Map entry) { - String tracer = (String) entry.get("tracer"); - if (tracer.equals("meta")) { - String event = (String) entry.get("event"); - if (event.equals("phase_change")) { - advisor.setInLivePhase(entry.get("phase").equals("live")); - } - } else { - assert entry.containsKey("stack_trace"); - JNIMethodId[] rawStackTrace = (JNIMethodId[]) entry.remove("stack_trace"); - Map transformedEntry = ConfigurationResultWriter.arraysToLists(entry); - - if (rawStackTrace == null) { - rootNode.traceEntry(this::createNewTraceProcessor, transformedEntry); - } else { - MethodInfo[] stackTrace = methodInfoRecordKeeper.getStackTraceInfo(rawStackTrace); - rootNode.dispatchTraceEntry(stackTrace, stackTrace.length - 1, transformedEntry, this::createNewTraceProcessor); - } - } - } - - private TraceProcessor createNewTraceProcessor() { - TypeConfiguration jniConfig = new TypeConfiguration(); - TypeConfiguration reflectConfig = new TypeConfiguration(); - ProxyConfiguration proxyConfig = new ProxyConfiguration(); - ResourceConfiguration resourceConfig = new ResourceConfiguration(); - SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); - PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(new Path[0], null); - return new TraceProcessor(advisor, jniConfig, reflectConfig, proxyConfig, resourceConfig, serializationConfiguration, predefinedClassesConfiguration, null); - } - - private static final class MethodCallNode { - - private final MethodInfo methodInfo; - private final MethodCallNode parent; - private final Map calledMethods; - private TraceProcessor processor; - - private MethodCallNode(MethodInfo methodInfo, MethodCallNode parent) { - this.methodInfo = methodInfo; - this.parent = parent; - this.calledMethods = new ConcurrentHashMap<>(); - this.processor = null; - } - - public static MethodCallNode createRoot() { - return new MethodCallNode(null, null); - } - - public boolean isRoot() { - return parent == null; - } - - public boolean hasConfig(ConfigurationFile configFile) { - return processor != null && !processor.getConfiguration(configFile).isEmpty(); - } - - public void dispatchTraceEntry(MethodInfo[] stackTrace, int stackTraceEntry, Map entry, Supplier traceProcessorSupplier) { - if (stackTraceEntry == -1) { - traceEntry(traceProcessorSupplier, entry); - } else { - MethodInfo next = stackTrace[stackTraceEntry]; - calledMethods.computeIfAbsent(next, nextNodeInfo -> new MethodCallNode(nextNodeInfo, this)); - MethodCallNode nextCall = calledMethods.get(next); - nextCall.dispatchTraceEntry(stackTrace, stackTraceEntry - 1, entry, traceProcessorSupplier); - } - } - - private void traceEntry(Supplier traceProcessorSupplier, Map entry) { - if (processor == null) { - synchronized (this) { - if (processor == null) { - processor = traceProcessorSupplier.get(); - } - } - } - processor.processEntry(entry); - } - - public void writeJson(JsonWriter writer, ConfigurationFile configFile) throws IOException { - Set includedNodes = new HashSet<>(); - - /* - * Recursively construct a set of included nodes. Included nodes are the ones that will - * eventually be printed. A node is considered included if it or its children have - * configuration. - */ - visitPostOrder(node -> { - /* Nodes with configuration are always included */ - if (node.hasConfig(configFile)) { - includedNodes.add(node); - } - /* If a node is already included, also include its parent */ - if (includedNodes.contains(node) && node.parent != null) { - includedNodes.add(node.parent); - } - }); - - writeJson(writer, configFile, includedNodes); - } - - private void writeJson(JsonWriter writer, ConfigurationFile configFile, Set includedNodes) throws IOException { - if (isRoot()) { - writer.append("[").newline() - .append("{").indent().newline() - .quote("configuration-with-origins").append(": ["); - printChildMethodJson(writer, configFile, includedNodes); - writer.newline() - .append("]").unindent().newline(); - if (hasConfig(configFile)) { - writer.quote("configuration-without-origins").append(": "); - writeConfigJson(writer, configFile); - writer.unindent().newline(); - } - writer.append("}").newline() - .append("]").newline(); - } else { - writer.append("{").indent().newline(); - - writer.quote("method").append(": ").quote(methodInfo.getJavaDeclaringClassName() + "#" + methodInfo.getJavaMethodNameAndSignature()).append(",").newline(); - - if (anyChildrenIncluded(includedNodes)) { - writer.quote("methods").append(": ["); - printChildMethodJson(writer, configFile, includedNodes); - writer.newline().append("]"); - if (hasConfig(configFile)) { - writer.append(",").newline(); - } - } - - if (hasConfig(configFile)) { - writeConfigJson(writer, configFile); - } - - writer.unindent().newline(); - writer.append("}"); - } - } - - private void printChildMethodJson(JsonWriter writer, ConfigurationFile configFile, Set includedNodes) throws IOException { - boolean first = true; - for (MethodCallNode methodCallNode : calledMethods.values()) { - if (!includedNodes.contains(methodCallNode)) { - continue; - } - if (first) { - first = false; - } else { - writer.append(","); - } - writer.newline(); - methodCallNode.writeJson(writer, configFile, includedNodes); - } - } - - private boolean anyChildrenIncluded(Set includedNodes) { - return calledMethods.values().stream().anyMatch(includedNodes::contains); - } - - private void writeConfigJson(JsonWriter writer, ConfigurationFile configFile) throws IOException { - writer.quote("config").append(": "); - processor.getConfiguration(configFile).printJson(writer); - } - - private void visitPostOrder(Consumer methodCallNodeConsumer) { - for (MethodCallNode node : calledMethods.values()) { - node.visitPostOrder(methodCallNodeConsumer); - } - methodCallNodeConsumer.accept(this); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MethodCallNode that = (MethodCallNode) o; - return methodInfo.equals(that.methodInfo) && Objects.equals(parent, that.parent) && calledMethods.equals(that.calledMethods) && Objects.equals(processor, that.processor); - } - - @Override - public int hashCode() { - return Objects.hash(methodInfo, parent, processor); - } - } - - @Override - public boolean supportsPeriodicTraceWriting() { - return false; - } - - @Override - public boolean supportsOnUnloadTraceWriting() { - return true; - } - - public static final String CONFIG_WITH_ORIGINS_FILE_SUFFIX = "-origins.json"; - - @Override - public List writeToDirectory(Path directoryPath) throws IOException { - List writtenPaths = new ArrayList<>(); - for (ConfigurationFile configFile : ConfigurationFile.values()) { - if (configFile.canBeGeneratedByAgent()) { - Path filePath = directoryPath.resolve(configFile.getFileName(CONFIG_WITH_ORIGINS_FILE_SUFFIX)); - try (JsonWriter writer = new JsonWriter(filePath)) { - rootNode.writeJson(writer, configFile); - } - writtenPaths.add(filePath); - } - } - return writtenPaths; - } -} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/ConfigurationResultWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/ConfigurationResultWriter.java index 5c654b3b6fd2..331b10c60dd1 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/ConfigurationResultWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/ConfigurationResultWriter.java @@ -26,27 +26,29 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import com.oracle.svm.agent.tracing.core.Tracer; import com.oracle.svm.agent.tracing.core.TracingResultWriter; -import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.trace.TraceProcessor; -import com.oracle.svm.core.configure.ConfigurationFile; public class ConfigurationResultWriter extends Tracer implements TracingResultWriter { private final TraceProcessor processor; + private final ConfigurationSet configuration; + private final ConfigurationSet omittedConfiguration; - public ConfigurationResultWriter(TraceProcessor processor) { + public ConfigurationResultWriter(TraceProcessor processor, ConfigurationSet configuration, ConfigurationSet omittedConfiguration) { this.processor = processor; + this.configuration = configuration; + this.omittedConfiguration = omittedConfiguration; } @Override protected void traceEntry(Map entry) { - processor.processEntry(arraysToLists(entry)); + processor.processEntry(arraysToLists(entry), configuration); } /** {@link TraceProcessor} expects {@link List} objects instead of plain arrays. */ @@ -81,18 +83,7 @@ public boolean supportsOnUnloadTraceWriting() { @Override public List writeToDirectory(Path directoryPath) throws IOException { - List writtenPaths = new ArrayList<>(); - for (ConfigurationFile configFile : ConfigurationFile.values()) { - if (configFile.canBeGeneratedByAgent()) { - Path filePath = directoryPath.resolve(configFile.getFileName()); - try (JsonWriter writer = new JsonWriter(filePath)) { - processor.getConfiguration(configFile).printJson(writer); - /* Add an extra EOF newline */ - writer.newline(); - } - writtenPaths.add(filePath); - } - } - return writtenPaths; + ConfigurationSet finalConfiguration = configuration.copyAndSubtract(omittedConfiguration); + return finalConfiguration.writeConfiguration(configFile -> directoryPath.resolve(configFile.getFileName())); } } 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 new file mode 100644 index 000000000000..8194d0392de3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java @@ -0,0 +1,188 @@ +/* + * 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 java.lang.reflect.Proxy; + +import org.junit.Assert; +import org.junit.Test; + +public class ConfigurationGenerator { + + @Test + public void createTestConfig() { + String enabledProperty = System.getProperty(ConfigurationGenerator.class.getName() + ".enabled"); + if (!Boolean.parseBoolean(enabledProperty)) { + return; + } + NoPropagationNecessary.runTest(); + PropagateToParent.runTest(); + PropagateButLeaveCommonConfiguration.runTest(); + } + +} + +/** + * Utility class for generating dynamic configuration. Note that this class should never end up in + * conditions. + */ +class ClassUtil { + + public static String qualifyName(String name) { + return "com.oracle.svm.configure.test.conditionalconfig." + name; + } + + public static void forName(String clazz) { + try { + Class.forName(qualifyName(clazz)); + } catch (ClassNotFoundException e) { + Assert.fail("Test class " + qualifyName(clazz) + " not found! Exception: " + e); + } + } + + public static void getResource(String resourceName) { + ClassUtil.class.getResource("resources/" + resourceName); + } + + @SuppressWarnings("SuspiciousInvocationHandlerImplementation") + public static void createProxy(Class... interfaceList) { + Proxy.newProxyInstance(ClassUtil.class.getClassLoader(), interfaceList, (proxy, method, args) -> null); + } +} + +/** + * Expected condition: NoPropagationNecessary + * + * The agent sees a single call to runTest() that generates the configuration. + */ +class NoPropagationNecessary { + + public static void runTest() { + ClassUtil.forName("NoPropagationNecessary$A"); + ClassUtil.getResource("NotPropagated.txt"); + ClassUtil.createProxy(IA.class); + } + + @SuppressWarnings("unused") + private static class A { + } + + interface IA { + } +} + +/** + * Expected conditions: ParentA -> PropagateToParent.ParentA, ParentB -> PropagateToParent.ParentB + * + * The agent sees the method Util.doWork called twice with different configuration. It deduces that + * the configuration must be propagated up the call chain. + */ +class PropagateToParent { + + public static void runTest() { + ParentA.doWork(); + ParentB.doWork(); + } + + private static class ParentA { + static void doWork() { + Util.doWork("PropagateToParent$ParentA", "PropagateToParentA.txt", IA.class); + } + } + + interface IA { + } + + private static class ParentB { + static void doWork() { + Util.doWork("PropagateToParent$ParentB", "PropagateToParentB.txt", IB.class); + } + } + + interface IB { + } + + private static class Util { + static void doWork(String clazz, String resource, Class... interfaceList) { + ClassUtil.forName(clazz); + ClassUtil.getResource(resource); + ClassUtil.createProxy(interfaceList); + } + } + +} + +/** + * Expected conditions: ParentA -> PropagateToParent.ParentA, ParentB -> PropagateToParent.ParentB, + * Util -> C + * + * The agent sees the method Util.doWork called twice with different configuration. It deduces that + * the configuration must be propagated up the call chain. However, the agent also sees the class C + * appearing in both Util.doWork calls and does not propagate this configuration upwards. + */ +class PropagateButLeaveCommonConfiguration { + + public static void runTest() { + ParentA.doWork(); + ParentB.doWork(); + } + + private static class ParentA { + static void doWork() { + Util.doWork("PropagateButLeaveCommonConfiguration$ParentA", "PropagateToParentA.txt", IA.class); + } + } + + private static class ParentB { + static void doWork() { + Util.doWork("PropagateButLeaveCommonConfiguration$ParentB", "PropagateToParentB.txt", IB.class); + } + } + + @SuppressWarnings("unused") + private static class C { + } + + interface IA { + } + + interface IB { + } + + interface IC { + } + + private static class Util { + static void doWork(String clazz, String resource, Class... intefaceList) { + ClassUtil.forName(clazz); + ClassUtil.forName("PropagateButLeaveCommonConfiguration$C"); + ClassUtil.getResource(resource); + ClassUtil.getResource("Common.txt"); + ClassUtil.createProxy(intefaceList); + ClassUtil.createProxy(IC.class); + } + } + +} 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 new file mode 100644 index 000000000000..3f836be78ed4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java @@ -0,0 +1,102 @@ +/* + * 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 java.io.IOException; +import java.io.StringWriter; +import java.net.URL; +import java.nio.file.Paths; + +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.configure.config.ConfigurationFileCollection; +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.core.util.VMError; + +public class ConfigurationVerifier { + + public static final String CONFIG_PATH_PROPERTY = ConfigurationVerifier.class.getName() + ".configpath"; + + @Test + public void testConfig() throws Exception { + String enabledProperty = System.getProperty(ConfigurationVerifier.class.getName() + ".enabled"); + if (!Boolean.parseBoolean(enabledProperty)) { + return; + } + ConfigurationSet actualConfig = loadActualConfig(); + ConfigurationSet expectedConfig = loadExpectedConfig(); + + ConfigurationSet missingConfig = expectedConfig.copyAndSubtract(actualConfig); + ConfigurationSet extraConfig = actualConfig.copyAndSubtract(expectedConfig); + + if (!missingConfig.isEmpty()) { + System.err.println("The following configuration was expected but not found:"); + System.err.println(getConfigurationJSON(missingConfig)); + } + if (!extraConfig.isEmpty()) { + System.err.println("The following configuration was not expected: "); + System.err.println(getConfigurationJSON(extraConfig)); + } + Assert.assertTrue(missingConfig.isEmpty() && extraConfig.isEmpty()); + } + + 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()) { + sw.append("\n").append(file.getName()).append("\n"); + config.getConfiguration(file).printJson(writer); + } + } + return sw.toString(); + } + } + + private static ConfigurationSet loadActualConfig() throws Exception { + String configurationPath = System.getProperty(CONFIG_PATH_PROPERTY); + ConfigurationFileCollection configurationFileCollection = new ConfigurationFileCollection(); + configurationFileCollection.addDirectory(Paths.get(configurationPath)); + return configurationFileCollection.loadConfigurationSet(e -> e, null, null); + } + + private static ConfigurationSet loadExpectedConfig() throws Exception { + ConfigurationFileCollection configurationFileCollection = new ConfigurationFileCollection(); + configurationFileCollection.addDirectory(resourceFileName -> { + try { + String resourceName = "config-dir/" + resourceFileName; + URL resourceURL = ConfigurationVerifier.class.getResource(resourceName); + return resourceURL == null ? null : resourceURL.toURI(); + } catch (Exception e) { + throw VMError.shouldNotReachHere("Unexpected error while locating the configuration files.", e); + } + }); + return configurationFileCollection.loadConfigurationSet(e -> e, null, null); + } + +} diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/jni-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/jni-config.json new file mode 100644 index 000000000000..0d4f101c7a37 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/jni-config.json @@ -0,0 +1,2 @@ +[ +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/predefined-classes-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/predefined-classes-config.json new file mode 100644 index 000000000000..9587dc3493c7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/predefined-classes-config.json @@ -0,0 +1,7 @@ +[ + { + "type": "agent-extracted", + "classes": [ + ] + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/proxy-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/proxy-config.json new file mode 100644 index 000000000000..fa4fd1ffda2d --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/proxy-config.json @@ -0,0 +1,26 @@ +[ + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.NoPropagationNecessary"}, + "interfaces":["com.oracle.svm.configure.test.conditionalconfig.NoPropagationNecessary$IA"] + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentA"}, + "interfaces":["com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$IA"] + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentB"}, + "interfaces":["com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$IB"] + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentA"}, + "interfaces":["com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$IA"] + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentB"}, + "interfaces":["com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$IB"] + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$Util"}, + "interfaces":["com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$IC"] + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/reflect-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/reflect-config.json new file mode 100644 index 000000000000..bb36b397a193 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/reflect-config.json @@ -0,0 +1,38 @@ +[ + { + "condition": { + "typeReachable": "com.oracle.svm.configure.test.conditionalconfig.NoPropagationNecessary" + }, + "name": "com.oracle.svm.configure.test.conditionalconfig.NoPropagationNecessary$A" + }, + { + "condition": { + "typeReachable": "com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentA" + }, + "name": "com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentA" + }, + { + "condition": { + "typeReachable": "com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentB" + }, + "name": "com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentB" + }, + { + "condition": { + "typeReachable": "com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentA" + }, + "name": "com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentA" + }, + { + "condition": { + "typeReachable": "com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentB" + }, + "name": "com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentB" + }, + { + "condition": { + "typeReachable": "com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$Util" + }, + "name": "com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$C" + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/resource-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/resource-config.json new file mode 100644 index 000000000000..5de92facbe8d --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/resource-config.json @@ -0,0 +1,30 @@ +{ + "resources":{ + "includes":[ + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.NoPropagationNecessary"}, + "pattern":"\\Qcom/oracle/svm/configure/test/conditionalconfig/resources/NotPropagated.txt\\E" + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentA"}, + "pattern":"\\Qcom/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentA.txt\\E" + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateToParent$ParentB"}, + "pattern":"\\Qcom/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentB.txt\\E" + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentA"}, + "pattern":"\\Qcom/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentA.txt\\E" + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$ParentB"}, + "pattern":"\\Qcom/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentB.txt\\E" + }, + { + "condition":{"typeReachable":"com.oracle.svm.configure.test.conditionalconfig.PropagateButLeaveCommonConfiguration$Util"}, + "pattern":"\\Qcom/oracle/svm/configure/test/conditionalconfig/resources/Common.txt\\E" + } + ]}, + "bundles":[] +} diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/serialization-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/serialization-config.json new file mode 100644 index 000000000000..0d4f101c7a37 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/serialization-config.json @@ -0,0 +1,2 @@ +[ +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/Common.txt b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/Common.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/NotPropagated.txt b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/NotPropagated.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentA.txt b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentA.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentB.txt b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentB.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index 70ad11164e6e..38c5440b0feb 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -39,6 +39,7 @@ import org.junit.Assert; import org.junit.Test; +import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.config.ConfigurationMemberInfo; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; @@ -51,8 +52,6 @@ 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.AccessAdvisor; -import com.oracle.svm.configure.trace.TraceProcessor; import com.oracle.svm.core.util.VMError; public class OmitPreviousConfigTests { @@ -60,10 +59,10 @@ public class OmitPreviousConfigTests { private static final String PREVIOUS_CONFIG_DIR_NAME = "prev-config-dir"; private static final String CURRENT_CONFIG_DIR_NAME = "config-dir"; - private static TraceProcessor loadTraceProcessorFromResourceDirectory(String resourceDirectory, TraceProcessor previous) { + private static ConfigurationSet loadTraceProcessorFromResourceDirectory(String resourceDirectory, ConfigurationSet omittedConfig) { try { - ConfigurationSet configurationSet = new ConfigurationSet(); - configurationSet.addDirectory(resourceFileName -> { + ConfigurationFileCollection configurationFileCollection = new ConfigurationFileCollection(); + configurationFileCollection.addDirectory(resourceFileName -> { try { String resourceName = resourceDirectory + "/" + resourceFileName; URL resourceURL = OmitPreviousConfigTests.class.getResource(resourceName); @@ -73,8 +72,6 @@ private static TraceProcessor loadTraceProcessorFromResourceDirectory(String res } }); - AccessAdvisor unusedAdvisor = new AccessAdvisor(); - Function handler = e -> { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); @@ -82,12 +79,10 @@ private static TraceProcessor loadTraceProcessorFromResourceDirectory(String res return e; }; Predicate shouldExcludeClassesWithHash = null; - if (previous != null) { - shouldExcludeClassesWithHash = previous.getPredefinedClassesConfiguration()::containsClassWithHash; + if (omittedConfig != null) { + shouldExcludeClassesWithHash = omittedConfig.getPredefinedClassesConfiguration()::containsClassWithHash; } - return new TraceProcessor(unusedAdvisor, configurationSet.loadJniConfig(handler), configurationSet.loadReflectConfig(handler), configurationSet.loadProxyConfig(handler), - configurationSet.loadResourceConfig(handler), configurationSet.loadSerializationConfig(handler), - configurationSet.loadPredefinedClassesConfig(null, shouldExcludeClassesWithHash, handler), previous); + return configurationFileCollection.loadConfigurationSet(handler, null, shouldExcludeClassesWithHash); } catch (Exception e) { throw VMError.shouldNotReachHere("Unexpected error while loading the configuration files.", e); } @@ -95,32 +90,34 @@ private static TraceProcessor loadTraceProcessorFromResourceDirectory(String res @Test public void testSameConfig() { - TraceProcessor previousConfigProcessor = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, null); - TraceProcessor sameConfigProcessor = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, previousConfigProcessor); - - assertTrue(sameConfigProcessor.getJniConfiguration().isEmpty()); - assertTrue(sameConfigProcessor.getReflectionConfiguration().isEmpty()); - assertTrue(sameConfigProcessor.getProxyConfiguration().isEmpty()); - assertTrue(sameConfigProcessor.getResourceConfiguration().isEmpty()); - assertTrue(sameConfigProcessor.getSerializationConfiguration().isEmpty()); - assertTrue(sameConfigProcessor.getPredefinedClassesConfiguration().isEmpty()); + ConfigurationSet omittedConfig = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, null); + ConfigurationSet config = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, omittedConfig); + config = config.copyAndSubtract(omittedConfig); + + assertTrue(config.getJniConfiguration().isEmpty()); + assertTrue(config.getReflectionConfiguration().isEmpty()); + assertTrue(config.getProxyConfiguration().isEmpty()); + assertTrue(config.getResourceConfiguration().isEmpty()); + assertTrue(config.getSerializationConfiguration().isEmpty()); + assertTrue(config.getPredefinedClassesConfiguration().isEmpty()); } @Test public void testConfigDifference() { - TraceProcessor previousConfigProcessor = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, null); - TraceProcessor currentConfigProcessor = loadTraceProcessorFromResourceDirectory(CURRENT_CONFIG_DIR_NAME, previousConfigProcessor); + ConfigurationSet omittedConfig = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, null); + ConfigurationSet config = loadTraceProcessorFromResourceDirectory(CURRENT_CONFIG_DIR_NAME, omittedConfig); + config = config.copyAndSubtract(omittedConfig); doTestGeneratedTypeConfig(); - doTestTypeConfig(currentConfigProcessor.getJniConfiguration()); + doTestTypeConfig(config.getJniConfiguration()); - doTestProxyConfig(currentConfigProcessor.getProxyConfiguration()); + doTestProxyConfig(config.getProxyConfiguration()); - doTestResourceConfig(currentConfigProcessor.getResourceConfiguration()); + doTestResourceConfig(config.getResourceConfiguration()); - doTestSerializationConfig(currentConfigProcessor.getSerializationConfiguration()); + doTestSerializationConfig(config.getSerializationConfiguration()); - doTestPredefinedClassesConfig(currentConfigProcessor.getPredefinedClassesConfiguration()); + doTestPredefinedClassesConfig(config.getPredefinedClassesConfiguration()); } private static void doTestGeneratedTypeConfig() { @@ -297,7 +294,7 @@ String getTypeName() { } void doTest() { - TypeConfiguration currentConfigWithoutPrevious = TypeConfiguration.copyAndSubtract(currentConfig, previousConfig); + TypeConfiguration currentConfigWithoutPrevious = currentConfig.copyAndSubtract(previousConfig); String name = getTypeName(); ConfigurationType configurationType = currentConfigWithoutPrevious.get(ConfigurationCondition.alwaysTrue(), name); 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 c4c1d231cf21..f1828e9a3b28 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java @@ -24,10 +24,46 @@ */ package com.oracle.svm.configure; +import java.util.function.Consumer; + import com.oracle.svm.configure.json.JsonPrintable; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +public abstract class ConfigurationBase, P> implements JsonPrintable { + + public abstract boolean isEmpty(); + + public abstract T copy(); + + protected abstract void merge(T other); + + protected abstract void mergeConditional(ConfigurationCondition condition, T other); + + protected abstract void subtract(T other); + + protected abstract void intersect(T other); + + protected abstract void removeIf(P predicate); + + protected T copyAnd(Consumer consumer) { + T copy = copy(); + consumer.accept(copy); + return copy; + } + + public T copyAndMerge(T other) { + return copyAnd(copy -> copy.merge(other)); + } -public interface ConfigurationBase extends JsonPrintable { + public T copyAndSubtract(T other) { + return copyAnd(copy -> copy.subtract(other)); + } - boolean isEmpty(); + public T copyAndIntersect(T other) { + return copyAnd(copy -> copy.intersect(other)); + } + public T copyAndFilter(P predicate) { + return copyAnd(copy -> copy.removeIf(predicate)); + } } 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 15449db3d29f..ccfeae60add0 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 @@ -29,6 +29,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.Reader; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -47,10 +49,13 @@ import org.graalvm.nativeimage.ImageInfo; +import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.filters.ComplexFilter; +import com.oracle.svm.configure.filters.ConfigurationFilter; import com.oracle.svm.configure.filters.FilterConfigurationParser; import com.oracle.svm.configure.filters.ModuleFilterTools; -import com.oracle.svm.configure.filters.RuleNode; +import com.oracle.svm.configure.filters.HierarchyFilterNode; import com.oracle.svm.configure.json.JsonWriter; import com.oracle.svm.configure.trace.AccessAdvisor; import com.oracle.svm.configure.trace.TraceProcessor; @@ -148,17 +153,17 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA boolean builtinHeuristicFilter = true; List callerFilters = new ArrayList<>(); - ConfigurationSet omittedInputSet = new ConfigurationSet(); - ConfigurationSet inputSet = new ConfigurationSet(); - ConfigurationSet outputSet = new ConfigurationSet(); + ConfigurationFileCollection omittedCollection = new ConfigurationFileCollection(); + ConfigurationFileCollection inputCollection = new ConfigurationFileCollection(); + ConfigurationFileCollection outputCollection = new ConfigurationFileCollection(); while (argsIter.hasNext()) { String[] parts = argsIter.next().split("=", 2); String current = parts[0]; String value = (parts.length > 1) ? parts[1] : null; - ConfigurationSet set = outputSet; + ConfigurationFileCollection collection = outputCollection; switch (current) { case "--input-dir": - inputSet.addDirectory(requirePath(current, value)); + inputCollection.addDirectory(requirePath(current, value)); break; case "--output-dir": Path directory = requirePath(current, value); @@ -167,47 +172,47 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA } else if (!Files.isDirectory(directory)) { throw new NoSuchFileException(value); } - outputSet.addDirectory(directory); + outputCollection.addDirectory(directory); break; case "--omit-from-input-dir": - omittedInputSet.addDirectory(requirePath(current, value)); + omittedCollection.addDirectory(requirePath(current, value)); break; case "--reflect-input": - set = inputSet; // fall through + collection = inputCollection; // fall through case "--reflect-output": - set.getReflectConfigPaths().add(requirePathUri(current, value)); + collection.getReflectConfigPaths().add(requirePathUri(current, value)); break; case "--jni-input": - set = inputSet; // fall through + collection = inputCollection; // fall through case "--jni-output": - set.getJniConfigPaths().add(requirePathUri(current, value)); + collection.getJniConfigPaths().add(requirePathUri(current, value)); break; case "--proxy-input": - set = inputSet; // fall through + collection = inputCollection; // fall through case "--proxy-output": - set.getProxyConfigPaths().add(requirePathUri(current, value)); + collection.getProxyConfigPaths().add(requirePathUri(current, value)); break; case "--resource-input": - set = inputSet; // fall through + collection = inputCollection; // fall through case "--resource-output": - set.getResourceConfigPaths().add(requirePathUri(current, value)); + collection.getResourceConfigPaths().add(requirePathUri(current, value)); break; case "--serialization-input": - set = inputSet; // fall through + collection = inputCollection; // fall through case "--serialization-output": - set.getSerializationConfigPaths().add(requirePathUri(current, value)); + collection.getSerializationConfigPaths().add(requirePathUri(current, value)); break; case "--predefined-classes-input": - set = inputSet; // fall through + collection = inputCollection; // fall through case "--predefined-classes-output": - set.getPredefinedClassesConfigPaths().add(requirePathUri(current, value)); + collection.getPredefinedClassesConfigPaths().add(requirePathUri(current, value)); break; case "--trace-input": @@ -241,16 +246,18 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA break; } } - failIfAgentLockFilesPresent(inputSet, omittedInputSet, outputSet); + failIfAgentLockFilesPresent(inputCollection, omittedCollection, outputCollection); - RuleNode callersFilter = null; + HierarchyFilterNode callersFilterHierarchyFilterNode = null; + ComplexFilter callersFilter = null; if (!builtinCallerFilter) { - callersFilter = RuleNode.createRoot(); - callersFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); + callersFilterHierarchyFilterNode = HierarchyFilterNode.createInclusiveRoot(); + callersFilter = new ComplexFilter(callersFilterHierarchyFilterNode); } if (!callerFilters.isEmpty()) { - if (callersFilter == null) { - callersFilter = AccessAdvisor.copyBuiltinCallerFilterTree(); + if (callersFilterHierarchyFilterNode == null) { + callersFilterHierarchyFilterNode = AccessAdvisor.copyBuiltinCallerFilterTree(); + callersFilter = new ComplexFilter(callersFilterHierarchyFilterNode); } for (URI uri : callerFilters) { try { @@ -260,87 +267,85 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA throw new UsageException("Cannot parse filter file " + uri + ": " + e); } } - callersFilter.removeRedundantNodes(); + callersFilter.getHierarchyFilterNode().removeRedundantNodes(); } - AccessAdvisor advisor = new AccessAdvisor(); - advisor.setHeuristicsEnabled(builtinHeuristicFilter); - if (callersFilter != null) { - advisor.setCallerFilterTree(callersFilter); - } - TraceProcessor p; - TraceProcessor omittedInputTraceProcessor; + ConfigurationSet configurationSet; + ConfigurationSet omittedConfigurationSet; + try { - omittedInputTraceProcessor = new TraceProcessor(advisor, omittedInputSet.loadJniConfig(ConfigurationSet.FAIL_ON_EXCEPTION), - omittedInputSet.loadReflectConfig(ConfigurationSet.FAIL_ON_EXCEPTION), - omittedInputSet.loadProxyConfig(ConfigurationSet.FAIL_ON_EXCEPTION), omittedInputSet.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION), - omittedInputSet.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION), omittedInputSet.loadPredefinedClassesConfig(null, null, ConfigurationSet.FAIL_ON_EXCEPTION), - null); + omittedConfigurationSet = omittedCollection.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, null, null); List predefinedClassDestDirs = new ArrayList<>(); - for (URI pathUri : outputSet.getPredefinedClassesConfigPaths()) { + for (URI pathUri : outputCollection.getPredefinedClassesConfigPaths()) { Path subdir = Files.createDirectories(Paths.get(pathUri).getParent().resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR)); subdir = Files.createDirectories(subdir); predefinedClassDestDirs.add(subdir); } - Predicate shouldExcludeClassesWithHash = omittedInputTraceProcessor.getPredefinedClassesConfiguration()::containsClassWithHash; - p = new TraceProcessor(advisor, inputSet.loadJniConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadReflectConfig(ConfigurationSet.FAIL_ON_EXCEPTION), - inputSet.loadProxyConfig(ConfigurationSet.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION), - inputSet.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION), - inputSet.loadPredefinedClassesConfig(predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash, ConfigurationSet.FAIL_ON_EXCEPTION), - omittedInputTraceProcessor); + Predicate shouldExcludeClassesWithHash = omittedConfigurationSet.getPredefinedClassesConfiguration()::containsClassWithHash; + configurationSet = inputCollection.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash); } catch (IOException e) { throw e; } catch (Throwable t) { throw new RuntimeException(t); } - if (traceInputs.isEmpty() && inputSet.isEmpty()) { + if (traceInputs.isEmpty() && inputCollection.isEmpty()) { throw new UsageException("No inputs specified."); } - for (URI uri : traceInputs) { - try (Reader reader = Files.newBufferedReader(Paths.get(uri))) { - p.process(reader); + + if (!traceInputs.isEmpty()) { + AccessAdvisor advisor = new AccessAdvisor(); + advisor.setHeuristicsEnabled(builtinHeuristicFilter); + if (callersFilter != null) { + advisor.setCallerFilterTree(callersFilter); + } + + TraceProcessor processor = new TraceProcessor(advisor); + for (URI uri : traceInputs) { + try (Reader reader = Files.newBufferedReader(Paths.get(uri))) { + processor.process(reader, configurationSet); + } } } - if (outputSet.isEmpty()) { + if (outputCollection.isEmpty()) { System.err.println("Warning: no outputs specified, validating inputs only."); } - for (URI uri : outputSet.getReflectConfigPaths()) { + for (URI uri : outputCollection.getReflectConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getReflectionConfiguration().printJson(writer); + configurationSet.getReflectionConfiguration().printJson(writer); } } - for (URI uri : outputSet.getJniConfigPaths()) { + for (URI uri : outputCollection.getJniConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getJniConfiguration().printJson(writer); + configurationSet.getJniConfiguration().printJson(writer); } } - for (URI uri : outputSet.getProxyConfigPaths()) { + for (URI uri : outputCollection.getProxyConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getProxyConfiguration().printJson(writer); + configurationSet.getProxyConfiguration().printJson(writer); } } - for (URI uri : outputSet.getResourceConfigPaths()) { + for (URI uri : outputCollection.getResourceConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getResourceConfiguration().printJson(writer); + configurationSet.getResourceConfiguration().printJson(writer); } } - for (URI uri : outputSet.getSerializationConfigPaths()) { + for (URI uri : outputCollection.getSerializationConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getSerializationConfiguration().printJson(writer); + configurationSet.getSerializationConfiguration().printJson(writer); } } - for (URI uri : outputSet.getPredefinedClassesConfigPaths()) { + for (URI uri : outputCollection.getPredefinedClassesConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getPredefinedClassesConfiguration().printJson(writer); + configurationSet.getPredefinedClassesConfiguration().printJson(writer); } } } - private static void failIfAgentLockFilesPresent(ConfigurationSet... sets) { + private static void failIfAgentLockFilesPresent(ConfigurationFileCollection... collections) { Set paths = null; - for (ConfigurationSet set : sets) { - for (URI path : set.getDetectedAgentLockPaths()) { + for (ConfigurationFileCollection coll : collections) { + for (URI path : coll.getDetectedAgentLockPaths()) { if (paths == null) { paths = new HashSet<>(); } @@ -368,7 +373,9 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce args.add(arg); } } - RuleNode rootNode = null; + HierarchyFilterNode rootNode = HierarchyFilterNode.createRoot(); + ComplexFilter filter = new ComplexFilter(rootNode); + boolean filterModified = false; for (String arg : args) { String[] parts = arg.split("=", 2); String current = parts[0]; @@ -378,17 +385,18 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce case "--exclude-packages-from-modules": case "--exclude-unexported-packages-from-modules": if (!ImageInfo.inImageCode()) { - if (rootNode != null) { + if (filterModified) { throw new UsageException(current + " must be specified before other rule-creating arguments"); } + filterModified = true; String[] moduleNames = (value != null) ? value.split(",") : new String[0]; - RuleNode.Inclusion exportedInclusion = current.startsWith("--include") ? RuleNode.Inclusion.Include : RuleNode.Inclusion.Exclude; - RuleNode.Inclusion unexportedInclusion = exportedInclusion; - RuleNode.Inclusion rootInclusion = exportedInclusion.invert(); + HierarchyFilterNode.Inclusion exportedInclusion = current.startsWith("--include") ? ConfigurationFilter.Inclusion.Include : ConfigurationFilter.Inclusion.Exclude; + HierarchyFilterNode.Inclusion unexportedInclusion = exportedInclusion; + HierarchyFilterNode.Inclusion rootInclusion = exportedInclusion.invert(); if (current.equals("--exclude-unexported-packages-from-modules")) { - rootInclusion = RuleNode.Inclusion.Include; - exportedInclusion = RuleNode.Inclusion.Include; - unexportedInclusion = RuleNode.Inclusion.Exclude; + rootInclusion = ConfigurationFilter.Inclusion.Include; + exportedInclusion = ConfigurationFilter.Inclusion.Include; + unexportedInclusion = ConfigurationFilter.Inclusion.Exclude; } rootNode = ModuleFilterTools.generateFromModules(moduleNames, rootInclusion, exportedInclusion, unexportedInclusion, reduce); } else { @@ -397,8 +405,8 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce break; case "--input-file": - rootNode = maybeCreateRootNode(rootNode); - new FilterConfigurationParser(rootNode).parseAndRegister(requirePathUri(current, value)); + filterModified = true; + new FilterConfigurationParser(filter).parseAndRegister(requirePathUri(current, value)); break; case "--output-file": @@ -406,35 +414,37 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce break; case "--include-classes": - rootNode = addSingleRule(rootNode, current, value, RuleNode.Inclusion.Include); + filterModified = true; + addSingleRule(rootNode, current, value, ConfigurationFilter.Inclusion.Include); break; case "--exclude-classes": - rootNode = addSingleRule(rootNode, current, value, RuleNode.Inclusion.Exclude); + filterModified = true; + addSingleRule(rootNode, current, value, ConfigurationFilter.Inclusion.Exclude); break; default: throw new UsageException("Unknown argument: " + current); } } - rootNode = maybeCreateRootNode(rootNode); // in case of no inputs rootNode.removeRedundantNodes(); if (outputPath != null) { try (FileOutputStream os = new FileOutputStream(outputPath.toFile())) { - rootNode.printJsonTree(os); + printFilterToStream(filter, os); } } else { - rootNode.printJsonTree(System.out); + printFilterToStream(filter, System.out); } } - private static RuleNode maybeCreateRootNode(RuleNode rootNode) { - return (rootNode != null) ? rootNode : RuleNode.createRoot(); + private static void printFilterToStream(ConfigurationFilter filter, OutputStream targetStream) throws IOException { + try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(targetStream))) { + filter.printJson(writer); + } } - private static RuleNode addSingleRule(RuleNode rootNode, String argName, String qualifiedPkg, RuleNode.Inclusion inclusion) { - RuleNode root = maybeCreateRootNode(rootNode); + private static void addSingleRule(HierarchyFilterNode root, String argName, String qualifiedPkg, HierarchyFilterNode.Inclusion inclusion) { if (qualifiedPkg == null || qualifiedPkg.isEmpty()) { throw new UsageException("Argument must be provided for: " + argName); } @@ -443,7 +453,6 @@ private static RuleNode addSingleRule(RuleNode rootNode, String argName, String "or as .** to include all classes in the package and all of its subpackages"); } root.addOrGetChildren(qualifiedPkg, inclusion); - return root; } private static String getResource(String resourceName) { 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/ConditionalConfigurationPredicate.java new file mode 100644 index 000000000000..a4b9c2fa1ea8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java @@ -0,0 +1,76 @@ +/* + * 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; + +import java.util.List; +import java.util.regex.Pattern; + +import com.oracle.svm.configure.filters.ComplexFilter; +import com.oracle.svm.core.configure.ConditionalElement; + +public class ConditionalConfigurationPredicate implements TypeConfiguration.Predicate, ProxyConfiguration.Predicate, + ResourceConfiguration.Predicate, SerializationConfiguration.Predicate, PredefinedClassesConfiguration.Predicate { + + private final ComplexFilter filter; + + public ConditionalConfigurationPredicate(ComplexFilter filter) { + this.filter = filter; + } + + @Override + public boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type) { + return !filter.includes(conditionalElement.getCondition().getTypeName()) || !filter.includes(type.getQualifiedJavaName()); + } + + @Override + public boolean testProxyInterfaceList(ConditionalElement> conditionalElement) { + return !filter.includes(conditionalElement.getCondition().getTypeName()); + } + + @Override + public boolean testIncludedResource(ConditionalElement condition, Pattern pattern) { + return !filter.includes(condition.getCondition().getTypeName()); + } + + @Override + public boolean testIncludedBundle(ConditionalElement condition, ResourceConfiguration.BundleConfiguration bundleConfiguration) { + return !filter.includes(condition.getCondition().getTypeName()); + } + + @Override + public boolean testSerializationType(SerializationConfigurationType type) { + return !(filter.includes(type.getCondition().getTypeName()) && filter.includes(type.getQualifiedJavaName())); + } + + @Override + public boolean testLambdaSerializationType(SerializationConfigurationLambdaCapturingType type) { + return !(filter.includes(type.getCondition().getTypeName()) && filter.includes(type.getQualifiedJavaName())); + } + + @Override + public boolean testPredefinedClass(ConfigurationPredefinedClass clazz) { + return clazz.getNameInfo() != null && !filter.includes(clazz.getNameInfo()); + } +} 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 new file mode 100644 index 000000000000..2f4e1df09b84 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2019, 2019, 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; + +import java.io.IOException; +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; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +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; + + private final Set jniConfigPaths = new LinkedHashSet<>(); + private final Set reflectConfigPaths = new LinkedHashSet<>(); + private final Set proxyConfigPaths = new LinkedHashSet<>(); + private final Set resourceConfigPaths = new LinkedHashSet<>(); + private final Set serializationConfigPaths = new LinkedHashSet<>(); + private final Set predefinedClassesConfigPaths = new LinkedHashSet<>(); + private Set lockFilePaths; + + public void addDirectory(Path path) { + jniConfigPaths.add(path.resolve(ConfigurationFile.JNI.getFileName()).toUri()); + reflectConfigPaths.add(path.resolve(ConfigurationFile.REFLECTION.getFileName()).toUri()); + proxyConfigPaths.add(path.resolve(ConfigurationFile.DYNAMIC_PROXY.getFileName()).toUri()); + resourceConfigPaths.add(path.resolve(ConfigurationFile.RESOURCES.getFileName()).toUri()); + serializationConfigPaths.add(path.resolve(ConfigurationFile.SERIALIZATION.getFileName()).toUri()); + predefinedClassesConfigPaths.add(path.resolve(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()).toUri()); + detectAgentLock(path.resolve(ConfigurationFile.LOCK_FILE_NAME), Files::exists, Path::toUri); + } + + private void detectAgentLock(T location, Predicate exists, Function toUri) { + if (exists.test(location)) { + if (lockFilePaths == null) { + lockFilePaths = new LinkedHashSet<>(); + } + lockFilePaths.add(toUri.apply(location)); + } + } + + public void addDirectory(Function fileResolver) { + jniConfigPaths.add(fileResolver.apply(ConfigurationFile.JNI.getFileName())); + reflectConfigPaths.add(fileResolver.apply(ConfigurationFile.REFLECTION.getFileName())); + proxyConfigPaths.add(fileResolver.apply(ConfigurationFile.DYNAMIC_PROXY.getFileName())); + resourceConfigPaths.add(fileResolver.apply(ConfigurationFile.RESOURCES.getFileName())); + serializationConfigPaths.add(fileResolver.apply(ConfigurationFile.SERIALIZATION.getFileName())); + predefinedClassesConfigPaths.add(fileResolver.apply(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName())); + detectAgentLock(fileResolver.apply(ConfigurationFile.LOCK_FILE_NAME), Objects::nonNull, Function.identity()); + } + + public Set getDetectedAgentLockPaths() { + return (lockFilePaths != null) ? lockFilePaths : Collections.emptySet(); + } + + public boolean isEmpty() { + return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && + resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() && predefinedClassesConfigPaths.isEmpty(); + } + + public Set getJniConfigPaths() { + return jniConfigPaths; + } + + public Set getReflectConfigPaths() { + return reflectConfigPaths; + } + + public Set getProxyConfigPaths() { + return proxyConfigPaths; + } + + public Set getResourceConfigPaths() { + return resourceConfigPaths; + } + + public Set getSerializationConfigPaths() { + return serializationConfigPaths; + } + + public Set getPredefinedClassesConfigPaths() { + return predefinedClassesConfigPaths; + } + + public TypeConfiguration loadJniConfig(Function exceptionHandler) throws Exception { + return loadTypeConfig(jniConfigPaths, exceptionHandler); + } + + public TypeConfiguration loadReflectConfig(Function exceptionHandler) throws Exception { + return loadTypeConfig(reflectConfigPaths, exceptionHandler); + } + + 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); + 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); + return predefinedClassesConfiguration; + } + + public ResourceConfiguration loadResourceConfig(Function exceptionHandler) throws Exception { + ResourceConfiguration resourceConfiguration = new ResourceConfiguration(); + loadConfig(resourceConfigPaths, new ResourceConfigurationParser(new ResourceConfiguration.ParserAdapter(resourceConfiguration), true), exceptionHandler); + return resourceConfiguration; + } + + public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { + SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); + loadConfig(serializationConfigPaths, new SerializationConfigurationParser(serializationConfiguration, true), exceptionHandler); + return serializationConfiguration; + } + + public ConfigurationSet loadConfigurationSet(Function exceptionHandler, Path[] predefinedConfigClassDestinationDirs, + Predicate predefinedConfigClassWithHashExclusionPredicate) throws Exception { + return new ConfigurationSet(loadReflectConfig(exceptionHandler), loadJniConfig(exceptionHandler), loadResourceConfig(exceptionHandler), loadProxyConfig(exceptionHandler), + loadSerializationConfig(exceptionHandler), + loadPredefinedClassesConfig(predefinedConfigClassDestinationDirs, predefinedConfigClassWithHashExclusionPredicate, exceptionHandler)); + } + + private static TypeConfiguration loadTypeConfig(Collection uris, Function exceptionHandler) throws Exception { + TypeConfiguration configuration = new TypeConfiguration(); + loadConfig(uris, new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(configuration)), exceptionHandler); + return configuration; + } + + private static void loadConfig(Collection configPaths, ConfigurationParser configurationParser, Function exceptionHandler) throws Exception { + for (URI uri : configPaths) { + try { + configurationParser.parseAndRegister(uri); + } catch (IOException ioe) { + Exception e = ioe; + if (exceptionHandler != null) { + e = exceptionHandler.apply(ioe); + } + if (e != null) { + throw e; + } + } + } + } +} 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 2438a7d7f011..7e1261de6386 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -25,159 +25,146 @@ package com.oracle.svm.configure.config; import java.io.IOException; -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; -import java.util.Objects; -import java.util.Set; +import java.util.List; import java.util.function.Function; -import java.util.function.Predicate; +import com.oracle.svm.configure.ConfigurationBase; +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.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; +import com.oracle.svm.core.util.VMError; public class ConfigurationSet { - public static final Function FAIL_ON_EXCEPTION = e -> e; - - private final Set jniConfigPaths = new LinkedHashSet<>(); - private final Set reflectConfigPaths = new LinkedHashSet<>(); - private final Set proxyConfigPaths = new LinkedHashSet<>(); - private final Set resourceConfigPaths = new LinkedHashSet<>(); - private final Set serializationConfigPaths = new LinkedHashSet<>(); - private final Set predefinedClassesConfigPaths = new LinkedHashSet<>(); - private Set lockFilePaths; - - public void addDirectory(Path path) { - jniConfigPaths.add(path.resolve(ConfigurationFile.JNI.getFileName()).toUri()); - reflectConfigPaths.add(path.resolve(ConfigurationFile.REFLECTION.getFileName()).toUri()); - proxyConfigPaths.add(path.resolve(ConfigurationFile.DYNAMIC_PROXY.getFileName()).toUri()); - resourceConfigPaths.add(path.resolve(ConfigurationFile.RESOURCES.getFileName()).toUri()); - serializationConfigPaths.add(path.resolve(ConfigurationFile.SERIALIZATION.getFileName()).toUri()); - predefinedClassesConfigPaths.add(path.resolve(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()).toUri()); - detectAgentLock(path.resolve(ConfigurationFile.LOCK_FILE_NAME), Files::exists, Path::toUri); - } - - private void detectAgentLock(T location, Predicate exists, Function toUri) { - if (exists.test(location)) { - if (lockFilePaths == null) { - lockFilePaths = new LinkedHashSet<>(); - } - lockFilePaths.add(toUri.apply(location)); - } + @FunctionalInterface + private interface Mutator { + > T apply(T first, T other); } - public void addDirectory(Function fileResolver) { - add(jniConfigPaths, fileResolver, ConfigurationFile.JNI); - add(reflectConfigPaths, fileResolver, ConfigurationFile.REFLECTION); - add(proxyConfigPaths, fileResolver, ConfigurationFile.DYNAMIC_PROXY); - add(resourceConfigPaths, fileResolver, ConfigurationFile.RESOURCES); - add(serializationConfigPaths, fileResolver, ConfigurationFile.SERIALIZATION); - add(predefinedClassesConfigPaths, fileResolver, ConfigurationFile.PREDEFINED_CLASSES_NAME); - detectAgentLock(fileResolver.apply(ConfigurationFile.LOCK_FILE_NAME), Objects::nonNull, Function.identity()); - } + private final TypeConfiguration reflectionConfiguration; + private final TypeConfiguration jniConfiguration; + private final ResourceConfiguration resourceConfiguration; + private final ProxyConfiguration proxyConfiguration; + private final SerializationConfiguration serializationConfiguration; + private final PredefinedClassesConfiguration predefinedClassesConfiguration; - private static void add(Set set, Function resolver, ConfigurationFile kind) { - URI location = resolver.apply(kind.getFileName()); - if (location == null) { - throw new RuntimeException("Configuration file " + location + " does not exist."); - } - set.add(location); + public ConfigurationSet(TypeConfiguration reflectionConfiguration, TypeConfiguration jniConfiguration, ResourceConfiguration resourceConfiguration, ProxyConfiguration proxyConfiguration, + SerializationConfiguration serializationConfiguration, PredefinedClassesConfiguration predefinedClassesConfiguration) { + this.reflectionConfiguration = reflectionConfiguration; + this.jniConfiguration = jniConfiguration; + this.resourceConfiguration = resourceConfiguration; + this.proxyConfiguration = proxyConfiguration; + this.serializationConfiguration = serializationConfiguration; + this.predefinedClassesConfiguration = predefinedClassesConfiguration; } - public boolean isEmpty() { - return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && - resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() && predefinedClassesConfigPaths.isEmpty(); + public ConfigurationSet(ConfigurationSet other) { + this(other.reflectionConfiguration.copy(), other.jniConfiguration.copy(), other.resourceConfiguration.copy(), other.proxyConfiguration.copy(), other.serializationConfiguration.copy(), + other.predefinedClassesConfiguration.copy()); } - public Set getDetectedAgentLockPaths() { - return (lockFilePaths != null) ? lockFilePaths : Collections.emptySet(); + public ConfigurationSet() { + this(new TypeConfiguration(), new TypeConfiguration(), new ResourceConfiguration(), new ProxyConfiguration(), new SerializationConfiguration(), + new PredefinedClassesConfiguration(new Path[0], hash -> false)); } - public Set getJniConfigPaths() { - return jniConfigPaths; + private ConfigurationSet mutate(ConfigurationSet other, Mutator mutator) { + TypeConfiguration reflectionConfig = mutator.apply(this.reflectionConfiguration, other.reflectionConfiguration); + TypeConfiguration jniConfig = mutator.apply(this.jniConfiguration, other.jniConfiguration); + ResourceConfiguration resourceConfig = mutator.apply(this.resourceConfiguration, other.resourceConfiguration); + ProxyConfiguration proxyConfig = mutator.apply(this.proxyConfiguration, other.proxyConfiguration); + SerializationConfiguration serializationConfig = mutator.apply(this.serializationConfiguration, other.serializationConfiguration); + PredefinedClassesConfiguration predefinedClassesConfig = mutator.apply(this.predefinedClassesConfiguration, other.predefinedClassesConfiguration); + return new ConfigurationSet(reflectionConfig, jniConfig, resourceConfig, proxyConfig, serializationConfig, predefinedClassesConfig); } - public Set getReflectConfigPaths() { - return reflectConfigPaths; + public ConfigurationSet copyAndMerge(ConfigurationSet other) { + return mutate(other, ConfigurationBase::copyAndMerge); } - public Set getProxyConfigPaths() { - return proxyConfigPaths; + public ConfigurationSet copyAndSubtract(ConfigurationSet other) { + return mutate(other, ConfigurationBase::copyAndSubtract); } - public Set getResourceConfigPaths() { - return resourceConfigPaths; + public ConfigurationSet copyAndIntersectWith(ConfigurationSet other) { + return mutate(other, ConfigurationBase::copyAndIntersect); } - public Set getSerializationConfigPaths() { - return serializationConfigPaths; + public ConfigurationSet filter(ConditionalConfigurationPredicate filter) { + TypeConfiguration reflectionConfig = this.reflectionConfiguration.copyAndFilter(filter); + TypeConfiguration jniConfig = this.jniConfiguration.copyAndFilter(filter); + ResourceConfiguration resourceConfig = this.resourceConfiguration.copyAndFilter(filter); + ProxyConfiguration proxyConfig = this.proxyConfiguration.copyAndFilter(filter); + SerializationConfiguration serializationConfig = this.serializationConfiguration.copyAndFilter(filter); + PredefinedClassesConfiguration predefinedClassesConfig = this.predefinedClassesConfiguration.copyAndFilter(filter); + return new ConfigurationSet(reflectionConfig, jniConfig, resourceConfig, proxyConfig, serializationConfig, predefinedClassesConfig); } - public Set getPredefinedClassesConfigPaths() { - return predefinedClassesConfigPaths; + public TypeConfiguration getReflectionConfiguration() { + return reflectionConfiguration; } - public TypeConfiguration loadJniConfig(Function exceptionHandler) throws Exception { - return loadTypeConfig(jniConfigPaths, exceptionHandler); + public TypeConfiguration getJniConfiguration() { + return jniConfiguration; } - public TypeConfiguration loadReflectConfig(Function exceptionHandler) throws Exception { - return loadTypeConfig(reflectConfigPaths, exceptionHandler); + public ResourceConfiguration getResourceConfiguration() { + return resourceConfiguration; } - 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); + public ProxyConfiguration getProxyConfiguration() { 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); - return predefinedClassesConfiguration; + public SerializationConfiguration getSerializationConfiguration() { + return serializationConfiguration; } - public ResourceConfiguration loadResourceConfig(Function exceptionHandler) throws Exception { - ResourceConfiguration resourceConfiguration = new ResourceConfiguration(); - loadConfig(resourceConfigPaths, new ResourceConfigurationParser(new ResourceConfiguration.ParserAdapter(resourceConfiguration), true), exceptionHandler); - return resourceConfiguration; + public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { + return predefinedClassesConfiguration; } - public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { - SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); - loadConfig(serializationConfigPaths, new SerializationConfigurationParser(serializationConfiguration, true), exceptionHandler); - return serializationConfiguration; + public ConfigurationBase getConfiguration(ConfigurationFile configurationFile) { + switch (configurationFile) { + case DYNAMIC_PROXY: + return proxyConfiguration; + case RESOURCES: + return resourceConfiguration; + case JNI: + return jniConfiguration; + case REFLECTION: + return reflectionConfiguration; + case SERIALIZATION: + return serializationConfiguration; + case PREDEFINED_CLASSES_NAME: + return predefinedClassesConfiguration; + default: + throw VMError.shouldNotReachHere("Unsupported configuration in configuration container: " + configurationFile); + } } - private static TypeConfiguration loadTypeConfig(Collection uris, Function exceptionHandler) throws Exception { - TypeConfiguration configuration = new TypeConfiguration(); - loadConfig(uris, new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(configuration)), exceptionHandler); - return configuration; - } - - private static void loadConfig(Collection configPaths, ConfigurationParser configurationParser, Function exceptionHandler) throws Exception { - for (URI uri : configPaths) { - try { - configurationParser.parseAndRegister(uri); - } catch (IOException ioe) { - Exception e = ioe; - if (exceptionHandler != null) { - e = exceptionHandler.apply(ioe); - } - if (e != null) { - throw e; - } + 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(); } } + return writtenFiles; + } + + public List writeConfiguration(Function configFilePathResolver) throws IOException { + return writeConfiguration(configFilePathResolver, this::getConfiguration); + } + + public boolean isEmpty() { + return reflectionConfiguration.isEmpty() && jniConfiguration.isEmpty() && resourceConfiguration.isEmpty() && proxyConfiguration.isEmpty() && serializationConfiguration.isEmpty() && + predefinedClassesConfiguration.isEmpty(); } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 5245033e81eb..03f1f78b1b8f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -67,6 +67,24 @@ static ConfigurationType copyAndSubtract(ConfigurationType type, ConfigurationTy return copy.isEmpty() ? null : copy; } + static ConfigurationType copyAndIntersect(ConfigurationType type, ConfigurationType toIntersect) { + ConfigurationType copy = new ConfigurationType(type); + if (copy.equals(toIntersect)) { + return copy; + } + + assert type.getCondition().equals(toIntersect.getCondition()); + assert type.getQualifiedJavaName().equals(toIntersect.getQualifiedJavaName()); + copy.intersectWith(toIntersect); + return copy; + } + + static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType toMerge) { + ConfigurationType copy = new ConfigurationType(type); + copy.mergeFrom(toMerge); + return copy; + } + private final ConfigurationCondition condition; private final String qualifiedJavaName; @@ -90,16 +108,20 @@ public ConfigurationType(ConfigurationCondition condition, String qualifiedJavaN this.qualifiedJavaName = qualifiedJavaName; } - private ConfigurationType(ConfigurationType other) { + ConfigurationType(ConfigurationType other, ConfigurationCondition condition) { // Our object is not yet published, so it is sufficient to take only the other object's lock synchronized (other) { qualifiedJavaName = other.qualifiedJavaName; - condition = other.condition; + this.condition = condition; mergeFrom(other); } } - private void mergeFrom(ConfigurationType other) { + ConfigurationType(ConfigurationType other) { + this(other, other.condition); + } + + void mergeFrom(ConfigurationType other) { assert condition.equals(other.condition); assert qualifiedJavaName.equals(other.qualifiedJavaName); mergeFlagsFrom(other); @@ -174,6 +196,37 @@ private void maybeRemoveMethods(ConfigurationMemberAccessibility hasAllDeclaredM } } + private void intersectWith(ConfigurationType other) { + intersectFlags(other); + intersectFields(other); + intersectMethods(other); + } + + private void intersectFlags(ConfigurationType other) { + setFlagsFromOther(other, (our, their) -> our && their, ConfigurationMemberAccessibility::remove); + } + + private void intersectFields(ConfigurationType other) { + if (fields != null) { + if (other.fields != null) { + fields.keySet().retainAll(other.fields.keySet()); + fields.replaceAll((key, value) -> value.newIntersectedWith(other.fields.get(key))); + } else { + fields = null; + } + } + } + + private void intersectMethods(ConfigurationType other) { + if (methods != null) { + if (other.methods != null) { + methods.keySet().retainAll(other.methods.keySet()); + } else { + methods = null; + } + } + } + private void removeAll(ConfigurationType other) { assert condition.equals(other.condition); assert qualifiedJavaName.equals(other.qualifiedJavaName); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java index e7d38150674c..0ae58761bc60 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java @@ -68,6 +68,12 @@ public FieldInfo newWithDifferencesFrom(FieldInfo other) { return get(kind, newFinalButWritable); } + public FieldInfo newIntersectedWith(FieldInfo other) { + assert kind.equals(other.kind); + boolean newFinalButWritable = finalButWritable && other.finalButWritable; + return get(kind, newFinalButWritable); + } + public ConfigurationMemberDeclaration getKind() { return kind; } 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 9d6a2cc84b53..5146db2c05f6 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 @@ -33,7 +33,8 @@ import java.nio.file.StandardCopyOption; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.function.Predicate; + +import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.json.JsonWriter; @@ -41,16 +42,53 @@ import com.oracle.svm.core.configure.PredefinedClassesConfigurationParser; import com.oracle.svm.core.hub.PredefinedClassesSupport; -public class PredefinedClassesConfiguration implements ConfigurationBase { +public final class PredefinedClassesConfiguration extends ConfigurationBase { private final Path[] classDestinationDirs; private final ConcurrentMap classes = new ConcurrentHashMap<>(); - private final Predicate shouldExcludeClassWithHash; + private final java.util.function.Predicate shouldExcludeClassWithHash; - public PredefinedClassesConfiguration(Path[] classDestinationDirs, Predicate shouldExcludeClassWithHash) { + public PredefinedClassesConfiguration(Path[] classDestinationDirs, java.util.function.Predicate shouldExcludeClassWithHash) { this.classDestinationDirs = classDestinationDirs; this.shouldExcludeClassWithHash = shouldExcludeClassWithHash; } + public PredefinedClassesConfiguration(PredefinedClassesConfiguration other) { + this.classDestinationDirs = other.classDestinationDirs; + classes.putAll(other.classes); + this.shouldExcludeClassWithHash = other.shouldExcludeClassWithHash; + } + + @Override + public PredefinedClassesConfiguration copy() { + return new PredefinedClassesConfiguration(this); + } + + @Override + protected void merge(PredefinedClassesConfiguration other) { + classes.putAll(other.classes); + } + + @Override + protected void subtract(PredefinedClassesConfiguration other) { + classes.keySet().removeAll(other.classes.keySet()); + } + + @Override + protected void intersect(PredefinedClassesConfiguration other) { + classes.keySet().retainAll(other.classes.keySet()); + } + + @Override + protected void removeIf(Predicate predicate) { + classes.values().removeIf(predicate::testPredefinedClass); + } + + @Override + public void mergeConditional(ConfigurationCondition condition, PredefinedClassesConfiguration other) { + /* Not implemented with conditions yet */ + classes.putAll(other.classes); + } + public void add(String nameInfo, byte[] classData) { String hash = PredefinedClassesSupport.hash(classData, 0, classData.length); if (shouldExcludeClassWithHash != null && shouldExcludeClassWithHash.test(hash)) { @@ -136,4 +174,10 @@ public boolean containsClassWithName(String className) { public boolean containsClassWithHash(String hash) { return classes.containsKey(hash); } + + public interface Predicate { + + boolean testPredefinedClass(ConfigurationPredefinedClass clazz); + + } } 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 7131b47bcb8c..8434f36188a6 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 @@ -37,7 +37,7 @@ import com.oracle.svm.configure.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; -public class ProxyConfiguration implements ConfigurationBase { +public final class ProxyConfiguration extends ConfigurationBase { private final Set>> interfaceLists = ConcurrentHashMap.newKeySet(); public ProxyConfiguration() { @@ -49,6 +49,40 @@ public ProxyConfiguration(ProxyConfiguration other) { } } + @Override + public ProxyConfiguration copy() { + return new ProxyConfiguration(this); + } + + @Override + protected void merge(ProxyConfiguration other) { + for (ConditionalElement> interfaceList : other.interfaceLists) { + interfaceLists.add(new ConditionalElement<>(interfaceList.getCondition(), new ArrayList<>(interfaceList.getElement()))); + } + } + + @Override + protected void intersect(ProxyConfiguration other) { + interfaceLists.retainAll(other.interfaceLists); + } + + @Override + protected void removeIf(Predicate predicate) { + interfaceLists.removeIf(predicate::testProxyInterfaceList); + } + + @Override + public void subtract(ProxyConfiguration other) { + interfaceLists.removeAll(other.interfaceLists); + } + + @Override + public void mergeConditional(ConfigurationCondition condition, ProxyConfiguration other) { + for (ConditionalElement> interfaceList : other.interfaceLists) { + add(condition, new ArrayList<>(interfaceList.getElement())); + } + } + public void add(ConfigurationCondition condition, List interfaceList) { interfaceLists.add(new ConditionalElement<>(condition, interfaceList)); } @@ -61,10 +95,6 @@ public boolean contains(ConfigurationCondition condition, String... interfaces) return contains(condition, Arrays.asList(interfaces)); } - public void removeAll(ProxyConfiguration other) { - interfaceLists.removeAll(other.interfaceLists); - } - @Override public void printJson(JsonWriter writer) throws IOException { List>> lists = new ArrayList<>(interfaceLists.size()); @@ -107,4 +137,9 @@ private static > int compareList(List l1, List l2) return l1.size() - l2.size(); } + public interface Predicate { + + boolean testProxyInterfaceList(ConditionalElement> conditionalElement); + + } } 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 88dc4eebe357..3be0e9f304d7 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 @@ -29,6 +29,7 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -42,7 +43,7 @@ import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ResourcesRegistry; -public class ResourceConfiguration implements ConfigurationBase { +public final class ResourceConfiguration extends ConfigurationBase { private static final String PROPERTY_BUNDLE = "java.util.PropertyResourceBundle"; @@ -80,7 +81,7 @@ public void addClassBasedResourceBundle(ConfigurationCondition condition, String } } - private static final class BundleConfiguration { + public static final class BundleConfiguration { public final ConfigurationCondition condition; public final String baseName; public final Set locales = ConcurrentHashMap.newKeySet(); @@ -90,6 +91,12 @@ private BundleConfiguration(ConfigurationCondition condition, String baseName) { this.condition = condition; this.baseName = baseName; } + + private BundleConfiguration(BundleConfiguration other) { + this(other.condition, other.baseName); + locales.addAll(other.locales); + classNames.addAll(other.classNames); + } } private final ConcurrentMap, Pattern> addedResources = new ConcurrentHashMap<>(); @@ -102,15 +109,56 @@ public ResourceConfiguration() { public ResourceConfiguration(ResourceConfiguration other) { addedResources.putAll(other.addedResources); ignoredResources.putAll(other.ignoredResources); - bundles.putAll(other.bundles); + for (Map.Entry, BundleConfiguration> entry : other.bundles.entrySet()) { + bundles.put(entry.getKey(), new BundleConfiguration(entry.getValue())); + } } - public void removeAll(ResourceConfiguration other) { + @Override + public ResourceConfiguration copy() { + return new ResourceConfiguration(this); + } + + @Override + public void subtract(ResourceConfiguration other) { addedResources.keySet().removeAll(other.addedResources.keySet()); ignoredResources.keySet().removeAll(other.ignoredResources.keySet()); bundles.keySet().removeAll(other.bundles.keySet()); } + @Override + protected void merge(ResourceConfiguration other) { + addedResources.putAll(other.addedResources); + ignoredResources.putAll(other.ignoredResources); + bundles.putAll(other.bundles); + } + + @Override + protected void intersect(ResourceConfiguration other) { + addedResources.keySet().retainAll(other.addedResources.keySet()); + ignoredResources.keySet().retainAll(other.ignoredResources.keySet()); + bundles.keySet().retainAll(other.bundles.keySet()); + } + + @Override + protected void removeIf(Predicate predicate) { + addedResources.entrySet().removeIf(entry -> predicate.testIncludedResource(entry.getKey(), entry.getValue())); + bundles.entrySet().removeIf(entry -> predicate.testIncludedBundle(entry.getKey(), entry.getValue())); + } + + @Override + public void mergeConditional(ConfigurationCondition condition, ResourceConfiguration other) { + for (Map.Entry, Pattern> entry : other.addedResources.entrySet()) { + addedResources.put(new ConditionalElement<>(condition, entry.getKey().getElement()), entry.getValue()); + } + for (Map.Entry, Pattern> entry : other.ignoredResources.entrySet()) { + ignoredResources.put(new ConditionalElement<>(condition, entry.getKey().getElement()), entry.getValue()); + } + for (Map.Entry, BundleConfiguration> entry : other.bundles.entrySet()) { + bundles.put(new ConditionalElement<>(condition, entry.getKey().getElement()), new BundleConfiguration(entry.getValue())); + } + } + public void addResourcePattern(ConfigurationCondition condition, String pattern) { addedResources.computeIfAbsent(new ConditionalElement<>(condition, pattern), p -> Pattern.compile(p.getElement())); } @@ -218,4 +266,10 @@ private static void conditionalElementJson(ConditionalElement p, JsonWri w.quote(elementName).append(':').quote(p.getElement()); w.unindent().newline().append('}'); } + + public interface Predicate { + boolean testIncludedResource(ConditionalElement condition, Pattern pattern); + + boolean testIncludedBundle(ConditionalElement condition, BundleConfiguration bundleConfiguration); + } } 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 103caed8e044..3d019e5f5949 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 @@ -40,7 +40,8 @@ import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.json.JsonWriter; -public class SerializationConfiguration implements ConfigurationBase, RuntimeSerializationSupport { +public final class SerializationConfiguration extends ConfigurationBase + implements RuntimeSerializationSupport { private final Set serializations = ConcurrentHashMap.newKeySet(); private final Set lambdaSerializationCapturingTypes = ConcurrentHashMap.newKeySet(); @@ -53,11 +54,42 @@ public SerializationConfiguration(SerializationConfiguration other) { lambdaSerializationCapturingTypes.addAll(other.lambdaSerializationCapturingTypes); } - public void removeAll(SerializationConfiguration other) { + @Override + public SerializationConfiguration copy() { + return new SerializationConfiguration(this); + } + + @Override + protected void merge(SerializationConfiguration other) { + serializations.addAll(other.serializations); + lambdaSerializationCapturingTypes.addAll(other.lambdaSerializationCapturingTypes); + } + + @Override + public void subtract(SerializationConfiguration other) { serializations.removeAll(other.serializations); lambdaSerializationCapturingTypes.removeAll(other.lambdaSerializationCapturingTypes); } + @Override + protected void intersect(SerializationConfiguration other) { + serializations.retainAll(other.serializations); + lambdaSerializationCapturingTypes.retainAll(other.lambdaSerializationCapturingTypes); + } + + @Override + protected void removeIf(Predicate predicate) { + serializations.removeIf(predicate::testSerializationType); + lambdaSerializationCapturingTypes.removeIf(predicate::testLambdaSerializationType); + } + + @Override + public void mergeConditional(ConfigurationCondition condition, SerializationConfiguration other) { + for (SerializationConfigurationType type : other.serializations) { + serializations.add(new SerializationConfigurationType(condition, type.getQualifiedJavaName(), type.getQualifiedCustomTargetConstructorJavaName())); + } + } + public boolean contains(ConfigurationCondition condition, String serializationTargetClass, String customTargetConstructorClass) { return serializations.contains(createConfigurationType(condition, serializationTargetClass, customTargetConstructorClass)) || lambdaSerializationCapturingTypes.contains(createLambdaCapturingClassConfigurationType(condition, serializationTargetClass)); @@ -140,4 +172,11 @@ private static SerializationConfigurationLambdaCapturingType createLambdaCapturi String convertedClassName = SignatureUtil.toInternalClassName(className); return new SerializationConfigurationLambdaCapturingType(condition, convertedClassName); } + + public interface Predicate { + + boolean testSerializationType(SerializationConfigurationType type); + + boolean testLambdaSerializationType(SerializationConfigurationLambdaCapturingType type); + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationLambdaCapturingType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationLambdaCapturingType.java index 670b1484f07a..c5dca03af43b 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationLambdaCapturingType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationLambdaCapturingType.java @@ -46,6 +46,14 @@ public SerializationConfigurationLambdaCapturingType(ConfigurationCondition cond this.qualifiedJavaName = qualifiedJavaName; } + public ConfigurationCondition getCondition() { + return condition; + } + + public String getQualifiedJavaName() { + return qualifiedJavaName; + } + @Override public void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java index d6b6bcdb44e2..95c9f122c6c6 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfigurationType.java @@ -51,6 +51,18 @@ public SerializationConfigurationType(ConfigurationCondition condition, String q this.qualifiedCustomTargetConstructorJavaName = qualifiedCustomTargetConstructorJavaName; } + public String getQualifiedJavaName() { + return qualifiedJavaName; + } + + public String getQualifiedCustomTargetConstructorJavaName() { + return qualifiedCustomTargetConstructorJavaName; + } + + public ConfigurationCondition getCondition() { + return condition; + } + @Override public void printJson(JsonWriter writer) throws IOException { writer.append('{').indent().newline(); 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 6fdf91c2da0c..19e3ed6ae671 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 @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -38,21 +39,58 @@ import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.util.VMError; -public class TypeConfiguration implements ConfigurationBase { - public static TypeConfiguration copyAndSubtract(TypeConfiguration config, TypeConfiguration toSubtract) { - TypeConfiguration copy = new TypeConfiguration(); - config.types.forEach((key, type) -> { - ConfigurationType subtractType = toSubtract.types.get(key); - copy.types.compute(key, (k, v) -> ConfigurationType.copyAndSubtract(type, subtractType)); - }); - return copy; - } +public final class TypeConfiguration extends ConfigurationBase { private final ConcurrentMap, ConfigurationType> types = new ConcurrentHashMap<>(); public TypeConfiguration() { } + public TypeConfiguration(TypeConfiguration other) { + other.types.forEach((key, value) -> types.put(key, new ConfigurationType(value))); + } + + @Override + public TypeConfiguration copy() { + return new TypeConfiguration(this); + } + + @Override + protected void merge(TypeConfiguration other) { + other.types.forEach((key, value) -> { + types.compute(key, (k, v) -> { + if (v != null) { + return ConfigurationType.copyAndMerge(v, value); + } + return value; + }); + }); + } + + @Override + public void subtract(TypeConfiguration other) { + other.types.forEach((key, type) -> { + types.computeIfPresent(key, (k, v) -> ConfigurationType.copyAndSubtract(v, type)); + }); + } + + @Override + protected void intersect(TypeConfiguration other) { + types.forEach((key, type) -> { + ConfigurationType intersectedType = other.types.get(key); + if (intersectedType != null) { + types.compute(key, (k, v) -> ConfigurationType.copyAndIntersect(type, intersectedType)); + } else { + types.remove(key); + } + }); + } + + @Override + protected void removeIf(Predicate predicate) { + types.entrySet().removeIf(entry -> predicate.testIncludedType(entry.getKey(), entry.getValue())); + } + public ConfigurationType get(ConfigurationCondition condition, String qualifiedJavaName) { return types.get(new ConditionalElement<>(condition, qualifiedJavaName)); } @@ -64,10 +102,28 @@ public void add(ConfigurationType type) { } } + public void addOrMerge(ConfigurationType type) { + types.compute(new ConditionalElement<>(type.getCondition(), type.getQualifiedJavaName()), (key, value) -> { + if (value == null) { + return type; + } else { + value.mergeFrom(type); + return value; + } + }); + } + public ConfigurationType getOrCreateType(ConfigurationCondition condition, String qualifiedForNameString) { return types.computeIfAbsent(new ConditionalElement<>(condition, qualifiedForNameString), p -> new ConfigurationType(p.getCondition(), p.getElement())); } + @Override + public void mergeConditional(ConfigurationCondition condition, TypeConfiguration other) { + other.types.forEach((key, value) -> { + addOrMerge(new ConfigurationType(value, condition)); + }); + } + @Override public void printJson(JsonWriter writer) throws IOException { List typesList = new ArrayList<>(this.types.values()); @@ -88,4 +144,26 @@ public boolean isEmpty() { return types.isEmpty(); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TypeConfiguration that = (TypeConfiguration) o; + return types.equals(that.types); + } + + @Override + public int hashCode() { + return Objects.hash(types); + } + + public interface Predicate { + + boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type); + + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ComplexFilter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ComplexFilter.java new file mode 100644 index 000000000000..51c30b19f9be --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ComplexFilter.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.configure.filters; + +import com.oracle.svm.configure.json.JsonWriter; + +import java.io.IOException; +import java.util.Map; + +public class ComplexFilter implements ConfigurationFilter { + private final HierarchyFilterNode hierarchyFilterNode; + private final RegexFilter regexFilter = new RegexFilter(); + + public ComplexFilter(HierarchyFilterNode hierarchyFilterNode) { + this.hierarchyFilterNode = hierarchyFilterNode; + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.append('{'); + writer.indent().newline(); + hierarchyFilterNode.printJson(writer); + regexFilter.printJson(writer); + writer.unindent().newline(); + writer.append('}').newline(); + } + + @Override + public void parseFromJson(Map topJsonObject) { + hierarchyFilterNode.parseFromJson(topJsonObject); + regexFilter.parseFromJson(topJsonObject); + } + + @Override + public boolean includes(String qualifiedName) { + return hierarchyFilterNode.includes(qualifiedName) && regexFilter.includes(qualifiedName); + } + + public HierarchyFilterNode getHierarchyFilterNode() { + return hierarchyFilterNode; + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ConfigurationFilter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ConfigurationFilter.java new file mode 100644 index 000000000000..99482ea03e3a --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ConfigurationFilter.java @@ -0,0 +1,57 @@ +/* + * 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.filters; + +import java.util.Map; + +import com.oracle.svm.configure.json.JsonPrintable; + +public interface ConfigurationFilter extends JsonPrintable { + + void parseFromJson(Map topJsonObject); + + boolean includes(String qualifiedName); + + /** Inclusion status of a filter. */ + enum Inclusion { + Include("+"), + Exclude("-"); + + final String s; + + Inclusion(String s) { + this.s = s; + } + + @Override + public String toString() { + return s; + } + + public Inclusion invert() { + return (this == Include) ? Exclude : Include; + } + } +} 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 baf4d2186ac1..6a4e409076ba 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 @@ -26,49 +26,27 @@ import java.io.IOException; import java.io.Reader; -import java.util.List; 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 { - private final RuleNode rootNode; + private final ConfigurationFilter filter; - public FilterConfigurationParser(RuleNode rootNode) { + public FilterConfigurationParser(ConfigurationFilter filter) { super(true); - assert rootNode != null; - this.rootNode = rootNode; + assert filter != null; + this.filter = filter; } - @Override - public void parseAndRegister(Reader reader) throws IOException { - Object json = new JSONParser(reader).parse(); - parseTopLevelObject(asMap(json, "First level of document must be an object")); - } - - private void parseTopLevelObject(Map top) { - Object rulesObject = null; - for (Map.Entry pair : top.entrySet()) { - if ("rules".equals(pair.getKey())) { - rulesObject = pair.getValue(); - } else { - throw new JSONParserException("Unknown attribute '" + pair.getKey() + "' (supported attributes: name) in resource definition"); - } - } - if (rulesObject != null) { - List rulesList = asList(rulesObject, "Attribute 'list' must be a list of rule objects"); - for (Object entryObject : rulesList) { - parseEntry(entryObject); - } - } - } - - private void parseEntry(Object entryObject) { + static void parseEntry(Object entryObject, BiConsumer parsedEntryConsumer) { Map entry = asMap(entryObject, "Filter entries must be objects"); Object qualified = null; - RuleNode.Inclusion inclusion = null; + HierarchyFilterNode.Inclusion inclusion = null; String exactlyOneMessage = "Exactly one of attributes 'includeClasses' and 'excludeClasses' must be specified for a filter entry"; for (Map.Entry pair : entry.entrySet()) { if (qualified != null) { @@ -76,9 +54,9 @@ private void parseEntry(Object entryObject) { } qualified = pair.getValue(); if ("includeClasses".equals(pair.getKey())) { - inclusion = RuleNode.Inclusion.Include; + inclusion = ConfigurationFilter.Inclusion.Include; } else if ("excludeClasses".equals(pair.getKey())) { - inclusion = RuleNode.Inclusion.Exclude; + inclusion = ConfigurationFilter.Inclusion.Exclude; } else { throw new JSONParserException("Unknown attribute '" + pair.getKey() + "' (supported attributes: 'includeClasses', 'excludeClasses') in filter"); } @@ -86,6 +64,38 @@ private void parseEntry(Object entryObject) { if (qualified == null) { throw new JSONParserException(exactlyOneMessage); } - rootNode.addOrGetChildren(asString(qualified), inclusion); + parsedEntryConsumer.accept(asString(qualified), inclusion); } + + static void printEntry(JsonWriter writer, boolean[] isFirstRule, ConfigurationFilter.Inclusion inclusion, String rule) throws IOException { + if (inclusion == null) { + return; + } + if (!isFirstRule[0]) { + writer.append(',').newline(); + } else { + isFirstRule[0] = false; + } + writer.append('{'); + switch (inclusion) { + case Include: + writer.quote("includeClasses"); + break; + case Exclude: + writer.quote("excludeClasses"); + break; + default: + throw new IllegalStateException("Unsupported inclusion value: " + inclusion.name()); + } + writer.append(':'); + writer.quote(rule); + writer.append("}"); + } + + @Override + public void parseAndRegister(Reader reader) throws IOException { + Object json = new JSONParser(reader).parse(); + 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/filters/RuleNode.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/HierarchyFilterNode.java similarity index 77% rename from substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RuleNode.java rename to substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/HierarchyFilterNode.java index 86037e030856..c1b0d2b4ca56 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RuleNode.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/HierarchyFilterNode.java @@ -25,18 +25,19 @@ package com.oracle.svm.configure.filters; import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.StringTokenizer; import com.oracle.svm.configure.json.JsonWriter; +import static com.oracle.svm.core.configure.ConfigurationParser.asList; + /** Represents a rule that includes or excludes a set of Java classes. */ -public final class RuleNode { +public final class HierarchyFilterNode implements ConfigurationFilter { /** Everything that is not included is considered excluded. */ private static final Inclusion DEFAULT_INCLUSION = Inclusion.Exclude; @@ -44,31 +45,10 @@ public final class RuleNode { private static final String CHILDREN_PATTERN = "*"; private static final String DESCENDANTS_PATTERN = "**"; - /** Inclusion status of a {@link RuleNode}. */ - public enum Inclusion { - Include("+"), - Exclude("-"); - - final String s; - - Inclusion(String s) { - this.s = s; - } - - @Override - public String toString() { - return s; - } - - public Inclusion invert() { - return (this == Include) ? Exclude : Include; - } - } - /** The non-qualified name. The qualified name is derived from the names of all parents. */ private final String name; - /** Inclusion for the exact qualified name when queried via {@link #treeIncludes}. */ + /** Inclusion for the exact qualified name when queried via {@link #includes}. */ private Inclusion inclusion; /** Inclusion for immediate children except those in {@link #children}. */ @@ -78,13 +58,19 @@ public Inclusion invert() { private Inclusion descendantsInclusion; /** Explicit rules for all immediate children. */ - private Map children; + private Map children; - public static RuleNode createRoot() { - return new RuleNode(""); + public static HierarchyFilterNode createRoot() { + return new HierarchyFilterNode(""); } - private RuleNode(String unqualifiedName) { + public static HierarchyFilterNode createInclusiveRoot() { + HierarchyFilterNode root = new HierarchyFilterNode(""); + root.addOrGetChildren("**", ConfigurationFilter.Inclusion.Include); + return root; + } + + private HierarchyFilterNode(String unqualifiedName) { this.name = unqualifiedName; } @@ -92,7 +78,7 @@ public void addOrGetChildren(String qualifiedName, Inclusion leafInclusion) { if (leafInclusion != Inclusion.Include && leafInclusion != Inclusion.Exclude) { throw new IllegalArgumentException(leafInclusion + " not supported"); } - RuleNode parent = this; + HierarchyFilterNode parent = this; StringTokenizer tokenizer = new StringTokenizer(qualifiedName, ".", false); /* * We split the qualified name, such as "com.sun.crypto.**", into its individual tokens. @@ -125,7 +111,7 @@ public void addOrGetChildren(String qualifiedName, Inclusion leafInclusion) { } } } else { - RuleNode node = null; + HierarchyFilterNode node = null; if (parent.children != null) { node = parent.children.get(token); } else { @@ -136,7 +122,7 @@ public void addOrGetChildren(String qualifiedName, Inclusion leafInclusion) { node.inclusion = leafInclusion; } } else { - node = new RuleNode(token); + node = new HierarchyFilterNode(token); node.inclusion = isLeaf ? leafInclusion : null; parent.children.put(token, node); } @@ -157,7 +143,7 @@ private boolean isLeafNode() { */ public void reduceExhaustiveTree() { if (children != null) { - for (RuleNode child : children.values()) { + for (HierarchyFilterNode child : children.values()) { child.reduceExhaustiveTree0(); } } @@ -170,7 +156,7 @@ private int reduceExhaustiveTree0() { } if (children != null) { int sum = 0; - for (RuleNode child : children.values()) { + for (HierarchyFilterNode child : children.values()) { sum += child.reduceExhaustiveTree0(); } if (descendantsInclusion == null) { @@ -226,16 +212,23 @@ private boolean isRedundantLeaf() { return inclusion == null && childrenInclusion == null && descendantsInclusion == null && isLeafNode(); } - public void printJsonTree(OutputStream out) throws IOException { - try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(out))) { - writer.append('{'); - writer.indent().newline(); - writer.quote("rules").append(": [").indent().newline(); - final boolean[] isFirstRule = {true}; - printJsonEntries(writer, isFirstRule, ""); - writer.unindent().newline(); - writer.append(']').unindent().newline(); - writer.append('}').newline(); + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.quote("rules").append(": [").indent().newline(); + final boolean[] isFirstRule = {true}; + printJsonEntries(writer, isFirstRule, ""); + writer.unindent().newline(); + writer.append(']'); + } + + @Override + public void parseFromJson(Map top) { + Object rulesObject = top.get("rules"); + if (rulesObject != null) { + List rulesList = asList(rulesObject, "Attribute 'list' must be a list of rule objects"); + for (Object entryObject : rulesList) { + FilterConfigurationParser.parseEntry(entryObject, this::addOrGetChildren); + } } } @@ -243,45 +236,21 @@ private void printJsonEntries(JsonWriter writer, boolean[] isFirstRule, String p String qualified = parentQualified.isEmpty() ? name : (parentQualified + '.' + name); // NOTE: the order in which these rules are printed is important! String patternBegin = qualified.isEmpty() ? qualified : (qualified + "."); - printJsonRule(writer, isFirstRule, patternBegin + DESCENDANTS_PATTERN, descendantsInclusion); - printJsonRule(writer, isFirstRule, patternBegin + CHILDREN_PATTERN, childrenInclusion); - printJsonRule(writer, isFirstRule, qualified, inclusion); + FilterConfigurationParser.printEntry(writer, isFirstRule, descendantsInclusion, patternBegin + DESCENDANTS_PATTERN); + FilterConfigurationParser.printEntry(writer, isFirstRule, childrenInclusion, patternBegin + CHILDREN_PATTERN); + FilterConfigurationParser.printEntry(writer, isFirstRule, inclusion, qualified); if (children != null) { - RuleNode[] sorted = children.values().toArray(new RuleNode[0]); + HierarchyFilterNode[] sorted = children.values().toArray(new HierarchyFilterNode[0]); Arrays.sort(sorted, Comparator.comparing(child -> child.name)); - for (RuleNode child : sorted) { + for (HierarchyFilterNode child : sorted) { child.printJsonEntries(writer, isFirstRule, qualified); } } } - private static void printJsonRule(JsonWriter writer, boolean[] isFirstRule, String qualified, Inclusion inclusion) throws IOException { - if (inclusion == null) { - return; - } - if (!isFirstRule[0]) { - writer.append(',').newline(); - } else { - isFirstRule[0] = false; - } - writer.append('{'); - switch (inclusion) { - case Include: - writer.quote("includeClasses"); - break; - case Exclude: - writer.quote("excludeClasses"); - break; - default: - throw new IllegalStateException("Unsupported inclusion value: " + inclusion.name()); - } - writer.append(':'); - writer.quote(qualified); - writer.append("}"); - } - - public boolean treeIncludes(String qualifiedName) { - RuleNode current = this; + @Override + public boolean includes(String qualifiedName) { + HierarchyFilterNode current = this; Inclusion inheritedInclusion = DEFAULT_INCLUSION; StringTokenizer tokenizer = new StringTokenizer(qualifiedName, ".", false); while (tokenizer.hasMoreTokens()) { @@ -289,7 +258,7 @@ public boolean treeIncludes(String qualifiedName) { inheritedInclusion = current.descendantsInclusion; } String part = tokenizer.nextToken(); - RuleNode child = (current.children != null) ? current.children.get(part) : null; + HierarchyFilterNode child = (current.children != null) ? current.children.get(part) : null; if (child == null) { boolean isDirectChild = !tokenizer.hasMoreTokens(); if (isDirectChild && current.childrenInclusion != null) { @@ -302,15 +271,15 @@ public boolean treeIncludes(String qualifiedName) { return (current.inclusion == Inclusion.Include); } - public RuleNode copy() { - RuleNode copy = new RuleNode(name); + public HierarchyFilterNode copy() { + HierarchyFilterNode copy = new HierarchyFilterNode(name); copy.inclusion = inclusion; copy.childrenInclusion = childrenInclusion; copy.descendantsInclusion = descendantsInclusion; if (children != null) { copy.children = new HashMap<>(); - for (Map.Entry entry : children.entrySet()) { - RuleNode childCopy = entry.getValue().copy(); + for (Map.Entry entry : children.entrySet()) { + HierarchyFilterNode childCopy = entry.getValue().copy(); copy.children.put(entry.getKey(), childCopy); } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ModuleFilterTools.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ModuleFilterTools.java index 0375f216adbc..079f50d64ad2 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ModuleFilterTools.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ModuleFilterTools.java @@ -29,11 +29,11 @@ import java.util.HashSet; import java.util.Set; -import com.oracle.svm.configure.filters.RuleNode.Inclusion; +import com.oracle.svm.configure.filters.ConfigurationFilter.Inclusion; public class ModuleFilterTools { - public static RuleNode generateFromModules(String[] moduleNames, Inclusion rootInclusion, Inclusion exportedInclusion, Inclusion unexportedInclusion, boolean reduce) { + public static HierarchyFilterNode generateFromModules(String[] moduleNames, Inclusion rootInclusion, Inclusion exportedInclusion, Inclusion unexportedInclusion, boolean reduce) { Set includedModuleNameSet = new HashSet<>(); Collections.addAll(includedModuleNameSet, moduleNames); for (Module module : ModuleLayer.boot().modules()) { @@ -41,7 +41,7 @@ public static RuleNode generateFromModules(String[] moduleNames, Inclusion rootI checkDependencies(module, includedModuleNameSet); } } - RuleNode rootNode = RuleNode.createRoot(); + HierarchyFilterNode rootNode = HierarchyFilterNode.createRoot(); rootNode.addOrGetChildren("**", rootInclusion); for (Module module : ModuleLayer.boot().modules()) { for (String qualifiedPkg : module.getPackages()) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RegexFilter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RegexFilter.java new file mode 100644 index 000000000000..a62d12274438 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RegexFilter.java @@ -0,0 +1,107 @@ +/* + * 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.filters; + +import static com.oracle.svm.core.configure.ConfigurationParser.asList; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import com.oracle.svm.configure.json.JsonWriter; + +public class RegexFilter implements ConfigurationFilter { + + private final Pattern[][] regexPatterns = new Pattern[Inclusion.values().length][]; + + public RegexFilter() { + for (Inclusion inclusion : Inclusion.values()) { + regexPatterns[inclusion.ordinal()] = new Pattern[0]; + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.quote("regexRules").append(": [").indent().newline(); + + boolean[] first = {true}; + for (Inclusion inclusion : Inclusion.values()) { + for (Pattern pattern : regexPatterns[inclusion.ordinal()]) { + FilterConfigurationParser.printEntry(writer, first, inclusion, pattern.pattern()); + } + } + + writer.unindent().newline(); + writer.append("]"); + writer.unindent().newline(); + writer.append("}"); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void parseFromJson(Map topJsonObject) { + Object regexRules = topJsonObject.get("regexRules"); + if (regexRules != null) { + List patternList = asList(regexRules, "Field 'regexRules' must be a list of objects."); + List[] patterns = new List[Inclusion.values().length]; + for (Inclusion inclusion : Inclusion.values()) { + patterns[inclusion.ordinal()] = new ArrayList<>(Arrays.asList(regexPatterns[inclusion.ordinal()])); + } + + for (Object patternObject : patternList) { + FilterConfigurationParser.parseEntry(patternObject, (pattern, inclusion) -> patterns[inclusion.ordinal()].add(Pattern.compile(pattern))); + } + + for (Inclusion inclusion : Inclusion.values()) { + regexPatterns[inclusion.ordinal()] = patterns[inclusion.ordinal()].toArray(new Pattern[0]); + } + } + } + + private boolean matchesForInclusion(Inclusion inclusion, String qualifiedName) { + for (Pattern p : regexPatterns[inclusion.ordinal()]) { + if (p.matcher(qualifiedName).matches()) { + return true; + } + } + return false; + } + + @Override + public boolean includes(String qualifiedName) { + if (regexPatterns[Inclusion.Include.ordinal()].length != 0) { + if (!matchesForInclusion(Inclusion.Include, qualifiedName)) { + return false; + } + } + if (regexPatterns[Inclusion.Exclude.ordinal()].length != 0) { + return !matchesForInclusion(Inclusion.Exclude, qualifiedName); + } + return true; + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java index 7e00e814b4c0..13d89d13b474 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AbstractProcessor.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.configure.trace; +import com.oracle.svm.configure.config.ConfigurationSet; + import java.util.Base64; import java.util.Collection; import java.util.List; @@ -33,7 +35,7 @@ public abstract class AbstractProcessor { AbstractProcessor() { } - abstract void processEntry(Map entry); + abstract void processEntry(Map entry, ConfigurationSet configurationSet); void setInLivePhase(@SuppressWarnings("unused") boolean live) { } 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 774030c5f085..c594694d4be6 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 @@ -28,7 +28,8 @@ import org.graalvm.compiler.phases.common.LazyValue; -import com.oracle.svm.configure.filters.RuleNode; +import com.oracle.svm.configure.filters.ConfigurationFilter; +import com.oracle.svm.configure.filters.HierarchyFilterNode; /** * Decides if a recorded access should be included in a configuration. Also advises the agent's @@ -43,70 +44,68 @@ public final class AccessAdvisor { public static final Pattern PROXY_CLASS_NAME_PATTERN = Pattern.compile("^(.+[/.])?\\$Proxy[0-9]+$"); /** Filter to ignore accesses that originate in methods of these internal classes. */ - private static final RuleNode internalCallerFilter; + private static final HierarchyFilterNode internalCallerFilter; /** Filter to unconditionally ignore accesses of these classes and their members. */ - private static final RuleNode internalAccessFilter; + private static final HierarchyFilterNode internalAccessFilter; /** * Filter to ignore accesses of these classes and their members when the caller * (accessing class) is unknown. Used in addition to {@link #accessFilter}, not instead. */ - private static final RuleNode accessWithoutCallerFilter; + private static final HierarchyFilterNode accessWithoutCallerFilter; static { - internalCallerFilter = RuleNode.createRoot(); - internalCallerFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); - - internalCallerFilter.addOrGetChildren("com.sun.crypto.provider.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("com.sun.java.util.jar.pack.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("com.sun.net.ssl.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("com.sun.nio.file.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("com.sun.nio.sctp.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("com.sun.nio.zipfs.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("java.io.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("java.lang.**", RuleNode.Inclusion.Exclude); + internalCallerFilter = HierarchyFilterNode.createInclusiveRoot(); + + internalCallerFilter.addOrGetChildren("com.sun.crypto.provider.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("com.sun.java.util.jar.pack.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("com.sun.net.ssl.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("com.sun.nio.file.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("com.sun.nio.sctp.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("com.sun.nio.zipfs.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.io.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.lang.**", ConfigurationFilter.Inclusion.Exclude); // The agent should not filter calls from native libraries (JDK11). - internalCallerFilter.addOrGetChildren("java.lang.ClassLoader$NativeLibrary", RuleNode.Inclusion.Include); - internalCallerFilter.addOrGetChildren("java.math.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("java.net.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("java.nio.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("java.text.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("java.time.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("java.util.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("javax.crypto.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("javax.lang.model.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("javax.net.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("javax.tools.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("jdk.internal.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.lang.ClassLoader$NativeLibrary", ConfigurationFilter.Inclusion.Include); + internalCallerFilter.addOrGetChildren("java.math.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.net.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.nio.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.text.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.time.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("java.util.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("javax.crypto.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("javax.lang.model.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("javax.net.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("javax.tools.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("jdk.internal.**", ConfigurationFilter.Inclusion.Exclude); // The agent should not filter calls from native libraries (JDK17). - internalCallerFilter.addOrGetChildren("jdk.internal.loader.NativeLibraries$NativeLibraryImpl", RuleNode.Inclusion.Include); - internalCallerFilter.addOrGetChildren("jdk.jfr.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("jdk.net.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("jdk.nio.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("jdk.vm.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.invoke.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.launcher.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.misc.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.net.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.nio.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.reflect.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.text.**", RuleNode.Inclusion.Exclude); - internalCallerFilter.addOrGetChildren("sun.util.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("jdk.internal.loader.NativeLibraries$NativeLibraryImpl", ConfigurationFilter.Inclusion.Include); + internalCallerFilter.addOrGetChildren("jdk.jfr.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("jdk.net.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("jdk.nio.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("jdk.vm.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.invoke.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.launcher.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.misc.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.net.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.nio.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.reflect.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.text.**", ConfigurationFilter.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("sun.util.**", ConfigurationFilter.Inclusion.Exclude); excludeInaccessiblePackages(internalCallerFilter); internalCallerFilter.removeRedundantNodes(); - internalAccessFilter = RuleNode.createRoot(); - internalAccessFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); + internalAccessFilter = HierarchyFilterNode.createInclusiveRoot(); excludeInaccessiblePackages(internalAccessFilter); internalAccessFilter.removeRedundantNodes(); - accessWithoutCallerFilter = RuleNode.createRoot(); // in addition to accessFilter - accessWithoutCallerFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); - accessWithoutCallerFilter.addOrGetChildren("jdk.vm.ci.**", RuleNode.Inclusion.Exclude); - accessWithoutCallerFilter.addOrGetChildren("[Ljava.lang.String;", RuleNode.Inclusion.Exclude); + 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 accessWithoutCallerFilter.removeRedundantNodes(); } @@ -116,23 +115,23 @@ public final class AccessAdvisor { * by their module and should not be accessible from application code. Generate all with: * native-image-configure generate-filters --exclude-unexported-packages-from-modules [--reduce] */ - private static void excludeInaccessiblePackages(RuleNode rootNode) { - rootNode.addOrGetChildren("com.oracle.graal.**", RuleNode.Inclusion.Exclude); - rootNode.addOrGetChildren("com.oracle.truffle.**", RuleNode.Inclusion.Exclude); - rootNode.addOrGetChildren("org.graalvm.compiler.**", RuleNode.Inclusion.Exclude); - rootNode.addOrGetChildren("org.graalvm.libgraal.**", RuleNode.Inclusion.Exclude); + private static void excludeInaccessiblePackages(HierarchyFilterNode rootNode) { + rootNode.addOrGetChildren("com.oracle.graal.**", ConfigurationFilter.Inclusion.Exclude); + rootNode.addOrGetChildren("com.oracle.truffle.**", ConfigurationFilter.Inclusion.Exclude); + rootNode.addOrGetChildren("org.graalvm.compiler.**", ConfigurationFilter.Inclusion.Exclude); + rootNode.addOrGetChildren("org.graalvm.libgraal.**", ConfigurationFilter.Inclusion.Exclude); } - public static RuleNode copyBuiltinCallerFilterTree() { + public static HierarchyFilterNode copyBuiltinCallerFilterTree() { return internalCallerFilter.copy(); } - public static RuleNode copyBuiltinAccessFilterTree() { + public static HierarchyFilterNode copyBuiltinAccessFilterTree() { return internalAccessFilter.copy(); } - private RuleNode callerFilter = internalCallerFilter; - private RuleNode accessFilter = internalAccessFilter; + private ConfigurationFilter callerFilter = internalCallerFilter; + private ConfigurationFilter accessFilter = internalAccessFilter; private boolean heuristicsEnabled = true; private boolean isInLivePhase = false; private int launchPhase = 0; @@ -141,11 +140,11 @@ public void setHeuristicsEnabled(boolean enable) { heuristicsEnabled = enable; } - public void setCallerFilterTree(RuleNode rootNode) { + public void setCallerFilterTree(ConfigurationFilter rootNode) { callerFilter = rootNode; } - public void setAccessFilterTree(RuleNode rootNode) { + public void setAccessFilterTree(ConfigurationFilter rootNode) { accessFilter = rootNode; } @@ -159,14 +158,14 @@ public boolean shouldIgnore(LazyValue queriedClass, LazyValue ca } String qualifiedCaller = callerClass.get(); assert qualifiedCaller == null || qualifiedCaller.indexOf('/') == -1 : "expecting Java-format qualifiers, not internal format"; - if (qualifiedCaller != null && !callerFilter.treeIncludes(qualifiedCaller)) { + if (qualifiedCaller != null && !callerFilter.includes(qualifiedCaller)) { return true; } // NOTE: queriedClass can be null for non-class accesses like resources - if (callerClass.get() == null && queriedClass.get() != null && !accessWithoutCallerFilter.treeIncludes(queriedClass.get())) { + if (callerClass.get() == null && queriedClass.get() != null && !accessWithoutCallerFilter.includes(queriedClass.get())) { return true; } - if (accessFilter != null && queriedClass.get() != null && !accessFilter.treeIncludes(queriedClass.get())) { + if (accessFilter != null && queriedClass.get() != null && !accessFilter.includes(queriedClass.get())) { return true; } return heuristicsEnabled && queriedClass.get() != null && PROXY_CLASS_NAME_PATTERN.matcher(queriedClass.get()).matches(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ClassLoadingProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ClassLoadingProcessor.java index 270ef1897178..49e88c4e1c7b 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ClassLoadingProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ClassLoadingProcessor.java @@ -28,21 +28,12 @@ import java.util.List; import java.util.Map; -import com.oracle.svm.configure.config.PredefinedClassesConfiguration; +import com.oracle.svm.configure.config.ConfigurationSet; public class ClassLoadingProcessor extends AbstractProcessor { - private final PredefinedClassesConfiguration configuration; - - public ClassLoadingProcessor(PredefinedClassesConfiguration configuration) { - this.configuration = configuration; - } - - public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { - return configuration; - } @Override - void processEntry(Map entry) { + void processEntry(Map entry, ConfigurationSet configurationSet) { boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); if (invalidResult) { return; @@ -54,7 +45,7 @@ void processEntry(Map entry) { expectSize(args, 2); String nameInfo = (String) args.get(0); byte[] classData = asBinary(args.get(1)); - configuration.add(nameInfo, classData); + configurationSet.getPredefinedClassesConfiguration().add(nameInfo, classData); } } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java index 1f0ba777fcef..d2cc32d3bd02 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java @@ -34,28 +34,21 @@ import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.configure.config.ConfigurationMethod; +import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.TypeConfiguration; import jdk.vm.ci.meta.MetaUtil; class JniProcessor extends AbstractProcessor { - private final TypeConfiguration configuration; - private final TypeConfiguration reflectionConfiguration; private final AccessAdvisor advisor; - JniProcessor(AccessAdvisor advisor, TypeConfiguration configuration, TypeConfiguration reflectionConfiguration) { + JniProcessor(AccessAdvisor advisor) { this.advisor = advisor; - this.configuration = configuration; - this.reflectionConfiguration = reflectionConfiguration; - } - - public TypeConfiguration getConfiguration() { - return configuration; } @Override @SuppressWarnings("fallthrough") - void processEntry(Map entry) { + void processEntry(Map entry, ConfigurationSet configurationSet) { ConfigurationCondition condition = ConfigurationCondition.alwaysTrue(); boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); if (invalidResult) { @@ -72,7 +65,7 @@ void processEntry(Map entry) { String forNameString = MetaUtil.internalNameToJava(internalName, true, true); if (!advisor.shouldIgnore(lazyValue(forNameString), callerClassLazyValue)) { if (function.equals("FindClass")) { - configuration.getOrCreateType(condition, forNameString); + configurationSet.getJniConfiguration().getOrCreateType(condition, forNameString); } else if (!AccessAdvisor.PROXY_CLASS_NAME_PATTERN.matcher(lookupName).matches()) { // DefineClass logWarning("Unsupported JNI function DefineClass used to load class " + forNameString); } @@ -86,7 +79,7 @@ void processEntry(Map entry) { String declaringClass = (String) entry.get("declaring_class"); String declaringClassOrClazz = (declaringClass != null) ? declaringClass : clazz; ConfigurationMemberDeclaration declaration = (declaringClass != null) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; - TypeConfiguration config = configuration; + TypeConfiguration config = configurationSet.getJniConfiguration(); switch (function) { case "GetStaticMethodID": case "GetMethodID": { @@ -122,7 +115,7 @@ void processEntry(Map entry) { break; } case "ToReflectedField": - config = reflectionConfiguration; // fall through + config = configurationSet.getReflectionConfiguration(); // fall through case "FromReflectedField": { expectSize(args, 1); String name = (String) args.get(0); @@ -130,7 +123,7 @@ void processEntry(Map entry) { break; } case "ToReflectedMethod": - config = reflectionConfiguration; // fall through + config = configurationSet.getReflectionConfiguration(); // fall through case "FromReflectedMethod": { expectSize(args, 2); String name = (String) args.get(0); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index 623933ba661f..6a50c0af5b03 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -32,13 +32,14 @@ import java.util.Map; import java.util.regex.Pattern; +import com.oracle.svm.configure.config.ProxyConfiguration; import org.graalvm.compiler.phases.common.LazyValue; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.configure.config.ConfigurationMethod; -import com.oracle.svm.configure.config.ProxyConfiguration; +import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.ResourceConfiguration; import com.oracle.svm.configure.config.SignatureUtil; import com.oracle.svm.configure.config.TypeConfiguration; @@ -47,39 +48,23 @@ class ReflectionProcessor extends AbstractProcessor { private final AccessAdvisor advisor; - private final TypeConfiguration configuration; - private final ProxyConfiguration proxyConfiguration; - private final ResourceConfiguration resourceConfiguration; - ReflectionProcessor(AccessAdvisor advisor, TypeConfiguration typeConfiguration, ProxyConfiguration proxyConfiguration, ResourceConfiguration resourceConfiguration) { + ReflectionProcessor(AccessAdvisor advisor) { this.advisor = advisor; - this.configuration = typeConfiguration; - this.proxyConfiguration = proxyConfiguration; - this.resourceConfiguration = resourceConfiguration; - } - - public TypeConfiguration getConfiguration() { - return configuration; - } - - public ProxyConfiguration getProxyConfiguration() { - return proxyConfiguration; - } - - public ResourceConfiguration getResourceConfiguration() { - return resourceConfiguration; } @Override @SuppressWarnings("fallthrough") - public void processEntry(Map entry) { + public void processEntry(Map entry, ConfigurationSet configurationSet) { boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); ConfigurationCondition condition = ConfigurationCondition.alwaysTrue(); if (invalidResult) { return; } + String function = (String) entry.get("function"); List args = (List) entry.get("args"); + ResourceConfiguration resourceConfiguration = configurationSet.getResourceConfiguration(); switch (function) { // These are called via java.lang.Class or via the class loader hierarchy, so we would // always filter based on the caller class. @@ -94,6 +79,7 @@ public void processEntry(Map entry) { resourceConfiguration.addResourcePattern(condition, regex); return; } + TypeConfiguration configuration = configurationSet.getReflectionConfiguration(); String callerClass = (String) entry.get("caller_class"); boolean isLoadClass = function.equals("loadClass"); if (isLoadClass || function.equals("forName") || function.equals("findClass")) { @@ -219,24 +205,24 @@ public void processEntry(Map entry) { case "getProxyClass": { expectSize(args, 2); - addDynamicProxy((List) args.get(1), lazyValue(callerClass)); + addDynamicProxy((List) args.get(1), lazyValue(callerClass), configurationSet.getProxyConfiguration()); break; } case "newProxyInstance": { expectSize(args, 3); - addDynamicProxy((List) args.get(1), lazyValue(callerClass)); + addDynamicProxy((List) args.get(1), lazyValue(callerClass), configurationSet.getProxyConfiguration()); break; } case "newMethodHandleProxyInstance": { expectSize(args, 1); - addDynamicProxyUnchecked((List) args.get(0), Collections.singletonList("sun.invoke.WrapperInstance"), lazyValue(callerClass)); + addDynamicProxyUnchecked((List) args.get(0), Collections.singletonList("sun.invoke.WrapperInstance"), lazyValue(callerClass), configurationSet.getProxyConfiguration()); break; } case "getEnclosingConstructor": case "getEnclosingMethod": { String result = (String) entry.get("result"); - addFullyQualifiedDeclaredMethod(result); + addFullyQualifiedDeclaredMethod(result, configuration); break; } @@ -275,7 +261,7 @@ public void processEntry(Map entry) { } } - private void addFullyQualifiedDeclaredMethod(String descriptor) { + private static void addFullyQualifiedDeclaredMethod(String descriptor, TypeConfiguration configuration) { int sigbegin = descriptor.indexOf('('); int classend = descriptor.lastIndexOf('.', sigbegin - 1); String qualifiedClass = descriptor.substring(0, classend); @@ -284,7 +270,7 @@ private void addFullyQualifiedDeclaredMethod(String descriptor) { configuration.getOrCreateType(ConfigurationCondition.alwaysTrue(), qualifiedClass).addMethod(methodName, signature, ConfigurationMemberDeclaration.DECLARED); } - private void addDynamicProxy(List interfaceList, LazyValue callerClass) { + private void addDynamicProxy(List interfaceList, LazyValue callerClass, ProxyConfiguration proxyConfiguration) { @SuppressWarnings("unchecked") List interfaces = (List) interfaceList; for (String iface : interfaces) { @@ -295,7 +281,7 @@ private void addDynamicProxy(List interfaceList, LazyValue callerClas proxyConfiguration.add(ConfigurationCondition.alwaysTrue(), interfaces); } - private void addDynamicProxyUnchecked(List checkedInterfaceList, List uncheckedInterfaceList, LazyValue callerClass) { + private void addDynamicProxyUnchecked(List checkedInterfaceList, List uncheckedInterfaceList, LazyValue callerClass, ProxyConfiguration proxyConfiguration) { @SuppressWarnings("unchecked") List checkedInterfaces = (List) checkedInterfaceList; for (String iface : checkedInterfaces) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java index 718824e5d6ff..340e8a0beb25 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/SerializationProcessor.java @@ -28,26 +28,21 @@ import java.util.List; import java.util.Map; +import com.oracle.svm.configure.config.SerializationConfiguration; import org.graalvm.compiler.java.LambdaUtils; import org.graalvm.nativeimage.impl.ConfigurationCondition; -import com.oracle.svm.configure.config.SerializationConfiguration; +import com.oracle.svm.configure.config.ConfigurationSet; public class SerializationProcessor extends AbstractProcessor { private final AccessAdvisor advisor; - private final SerializationConfiguration serializationConfiguration; - public SerializationProcessor(AccessAdvisor advisor, SerializationConfiguration serializationConfiguration) { + public SerializationProcessor(AccessAdvisor advisor) { this.advisor = advisor; - this.serializationConfiguration = serializationConfiguration; - } - - public SerializationConfiguration getSerializationConfiguration() { - return serializationConfiguration; } @Override - void processEntry(Map entry) { + void processEntry(Map entry, ConfigurationSet configurationSet) { boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); ConfigurationCondition condition = ConfigurationCondition.alwaysTrue(); if (invalidResult) { @@ -55,6 +50,7 @@ void processEntry(Map entry) { } String function = (String) entry.get("function"); List args = (List) entry.get("args"); + SerializationConfiguration serializationConfiguration = configurationSet.getSerializationConfiguration(); if ("ObjectStreamClass.".equals(function)) { expectSize(args, 2); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java index 84ecb3eb49ae..7ff37d781382 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java @@ -31,13 +31,7 @@ import java.util.List; import java.util.Map; -import com.oracle.svm.configure.ConfigurationBase; -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.core.configure.ConfigurationFile; +import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.core.util.json.JSONParser; public class TraceProcessor extends AbstractProcessor { @@ -47,103 +41,30 @@ public class TraceProcessor extends AbstractProcessor { private final SerializationProcessor serializationProcessor; private final ClassLoadingProcessor classLoadingProcessor; - private final TraceProcessor omittedConfigProcessor; - - public TraceProcessor(AccessAdvisor accessAdvisor, TypeConfiguration jniConfiguration, TypeConfiguration reflectionConfiguration, - ProxyConfiguration proxyConfiguration, ResourceConfiguration resourceConfiguration, SerializationConfiguration serializationConfiguration, - PredefinedClassesConfiguration predefinedClassesConfiguration, TraceProcessor omittedConfigProcessor) { + public TraceProcessor(AccessAdvisor accessAdvisor) { advisor = accessAdvisor; - jniProcessor = new JniProcessor(this.advisor, jniConfiguration, reflectionConfiguration); - reflectionProcessor = new ReflectionProcessor(this.advisor, reflectionConfiguration, proxyConfiguration, resourceConfiguration); - serializationProcessor = new SerializationProcessor(this.advisor, serializationConfiguration); - classLoadingProcessor = new ClassLoadingProcessor(predefinedClassesConfiguration); - this.omittedConfigProcessor = omittedConfigProcessor; - } - - public TypeConfiguration getJniConfiguration() { - TypeConfiguration result = jniProcessor.getConfiguration(); - if (omittedConfigProcessor != null) { - result = TypeConfiguration.copyAndSubtract(result, omittedConfigProcessor.jniProcessor.getConfiguration()); - } - return result; - } - - public TypeConfiguration getReflectionConfiguration() { - TypeConfiguration result = reflectionProcessor.getConfiguration(); - if (omittedConfigProcessor != null) { - result = TypeConfiguration.copyAndSubtract(result, omittedConfigProcessor.reflectionProcessor.getConfiguration()); - } - return result; - } - - public ProxyConfiguration getProxyConfiguration() { - ProxyConfiguration result = reflectionProcessor.getProxyConfiguration(); - if (omittedConfigProcessor != null) { - result = new ProxyConfiguration(result); - result.removeAll(omittedConfigProcessor.reflectionProcessor.getProxyConfiguration()); - } - return result; - } - - public ResourceConfiguration getResourceConfiguration() { - ResourceConfiguration result = reflectionProcessor.getResourceConfiguration(); - if (omittedConfigProcessor != null) { - result = new ResourceConfiguration(result); - result.removeAll(omittedConfigProcessor.reflectionProcessor.getResourceConfiguration()); - } - return result; - } - - public SerializationConfiguration getSerializationConfiguration() { - SerializationConfiguration result = serializationProcessor.getSerializationConfiguration(); - if (omittedConfigProcessor != null) { - result = new SerializationConfiguration(result); - result.removeAll(omittedConfigProcessor.serializationProcessor.getSerializationConfiguration()); - } - return result; - } - - public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { - return classLoadingProcessor.getPredefinedClassesConfiguration(); - } - - public ConfigurationBase getConfiguration(ConfigurationFile configFile) { - assert configFile.canBeGeneratedByAgent(); - switch (configFile) { - case DYNAMIC_PROXY: - return getProxyConfiguration(); - case JNI: - return getJniConfiguration(); - case REFLECTION: - return getReflectionConfiguration(); - case RESOURCES: - return getResourceConfiguration(); - case SERIALIZATION: - return getSerializationConfiguration(); - case PREDEFINED_CLASSES_NAME: - return getPredefinedClassesConfiguration(); - default: - assert false; // should never reach here - } - return null; + jniProcessor = new JniProcessor(this.advisor); + reflectionProcessor = new ReflectionProcessor(this.advisor); + serializationProcessor = new SerializationProcessor(this.advisor); + classLoadingProcessor = new ClassLoadingProcessor(); } @SuppressWarnings("unchecked") - public void process(Reader reader) throws IOException { + public void process(Reader reader, ConfigurationSet configurationSet) throws IOException { setInLivePhase(false); JSONParser parser = new JSONParser(reader); List> trace = (List>) parser.parse(); - processTrace(trace); + processTrace(trace, configurationSet); } - private void processTrace(List> trace) { + private void processTrace(List> trace, ConfigurationSet configurationSet) { for (Map entry : trace) { - processEntry(entry); + processEntry(entry, configurationSet); } } @Override - public void processEntry(Map entry) { + public void processEntry(Map entry, ConfigurationSet configurationSet) { try { String tracer = (String) entry.get("tracer"); switch (tracer) { @@ -159,16 +80,16 @@ public void processEntry(Map entry) { break; } case "jni": - jniProcessor.processEntry(entry); + jniProcessor.processEntry(entry, configurationSet); break; case "reflect": - reflectionProcessor.processEntry(entry); + reflectionProcessor.processEntry(entry, configurationSet); break; case "serialization": - serializationProcessor.processEntry(entry); + serializationProcessor.processEntry(entry, configurationSet); break; case "classloading": - classLoadingProcessor.processEntry(entry); + classLoadingProcessor.processEntry(entry, configurationSet); break; default: logWarning("Unknown tracer, ignoring: " + tracer); 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 4f6181539515..1517388032c4 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 @@ -33,25 +33,29 @@ public enum ConfigurationFile { SERIALIZATION_DENY("serialization-deny", false), PREDEFINED_CLASSES_NAME("predefined-classes", true); - private static final String DEFAULT_FILE_NAME_SUFFIX = "-config.json"; - private final String fileName; + public static final String DEFAULT_FILE_NAME_SUFFIX = "-config.json"; + private final String name; private final boolean canAgentGenerate; 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"; - ConfigurationFile(String fileName, boolean canAgentGenerate) { - this.fileName = fileName; + ConfigurationFile(String name, boolean canAgentGenerate) { + this.name = name; this.canAgentGenerate = canAgentGenerate; } + public String getName() { + return name; + } + public String getFileName() { - return fileName + DEFAULT_FILE_NAME_SUFFIX; + return name + DEFAULT_FILE_NAME_SUFFIX; } public String getFileName(String suffix) { - return fileName + suffix; + return name + suffix; } public boolean canBeGeneratedByAgent() { 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 6ca543ea9b76..7ac92675a997 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 @@ -78,7 +78,7 @@ protected static BufferedReader openReader(URI uri) throws IOException { public abstract void parseAndRegister(Reader reader) throws IOException; @SuppressWarnings("unchecked") - protected static List asList(Object data, String errorMessage) { + public static List asList(Object data, String errorMessage) { if (data instanceof List) { return (List) data; } @@ -86,7 +86,7 @@ protected static List asList(Object data, String errorMessage) { } @SuppressWarnings("unchecked") - protected static Map asMap(Object data, String errorMessage) { + public static Map asMap(Object data, String errorMessage) { if (data instanceof Map) { return (Map) data; } @@ -127,7 +127,7 @@ protected void checkAttributes(Map map, String type, Collection< checkAttributes(map, type, requiredAttrs, Collections.emptyList()); } - protected static String asString(Object value) { + public static String asString(Object value) { if (value instanceof String) { return (String) value; } diff --git a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java index 6682b41af9bc..34029fa67386 100644 --- a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java +++ b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java @@ -429,10 +429,21 @@ public static void check(JvmtiError resultCode) { guarantee(resultCode.equals(JvmtiError.JVMTI_ERROR_NONE), "JVMTI call failed with " + resultCode.name()); } + public static void checkPhase(JvmtiError resultCode) throws WrongPhaseException { + if (resultCode == JvmtiError.JVMTI_ERROR_WRONG_PHASE) { + throw new WrongPhaseException(); + } + check(resultCode); + } + public static void checkJni(int resultCode) { guarantee(resultCode == JNIErrors.JNI_OK()); } private Support() { } + + public static class WrongPhaseException extends Exception { + private static final long serialVersionUID = 8503239518909756105L; + } }