From 06c2891568514f09ad79e4b5036fcf3243eaf741 Mon Sep 17 00:00:00 2001 From: Aleksandar Gradinac Date: Fri, 28 Jan 2022 13:42:18 +0100 Subject: [PATCH 1/7] Allow the native-image agent to generate conditional configuration. --- .../oracle/svm/agent/NativeImageAgent.java | 39 ++- .../agent/NativeImageAgentJNIHandleSet.java | 35 +++ .../ClassInfo.java | 7 +- .../ConfigurationWithOriginsResultWriter.java | 142 +++++++++ ...igurationWithOriginsResultWriterBase.java} | 153 +++------ .../MethodInfo.java | 4 +- .../MethodInfoRecordKeeper.java | 28 +- .../ignoredconfig/AgentMetaInfProcessor.java | 6 +- .../ConditionalConfigurationWriter.java | 295 ++++++++++++++++++ .../test/config/OmitPreviousConfigTests.java | 14 +- .../svm/configure/ConfigurationBase.java | 37 ++- .../svm/configure/ConfigurationTool.java | 30 +- .../ConditionalConfigurationFilter.java | 57 ++++ .../config/ConfigurationFileCollection.java | 184 +++++++++++ .../configure/config/ConfigurationSet.java | 205 ++++++------ .../configure/config/ConfigurationType.java | 61 +++- .../svm/configure/config/FieldInfo.java | 6 + .../PredefinedClassesConfiguration.java | 46 ++- .../configure/config/ProxyConfiguration.java | 44 ++- .../config/ResourceConfiguration.java | 61 +++- .../config/SerializationConfiguration.java | 42 ++- ...ationConfigurationLambdaCapturingType.java | 8 + .../SerializationConfigurationType.java | 12 + .../configure/config/TypeConfiguration.java | 94 +++++- .../svm/configure/trace/TraceProcessor.java | 15 +- .../oracle/svm/jvmtiagentbase/Support.java | 10 + 26 files changed, 1325 insertions(+), 310 deletions(-) rename substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/{predicatedconfig => configwithorigins}/ClassInfo.java (90%) create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriter.java rename substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/{predicatedconfig/ConfigurationWithOriginsResultWriter.java => configwithorigins/ConfigurationWithOriginsResultWriterBase.java} (59%) rename substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/{predicatedconfig => configwithorigins}/MethodInfo.java (97%) rename substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/{predicatedconfig => configwithorigins}/MethodInfoRecordKeeper.java (84%) create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationFilter.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java 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..33aaa2f66a30 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 @@ -40,8 +40,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.ConcurrentModificationException; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -54,9 +57,10 @@ import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.hosted.Feature; +import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsResultWriter; +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.predicatedconfig.ConditionalConfigurationWriter; import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess; import com.oracle.svm.agent.stackaccess.InterceptedState; import com.oracle.svm.agent.stackaccess.OnDemandJavaStackAccess; @@ -64,7 +68,8 @@ 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.ConfigurationSet; +import com.oracle.svm.configure.config.ConditionalConfigurationFilter; +import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.filters.FilterConfigurationParser; import com.oracle.svm.configure.filters.RuleNode; import com.oracle.svm.configure.trace.AccessAdvisor; @@ -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; + Set predefinedConfigurationPackages = new HashSet<>(); + Set classNamePatterns = new HashSet<>(); int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds boolean trackReflectionMetadata = true; @@ -196,6 +203,12 @@ 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-configuration=")) { + String applicationPackages = getTokenValue(token); + Collections.addAll(predefinedConfigurationPackages, applicationPackages.split(",")); + } else if (token.startsWith("class-name-filter=")) { + String classNamePattern = getTokenValue(token); + Arrays.stream(classNamePattern.split(",")).map(Pattern::compile).forEach(classNamePatterns::add); } else if (token.equals("track-reflection-metadata")) { trackReflectionMetadata = true; } else if (token.startsWith("track-reflection-metadata=")) { @@ -210,6 +223,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 && !predefinedConfigurationPackages.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."); @@ -241,8 +258,9 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } } - final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(configurationWithOrigins); - final Supplier interceptedStateSupplier = configurationWithOrigins ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() + boolean shouldTraceOriginInformation = configurationWithOrigins || !predefinedConfigurationPackages.isEmpty(); + final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(shouldTraceOriginInformation); + final Supplier interceptedStateSupplier = shouldTraceOriginInformation ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() : OnDemandJavaStackAccess.stackAccessSupplier(); if (configOutputDir != null) { @@ -290,6 +308,11 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c ConfigurationWithOriginsResultWriter writer = new ConfigurationWithOriginsResultWriter(advisor, recordKeeper); tracer = writer; tracingResultWriter = writer; + } else if (!predefinedConfigurationPackages.isEmpty()) { + ConditionalConfigurationFilter filter = new ConditionalConfigurationFilter(classNamePatterns); + ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(advisor, recordKeeper, predefinedConfigurationPackages, filter); + tracer = writer; + tracingResultWriter = writer; } else { Path[] predefinedClassDestDirs = {Files.createDirectories(configOutputDirPath.resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR))}; Function handler = e -> { @@ -422,7 +445,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 ignoredConfigSet) { String classpath = Support.getSystemProperty(jvmti, "java.class.path"); String sep = Support.getSystemProperty(jvmti, "path.separator"); if (sep == null) { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index bebe65427ff8..fb2c20126949 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -26,6 +26,8 @@ import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; +import com.oracle.svm.jvmtiagentbase.Support; +import jdk.vm.ci.meta.MetaUtil; import org.graalvm.word.WordFactory; import com.oracle.svm.jni.nativeapi.JNIEnvironment; @@ -233,4 +235,37 @@ public JNIMethodId getJavaUtilResourceBundleGetLocale(JNIEnvironment env) { } return javaUtilResourceBundleGetLocale; } + + private JNIMethodId javaLangClassGetProtectionDomain = WordFactory.nullPointer(); + private JNIMethodId javaSecurityProtectionDomainGetCodeSource = WordFactory.nullPointer(); + private JNIMethodId javaSecurityCodeSourceGetLocation = WordFactory.nullPointer(); + private JNIMethodId javaNetURLGetPath = WordFactory.nullPointer(); + + public String getClassLocationOrNull(JNIEnvironment env, String className) { + String classNameInternal = MetaUtil.toInternalName(className); + JNIObjectHandle clazz = findClass(env, classNameInternal); + if (clazz == null) { + return null; + } + + if (javaLangClassGetProtectionDomain.isNull()) { + javaLangClassGetProtectionDomain = getMethodId(env, javaLangClass, "getProtectionDomain", "()Ljava/security/ProtectionDomain;", false); + + JNIObjectHandle protDomainClass = findClass(env, "java/security/ProtectionDomain"); + javaSecurityProtectionDomainGetCodeSource = getMethodId(env, protDomainClass, "getCodeSource", "()Ljava/security/CodeSource;", false); + + JNIObjectHandle codeSourceClass = findClass(env, "java/security/CodeSource"); + javaSecurityCodeSourceGetLocation = getMethodId(env, codeSourceClass, "getLocation", "()Ljava/net/URL;", false); + + JNIObjectHandle urlClass = findClass(env, "java/net/URL"); + javaNetURLGetPath = getMethodId(env, urlClass, "getPath", "()Ljava/lang/String;", false); + } + + /* return clazz.getProtectionDomain().getCodeSource().getLocation().getPath() */ + JNIObjectHandle protectionDomain = Support.callObjectMethod(env, clazz, javaLangClassGetProtectionDomain); + JNIObjectHandle codeSource = Support.callObjectMethod(env, protectionDomain, javaSecurityProtectionDomainGetCodeSource); + JNIObjectHandle location = Support.callObjectMethod(env, codeSource, javaSecurityCodeSourceGetLocation); + JNIObjectHandle path = Support.callObjectMethod(env, location, javaNetURLGetPath); + return Support.fromJniString(env, path); + } } 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..b66e812e716c 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,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 com.oracle.svm.jni.nativeapi.JNIMethodId; import com.oracle.svm.jvmtiagentbase.Support; @@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentHashMap; import static com.oracle.svm.jvmtiagentbase.Support.check; +import static com.oracle.svm.jvmtiagentbase.Support.checkPhase; import static org.graalvm.word.WordFactory.nullPointer; class ClassInfo { @@ -48,13 +49,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/ConfigurationWithOriginsResultWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriter.java new file mode 100644 index 000000000000..9427374621da --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriter.java @@ -0,0 +1,142 @@ +/* + * 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.util.HashSet; +import java.util.Set; + +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.configure.trace.AccessAdvisor; +import com.oracle.svm.core.configure.ConfigurationFile; + +public class ConfigurationWithOriginsResultWriter extends ConfigurationWithOriginsResultWriterBase { + + public static final String CONFIG_WITH_ORIGINS_SUFFIX = "-origins.json"; + + public ConfigurationWithOriginsResultWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper) { + super(advisor, methodInfoRecordKeeper); + } + + @Override + public boolean supportsOnUnloadTraceWriting() { + return true; + } + + @Override + protected String getConfigFileSuffix() { + return CONFIG_WITH_ORIGINS_SUFFIX; + } + + public static void writeJson(MethodCallNode root, 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. + */ + root.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(root, writer, configFile, includedNodes); + } + + private static void writeJson(MethodCallNode node, JsonWriter writer, ConfigurationFile configFile, Set includedNodes) throws IOException { + if (node.isRoot()) { + writer.append("[").newline() + .append("{").indent().newline() + .quote("configuration-with-origins").append(": ["); + printChildMethodJson(node, writer, configFile, includedNodes); + writer.newline() + .append("]").unindent().newline(); + if (node.hasConfig(configFile)) { + writer.quote("configuration-without-origins").append(": ").indent(); + writeConfigJson(node, writer, configFile); + writer.unindent().newline(); + } + writer.append("}").newline() + .append("]").newline(); + } else { + writer.append("{").indent().newline(); + + writer.quote("method").append(": ").quote(node.methodInfo.getJavaDeclaringClassName() + "#" + node.methodInfo.getJavaMethodNameAndSignature()).append(",").newline(); + + if (anyChildrenIncluded(node, includedNodes)) { + writer.quote("methods").append(": ["); + printChildMethodJson(node, writer, configFile, includedNodes); + writer.newline().append("]"); + if (node.hasConfig(configFile)) { + writer.append(",").newline(); + } + } + + if (node.hasConfig(configFile)) { + writeConfigJson(node, writer, configFile); + } + + writer.unindent().newline(); + writer.append("}"); + } + } + + private static void printChildMethodJson(MethodCallNode node, JsonWriter writer, ConfigurationFile configFile, Set includedNodes) throws IOException { + boolean first = true; + for (MethodCallNode methodCallNode : node.calledMethods.values()) { + if (!includedNodes.contains(methodCallNode)) { + continue; + } + if (first) { + first = false; + } else { + writer.append(","); + } + writer.newline(); + writeJson(methodCallNode, writer, configFile, includedNodes); + } + } + + private static boolean anyChildrenIncluded(MethodCallNode node, Set includedNodes) { + return node.calledMethods.values().stream().anyMatch(includedNodes::contains); + } + + private static void writeConfigJson(MethodCallNode node, JsonWriter writer, ConfigurationFile configFile) throws IOException { + writer.quote("config").append(": "); + node.processor.getConfiguration(configFile).printJson(writer); + } + + @Override + protected void writeConfig(JsonWriter writer, ConfigurationFile configFile) throws IOException { + writeJson(rootNode, writer, configFile); + } +} 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/configwithorigins/ConfigurationWithOriginsResultWriterBase.java similarity index 59% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConfigurationWithOriginsResultWriter.java rename to substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriterBase.java index be8807dd39d4..3c0a07f8f9e5 100644 --- 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/configwithorigins/ConfigurationWithOriginsResultWriterBase.java @@ -22,18 +22,17 @@ * 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.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.Function; import java.util.function.Supplier; import com.oracle.svm.agent.tracing.ConfigurationResultWriter; @@ -58,13 +57,13 @@ * 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 { +public abstract class ConfigurationWithOriginsResultWriterBase extends Tracer implements TracingResultWriter { - private final AccessAdvisor advisor; - private final MethodCallNode rootNode; - private final MethodInfoRecordKeeper methodInfoRecordKeeper; + protected final AccessAdvisor advisor; + protected final MethodCallNode rootNode; + protected final MethodInfoRecordKeeper methodInfoRecordKeeper; - public ConfigurationWithOriginsResultWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper) { + public ConfigurationWithOriginsResultWriterBase(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper) { this.advisor = advisor; this.rootNode = MethodCallNode.createRoot(); this.methodInfoRecordKeeper = methodInfoRecordKeeper; @@ -81,18 +80,21 @@ public void traceEntry(Map entry) { } else { assert entry.containsKey("stack_trace"); JNIMethodId[] rawStackTrace = (JNIMethodId[]) entry.remove("stack_trace"); + MethodInfo[] stackTrace = methodInfoRecordKeeper.getStackTraceInfo(rawStackTrace); Map transformedEntry = ConfigurationResultWriter.arraysToLists(entry); - if (rawStackTrace == null) { + if (stackTrace == null) { rootNode.traceEntry(this::createNewTraceProcessor, transformedEntry); } else { - MethodInfo[] stackTrace = methodInfoRecordKeeper.getStackTraceInfo(rawStackTrace); - rootNode.dispatchTraceEntry(stackTrace, stackTrace.length - 1, transformedEntry, this::createNewTraceProcessor); + stackTrace = filterStackTrace(stackTrace); + if (stackTrace != null) { + rootNode.dispatchTraceEntry(stackTrace, stackTrace.length - 1, transformedEntry, this::createNewTraceProcessor); + } } } } - private TraceProcessor createNewTraceProcessor() { + protected TraceProcessor createNewTraceProcessor() { TypeConfiguration jniConfig = new TypeConfiguration(); TypeConfiguration reflectConfig = new TypeConfiguration(); ProxyConfiguration proxyConfig = new ProxyConfiguration(); @@ -102,12 +104,12 @@ private TraceProcessor createNewTraceProcessor() { return new TraceProcessor(advisor, jniConfig, reflectConfig, proxyConfig, resourceConfig, serializationConfiguration, predefinedClassesConfiguration, null); } - private static final class MethodCallNode { + protected static final class MethodCallNode { - private final MethodInfo methodInfo; - private final MethodCallNode parent; - private final Map calledMethods; - private TraceProcessor processor; + public final MethodInfo methodInfo; + public final MethodCallNode parent; + public final Map calledMethods; + public TraceProcessor processor; private MethodCallNode(MethodInfo methodInfo, MethodCallNode parent) { this.methodInfo = methodInfo; @@ -150,92 +152,7 @@ private void traceEntry(Supplier traceProcessorSupplier, Map 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) { + public void visitPostOrder(Consumer methodCallNodeConsumer) { for (MethodCallNode node : calledMethods.values()) { node.visitPostOrder(methodCallNodeConsumer); } @@ -267,23 +184,41 @@ public boolean supportsPeriodicTraceWriting() { @Override public boolean supportsOnUnloadTraceWriting() { - return true; + return false; } - public static final String CONFIG_WITH_ORIGINS_FILE_SUFFIX = "-origins.json"; + protected abstract String getConfigFileSuffix(); - @Override - public List writeToDirectory(Path directoryPath) throws IOException { + protected abstract void writeConfig(JsonWriter writer, ConfigurationFile configFile) throws IOException; + + protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { + return stackTrace; + } + + protected void beforeWritingConfig() { + } + + protected static List writeToDirectory(Path directoryPath, ConfigurationWriter configWriter, Function configFileResolver) 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)); + Path filePath = directoryPath.resolve(configFileResolver.apply(configFile)); try (JsonWriter writer = new JsonWriter(filePath)) { - rootNode.writeJson(writer, configFile); + configWriter.writeConfig(writer, configFile); } writtenPaths.add(filePath); } } return writtenPaths; } + + public List writeToDirectory(Path directoryPath) throws IOException { + beforeWritingConfig(); + return writeToDirectory(directoryPath, this::writeConfig, configFile -> configFile.getFileName(getConfigFileSuffix())); + } + + @FunctionalInterface + protected interface ConfigurationWriter { + void writeConfig(JsonWriter writer, ConfigurationFile file) throws IOException; + } } 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 97% 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..fc9bace31443 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) { 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..5d23ef9557aa 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,15 +26,15 @@ 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 ConfigurationFileCollection ignoredConfigSet; - public AgentMetaInfProcessor(ConfigurationSet ignoredConfigSet) { + public AgentMetaInfProcessor(ConfigurationFileCollection ignoredConfigSet) { this.ignoredConfigSet = ignoredConfigSet; } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java new file mode 100644 index 000000000000..49eb8e7aac8c --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java @@ -0,0 +1,295 @@ +/* + * 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.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 org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsResultWriter; +import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsResultWriterBase; +import com.oracle.svm.agent.configwithorigins.MethodInfo; +import com.oracle.svm.agent.configwithorigins.MethodInfoRecordKeeper; +import com.oracle.svm.configure.config.ConditionalConfigurationFilter; +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.json.JsonWriter; +import com.oracle.svm.configure.trace.AccessAdvisor; +import com.oracle.svm.core.configure.ConfigurationFile; + +/** + * 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 ConfigurationWithOriginsResultWriterBase { + private final Set applicationPackagePrefixes; + private ConfigurationSet configurationContainer = new ConfigurationSet(); + private final ConditionalConfigurationFilter filter; + + public ConditionalConfigurationWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper, Set applicationPackagePrefixes, ConditionalConfigurationFilter filter) { + super(advisor, methodInfoRecordKeeper); + this.applicationPackagePrefixes = applicationPackagePrefixes; + this.filter = filter; + } + + 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<>(); + Map> children = new HashMap<>(); + rootNode.visitPostOrder(node -> { + List callNodes = methodCallNodes.computeIfAbsent(node.methodInfo, info -> new ArrayList<>()); + ConfigurationSet configurationContainer = node.processor == null ? new ConfigurationSet() : new ConfigurationSet(node.processor); + CallNodeWithConfig newNode = new CallNodeWithConfig(node, configurationContainer); + callNodes.add(newNode); + + List parentNodes = children.computeIfAbsent(node.parent, parent -> new ArrayList<>()); + parentNodes.add(newNode); + if (children.containsKey(node)) { + for (CallNodeWithConfig callNodeWithConfig : children.get(node)) { + callNodeWithConfig.parent = newNode; + } + } + }); + + return methodCallNodes; + } + + private 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 (CallNodeWithConfig node : callNodes) { + ConfigurationSet callParentConfig = node.configurationSet.subtract(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++) { + CallNodeWithConfig node = callNodes.get(i); + ConfigurationSet uniqueNodeConfig = newNodeConfiguration.get(i); + node.configurationSet = new ConfigurationSet(commonConfig); + node.parent.configurationSet = node.parent.configurationSet.merge(uniqueNodeConfig); + affectedNodes.add(node.parent.node.methodInfo); + } + + return affectedNodes; + } + + private ConfigurationSet findCommonConfigurationForMethod(List callNodes) { + ConfigurationSet config = null; + for (CallNodeWithConfig node : callNodes) { + if (config == null) { + config = node.configurationSet; + } else { + config = config.intersectWith(node.configurationSet); + } + } + return config; + } + + @Override + protected void beforeWritingConfig() { + createConditionalConfiguration(); + } + + 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. + */ + CallNodeWithConfig rootNode = methodCallNodes.remove(null).get(0); + + for (List value : methodCallNodes.values()) { + for (CallNodeWithConfig node : value) { + String className = node.node.methodInfo.getJavaDeclaringClassName(); + ConfigurationCondition condition = ConfigurationCondition.create(className); + + addConfigurationWithCondition(node.configurationSet, condition); + } + } + + addConfigurationWithCondition(rootNode.configurationSet, ConfigurationCondition.alwaysTrue()); + + filterConfiguration(); + } + + private void addConfigurationWithCondition(ConfigurationSet nodeConfig, ConfigurationCondition condition) { + TypeConfiguration reflectionConfig = nodeConfig.getReflectionConfiguration(); + configurationContainer.getReflectionConfiguration().addWithCondition(condition, reflectionConfig); + + TypeConfiguration jniConfig = nodeConfig.getJniConfiguration(); + configurationContainer.getJniConfiguration().addWithCondition(condition, jniConfig); + + ResourceConfiguration resourceConfiguration = nodeConfig.getResourceConfiguration(); + configurationContainer.getResourceConfiguration().addWithCondition(condition, resourceConfiguration); + + ProxyConfiguration proxyConfiguration = nodeConfig.getProxyConfiguration(); + configurationContainer.getProxyConfiguration().addWithCondition(condition, proxyConfiguration); + + SerializationConfiguration serializationConfiguration = nodeConfig.getSerializationConfiguration(); + configurationContainer.getSerializationConfiguration().addWithCondition(condition, serializationConfiguration); + + PredefinedClassesConfiguration predefinedClassesConfiguration = nodeConfig.getPredefinedClassesConfiguration(); + configurationContainer.getPredefinedClassesConfiguration().addWithCondition(condition, predefinedClassesConfiguration); + } + + private void filterConfiguration() { + configurationContainer = configurationContainer.filter(filter); + } + + private 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; + } + } + + @Override + protected String getConfigFileSuffix() { + return "-conditional-config.json"; + } + + private boolean methodOriginatesFromApplicationPackage(MethodInfo methodInfo) { + return applicationPackagePrefixes.stream().anyMatch(prefix -> methodInfo.getJavaDeclaringClassName().startsWith(prefix)); + } + + private int findIndexOfMethodFromApplicationPackage(MethodInfo[] stackTrace, boolean fromBeginning) { + int firstMethodOriginatingInPackage = fromBeginning ? 0 : stackTrace.length - 1; + int step = fromBeginning ? 1 : -1; + while (firstMethodOriginatingInPackage >= 0 && firstMethodOriginatingInPackage < stackTrace.length) { + if (methodOriginatesFromApplicationPackage(stackTrace[firstMethodOriginatingInPackage])) { + return firstMethodOriginatingInPackage; + } + firstMethodOriginatingInPackage = firstMethodOriginatingInPackage + step; + } + return -1; + } + + @Override + protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { + /* Keep only the classes from the selected package names on the stack trace. */ + int firstMethodIndex = findIndexOfMethodFromApplicationPackage(stackTrace, true); + if (firstMethodIndex == -1) { + return null; + } + int lastMethodIndex = findIndexOfMethodFromApplicationPackage(stackTrace, false); + + return Arrays.copyOfRange(stackTrace, firstMethodIndex, lastMethodIndex); + } + + @Override + public boolean supportsOnUnloadTraceWriting() { + return true; + } + + @Override + protected void writeConfig(JsonWriter writer, ConfigurationFile configurationFile) throws IOException { + configurationContainer.getConfiguration(configurationFile).printJson(writer); + } + + protected void writeOriginConfig(JsonWriter writer, ConfigurationFile configurationFile) throws IOException { + ConfigurationWithOriginsResultWriter.writeJson(rootNode, writer, configurationFile); + } + + @Override + public List writeToDirectory(Path directoryPath) throws IOException { + List writtenPaths = new ArrayList<>(); + writtenPaths.addAll(super.writeToDirectory(directoryPath)); + writtenPaths.addAll( + writeToDirectory(directoryPath, this::writeOriginConfig, configurationFile -> configurationFile.getFileName(ConfigurationWithOriginsResultWriter.CONFIG_WITH_ORIGINS_SUFFIX))); + return writtenPaths; + } + + private static class CallNodeWithConfig { + protected final MethodCallNode node; + protected CallNodeWithConfig parent; + protected ConfigurationSet configurationSet; + + CallNodeWithConfig(MethodCallNode node, ConfigurationSet configurationSet) { + this.node = node; + this.parent = null; + this.configurationSet = configurationSet; + } + } +} 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..d92c0681d244 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 @@ -43,7 +43,7 @@ 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.ConfigurationSet; +import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.config.ConfigurationType; import com.oracle.svm.configure.config.FieldInfo; import com.oracle.svm.configure.config.PredefinedClassesConfiguration; @@ -62,8 +62,8 @@ public class OmitPreviousConfigTests { private static TraceProcessor loadTraceProcessorFromResourceDirectory(String resourceDirectory, TraceProcessor previous) { 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); @@ -85,9 +85,9 @@ private static TraceProcessor loadTraceProcessorFromResourceDirectory(String res if (previous != null) { shouldExcludeClassesWithHash = previous.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 new TraceProcessor(unusedAdvisor, configurationFileCollection.loadJniConfig(handler), configurationFileCollection.loadReflectConfig(handler), configurationFileCollection.loadProxyConfig(handler), + configurationFileCollection.loadResourceConfig(handler), configurationFileCollection.loadSerializationConfig(handler), + configurationFileCollection.loadPredefinedClassesConfig(null, shouldExcludeClassesWithHash, handler), previous); } catch (Exception e) { throw VMError.shouldNotReachHere("Unexpected error while loading the configuration files.", e); } @@ -297,7 +297,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..ec2119bffccc 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,43 @@ */ package com.oracle.svm.configure; +import java.util.function.Consumer; + import com.oracle.svm.configure.json.JsonPrintable; -public interface ConfigurationBase extends JsonPrintable { +public abstract class ConfigurationBase, U> implements JsonPrintable { + + public abstract boolean isEmpty(); + + public abstract T copy(); + + protected abstract void merge(T other); + + protected abstract void subtract(T other); + + protected abstract void intersect(T other); + + protected abstract void filter(U 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 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(U predicate) { + return copyAnd(copy -> copy.filter(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..47c114910f4a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java @@ -47,7 +47,7 @@ import org.graalvm.nativeimage.ImageInfo; -import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.filters.FilterConfigurationParser; import com.oracle.svm.configure.filters.ModuleFilterTools; import com.oracle.svm.configure.filters.RuleNode; @@ -148,14 +148,14 @@ 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 omittedInputSet = new ConfigurationFileCollection(); + ConfigurationFileCollection inputSet = new ConfigurationFileCollection(); + ConfigurationFileCollection outputSet = 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 set = outputSet; switch (current) { case "--input-dir": inputSet.addDirectory(requirePath(current, value)); @@ -271,10 +271,10 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA TraceProcessor p; TraceProcessor omittedInputTraceProcessor; 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), + omittedInputTraceProcessor = new TraceProcessor(advisor, omittedInputSet.loadJniConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), + omittedInputSet.loadReflectConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), + omittedInputSet.loadProxyConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), omittedInputSet.loadResourceConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), + omittedInputSet.loadSerializationConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), omittedInputSet.loadPredefinedClassesConfig(null, null, ConfigurationFileCollection.FAIL_ON_EXCEPTION), null); List predefinedClassDestDirs = new ArrayList<>(); for (URI pathUri : outputSet.getPredefinedClassesConfigPaths()) { @@ -283,10 +283,10 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA 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), + p = new TraceProcessor(advisor, inputSet.loadJniConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), inputSet.loadReflectConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), + inputSet.loadProxyConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), + inputSet.loadSerializationConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), + inputSet.loadPredefinedClassesConfig(predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash, ConfigurationFileCollection.FAIL_ON_EXCEPTION), omittedInputTraceProcessor); } catch (IOException e) { throw e; @@ -337,9 +337,9 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA } } - private static void failIfAgentLockFilesPresent(ConfigurationSet... sets) { + private static void failIfAgentLockFilesPresent(ConfigurationFileCollection... sets) { Set paths = null; - for (ConfigurationSet set : sets) { + for (ConfigurationFileCollection set : sets) { for (URI path : set.getDetectedAgentLockPaths()) { if (paths == null) { paths = new HashSet<>(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationFilter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationFilter.java new file mode 100644 index 000000000000..a0ad69cd3c7e --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationFilter.java @@ -0,0 +1,57 @@ +package com.oracle.svm.configure.config; + +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import com.oracle.svm.core.configure.ConditionalElement; + +public class ConditionalConfigurationFilter + implements TypeConfiguration.TypeConfigurationFilterPredicate, ProxyConfiguration.ProxyConfigurationFilterPredicate, ResourceConfiguration.ResourceConfigurationFilterPredicate, + SerializationConfiguration.SerializationConfigurationFilter, PredefinedClassesConfiguration.PredefinedClassFilterPredicate { + + private final Set classNamePatterns; + + public ConditionalConfigurationFilter(Set classNamePatterns) { + this.classNamePatterns = classNamePatterns; + } + + private boolean classNameMatchesAny(String className) { + return classNamePatterns.stream().anyMatch(p -> p.matcher(className).find()); + } + + @Override + public boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type) { + return classNameMatchesAny(conditionalElement.getCondition().getTypeName()) || classNameMatchesAny(type.getQualifiedJavaName()); + } + + @Override + public boolean testProxyInterfaceList(ConditionalElement> conditionalElement) { + return classNameMatchesAny(conditionalElement.getCondition().getTypeName()); + } + + @Override + public boolean testIncludedResource(ConditionalElement condition, Pattern pattern) { + return classNameMatchesAny(condition.getCondition().getTypeName()); + } + + @Override + public boolean testIncludedBundle(ConditionalElement condition, ResourceConfiguration.BundleConfiguration bundleConfiguration) { + return classNameMatchesAny(condition.getCondition().getTypeName()); + } + + @Override + public boolean testSerializationType(SerializationConfigurationType type) { + return classNameMatchesAny(type.getCondition().getTypeName()) || classNameMatchesAny(type.getQualifiedJavaName()); + } + + @Override + public boolean testLambdaSerializationType(SerializationConfigurationLambdaCapturingType type) { + return classNameMatchesAny(type.getCondition().getTypeName()) || classNameMatchesAny(type.getQualifiedJavaName()); + } + + @Override + public boolean testPredefinedClass(ConfigurationPredefinedClass clazz) { + return clazz.getNameInfo() != null && classNameMatchesAny(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..56a75eac9f4d --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -0,0 +1,184 @@ +/* + * 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.nio.file.Paths; +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; + } + + 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; + } + } + } + } + + private static Path tryGetPath(URI uri) { + try { + return Paths.get(uri); + } catch (Exception e) { + return null; + } + } +} 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..2b84675e3ba9 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 @@ -24,160 +24,131 @@ */ 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 java.util.function.BiFunction; + +import com.oracle.svm.configure.ConfigurationBase; +import com.oracle.svm.configure.trace.TraceProcessor; 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)); - } - } + private final TypeConfiguration reflectionConfiguration; + private final TypeConfiguration jniConfiguration; + private final ResourceConfiguration resourceConfiguration; + private final ProxyConfiguration proxyConfiguration; + private final SerializationConfiguration serializationConfiguration; + private final PredefinedClassesConfiguration predefinedClassesConfiguration; - 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 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; } - 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(TraceProcessor processor) { + this(processor.getReflectionConfiguration(), processor.getJniConfiguration(), processor.getResourceConfiguration(), processor.getProxyConfiguration(), + processor.getSerializationConfiguration(), processor.getPredefinedClassesConfiguration()); } - public boolean isEmpty() { - return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && - resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() && predefinedClassesConfigPaths.isEmpty(); + public ConfigurationSet(ConfigurationSet other) { + this(new TypeConfiguration(other.reflectionConfiguration), new TypeConfiguration(other.jniConfiguration), new ResourceConfiguration(other.resourceConfiguration), + new ProxyConfiguration(other.proxyConfiguration), new SerializationConfiguration(other.serializationConfiguration), + new PredefinedClassesConfiguration(other.predefinedClassesConfiguration)); } - 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; + @SuppressWarnings("rawtypes") + private ConfigurationSet mutate(ConfigurationSet other, BiFunction mutator) { + TypeConfiguration reflectionConfiguration = (TypeConfiguration) mutator.apply(this.reflectionConfiguration, other.reflectionConfiguration); + TypeConfiguration jniConfiguration = (TypeConfiguration) mutator.apply(this.jniConfiguration, other.jniConfiguration); + ResourceConfiguration resourceConfiguration = (ResourceConfiguration) mutator.apply(this.resourceConfiguration, other.resourceConfiguration); + ProxyConfiguration proxyConfiguration = (ProxyConfiguration) mutator.apply(this.proxyConfiguration, other.proxyConfiguration); + SerializationConfiguration serializationConfiguration = (SerializationConfiguration) mutator.apply(this.serializationConfiguration, other.serializationConfiguration); + PredefinedClassesConfiguration predefinedClassesConfiguration = (PredefinedClassesConfiguration) mutator.apply(this.predefinedClassesConfiguration, other.predefinedClassesConfiguration); + return new ConfigurationSet(reflectionConfiguration, jniConfiguration, resourceConfiguration, proxyConfiguration, serializationConfiguration, predefinedClassesConfiguration); } - public Set getReflectConfigPaths() { - return reflectConfigPaths; + @SuppressWarnings("unchecked") + public ConfigurationSet merge(ConfigurationSet other) { + return mutate(other, ConfigurationBase::copyAndMerge); } - public Set getProxyConfigPaths() { - return proxyConfigPaths; + @SuppressWarnings("unchecked") + public ConfigurationSet subtract(ConfigurationSet other) { + return mutate(other, ConfigurationBase::copyAndSubtract); } - public Set getResourceConfigPaths() { - return resourceConfigPaths; + @SuppressWarnings("unchecked") + public ConfigurationSet intersectWith(ConfigurationSet other) { + return mutate(other, ConfigurationBase::copyAndIntersect); } - public Set getSerializationConfigPaths() { - return serializationConfigPaths; + public ConfigurationSet filter(ConditionalConfigurationFilter filter) { + TypeConfiguration reflectionConfiguration = this.reflectionConfiguration.copyAndFilter(filter); + TypeConfiguration jniConfiguration = this.jniConfiguration.copyAndFilter(filter); + ResourceConfiguration resourceConfiguration = this.resourceConfiguration.copyAndFilter(filter); + ProxyConfiguration proxyConfiguration = this.proxyConfiguration.copyAndFilter(filter); + SerializationConfiguration serializationConfiguration = this.serializationConfiguration.copyAndFilter(filter); + PredefinedClassesConfiguration predefinedClassesConfiguration = this.predefinedClassesConfiguration.copyAndFilter(filter); + return new ConfigurationSet(reflectionConfiguration, jniConfiguration, resourceConfiguration, proxyConfiguration, serializationConfiguration, predefinedClassesConfiguration); } - 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 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..926c7eaebfa2 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.isEmpty() ? null : 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,39 @@ 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()); + for (Map.Entry fieldEntry : other.fields.entrySet()) { + fields.computeIfPresent(fieldEntry.getKey(), (key, value) -> value.newIntersectedWith(fieldEntry.getValue())); + } + } 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..cc8b259d3516 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 @@ -35,13 +35,15 @@ 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; import com.oracle.svm.core.configure.ConfigurationFile; 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; @@ -51,6 +53,42 @@ public PredefinedClassesConfiguration(Path[] classDestinationDirs, Predicate { private final Set>> interfaceLists = ConcurrentHashMap.newKeySet(); public ProxyConfiguration() { @@ -49,6 +49,39 @@ 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 filter(ProxyConfigurationFilterPredicate predicate) { + interfaceLists.removeIf(predicate::testProxyInterfaceList); + } + + @Override + public void subtract(ProxyConfiguration other) { + interfaceLists.removeAll(other.interfaceLists); + } + + public void addWithCondition(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 +94,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 +136,9 @@ private static > int compareList(List l1, List l2) return l1.size() - l2.size(); } + public interface ProxyConfigurationFilterPredicate { + + 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..878d3b9fab6e 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,55 @@ 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())); + } + } + + @Override + public ResourceConfiguration copy() { + return new ResourceConfiguration(this); } - public void removeAll(ResourceConfiguration other) { + @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 filter(ResourceConfigurationFilterPredicate predicate) { + addedResources.entrySet().removeIf(entry -> predicate.testIncludedResource(entry.getKey(), entry.getValue())); + bundles.entrySet().removeIf(entry -> predicate.testIncludedBundle(entry.getKey(), entry.getValue())); + } + + public void addWithCondition(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 +265,10 @@ private static void conditionalElementJson(ConditionalElement p, JsonWri w.quote(elementName).append(':').quote(p.getElement()); w.unindent().newline().append('}'); } + + public interface ResourceConfigurationFilterPredicate { + 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..dde93205b00e 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,41 @@ 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 filter(SerializationConfigurationFilter predicate) { + serializations.removeIf(predicate::testSerializationType); + lambdaSerializationCapturingTypes.removeIf(predicate::testLambdaSerializationType); + } + + public void addWithCondition(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 +171,11 @@ private static SerializationConfigurationLambdaCapturingType createLambdaCapturi String convertedClassName = SignatureUtil.toInternalClassName(className); return new SerializationConfigurationLambdaCapturingType(condition, convertedClassName); } + + public interface SerializationConfigurationFilter { + + 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..b62a1634103f 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,57 @@ 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 + protected void subtract(TypeConfiguration other) { + types.forEach((key, type) -> { + ConfigurationType subtractType = other.types.get(key); + types.compute(key, (k, v) -> ConfigurationType.copyAndSubtract(type, subtractType)); + }); + } + + @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)); + } + }); + } + + @Override + protected void filter(TypeConfigurationFilterPredicate 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 +101,27 @@ 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())); } + public void addWithCondition(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 +142,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 TypeConfigurationFilterPredicate { + + boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type); + + } } 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..e88a9e08b227 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 @@ -63,7 +63,7 @@ public TraceProcessor(AccessAdvisor accessAdvisor, TypeConfiguration jniConfigur public TypeConfiguration getJniConfiguration() { TypeConfiguration result = jniProcessor.getConfiguration(); if (omittedConfigProcessor != null) { - result = TypeConfiguration.copyAndSubtract(result, omittedConfigProcessor.jniProcessor.getConfiguration()); + result = result.copyAndSubtract(omittedConfigProcessor.jniProcessor.getConfiguration()); } return result; } @@ -71,7 +71,7 @@ public TypeConfiguration getJniConfiguration() { public TypeConfiguration getReflectionConfiguration() { TypeConfiguration result = reflectionProcessor.getConfiguration(); if (omittedConfigProcessor != null) { - result = TypeConfiguration.copyAndSubtract(result, omittedConfigProcessor.reflectionProcessor.getConfiguration()); + result = result.copyAndSubtract(omittedConfigProcessor.reflectionProcessor.getConfiguration()); } return result; } @@ -79,8 +79,7 @@ public TypeConfiguration getReflectionConfiguration() { public ProxyConfiguration getProxyConfiguration() { ProxyConfiguration result = reflectionProcessor.getProxyConfiguration(); if (omittedConfigProcessor != null) { - result = new ProxyConfiguration(result); - result.removeAll(omittedConfigProcessor.reflectionProcessor.getProxyConfiguration()); + result.copyAndSubtract(omittedConfigProcessor.reflectionProcessor.getProxyConfiguration()); } return result; } @@ -88,8 +87,7 @@ public ProxyConfiguration getProxyConfiguration() { public ResourceConfiguration getResourceConfiguration() { ResourceConfiguration result = reflectionProcessor.getResourceConfiguration(); if (omittedConfigProcessor != null) { - result = new ResourceConfiguration(result); - result.removeAll(omittedConfigProcessor.reflectionProcessor.getResourceConfiguration()); + result.copyAndSubtract(omittedConfigProcessor.reflectionProcessor.getResourceConfiguration()); } return result; } @@ -97,8 +95,7 @@ public ResourceConfiguration getResourceConfiguration() { public SerializationConfiguration getSerializationConfiguration() { SerializationConfiguration result = serializationProcessor.getSerializationConfiguration(); if (omittedConfigProcessor != null) { - result = new SerializationConfiguration(result); - result.removeAll(omittedConfigProcessor.serializationProcessor.getSerializationConfiguration()); + result.copyAndSubtract(omittedConfigProcessor.serializationProcessor.getSerializationConfiguration()); } return result; } @@ -107,7 +104,7 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { return classLoadingProcessor.getPredefinedClassesConfiguration(); } - public ConfigurationBase getConfiguration(ConfigurationFile configFile) { + public ConfigurationBase getConfiguration(ConfigurationFile configFile) { assert configFile.canBeGeneratedByAgent(); switch (configFile) { case DYNAMIC_PROXY: 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..a6fa6aa12296 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,20 @@ 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 { + } } From aef27a275a6d0bc213d4756fa15eb9b2d69765e9 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 28 Jan 2022 15:27:35 +0100 Subject: [PATCH 2/7] Simplifications. --- .../oracle/svm/agent/NativeImageAgent.java | 4 +-- .../ConditionalConfigurationWriter.java | 6 ++--- .../svm/configure/ConfigurationBase.java | 8 +++--- ...=> ConditionalConfigurationPredicate.java} | 7 +++-- .../configure/config/ConfigurationSet.java | 26 +++++++++---------- .../PredefinedClassesConfiguration.java | 11 ++++---- .../configure/config/ProxyConfiguration.java | 6 ++--- .../config/ResourceConfiguration.java | 6 ++--- .../config/SerializationConfiguration.java | 7 ++--- .../configure/config/TypeConfiguration.java | 6 ++--- 10 files changed, 43 insertions(+), 44 deletions(-) rename substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/{ConditionalConfigurationFilter.java => ConditionalConfigurationPredicate.java} (81%) 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 33aaa2f66a30..f65b8dd60177 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 @@ -68,7 +68,7 @@ import com.oracle.svm.agent.tracing.TraceFileWriter; import com.oracle.svm.agent.tracing.core.Tracer; import com.oracle.svm.agent.tracing.core.TracingResultWriter; -import com.oracle.svm.configure.config.ConditionalConfigurationFilter; +import com.oracle.svm.configure.config.ConditionalConfigurationPredicate; import com.oracle.svm.configure.config.ConfigurationFileCollection; import com.oracle.svm.configure.filters.FilterConfigurationParser; import com.oracle.svm.configure.filters.RuleNode; @@ -309,7 +309,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c tracer = writer; tracingResultWriter = writer; } else if (!predefinedConfigurationPackages.isEmpty()) { - ConditionalConfigurationFilter filter = new ConditionalConfigurationFilter(classNamePatterns); + ConditionalConfigurationPredicate filter = new ConditionalConfigurationPredicate(classNamePatterns); ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(advisor, recordKeeper, predefinedConfigurationPackages, filter); tracer = writer; tracingResultWriter = writer; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java index 49eb8e7aac8c..a2fd6eb3baf9 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java @@ -41,7 +41,7 @@ import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsResultWriterBase; import com.oracle.svm.agent.configwithorigins.MethodInfo; import com.oracle.svm.agent.configwithorigins.MethodInfoRecordKeeper; -import com.oracle.svm.configure.config.ConditionalConfigurationFilter; +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; @@ -61,9 +61,9 @@ public class ConditionalConfigurationWriter extends ConfigurationWithOriginsResultWriterBase { private final Set applicationPackagePrefixes; private ConfigurationSet configurationContainer = new ConfigurationSet(); - private final ConditionalConfigurationFilter filter; + private final ConditionalConfigurationPredicate filter; - public ConditionalConfigurationWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper, Set applicationPackagePrefixes, ConditionalConfigurationFilter filter) { + public ConditionalConfigurationWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper, Set applicationPackagePrefixes, ConditionalConfigurationPredicate filter) { super(advisor, methodInfoRecordKeeper); this.applicationPackagePrefixes = applicationPackagePrefixes; this.filter = filter; 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 ec2119bffccc..7e9dcba27f03 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 @@ -28,7 +28,7 @@ import com.oracle.svm.configure.json.JsonPrintable; -public abstract class ConfigurationBase, U> implements JsonPrintable { +public abstract class ConfigurationBase, P> implements JsonPrintable { public abstract boolean isEmpty(); @@ -40,7 +40,7 @@ public abstract class ConfigurationBase, U> im protected abstract void intersect(T other); - protected abstract void filter(U predicate); + protected abstract void removeIf(P predicate); protected T copyAnd(Consumer consumer) { T copy = copy(); @@ -60,7 +60,7 @@ public T copyAndIntersect(T other) { return copyAnd(copy -> copy.intersect(other)); } - public T copyAndFilter(U predicate) { - return copyAnd(copy -> copy.filter(predicate)); + 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/config/ConditionalConfigurationFilter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java similarity index 81% rename from substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationFilter.java rename to substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java index a0ad69cd3c7e..63737e88ff3a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationFilter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java @@ -6,13 +6,12 @@ import com.oracle.svm.core.configure.ConditionalElement; -public class ConditionalConfigurationFilter - implements TypeConfiguration.TypeConfigurationFilterPredicate, ProxyConfiguration.ProxyConfigurationFilterPredicate, ResourceConfiguration.ResourceConfigurationFilterPredicate, - SerializationConfiguration.SerializationConfigurationFilter, PredefinedClassesConfiguration.PredefinedClassFilterPredicate { +public class ConditionalConfigurationPredicate implements TypeConfiguration.Predicate, ProxyConfiguration.Predicate, + ResourceConfiguration.Predicate, SerializationConfiguration.Predicate, PredefinedClassesConfiguration.Predicate { private final Set classNamePatterns; - public ConditionalConfigurationFilter(Set classNamePatterns) { + public ConditionalConfigurationPredicate(Set classNamePatterns) { this.classNamePatterns = classNamePatterns; } 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 2b84675e3ba9..894fe7ad73a6 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 @@ -25,7 +25,6 @@ package com.oracle.svm.configure.config; import java.nio.file.Path; -import java.util.function.BiFunction; import com.oracle.svm.configure.ConfigurationBase; @@ -34,6 +33,11 @@ import com.oracle.svm.core.util.VMError; public class ConfigurationSet { + @FunctionalInterface + private interface Mutator { + > T apply(T first, T other); + } + private final TypeConfiguration reflectionConfiguration; private final TypeConfiguration jniConfiguration; private final ResourceConfiguration resourceConfiguration; @@ -67,33 +71,29 @@ public ConfigurationSet() { new PredefinedClassesConfiguration(new Path[0], hash -> false)); } - @SuppressWarnings("rawtypes") - private ConfigurationSet mutate(ConfigurationSet other, BiFunction mutator) { - TypeConfiguration reflectionConfiguration = (TypeConfiguration) mutator.apply(this.reflectionConfiguration, other.reflectionConfiguration); - TypeConfiguration jniConfiguration = (TypeConfiguration) mutator.apply(this.jniConfiguration, other.jniConfiguration); - ResourceConfiguration resourceConfiguration = (ResourceConfiguration) mutator.apply(this.resourceConfiguration, other.resourceConfiguration); - ProxyConfiguration proxyConfiguration = (ProxyConfiguration) mutator.apply(this.proxyConfiguration, other.proxyConfiguration); - SerializationConfiguration serializationConfiguration = (SerializationConfiguration) mutator.apply(this.serializationConfiguration, other.serializationConfiguration); - PredefinedClassesConfiguration predefinedClassesConfiguration = (PredefinedClassesConfiguration) mutator.apply(this.predefinedClassesConfiguration, other.predefinedClassesConfiguration); + private ConfigurationSet mutate(ConfigurationSet other, Mutator mutator) { + TypeConfiguration reflectionConfiguration = mutator.apply(this.reflectionConfiguration, other.reflectionConfiguration); + TypeConfiguration jniConfiguration = mutator.apply(this.jniConfiguration, other.jniConfiguration); + ResourceConfiguration resourceConfiguration = mutator.apply(this.resourceConfiguration, other.resourceConfiguration); + ProxyConfiguration proxyConfiguration = mutator.apply(this.proxyConfiguration, other.proxyConfiguration); + SerializationConfiguration serializationConfiguration = mutator.apply(this.serializationConfiguration, other.serializationConfiguration); + PredefinedClassesConfiguration predefinedClassesConfiguration = mutator.apply(this.predefinedClassesConfiguration, other.predefinedClassesConfiguration); return new ConfigurationSet(reflectionConfiguration, jniConfiguration, resourceConfiguration, proxyConfiguration, serializationConfiguration, predefinedClassesConfiguration); } - @SuppressWarnings("unchecked") public ConfigurationSet merge(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndMerge); } - @SuppressWarnings("unchecked") public ConfigurationSet subtract(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndSubtract); } - @SuppressWarnings("unchecked") public ConfigurationSet intersectWith(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndIntersect); } - public ConfigurationSet filter(ConditionalConfigurationFilter filter) { + public ConfigurationSet filter(ConditionalConfigurationPredicate filter) { TypeConfiguration reflectionConfiguration = this.reflectionConfiguration.copyAndFilter(filter); TypeConfiguration jniConfiguration = this.jniConfiguration.copyAndFilter(filter); ResourceConfiguration resourceConfiguration = this.resourceConfiguration.copyAndFilter(filter); 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 cc8b259d3516..98e1a64499bd 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,6 @@ 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; @@ -43,12 +42,12 @@ import com.oracle.svm.core.configure.PredefinedClassesConfigurationParser; import com.oracle.svm.core.hub.PredefinedClassesSupport; -public final class PredefinedClassesConfiguration extends 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; } @@ -80,7 +79,7 @@ protected void intersect(PredefinedClassesConfiguration other) { } @Override - protected void filter(PredefinedClassFilterPredicate predicate) { + protected void removeIf(Predicate predicate) { classes.values().removeIf(predicate::testPredefinedClass); } @@ -175,7 +174,7 @@ public boolean containsClassWithHash(String hash) { return classes.containsKey(hash); } - public interface PredefinedClassFilterPredicate { + 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 1fb472e571af..dbeed01ebb43 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 final class ProxyConfiguration extends ConfigurationBase { +public final class ProxyConfiguration extends ConfigurationBase { private final Set>> interfaceLists = ConcurrentHashMap.newKeySet(); public ProxyConfiguration() { @@ -67,7 +67,7 @@ protected void intersect(ProxyConfiguration other) { } @Override - protected void filter(ProxyConfigurationFilterPredicate predicate) { + protected void removeIf(Predicate predicate) { interfaceLists.removeIf(predicate::testProxyInterfaceList); } @@ -136,7 +136,7 @@ private static > int compareList(List l1, List l2) return l1.size() - l2.size(); } - public interface ProxyConfigurationFilterPredicate { + 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 878d3b9fab6e..986203c838e3 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 @@ -43,7 +43,7 @@ import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ResourcesRegistry; -public final class ResourceConfiguration extends ConfigurationBase { +public final class ResourceConfiguration extends ConfigurationBase { private static final String PROPERTY_BUNDLE = "java.util.PropertyResourceBundle"; @@ -141,7 +141,7 @@ protected void intersect(ResourceConfiguration other) { } @Override - protected void filter(ResourceConfigurationFilterPredicate predicate) { + 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())); } @@ -266,7 +266,7 @@ private static void conditionalElementJson(ConditionalElement p, JsonWri w.unindent().newline().append('}'); } - public interface ResourceConfigurationFilterPredicate { + 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 dde93205b00e..f0dbc6c7932d 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,7 @@ import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.json.JsonWriter; -public final class SerializationConfiguration extends ConfigurationBase +public final class SerializationConfiguration extends ConfigurationBase implements RuntimeSerializationSupport { private final Set serializations = ConcurrentHashMap.newKeySet(); @@ -78,7 +78,7 @@ protected void intersect(SerializationConfiguration other) { } @Override - protected void filter(SerializationConfigurationFilter predicate) { + protected void removeIf(Predicate predicate) { serializations.removeIf(predicate::testSerializationType); lambdaSerializationCapturingTypes.removeIf(predicate::testLambdaSerializationType); } @@ -167,12 +167,13 @@ private static SerializationConfigurationType createConfigurationType(Configurat return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName); } + private static SerializationConfigurationLambdaCapturingType createLambdaCapturingClassConfigurationType(ConfigurationCondition condition, String className) { String convertedClassName = SignatureUtil.toInternalClassName(className); return new SerializationConfigurationLambdaCapturingType(condition, convertedClassName); } - public interface SerializationConfigurationFilter { + public interface Predicate { boolean testSerializationType(SerializationConfigurationType type); 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 b62a1634103f..05bfa16ab6d2 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 @@ -39,7 +39,7 @@ import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.util.VMError; -public final class TypeConfiguration extends ConfigurationBase { +public final class TypeConfiguration extends ConfigurationBase { private final ConcurrentMap, ConfigurationType> types = new ConcurrentHashMap<>(); @@ -86,7 +86,7 @@ protected void intersect(TypeConfiguration other) { } @Override - protected void filter(TypeConfigurationFilterPredicate predicate) { + protected void removeIf(Predicate predicate) { types.entrySet().removeIf(entry -> predicate.testIncludedType(entry.getKey(), entry.getValue())); } @@ -159,7 +159,7 @@ public int hashCode() { return Objects.hash(types); } - public interface TypeConfigurationFilterPredicate { + public interface Predicate { boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type); From 3cb601598f731b0ed2580828089038aedb1d07e8 Mon Sep 17 00:00:00 2001 From: Aleksandar Gradinac Date: Fri, 4 Feb 2022 16:36:52 +0100 Subject: [PATCH 3/7] Add conditional configuration tests --- substratevm/mx.substratevm/mx_substratevm.py | 37 +++- .../ConditionalConfigurationWriter.java | 9 +- .../ConfigurationGenerator.java | 182 ++++++++++++++++++ .../ConfigurationVerifier.java | 101 ++++++++++ .../config-dir/jni-config.json | 2 + .../config-dir/predefined-classes-config.json | 7 + .../config-dir/proxy-config.json | 26 +++ .../config-dir/reflect-config.json | 38 ++++ .../config-dir/resource-config.json | 30 +++ .../config-dir/serialization-config.json | 2 + .../conditionalconfig/resources/Common.txt | 0 .../resources/NotPropagated.txt | 0 .../resources/PropagateToParentA.txt | 0 .../resources/PropagateToParentB.txt | 0 .../configure/config/ConfigurationSet.java | 14 +- .../configure/config/ConfigurationType.java | 2 +- .../configure/config/TypeConfiguration.java | 4 +- .../svm/core/configure/ConfigurationFile.java | 16 +- 18 files changed, 452 insertions(+), 18 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/jni-config.json create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/predefined-classes-config.json create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/proxy-config.json create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/reflect-config.json create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/resource-config.json create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/config-dir/serialization-config.json create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/Common.txt create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/NotPropagated.txt create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentA.txt create mode 100644 substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/resources/PropagateToParentB.txt diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 9240a530d81a..1bc31b475528 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: @@ -462,6 +475,7 @@ def help_stdout_check(output): def native_unittests_task(extra_build_args=None): + tests = ['com.oracle.svm.test', 'com.oracle.svm.configure.test.config'] if mx.is_windows(): # GR-24075 mx_unittest.add_global_ignore_glob('com.oracle.svm.test.ProcessPropertiesTest') @@ -477,7 +491,20 @@ def native_unittests_task(extra_build_args=None): if mx.is_windows(): mx_unittest.add_global_ignore_glob('com.oracle.svm.test.SecurityServiceTest') - native_unittest(['--builder-on-modulepath', '--build-args', _native_unittest_features] + additional_build_args) + native_unittest(['--builder-on-modulepath', '--build-args', _native_unittest_features] + additional_build_args + tests) + + +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) + agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-configuration=com.oracle.svm.configure.test.conditionalconfig'] + jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts)] + + ['com.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator']) + + jvm_unittest(['-Dcom.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier.configpath=' + config_dir] + + ['com.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier']) def javac_image_command(javac_path): @@ -567,11 +594,15 @@ def _native_unittest(native_image, cmdline_args): except IOError: mx.log('warning: could not read blacklist: ' + blacklist) - unittest_args = unmask(pargs.unittest_args) if unmask(pargs.unittest_args) else ['com.oracle.svm.test', 'com.oracle.svm.configure.test'] + unittest_args = unmask(pargs.unittest_args) if unmask(pargs.unittest_args) else ['com.oracle.svm.test'] builder_on_modulepath = pargs.builder_on_modulepath _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/predicatedconfig/ConditionalConfigurationWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java index a2fd6eb3baf9..1e1fc031ccc5 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java @@ -115,7 +115,7 @@ private Set maybePropagateConfiguration(List cal List newNodeConfiguration = new ArrayList<>(); boolean hasNonEmptyNode = false; for (CallNodeWithConfig node : callNodes) { - ConfigurationSet callParentConfig = node.configurationSet.subtract(commonConfig); + ConfigurationSet callParentConfig = node.configurationSet.copyAndSubtract(commonConfig); if (!callParentConfig.isEmpty()) { hasNonEmptyNode = true; } @@ -132,7 +132,7 @@ private Set maybePropagateConfiguration(List cal CallNodeWithConfig node = callNodes.get(i); ConfigurationSet uniqueNodeConfig = newNodeConfiguration.get(i); node.configurationSet = new ConfigurationSet(commonConfig); - node.parent.configurationSet = node.parent.configurationSet.merge(uniqueNodeConfig); + node.parent.configurationSet = node.parent.configurationSet.copyAndMerge(uniqueNodeConfig); affectedNodes.add(node.parent.node.methodInfo); } @@ -145,7 +145,7 @@ private ConfigurationSet findCommonConfigurationForMethod(List> me * 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<>(); @@ -227,7 +228,7 @@ private void propagateConfiguration(Map> me @Override protected String getConfigFileSuffix() { - return "-conditional-config.json"; + return ConfigurationFile.DEFAULT_FILE_NAME_SUFFIX; } private boolean methodOriginatesFromApplicationPackage(MethodInfo methodInfo) { 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..fdd4931ae664 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java @@ -0,0 +1,182 @@ +/* + * 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() { + 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); + } + + 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); + } + } + + 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..24fbba024700 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java @@ -0,0 +1,101 @@ +/* + * 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 { + 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 new ConfigurationSet(configurationFileCollection, e -> e); + } + + 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); + if (resourceURL == null) { + Assert.fail("Configuration file " + resourceName + " does not exist. Make sure that the test or the config directory have not been moved."); + } + return resourceURL.toURI(); + } catch (Exception e) { + throw VMError.shouldNotReachHere("Unexpected error while locating the configuration files.", e); + } + }); + return new ConfigurationSet(configurationFileCollection, e -> e); + } + +} 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/src/com/oracle/svm/configure/config/ConfigurationSet.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java index 894fe7ad73a6..333383e9980f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java @@ -24,7 +24,9 @@ */ package com.oracle.svm.configure.config; +import java.io.IOException; import java.nio.file.Path; +import java.util.function.Function; import com.oracle.svm.configure.ConfigurationBase; @@ -60,6 +62,12 @@ public ConfigurationSet(TraceProcessor processor) { processor.getSerializationConfiguration(), processor.getPredefinedClassesConfiguration()); } + public ConfigurationSet(ConfigurationFileCollection configurationFileCollection, Function exceptionHandler) throws Exception { + this(configurationFileCollection.loadReflectConfig(exceptionHandler), configurationFileCollection.loadJniConfig(exceptionHandler), + configurationFileCollection.loadResourceConfig(exceptionHandler), configurationFileCollection.loadProxyConfig(exceptionHandler), + configurationFileCollection.loadSerializationConfig(exceptionHandler), configurationFileCollection.loadPredefinedClassesConfig(new Path[0], null, exceptionHandler)); + } + public ConfigurationSet(ConfigurationSet other) { this(new TypeConfiguration(other.reflectionConfiguration), new TypeConfiguration(other.jniConfiguration), new ResourceConfiguration(other.resourceConfiguration), new ProxyConfiguration(other.proxyConfiguration), new SerializationConfiguration(other.serializationConfiguration), @@ -81,15 +89,15 @@ private ConfigurationSet mutate(ConfigurationSet other, Mutator mutator) { return new ConfigurationSet(reflectionConfiguration, jniConfiguration, resourceConfiguration, proxyConfiguration, serializationConfiguration, predefinedClassesConfiguration); } - public ConfigurationSet merge(ConfigurationSet other) { + public ConfigurationSet copyAndMerge(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndMerge); } - public ConfigurationSet subtract(ConfigurationSet other) { + public ConfigurationSet copyAndSubtract(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndSubtract); } - public ConfigurationSet intersectWith(ConfigurationSet other) { + public ConfigurationSet copyAndintersectWith(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndIntersect); } 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 926c7eaebfa2..814ca0b7d6fc 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 @@ -76,7 +76,7 @@ static ConfigurationType copyAndIntersect(ConfigurationType type, ConfigurationT assert type.getCondition().equals(toIntersect.getCondition()); assert type.getQualifiedJavaName().equals(toIntersect.getQualifiedJavaName()); copy.intersectWith(toIntersect); - return copy.isEmpty() ? null : copy; + return copy; } static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType toMerge) { 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 05bfa16ab6d2..5c47c87b0e75 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 @@ -68,7 +68,7 @@ protected void merge(TypeConfiguration other) { } @Override - protected void subtract(TypeConfiguration other) { + public void subtract(TypeConfiguration other) { types.forEach((key, type) -> { ConfigurationType subtractType = other.types.get(key); types.compute(key, (k, v) -> ConfigurationType.copyAndSubtract(type, subtractType)); @@ -81,6 +81,8 @@ protected void intersect(TypeConfiguration other) { ConfigurationType intersectedType = other.types.get(key); if (intersectedType != null) { types.compute(key, (k, v) -> ConfigurationType.copyAndIntersect(type, intersectedType)); + } else { + types.remove(key); } }); } 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() { From 9761a10d40d303f3f38bd9f885b65d3bc0f56682 Mon Sep 17 00:00:00 2001 From: Aleksandar Gradinac Date: Tue, 8 Feb 2022 13:03:03 +0100 Subject: [PATCH 4/7] Cleanup --- substratevm/mx.substratevm/mx_substratevm.py | 2 +- .../oracle/svm/agent/NativeImageAgent.java | 24 ++++++------ .../agent/NativeImageAgentJNIHandleSet.java | 35 ----------------- .../ConditionalConfigurationWriter.java | 39 ++++++++----------- .../svm/configure/ConfigurationBase.java | 3 ++ .../configure/config/ConfigurationType.java | 4 +- .../PredefinedClassesConfiguration.java | 5 ++- .../configure/config/ProxyConfiguration.java | 3 +- .../config/ResourceConfiguration.java | 3 +- .../config/SerializationConfiguration.java | 3 +- .../configure/config/TypeConfiguration.java | 8 ++-- .../svm/configure/trace/TraceProcessor.java | 15 +++---- 12 files changed, 54 insertions(+), 90 deletions(-) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 1bc31b475528..853116a7a8e9 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -499,7 +499,7 @@ def conditional_config_task(native_image): config_dir = join(svmbuild_dir(), 'cond-config-test-config') if exists(config_dir): mx.rmtree(config_dir) - agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-configuration=com.oracle.svm.configure.test.conditionalconfig'] + agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-configuration-for-packages=com.oracle.svm.configure.test.conditionalconfig'] jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts)] + ['com.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator']) 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 f65b8dd60177..ad5abaa780a6 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 @@ -130,8 +130,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean experimentalOmitClasspathConfig = false; boolean build = false; boolean configurationWithOrigins = false; - Set predefinedConfigurationPackages = new HashSet<>(); - Set classNamePatterns = new HashSet<>(); + Set conditionalConfigurationUserCodePackagePrefixes = new HashSet<>(); + Set conditionalConfigurationClassNameExcludePatterns = new HashSet<>(); int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds boolean trackReflectionMetadata = true; @@ -203,12 +203,12 @@ 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-configuration=")) { - String applicationPackages = getTokenValue(token); - Collections.addAll(predefinedConfigurationPackages, applicationPackages.split(",")); - } else if (token.startsWith("class-name-filter=")) { + } else if (token.startsWith("experimental-conditional-configuration-for-packages=")) { + String userPackagePrefixes = getTokenValue(token); + Collections.addAll(conditionalConfigurationUserCodePackagePrefixes, userPackagePrefixes.split(",")); + } else if (token.startsWith("conditional-configuration-class-name-exclude-patterns=")) { String classNamePattern = getTokenValue(token); - Arrays.stream(classNamePattern.split(",")).map(Pattern::compile).forEach(classNamePatterns::add); + Arrays.stream(classNamePattern.split(",")).map(Pattern::compile).forEach(conditionalConfigurationClassNameExcludePatterns::add); } else if (token.equals("track-reflection-metadata")) { trackReflectionMetadata = true; } else if (token.startsWith("track-reflection-metadata=")) { @@ -223,7 +223,7 @@ 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 && !predefinedConfigurationPackages.isEmpty()) { + if (configurationWithOrigins && !conditionalConfigurationUserCodePackagePrefixes.isEmpty()) { return error(5, "The agent can only be used in either the configuration with origins mode or the predefined classes mode."); } @@ -258,7 +258,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } } - boolean shouldTraceOriginInformation = configurationWithOrigins || !predefinedConfigurationPackages.isEmpty(); + boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigurationUserCodePackagePrefixes.isEmpty(); final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(shouldTraceOriginInformation); final Supplier interceptedStateSupplier = shouldTraceOriginInformation ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() : OnDemandJavaStackAccess.stackAccessSupplier(); @@ -308,9 +308,9 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c ConfigurationWithOriginsResultWriter writer = new ConfigurationWithOriginsResultWriter(advisor, recordKeeper); tracer = writer; tracingResultWriter = writer; - } else if (!predefinedConfigurationPackages.isEmpty()) { - ConditionalConfigurationPredicate filter = new ConditionalConfigurationPredicate(classNamePatterns); - ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(advisor, recordKeeper, predefinedConfigurationPackages, filter); + } else if (!conditionalConfigurationUserCodePackagePrefixes.isEmpty()) { + ConditionalConfigurationPredicate filter = new ConditionalConfigurationPredicate(conditionalConfigurationClassNameExcludePatterns); + ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(advisor, recordKeeper, conditionalConfigurationUserCodePackagePrefixes, filter); tracer = writer; tracingResultWriter = writer; } else { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index fb2c20126949..bebe65427ff8 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -26,8 +26,6 @@ import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; -import com.oracle.svm.jvmtiagentbase.Support; -import jdk.vm.ci.meta.MetaUtil; import org.graalvm.word.WordFactory; import com.oracle.svm.jni.nativeapi.JNIEnvironment; @@ -235,37 +233,4 @@ public JNIMethodId getJavaUtilResourceBundleGetLocale(JNIEnvironment env) { } return javaUtilResourceBundleGetLocale; } - - private JNIMethodId javaLangClassGetProtectionDomain = WordFactory.nullPointer(); - private JNIMethodId javaSecurityProtectionDomainGetCodeSource = WordFactory.nullPointer(); - private JNIMethodId javaSecurityCodeSourceGetLocation = WordFactory.nullPointer(); - private JNIMethodId javaNetURLGetPath = WordFactory.nullPointer(); - - public String getClassLocationOrNull(JNIEnvironment env, String className) { - String classNameInternal = MetaUtil.toInternalName(className); - JNIObjectHandle clazz = findClass(env, classNameInternal); - if (clazz == null) { - return null; - } - - if (javaLangClassGetProtectionDomain.isNull()) { - javaLangClassGetProtectionDomain = getMethodId(env, javaLangClass, "getProtectionDomain", "()Ljava/security/ProtectionDomain;", false); - - JNIObjectHandle protDomainClass = findClass(env, "java/security/ProtectionDomain"); - javaSecurityProtectionDomainGetCodeSource = getMethodId(env, protDomainClass, "getCodeSource", "()Ljava/security/CodeSource;", false); - - JNIObjectHandle codeSourceClass = findClass(env, "java/security/CodeSource"); - javaSecurityCodeSourceGetLocation = getMethodId(env, codeSourceClass, "getLocation", "()Ljava/net/URL;", false); - - JNIObjectHandle urlClass = findClass(env, "java/net/URL"); - javaNetURLGetPath = getMethodId(env, urlClass, "getPath", "()Ljava/lang/String;", false); - } - - /* return clazz.getProtectionDomain().getCodeSource().getLocation().getPath() */ - JNIObjectHandle protectionDomain = Support.callObjectMethod(env, clazz, javaLangClassGetProtectionDomain); - JNIObjectHandle codeSource = Support.callObjectMethod(env, protectionDomain, javaSecurityProtectionDomainGetCodeSource); - JNIObjectHandle location = Support.callObjectMethod(env, codeSource, javaSecurityCodeSourceGetLocation); - JNIObjectHandle path = Support.callObjectMethod(env, location, javaNetURLGetPath); - return Support.fromJniString(env, path); - } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java index 1e1fc031ccc5..6d3e36c795df 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java @@ -187,22 +187,22 @@ private void deduceConditionalConfiguration(Map methodInfo.getJavaDeclaringClassName().startsWith(prefix)); } - - private int findIndexOfMethodFromApplicationPackage(MethodInfo[] stackTrace, boolean fromBeginning) { - int firstMethodOriginatingInPackage = fromBeginning ? 0 : stackTrace.length - 1; - int step = fromBeginning ? 1 : -1; - while (firstMethodOriginatingInPackage >= 0 && firstMethodOriginatingInPackage < stackTrace.length) { - if (methodOriginatesFromApplicationPackage(stackTrace[firstMethodOriginatingInPackage])) { - return firstMethodOriginatingInPackage; - } - firstMethodOriginatingInPackage = firstMethodOriginatingInPackage + step; - } - return -1; - } - @Override protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { /* Keep only the classes from the selected package names on the stack trace. */ - int firstMethodIndex = findIndexOfMethodFromApplicationPackage(stackTrace, true); - if (firstMethodIndex == -1) { + 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; } - int lastMethodIndex = findIndexOfMethodFromApplicationPackage(stackTrace, false); - return Arrays.copyOfRange(stackTrace, firstMethodIndex, lastMethodIndex); + return Arrays.copyOfRange(stackTrace, 0, dest); } @Override 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 7e9dcba27f03..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 @@ -27,6 +27,7 @@ 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 { @@ -36,6 +37,8 @@ public abstract class ConfigurationBase, P> im 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); 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 814ca0b7d6fc..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 @@ -210,9 +210,7 @@ private void intersectFields(ConfigurationType other) { if (fields != null) { if (other.fields != null) { fields.keySet().retainAll(other.fields.keySet()); - for (Map.Entry fieldEntry : other.fields.entrySet()) { - fields.computeIfPresent(fieldEntry.getKey(), (key, value) -> value.newIntersectedWith(fieldEntry.getValue())); - } + fields.replaceAll((key, value) -> value.newIntersectedWith(other.fields.get(key))); } else { fields = null; } 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 98e1a64499bd..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 @@ -75,7 +75,7 @@ protected void subtract(PredefinedClassesConfiguration other) { @Override protected void intersect(PredefinedClassesConfiguration other) { - classes.keySet().retainAll(classes.keySet()); + classes.keySet().retainAll(other.classes.keySet()); } @Override @@ -83,7 +83,8 @@ protected void removeIf(Predicate predicate) { classes.values().removeIf(predicate::testPredefinedClass); } - public void addWithCondition(ConfigurationCondition condition, PredefinedClassesConfiguration other) { + @Override + public void mergeConditional(ConfigurationCondition condition, PredefinedClassesConfiguration other) { /* Not implemented with conditions yet */ classes.putAll(other.classes); } 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 dbeed01ebb43..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 @@ -76,7 +76,8 @@ public void subtract(ProxyConfiguration other) { interfaceLists.removeAll(other.interfaceLists); } - public void addWithCondition(ConfigurationCondition condition, ProxyConfiguration other) { + @Override + public void mergeConditional(ConfigurationCondition condition, ProxyConfiguration other) { for (ConditionalElement> interfaceList : other.interfaceLists) { add(condition, new ArrayList<>(interfaceList.getElement())); } 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 986203c838e3..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 @@ -146,7 +146,8 @@ protected void removeIf(Predicate predicate) { bundles.entrySet().removeIf(entry -> predicate.testIncludedBundle(entry.getKey(), entry.getValue())); } - public void addWithCondition(ConfigurationCondition condition, ResourceConfiguration other) { + @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()); } 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 f0dbc6c7932d..f014e3b94d39 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 @@ -83,7 +83,8 @@ protected void removeIf(Predicate predicate) { lambdaSerializationCapturingTypes.removeIf(predicate::testLambdaSerializationType); } - public void addWithCondition(ConfigurationCondition condition, SerializationConfiguration other) { + @Override + public void mergeConditional(ConfigurationCondition condition, SerializationConfiguration other) { for (SerializationConfigurationType type : other.serializations) { serializations.add(new SerializationConfigurationType(condition, type.getQualifiedJavaName(), type.getQualifiedCustomTargetConstructorJavaName())); } 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 5c47c87b0e75..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 @@ -69,9 +69,8 @@ protected void merge(TypeConfiguration other) { @Override public void subtract(TypeConfiguration other) { - types.forEach((key, type) -> { - ConfigurationType subtractType = other.types.get(key); - types.compute(key, (k, v) -> ConfigurationType.copyAndSubtract(type, subtractType)); + other.types.forEach((key, type) -> { + types.computeIfPresent(key, (k, v) -> ConfigurationType.copyAndSubtract(v, type)); }); } @@ -118,7 +117,8 @@ public ConfigurationType getOrCreateType(ConfigurationCondition condition, Strin return types.computeIfAbsent(new ConditionalElement<>(condition, qualifiedForNameString), p -> new ConfigurationType(p.getCondition(), p.getElement())); } - public void addWithCondition(ConfigurationCondition condition, TypeConfiguration other) { + @Override + public void mergeConditional(ConfigurationCondition condition, TypeConfiguration other) { other.types.forEach((key, value) -> { addOrMerge(new ConfigurationType(value, condition)); }); 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 e88a9e08b227..9312bcabdda0 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 @@ -104,21 +104,22 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { return classLoadingProcessor.getPredefinedClassesConfiguration(); } - public ConfigurationBase getConfiguration(ConfigurationFile configFile) { + @SuppressWarnings("unchecked") + public > T getConfiguration(ConfigurationFile configFile) { assert configFile.canBeGeneratedByAgent(); switch (configFile) { case DYNAMIC_PROXY: - return getProxyConfiguration(); + return (T) getProxyConfiguration(); case JNI: - return getJniConfiguration(); + return (T) getJniConfiguration(); case REFLECTION: - return getReflectionConfiguration(); + return (T) getReflectionConfiguration(); case RESOURCES: - return getResourceConfiguration(); + return (T) getResourceConfiguration(); case SERIALIZATION: - return getSerializationConfiguration(); + return (T) getSerializationConfiguration(); case PREDEFINED_CLASSES_NAME: - return getPredefinedClassesConfiguration(); + return (T) getPredefinedClassesConfiguration(); default: assert false; // should never reach here } From e2c7dbfef4957fe8f7ed98b5c5b400623dd8d90c Mon Sep 17 00:00:00 2001 From: Aleksandar Gradinac Date: Thu, 24 Feb 2022 21:36:52 +0100 Subject: [PATCH 5/7] General code cleanup. Introduce new format for human readable configuration with origins. --- .../oracle/svm/agent/NativeImageAgent.java | 48 ++-- .../ConditionalConfigurationWriter.java | 124 ++++------ ...HumanReadableConfigurationWithOrigins.java | 141 +++++++++++ .../ConfigurationWithOrigins.java | 132 +++++++++++ .../ConfigurationWithOriginsResultWriter.java | 142 ----------- ...figurationWithOriginsResultWriterBase.java | 224 ------------------ .../ConfigurationWithOriginsTracer.java | 89 +++++++ .../ConfigurationWithOriginsWriter.java | 58 +++++ .../configwithorigins/MethodCallNode.java | 114 +++++++++ .../agent/configwithorigins/MethodInfo.java | 5 + .../tracing/ConfigurationResultWriter.java | 27 +-- .../ConfigurationVerifier.java | 4 +- .../test/config/OmitPreviousConfigTests.java | 48 ++-- .../svm/configure/ConfigurationTool.java | 53 ++--- .../config/ConfigurationFileCollection.java | 7 + .../configure/config/ConfigurationSet.java | 44 ++-- .../configure/trace/AbstractProcessor.java | 4 +- .../trace/ClassLoadingProcessor.java | 15 +- .../svm/configure/trace/JniProcessor.java | 21 +- .../configure/trace/ReflectionProcessor.java | 42 ++-- .../trace/SerializationProcessor.java | 14 +- .../svm/configure/trace/TraceProcessor.java | 107 ++------- 22 files changed, 750 insertions(+), 713 deletions(-) rename substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/{predicatedconfig => conditionalconfig}/ConditionalConfigurationWriter.java (66%) create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/HumanReadableConfigurationWithOrigins.java create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOrigins.java delete mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriter.java delete mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriterBase.java create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsTracer.java create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java create mode 100644 substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodCallNode.java 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 ad5abaa780a6..193fc39aab2c 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 @@ -40,7 +40,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.ConcurrentModificationException; -import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -57,10 +56,10 @@ import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.hosted.Feature; -import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsResultWriter; +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.ConditionalConfigurationWriter; +import com.oracle.svm.agent.conditionalconfig.ConditionalConfigurationWriter; import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess; import com.oracle.svm.agent.stackaccess.InterceptedState; import com.oracle.svm.agent.stackaccess.OnDemandJavaStackAccess; @@ -70,6 +69,7 @@ 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.FilterConfigurationParser; import com.oracle.svm.configure.filters.RuleNode; import com.oracle.svm.configure.trace.AccessAdvisor; @@ -130,8 +130,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean experimentalOmitClasspathConfig = false; boolean build = false; boolean configurationWithOrigins = false; - Set conditionalConfigurationUserCodePackagePrefixes = new HashSet<>(); - Set conditionalConfigurationClassNameExcludePatterns = new HashSet<>(); + Set conditionalConfigUserPackagePrefixes = new HashSet<>(); + Set conditionalConfigClassNameExcludePatterns = new HashSet<>(); int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds boolean trackReflectionMetadata = true; @@ -203,12 +203,12 @@ 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-configuration-for-packages=")) { - String userPackagePrefixes = getTokenValue(token); - Collections.addAll(conditionalConfigurationUserCodePackagePrefixes, userPackagePrefixes.split(",")); - } else if (token.startsWith("conditional-configuration-class-name-exclude-patterns=")) { + } else if (token.startsWith("experimental-conditional-config-for-package=")) { + String userPackagePrefix = getTokenValue(token); + conditionalConfigUserPackagePrefixes.add(userPackagePrefix); + } else if (token.startsWith("conditional-config-class-name-exclude-pattern=")) { String classNamePattern = getTokenValue(token); - Arrays.stream(classNamePattern.split(",")).map(Pattern::compile).forEach(conditionalConfigurationClassNameExcludePatterns::add); + conditionalConfigClassNameExcludePatterns.add(Pattern.compile(classNamePattern)); } else if (token.equals("track-reflection-metadata")) { trackReflectionMetadata = true; } else if (token.startsWith("track-reflection-metadata=")) { @@ -223,7 +223,7 @@ 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 && !conditionalConfigurationUserCodePackagePrefixes.isEmpty()) { + if (configurationWithOrigins && !conditionalConfigUserPackagePrefixes.isEmpty()) { return error(5, "The agent can only be used in either the configuration with origins mode or the predefined classes mode."); } @@ -258,7 +258,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } } - boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigurationUserCodePackagePrefixes.isEmpty(); + boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigUserPackagePrefixes.isEmpty(); final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(shouldTraceOriginInformation); final Supplier interceptedStateSupplier = shouldTraceOriginInformation ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() : OnDemandJavaStackAccess.stackAccessSupplier(); @@ -291,26 +291,25 @@ 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 (!conditionalConfigurationUserCodePackagePrefixes.isEmpty()) { - ConditionalConfigurationPredicate filter = new ConditionalConfigurationPredicate(conditionalConfigurationClassNameExcludePatterns); - ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(advisor, recordKeeper, conditionalConfigurationUserCodePackagePrefixes, filter); + } else if (!conditionalConfigUserPackagePrefixes.isEmpty()) { + ConditionalConfigurationPredicate filter = new ConditionalConfigurationPredicate(conditionalConfigClassNameExcludePatterns); + ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(processor, recordKeeper, conditionalConfigUserPackagePrefixes, filter); tracer = writer; tracingResultWriter = writer; } else { @@ -325,10 +324,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; } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java similarity index 66% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java rename to substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java index 6d3e36c795df..93412a25de41 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConditionalConfigurationWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.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.conditionalconfig; import java.io.IOException; import java.nio.file.Path; @@ -37,10 +37,11 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; -import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsResultWriter; -import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsResultWriterBase; +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; @@ -48,9 +49,7 @@ 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.core.configure.ConfigurationFile; +import com.oracle.svm.configure.trace.TraceProcessor; /** * Outputs configuration augmented with reachability conditions. @@ -58,40 +57,35 @@ * This writer leverages the configuration origin information to deduce conditions for the * configuration. See {@link #createConditionalConfiguration()} */ -public class ConditionalConfigurationWriter extends ConfigurationWithOriginsResultWriterBase { +public class ConditionalConfigurationWriter extends ConfigurationWithOriginsTracer implements TracingResultWriter { private final Set applicationPackagePrefixes; private ConfigurationSet configurationContainer = new ConfigurationSet(); private final ConditionalConfigurationPredicate filter; - public ConditionalConfigurationWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper, Set applicationPackagePrefixes, ConditionalConfigurationPredicate filter) { - super(advisor, methodInfoRecordKeeper); + public ConditionalConfigurationWriter(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper, Set applicationPackagePrefixes, ConditionalConfigurationPredicate filter) { + super(processor, methodInfoRecordKeeper); this.applicationPackagePrefixes = applicationPackagePrefixes; this.filter = filter; } - private Map> mapMethodsToCallNodes() { + 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<>(); - Map> children = new HashMap<>(); + Map> methodCallNodes = new HashMap<>(); + ConfigurationSet emptyConfigurationSet = new ConfigurationSet(); rootNode.visitPostOrder(node -> { - List callNodes = methodCallNodes.computeIfAbsent(node.methodInfo, info -> new ArrayList<>()); - ConfigurationSet configurationContainer = node.processor == null ? new ConfigurationSet() : new ConfigurationSet(node.processor); - CallNodeWithConfig newNode = new CallNodeWithConfig(node, configurationContainer); - callNodes.add(newNode); - - List parentNodes = children.computeIfAbsent(node.parent, parent -> new ArrayList<>()); - parentNodes.add(newNode); - if (children.containsKey(node)) { - for (CallNodeWithConfig callNodeWithConfig : children.get(node)) { - callNodeWithConfig.parent = newNode; - } + if (node.configuration == null) { + node.configuration = emptyConfigurationSet; } + List callNodes = methodCallNodes.computeIfAbsent(node.methodInfo, info -> new ArrayList<>()); + callNodes.add(node); }); return methodCallNodes; } - private Set maybePropagateConfiguration(List callNodes) { + /* This code is only ever executed by one thread. */ + @SuppressWarnings("NonAtomicOperationOnVolatileField") + private 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 @@ -114,8 +108,8 @@ private Set maybePropagateConfiguration(List cal */ List newNodeConfiguration = new ArrayList<>(); boolean hasNonEmptyNode = false; - for (CallNodeWithConfig node : callNodes) { - ConfigurationSet callParentConfig = node.configurationSet.copyAndSubtract(commonConfig); + for (MethodCallNode node : callNodes) { + ConfigurationSet callParentConfig = node.configuration.copyAndSubtract(commonConfig); if (!callParentConfig.isEmpty()) { hasNonEmptyNode = true; } @@ -129,58 +123,53 @@ private Set maybePropagateConfiguration(List cal Set affectedNodes = new HashSet<>(); for (int i = 0; i < callNodes.size(); i++) { - CallNodeWithConfig node = callNodes.get(i); + MethodCallNode node = callNodes.get(i); ConfigurationSet uniqueNodeConfig = newNodeConfiguration.get(i); - node.configurationSet = new ConfigurationSet(commonConfig); - node.parent.configurationSet = node.parent.configurationSet.copyAndMerge(uniqueNodeConfig); - affectedNodes.add(node.parent.node.methodInfo); + node.configuration = new ConfigurationSet(commonConfig); + node.parent.configuration = node.parent.configuration.copyAndMerge(uniqueNodeConfig); + affectedNodes.add(node.parent.methodInfo); } return affectedNodes; } - private ConfigurationSet findCommonConfigurationForMethod(List callNodes) { + private ConfigurationSet findCommonConfigurationForMethod(List callNodes) { ConfigurationSet config = null; - for (CallNodeWithConfig node : callNodes) { + for (MethodCallNode node : callNodes) { if (config == null) { - config = node.configurationSet; + config = node.configuration; } else { - config = config.copyAndintersectWith(node.configurationSet); + config = config.copyAndIntersectWith(node.configuration); } } return config; } - @Override - protected void beforeWritingConfig() { - createConditionalConfiguration(); - } - private void createConditionalConfiguration() { - Map> methodCallNodes = mapMethodsToCallNodes(); + Map> methodCallNodes = mapMethodsToCallNodes(); propagateConfiguration(methodCallNodes); deduceConditionalConfiguration(methodCallNodes); } - private void deduceConditionalConfiguration(Map> 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. */ - CallNodeWithConfig rootNode = methodCallNodes.remove(null).get(0); + MethodCallNode rootNode = methodCallNodes.remove(null).get(0); - for (List value : methodCallNodes.values()) { - for (CallNodeWithConfig node : value) { - String className = node.node.methodInfo.getJavaDeclaringClassName(); + for (List value : methodCallNodes.values()) { + for (MethodCallNode node : value) { + String className = node.methodInfo.getJavaDeclaringClassName(); ConfigurationCondition condition = ConfigurationCondition.create(className); - addConfigurationWithCondition(node.configurationSet, condition); + addConfigurationWithCondition(node.configuration, condition); } } - addConfigurationWithCondition(rootNode.configurationSet, ConfigurationCondition.alwaysTrue()); + addConfigurationWithCondition(rootNode.configuration, ConfigurationCondition.alwaysTrue()); filterConfiguration(); } @@ -209,7 +198,7 @@ private void filterConfiguration() { configurationContainer = configurationContainer.filter(filter); } - private void propagateConfiguration(Map> methodCallNodes) { + private void propagateConfiguration(Map> methodCallNodes) { /* * Iteratively propagate configuration from children to parent calls until an iteration * doesn't produce any changes. @@ -218,7 +207,7 @@ private void propagateConfiguration(Map> me Set methodsToHandle = methodCallNodes.keySet(); while (methodsToHandle.size() != 0) { Set nextIterationMethodsToHandle = new HashSet<>(); - for (List callNodes : methodCallNodes.values()) { + for (List callNodes : methodCallNodes.values()) { Set affectedMethods = maybePropagateConfiguration(callNodes); nextIterationMethodsToHandle.addAll(affectedMethods); } @@ -226,14 +215,10 @@ private void propagateConfiguration(Map> me } } - @Override - protected String getConfigFileSuffix() { - return ConfigurationFile.DEFAULT_FILE_NAME_SUFFIX; - } - private boolean methodOriginatesFromApplicationPackage(MethodInfo methodInfo) { return applicationPackagePrefixes.stream().anyMatch(prefix -> methodInfo.getJavaDeclaringClassName().startsWith(prefix)); } + @Override protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { /* Keep only the classes from the selected package names on the stack trace. */ @@ -258,32 +243,17 @@ public boolean supportsOnUnloadTraceWriting() { } @Override - protected void writeConfig(JsonWriter writer, ConfigurationFile configurationFile) throws IOException { - configurationContainer.getConfiguration(configurationFile).printJson(writer); - } - - protected void writeOriginConfig(JsonWriter writer, ConfigurationFile configurationFile) throws IOException { - ConfigurationWithOriginsResultWriter.writeJson(rootNode, writer, configurationFile); + public boolean supportsPeriodicTraceWriting() { + return false; } @Override public List writeToDirectory(Path directoryPath) throws IOException { - List writtenPaths = new ArrayList<>(); - writtenPaths.addAll(super.writeToDirectory(directoryPath)); - writtenPaths.addAll( - writeToDirectory(directoryPath, this::writeOriginConfig, configurationFile -> configurationFile.getFileName(ConfigurationWithOriginsResultWriter.CONFIG_WITH_ORIGINS_SUFFIX))); - return writtenPaths; - } - - private static class CallNodeWithConfig { - protected final MethodCallNode node; - protected CallNodeWithConfig parent; - protected ConfigurationSet configurationSet; - - CallNodeWithConfig(MethodCallNode node, ConfigurationSet configurationSet) { - this.node = node; - this.parent = null; - this.configurationSet = configurationSet; - } + 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/conditionalconfig/HumanReadableConfigurationWithOrigins.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/HumanReadableConfigurationWithOrigins.java new file mode 100644 index 000000000000..6e2c6dc22cf0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/HumanReadableConfigurationWithOrigins.java @@ -0,0 +1,141 @@ +/* + * 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.conditionalconfig; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Set; + +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.agent.configwithorigins.MethodCallNode; +import com.oracle.svm.configure.ConfigurationBase; +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.ConfigurationFile; + +public class HumanReadableConfigurationWithOrigins extends ConfigurationBase { + + 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 { + Set nodesWithNonEmptyConfig = root.getNodesWithNonEmptyConfig(configFile); + 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); + } + } + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + @Override + public HumanReadableConfigurationWithOrigins copy() { + throw new UnsupportedOperationException(); + } + + @Override + protected void merge(HumanReadableConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void mergeConditional(ConfigurationCondition condition, HumanReadableConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void subtract(HumanReadableConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void intersect(HumanReadableConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void removeIf(DummyPredicate predicate) { + throw new UnsupportedOperationException(); + } + + interface DummyPredicate { + + } +} 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..ab377d4c50c9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOrigins.java @@ -0,0 +1,132 @@ +/* + * 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 org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.configure.ConfigurationBase; +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.configure.ConfigurationFile; + +public class ConfigurationWithOrigins extends ConfigurationBase { + + 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("}"); + + } + + @Override + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + @Override + public ConfigurationWithOrigins copy() { + throw new UnsupportedOperationException(); + } + + @Override + protected void merge(ConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void mergeConditional(ConfigurationCondition condition, ConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void subtract(ConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void intersect(ConfigurationWithOrigins other) { + throw new UnsupportedOperationException(); + } + + @Override + protected void removeIf(DummyPredicate predicate) { + throw new UnsupportedOperationException(); + } + + interface DummyPredicate { + + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriter.java deleted file mode 100644 index 9427374621da..000000000000 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriter.java +++ /dev/null @@ -1,142 +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.configwithorigins; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -import com.oracle.svm.configure.json.JsonWriter; -import com.oracle.svm.configure.trace.AccessAdvisor; -import com.oracle.svm.core.configure.ConfigurationFile; - -public class ConfigurationWithOriginsResultWriter extends ConfigurationWithOriginsResultWriterBase { - - public static final String CONFIG_WITH_ORIGINS_SUFFIX = "-origins.json"; - - public ConfigurationWithOriginsResultWriter(AccessAdvisor advisor, MethodInfoRecordKeeper methodInfoRecordKeeper) { - super(advisor, methodInfoRecordKeeper); - } - - @Override - public boolean supportsOnUnloadTraceWriting() { - return true; - } - - @Override - protected String getConfigFileSuffix() { - return CONFIG_WITH_ORIGINS_SUFFIX; - } - - public static void writeJson(MethodCallNode root, 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. - */ - root.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(root, writer, configFile, includedNodes); - } - - private static void writeJson(MethodCallNode node, JsonWriter writer, ConfigurationFile configFile, Set includedNodes) throws IOException { - if (node.isRoot()) { - writer.append("[").newline() - .append("{").indent().newline() - .quote("configuration-with-origins").append(": ["); - printChildMethodJson(node, writer, configFile, includedNodes); - writer.newline() - .append("]").unindent().newline(); - if (node.hasConfig(configFile)) { - writer.quote("configuration-without-origins").append(": ").indent(); - writeConfigJson(node, writer, configFile); - writer.unindent().newline(); - } - writer.append("}").newline() - .append("]").newline(); - } else { - writer.append("{").indent().newline(); - - writer.quote("method").append(": ").quote(node.methodInfo.getJavaDeclaringClassName() + "#" + node.methodInfo.getJavaMethodNameAndSignature()).append(",").newline(); - - if (anyChildrenIncluded(node, includedNodes)) { - writer.quote("methods").append(": ["); - printChildMethodJson(node, writer, configFile, includedNodes); - writer.newline().append("]"); - if (node.hasConfig(configFile)) { - writer.append(",").newline(); - } - } - - if (node.hasConfig(configFile)) { - writeConfigJson(node, writer, configFile); - } - - writer.unindent().newline(); - writer.append("}"); - } - } - - private static void printChildMethodJson(MethodCallNode node, JsonWriter writer, ConfigurationFile configFile, Set includedNodes) throws IOException { - boolean first = true; - for (MethodCallNode methodCallNode : node.calledMethods.values()) { - if (!includedNodes.contains(methodCallNode)) { - continue; - } - if (first) { - first = false; - } else { - writer.append(","); - } - writer.newline(); - writeJson(methodCallNode, writer, configFile, includedNodes); - } - } - - private static boolean anyChildrenIncluded(MethodCallNode node, Set includedNodes) { - return node.calledMethods.values().stream().anyMatch(includedNodes::contains); - } - - private static void writeConfigJson(MethodCallNode node, JsonWriter writer, ConfigurationFile configFile) throws IOException { - writer.quote("config").append(": "); - node.processor.getConfiguration(configFile).printJson(writer); - } - - @Override - protected void writeConfig(JsonWriter writer, ConfigurationFile configFile) throws IOException { - writeJson(rootNode, writer, configFile); - } -} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriterBase.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriterBase.java deleted file mode 100644 index 3c0a07f8f9e5..000000000000 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsResultWriterBase.java +++ /dev/null @@ -1,224 +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.configwithorigins; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import java.util.function.Function; -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 abstract class ConfigurationWithOriginsResultWriterBase extends Tracer implements TracingResultWriter { - - protected final AccessAdvisor advisor; - protected final MethodCallNode rootNode; - protected final MethodInfoRecordKeeper methodInfoRecordKeeper; - - public ConfigurationWithOriginsResultWriterBase(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"); - MethodInfo[] stackTrace = methodInfoRecordKeeper.getStackTraceInfo(rawStackTrace); - Map transformedEntry = ConfigurationResultWriter.arraysToLists(entry); - - if (stackTrace == null) { - rootNode.traceEntry(this::createNewTraceProcessor, transformedEntry); - } else { - stackTrace = filterStackTrace(stackTrace); - if (stackTrace != null) { - rootNode.dispatchTraceEntry(stackTrace, stackTrace.length - 1, transformedEntry, this::createNewTraceProcessor); - } - } - } - } - - protected 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); - } - - protected static final class MethodCallNode { - - public final MethodInfo methodInfo; - public final MethodCallNode parent; - public final Map calledMethods; - public 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 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 false; - } - - protected abstract String getConfigFileSuffix(); - - protected abstract void writeConfig(JsonWriter writer, ConfigurationFile configFile) throws IOException; - - protected MethodInfo[] filterStackTrace(MethodInfo[] stackTrace) { - return stackTrace; - } - - protected void beforeWritingConfig() { - } - - protected static List writeToDirectory(Path directoryPath, ConfigurationWriter configWriter, Function configFileResolver) throws IOException { - List writtenPaths = new ArrayList<>(); - for (ConfigurationFile configFile : ConfigurationFile.values()) { - if (configFile.canBeGeneratedByAgent()) { - Path filePath = directoryPath.resolve(configFileResolver.apply(configFile)); - try (JsonWriter writer = new JsonWriter(filePath)) { - configWriter.writeConfig(writer, configFile); - } - writtenPaths.add(filePath); - } - } - return writtenPaths; - } - - public List writeToDirectory(Path directoryPath) throws IOException { - beforeWritingConfig(); - return writeToDirectory(directoryPath, this::writeConfig, configFile -> configFile.getFileName(getConfigFileSuffix())); - } - - @FunctionalInterface - protected interface ConfigurationWriter { - void writeConfig(JsonWriter writer, ConfigurationFile file) throws IOException; - } -} 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..a5dca8a48507 --- /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, processor, entry); + } + } + } + } + + public void dispatchTraceEntry(MethodInfo[] stackTrace, TraceProcessor processor, 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..768125d00ed8 --- /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.json"; + + 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 ConfigurationWithOrigins(rootNode, configFile)); + } +} 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/configwithorigins/MethodInfo.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java index fc9bace31443..1806988cf35c 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/MethodInfo.java @@ -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/tracing/ConfigurationResultWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/ConfigurationResultWriter.java index 5c654b3b6fd2..a79c7581a263 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 omitedConfiguration; - public ConfigurationResultWriter(TraceProcessor processor) { + public ConfigurationResultWriter(TraceProcessor processor, ConfigurationSet configuration, ConfigurationSet omittedConfiguration) { this.processor = processor; + this.configuration = configuration; + this.omitedConfiguration = 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(omitedConfiguration); + 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/ConfigurationVerifier.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java index 24fbba024700..505244527b8d 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java @@ -78,7 +78,7 @@ private static ConfigurationSet loadActualConfig() throws Exception { String configurationPath = System.getProperty(CONFIG_PATH_PROPERTY); ConfigurationFileCollection configurationFileCollection = new ConfigurationFileCollection(); configurationFileCollection.addDirectory(Paths.get(configurationPath)); - return new ConfigurationSet(configurationFileCollection, e -> e); + return configurationFileCollection.loadConfigurationSet(e -> e, null, null); } private static ConfigurationSet loadExpectedConfig() throws Exception { @@ -95,7 +95,7 @@ private static ConfigurationSet loadExpectedConfig() throws Exception { throw VMError.shouldNotReachHere("Unexpected error while locating the configuration files.", e); } }); - return new ConfigurationSet(configurationFileCollection, e -> e); + return configurationFileCollection.loadConfigurationSet(e -> e, null, null); } } 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 d92c0681d244..70946f9c891b 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,11 +39,12 @@ 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; import com.oracle.svm.configure.config.ConfigurationMethod; -import com.oracle.svm.configure.config.ConfigurationFileCollection; +import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.ConfigurationType; import com.oracle.svm.configure.config.FieldInfo; import com.oracle.svm.configure.config.PredefinedClassesConfiguration; @@ -52,7 +53,6 @@ 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,7 +60,7 @@ 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 { ConfigurationFileCollection configurationFileCollection = new ConfigurationFileCollection(); configurationFileCollection.addDirectory(resourceFileName -> { @@ -82,12 +82,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, configurationFileCollection.loadJniConfig(handler), configurationFileCollection.loadReflectConfig(handler), configurationFileCollection.loadProxyConfig(handler), - configurationFileCollection.loadResourceConfig(handler), configurationFileCollection.loadSerializationConfig(handler), - configurationFileCollection.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 +93,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() { 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 47c114910f4a..869d5cea162e 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 @@ -45,6 +45,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import com.oracle.svm.configure.config.ConfigurationSet; import org.graalvm.nativeimage.ImageInfo; import com.oracle.svm.configure.config.ConfigurationFileCollection; @@ -263,31 +264,19 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA callersFilter.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(ConfigurationFileCollection.FAIL_ON_EXCEPTION), - omittedInputSet.loadReflectConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), - omittedInputSet.loadProxyConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), omittedInputSet.loadResourceConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), - omittedInputSet.loadSerializationConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), omittedInputSet.loadPredefinedClassesConfig(null, null, ConfigurationFileCollection.FAIL_ON_EXCEPTION), - null); + omittedConfigurationSet = omittedInputSet.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, null, null); List predefinedClassDestDirs = new ArrayList<>(); for (URI pathUri : outputSet.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(ConfigurationFileCollection.FAIL_ON_EXCEPTION), inputSet.loadReflectConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), - inputSet.loadProxyConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), inputSet.loadResourceConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), - inputSet.loadSerializationConfig(ConfigurationFileCollection.FAIL_ON_EXCEPTION), - inputSet.loadPredefinedClassesConfig(predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash, ConfigurationFileCollection.FAIL_ON_EXCEPTION), - omittedInputTraceProcessor); + Predicate shouldExcludeClassesWithHash = omittedConfigurationSet.getPredefinedClassesConfiguration()::containsClassWithHash; + configurationSet = inputSet.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash); } catch (IOException e) { throw e; } catch (Throwable t) { @@ -296,9 +285,19 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA if (traceInputs.isEmpty() && inputSet.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); + } } } @@ -307,32 +306,32 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA } for (URI uri : outputSet.getReflectConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getReflectionConfiguration().printJson(writer); + configurationSet.getReflectionConfiguration().printJson(writer); } } for (URI uri : outputSet.getJniConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getJniConfiguration().printJson(writer); + configurationSet.getJniConfiguration().printJson(writer); } } for (URI uri : outputSet.getProxyConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getProxyConfiguration().printJson(writer); + configurationSet.getProxyConfiguration().printJson(writer); } } for (URI uri : outputSet.getResourceConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getResourceConfiguration().printJson(writer); + configurationSet.getResourceConfiguration().printJson(writer); } } for (URI uri : outputSet.getSerializationConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getSerializationConfiguration().printJson(writer); + configurationSet.getSerializationConfiguration().printJson(writer); } } for (URI uri : outputSet.getPredefinedClassesConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { - p.getPredefinedClassesConfiguration().printJson(writer); + configurationSet.getPredefinedClassesConfiguration().printJson(writer); } } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java index 56a75eac9f4d..52d8dbffa4de 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -152,6 +152,13 @@ public SerializationConfiguration loadSerializationConfig(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); 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 333383e9980f..87c0ce43b502 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 @@ -26,11 +26,13 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.function.Function; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.configure.trace.TraceProcessor; +import com.oracle.svm.configure.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.util.VMError; @@ -47,7 +49,7 @@ private interface Mutator { private final SerializationConfiguration serializationConfiguration; private final PredefinedClassesConfiguration predefinedClassesConfiguration; - private ConfigurationSet(TypeConfiguration reflectionConfiguration, TypeConfiguration jniConfiguration, ResourceConfiguration resourceConfiguration, ProxyConfiguration proxyConfiguration, + public ConfigurationSet(TypeConfiguration reflectionConfiguration, TypeConfiguration jniConfiguration, ResourceConfiguration resourceConfiguration, ProxyConfiguration proxyConfiguration, SerializationConfiguration serializationConfiguration, PredefinedClassesConfiguration predefinedClassesConfiguration) { this.reflectionConfiguration = reflectionConfiguration; this.jniConfiguration = jniConfiguration; @@ -57,21 +59,9 @@ private ConfigurationSet(TypeConfiguration reflectionConfiguration, TypeConfigur this.predefinedClassesConfiguration = predefinedClassesConfiguration; } - public ConfigurationSet(TraceProcessor processor) { - this(processor.getReflectionConfiguration(), processor.getJniConfiguration(), processor.getResourceConfiguration(), processor.getProxyConfiguration(), - processor.getSerializationConfiguration(), processor.getPredefinedClassesConfiguration()); - } - - public ConfigurationSet(ConfigurationFileCollection configurationFileCollection, Function exceptionHandler) throws Exception { - this(configurationFileCollection.loadReflectConfig(exceptionHandler), configurationFileCollection.loadJniConfig(exceptionHandler), - configurationFileCollection.loadResourceConfig(exceptionHandler), configurationFileCollection.loadProxyConfig(exceptionHandler), - configurationFileCollection.loadSerializationConfig(exceptionHandler), configurationFileCollection.loadPredefinedClassesConfig(new Path[0], null, exceptionHandler)); - } - public ConfigurationSet(ConfigurationSet other) { - this(new TypeConfiguration(other.reflectionConfiguration), new TypeConfiguration(other.jniConfiguration), new ResourceConfiguration(other.resourceConfiguration), - new ProxyConfiguration(other.proxyConfiguration), new SerializationConfiguration(other.serializationConfiguration), - new PredefinedClassesConfiguration(other.predefinedClassesConfiguration)); + this(other.reflectionConfiguration.copy(), other.jniConfiguration.copy(), other.resourceConfiguration.copy(), other.proxyConfiguration.copy(), other.serializationConfiguration.copy(), + other.predefinedClassesConfiguration.copy()); } public ConfigurationSet() { @@ -97,7 +87,7 @@ public ConfigurationSet copyAndSubtract(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndSubtract); } - public ConfigurationSet copyAndintersectWith(ConfigurationSet other) { + public ConfigurationSet copyAndIntersectWith(ConfigurationSet other) { return mutate(other, ConfigurationBase::copyAndIntersect); } @@ -154,9 +144,27 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { } } + public static List writeConfiguration(Function configFilePathResolver, Function> configSupplier) throws IOException { + List writtenFiles = new ArrayList<>(); + for (ConfigurationFile configFile : ConfigurationFile.values()) { + if (configFile.canBeGeneratedByAgent()) { + Path path = configFilePathResolver.apply(configFile); + writtenFiles.add(path); + JsonWriter writer = new JsonWriter(path); + configSupplier.apply(configFile).printJson(writer); + writer.newline(); + writer.close(); + } + } + 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/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/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..9d13fa687f90 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 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 9312bcabdda0..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,101 +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 = result.copyAndSubtract(omittedConfigProcessor.jniProcessor.getConfiguration()); - } - return result; - } - - public TypeConfiguration getReflectionConfiguration() { - TypeConfiguration result = reflectionProcessor.getConfiguration(); - if (omittedConfigProcessor != null) { - result = result.copyAndSubtract(omittedConfigProcessor.reflectionProcessor.getConfiguration()); - } - return result; - } - - public ProxyConfiguration getProxyConfiguration() { - ProxyConfiguration result = reflectionProcessor.getProxyConfiguration(); - if (omittedConfigProcessor != null) { - result.copyAndSubtract(omittedConfigProcessor.reflectionProcessor.getProxyConfiguration()); - } - return result; - } - - public ResourceConfiguration getResourceConfiguration() { - ResourceConfiguration result = reflectionProcessor.getResourceConfiguration(); - if (omittedConfigProcessor != null) { - result.copyAndSubtract(omittedConfigProcessor.reflectionProcessor.getResourceConfiguration()); - } - return result; - } - - public SerializationConfiguration getSerializationConfiguration() { - SerializationConfiguration result = serializationProcessor.getSerializationConfiguration(); - if (omittedConfigProcessor != null) { - result.copyAndSubtract(omittedConfigProcessor.serializationProcessor.getSerializationConfiguration()); - } - return result; - } - - public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { - return classLoadingProcessor.getPredefinedClassesConfiguration(); - } - - @SuppressWarnings("unchecked") - public > T getConfiguration(ConfigurationFile configFile) { - assert configFile.canBeGeneratedByAgent(); - switch (configFile) { - case DYNAMIC_PROXY: - return (T) getProxyConfiguration(); - case JNI: - return (T) getJniConfiguration(); - case REFLECTION: - return (T) getReflectionConfiguration(); - case RESOURCES: - return (T) getResourceConfiguration(); - case SERIALIZATION: - return (T) getSerializationConfiguration(); - case PREDEFINED_CLASSES_NAME: - return (T) 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) { @@ -157,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); From 555d1e6bbfb93083ddf244daa20c68546ece8ba6 Mon Sep 17 00:00:00 2001 From: Aleksandar Gradinac Date: Mon, 28 Feb 2022 15:30:49 +0100 Subject: [PATCH 6/7] Add the capability to filter by regex in filter files --- substratevm/mx.substratevm/mx_substratevm.py | 13 ++- .../oracle/svm/agent/NativeImageAgent.java | 67 ++++++----- .../ConditionalConfigurationWriter.java | 15 +-- .../ConfigurationVerifier.java | 5 +- .../svm/configure/ConfigurationTool.java | 59 ++++++---- .../ConditionalConfigurationPredicate.java | 26 ++--- .../config/ConfigurationFileCollection.java | 8 -- .../svm/configure/filters/ComplexFilter.java | 64 +++++++++++ .../filters/ConfigurationFilter.java | 60 ++++++++++ .../filters/FilterConfigurationParser.java | 53 +-------- .../configure/filters/ModuleFilterTools.java | 2 +- .../svm/configure/filters/RegexFilter.java | 101 +++++++++++++++++ .../svm/configure/filters/RuleNode.java | 94 ++++++++++------ .../svm/configure/trace/AccessAdvisor.java | 104 +++++++++--------- .../core/configure/ConfigurationParser.java | 6 +- 15 files changed, 452 insertions(+), 225 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ComplexFilter.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ConfigurationFilter.java create mode 100644 substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RegexFilter.java diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 853116a7a8e9..92348e511786 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -499,7 +499,18 @@ def conditional_config_task(native_image): config_dir = join(svmbuild_dir(), 'cond-config-test-config') if exists(config_dir): mx.rmtree(config_dir) - agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-configuration-for-packages=com.oracle.svm.configure.test.conditionalconfig'] + 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)] + ['com.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator']) 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 193fc39aab2c..01c07aed159c 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 @@ -41,9 +41,7 @@ import java.util.Arrays; import java.util.ConcurrentModificationException; import java.util.Date; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -56,10 +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.conditionalconfig.ConditionalConfigurationWriter; import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess; import com.oracle.svm.agent.stackaccess.InterceptedState; import com.oracle.svm.agent.stackaccess.OnDemandJavaStackAccess; @@ -70,6 +68,8 @@ 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.trace.AccessAdvisor; @@ -130,8 +130,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean experimentalOmitClasspathConfig = false; boolean build = false; boolean configurationWithOrigins = false; - Set conditionalConfigUserPackagePrefixes = new HashSet<>(); - Set conditionalConfigClassNameExcludePatterns = new HashSet<>(); + List conditionalConfigUserPackageFilterFiles = new ArrayList<>(); + List conditionalConfigClassNameFilterFiles = new ArrayList<>(); int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds boolean trackReflectionMetadata = true; @@ -203,12 +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-for-package=")) { - String userPackagePrefix = getTokenValue(token); - conditionalConfigUserPackagePrefixes.add(userPackagePrefix); - } else if (token.startsWith("conditional-config-class-name-exclude-pattern=")) { - String classNamePattern = getTokenValue(token); - conditionalConfigClassNameExcludePatterns.add(Pattern.compile(classNamePattern)); + } else if (token.startsWith("experimental-conditional-config-filter-file=")) { + conditionalConfigUserPackageFilterFiles.add(getTokenValue(token)); + } else if (token.startsWith("conditional-config-class-name-filter-file=")) { + conditionalConfigClassNameFilterFiles.add(getTokenValue(token)); } else if (token.equals("track-reflection-metadata")) { trackReflectionMetadata = true; } else if (token.startsWith("track-reflection-metadata=")) { @@ -223,7 +221,7 @@ 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 && !conditionalConfigUserPackagePrefixes.isEmpty()) { + 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."); } @@ -236,29 +234,32 @@ 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; + RuleNode callerFilterRuleNode = null; if (!builtinCallerFilter) { - callerFilter = RuleNode.createRoot(); - callerFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); + callerFilterRuleNode = RuleNode.createRootWithIncludedChildren(); + callerFilter = new ComplexFilter(callerFilterRuleNode); } + if (!callerFilterFiles.isEmpty()) { - if (callerFilter == null) { - callerFilter = AccessAdvisor.copyBuiltinCallerFilterTree(); + if (callerFilterRuleNode == null) { + callerFilterRuleNode = AccessAdvisor.copyBuiltinCallerFilterTree(); + callerFilter = new ComplexFilter(callerFilterRuleNode); } 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; } } - boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigUserPackagePrefixes.isEmpty(); + boolean shouldTraceOriginInformation = configurationWithOrigins || !conditionalConfigUserPackageFilterFiles.isEmpty(); final MethodInfoRecordKeeper recordKeeper = new MethodInfoRecordKeeper(shouldTraceOriginInformation); final Supplier interceptedStateSupplier = shouldTraceOriginInformation ? EagerlyLoadedJavaStackAccess.stackAccessSupplier() : OnDemandJavaStackAccess.stackAccessSupplier(); @@ -307,9 +308,23 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c ConfigurationWithOriginsWriter writer = new ConfigurationWithOriginsWriter(processor, recordKeeper); tracer = writer; tracingResultWriter = writer; - } else if (!conditionalConfigUserPackagePrefixes.isEmpty()) { - ConditionalConfigurationPredicate filter = new ConditionalConfigurationPredicate(conditionalConfigClassNameExcludePatterns); - ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(processor, recordKeeper, conditionalConfigUserPackagePrefixes, filter); + } else if (!conditionalConfigUserPackageFilterFiles.isEmpty()) { + ComplexFilter userCodeFilter = new ComplexFilter(RuleNode.createRoot()); + if (!parseFilterFiles(userCodeFilter, conditionalConfigUserPackageFilterFiles)) { + return 2; + } + ComplexFilter classNameFilter; + if (!conditionalConfigClassNameFilterFiles.isEmpty()) { + classNameFilter = new ComplexFilter(RuleNode.createRoot()); + if(!parseFilterFiles(classNameFilter, conditionalConfigClassNameFilterFiles)) { + return 3; + } + } else { + classNameFilter = new ComplexFilter(RuleNode.createRootWithIncludedChildren()); + } + + ConditionalConfigurationPredicate predicate = new ConditionalConfigurationPredicate(classNameFilter); + ConditionalConfigurationWriter writer = new ConditionalConfigurationWriter(processor, recordKeeper, userCodeFilter, predicate); tracer = writer; tracingResultWriter = writer; } else { @@ -389,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) { @@ -409,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()); @@ -417,7 +432,7 @@ private static boolean parseFilterFiles(RuleNode filter, List filterFile return error(false, "cannot parse filter file " + path + ": " + e); } } - filter.removeRedundantNodes(); + filter.getRuleNode().removeRedundantNodes(); return true; } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java index 93412a25de41..ca2c3f60ec85 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Set; +import com.oracle.svm.configure.filters.ComplexFilter; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.agent.configwithorigins.ConfigurationWithOriginsTracer; @@ -58,14 +59,14 @@ * configuration. See {@link #createConditionalConfiguration()} */ public class ConditionalConfigurationWriter extends ConfigurationWithOriginsTracer implements TracingResultWriter { - private final Set applicationPackagePrefixes; + private final ComplexFilter userCodeFilter; private ConfigurationSet configurationContainer = new ConfigurationSet(); - private final ConditionalConfigurationPredicate filter; + private final ConditionalConfigurationPredicate predicate; - public ConditionalConfigurationWriter(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper, Set applicationPackagePrefixes, ConditionalConfigurationPredicate filter) { + public ConditionalConfigurationWriter(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper, ComplexFilter userCodeFilter, ConditionalConfigurationPredicate predicate) { super(processor, methodInfoRecordKeeper); - this.applicationPackagePrefixes = applicationPackagePrefixes; - this.filter = filter; + this.userCodeFilter = userCodeFilter; + this.predicate = predicate; } private Map> mapMethodsToCallNodes() { @@ -195,7 +196,7 @@ private void addConfigurationWithCondition(ConfigurationSet nodeConfig, Configur } private void filterConfiguration() { - configurationContainer = configurationContainer.filter(filter); + configurationContainer = configurationContainer.filter(predicate); } private void propagateConfiguration(Map> methodCallNodes) { @@ -216,7 +217,7 @@ private void propagateConfiguration(Map> method } private boolean methodOriginatesFromApplicationPackage(MethodInfo methodInfo) { - return applicationPackagePrefixes.stream().anyMatch(prefix -> methodInfo.getJavaDeclaringClassName().startsWith(prefix)); + return userCodeFilter.includes(methodInfo.getJavaDeclaringClassName()); } @Override diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java index 505244527b8d..138fff701488 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java @@ -87,10 +87,7 @@ private static ConfigurationSet loadExpectedConfig() throws Exception { try { String resourceName = "config-dir/" + resourceFileName; URL resourceURL = ConfigurationVerifier.class.getResource(resourceName); - if (resourceURL == null) { - Assert.fail("Configuration file " + resourceName + " does not exist. Make sure that the test or the config directory have not been moved."); - } - return resourceURL.toURI(); + return resourceURL == null ? null : resourceURL.toURI(); } catch (Exception e) { throw VMError.shouldNotReachHere("Unexpected error while locating the configuration files.", e); } 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 869d5cea162e..41c0c86a31db 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; @@ -45,10 +47,12 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import com.oracle.svm.configure.config.ConfigurationSet; 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; @@ -244,14 +248,17 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA } failIfAgentLockFilesPresent(inputSet, omittedInputSet, outputSet); - RuleNode callersFilter = null; + RuleNode callersFilterRuleNode = null; + ComplexFilter callersFilter = null; if (!builtinCallerFilter) { - callersFilter = RuleNode.createRoot(); - callersFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); + callersFilterRuleNode = RuleNode.createRoot(); + callersFilterRuleNode.addOrGetChildren("**", ConfigurationFilter.Inclusion.Include); + callersFilter = new ComplexFilter(callersFilterRuleNode); } if (!callerFilters.isEmpty()) { - if (callersFilter == null) { - callersFilter = AccessAdvisor.copyBuiltinCallerFilterTree(); + if (callersFilterRuleNode == null) { + callersFilterRuleNode = AccessAdvisor.copyBuiltinCallerFilterTree(); + callersFilter = new ComplexFilter(callersFilterRuleNode); } for (URI uri : callerFilters) { try { @@ -261,7 +268,7 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA throw new UsageException("Cannot parse filter file " + uri + ": " + e); } } - callersFilter.removeRedundantNodes(); + callersFilter.getRuleNode().removeRedundantNodes(); } ConfigurationSet configurationSet; @@ -367,7 +374,9 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce args.add(arg); } } - RuleNode rootNode = null; + RuleNode rootNode = RuleNode.createRoot(); + ComplexFilter filter = new ComplexFilter(rootNode); + boolean filterModified = false; for (String arg : args) { String[] parts = arg.split("=", 2); String current = parts[0]; @@ -377,17 +386,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 exportedInclusion = current.startsWith("--include") ? ConfigurationFilter.Inclusion.Include : ConfigurationFilter.Inclusion.Exclude; RuleNode.Inclusion unexportedInclusion = exportedInclusion; RuleNode.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 { @@ -396,8 +406,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": @@ -405,26 +415,31 @@ 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(); + OutputStream targetStream; if (outputPath != null) { try (FileOutputStream os = new FileOutputStream(outputPath.toFile())) { - rootNode.printJsonTree(os); + targetStream = os; } } else { - rootNode.printJsonTree(System.out); + targetStream = System.out; + } + try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(targetStream))) { + filter.printJson(writer); } } @@ -432,8 +447,7 @@ private static RuleNode maybeCreateRootNode(RuleNode rootNode) { return (rootNode != null) ? rootNode : RuleNode.createRoot(); } - private static RuleNode addSingleRule(RuleNode rootNode, String argName, String qualifiedPkg, RuleNode.Inclusion inclusion) { - RuleNode root = maybeCreateRootNode(rootNode); + private static void addSingleRule(RuleNode root, String argName, String qualifiedPkg, RuleNode.Inclusion inclusion) { if (qualifiedPkg == null || qualifiedPkg.isEmpty()) { throw new UsageException("Argument must be provided for: " + argName); } @@ -442,7 +456,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 index 63737e88ff3a..e81d88288128 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java @@ -1,56 +1,52 @@ package com.oracle.svm.configure.config; import java.util.List; -import java.util.Set; 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 Set classNamePatterns; + private final ComplexFilter filter; - public ConditionalConfigurationPredicate(Set classNamePatterns) { - this.classNamePatterns = classNamePatterns; - } - - private boolean classNameMatchesAny(String className) { - return classNamePatterns.stream().anyMatch(p -> p.matcher(className).find()); + public ConditionalConfigurationPredicate(ComplexFilter filter) { + this.filter = filter; } @Override public boolean testIncludedType(ConditionalElement conditionalElement, ConfigurationType type) { - return classNameMatchesAny(conditionalElement.getCondition().getTypeName()) || classNameMatchesAny(type.getQualifiedJavaName()); + return !filter.includes(conditionalElement.getCondition().getTypeName()) || !filter.includes(type.getQualifiedJavaName()); } @Override public boolean testProxyInterfaceList(ConditionalElement> conditionalElement) { - return classNameMatchesAny(conditionalElement.getCondition().getTypeName()); + return !filter.includes(conditionalElement.getCondition().getTypeName()); } @Override public boolean testIncludedResource(ConditionalElement condition, Pattern pattern) { - return classNameMatchesAny(condition.getCondition().getTypeName()); + return !filter.includes(condition.getCondition().getTypeName()); } @Override public boolean testIncludedBundle(ConditionalElement condition, ResourceConfiguration.BundleConfiguration bundleConfiguration) { - return classNameMatchesAny(condition.getCondition().getTypeName()); + return !filter.includes(condition.getCondition().getTypeName()); } @Override public boolean testSerializationType(SerializationConfigurationType type) { - return classNameMatchesAny(type.getCondition().getTypeName()) || classNameMatchesAny(type.getQualifiedJavaName()); + return !(filter.includes(type.getCondition().getTypeName()) && filter.includes(type.getQualifiedJavaName())); } @Override public boolean testLambdaSerializationType(SerializationConfigurationLambdaCapturingType type) { - return classNameMatchesAny(type.getCondition().getTypeName()) || classNameMatchesAny(type.getQualifiedJavaName()); + return !(filter.includes(type.getCondition().getTypeName()) && filter.includes(type.getQualifiedJavaName())); } @Override public boolean testPredefinedClass(ConfigurationPredefinedClass clazz) { - return clazz.getNameInfo() != null && classNameMatchesAny(clazz.getNameInfo()); + 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 index 52d8dbffa4de..b6e5480c11c7 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -180,12 +180,4 @@ private static void loadConfig(Collection configPaths, ConfigurationParser } } } - - private static Path tryGetPath(URI uri) { - try { - return Paths.get(uri); - } catch (Exception e) { - return null; - } - } } 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..de1cc8d941f4 --- /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 RuleNode ruleNode; + private final RegexFilter regexFilter = new RegexFilter(); + + public ComplexFilter(RuleNode ruleNode) { + this.ruleNode = ruleNode; + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.append('{'); + writer.indent().newline(); + ruleNode.printJson(writer); + regexFilter.printJson(writer); + writer.unindent().newline(); + writer.append('}').newline(); + } + + @Override + public void parseFromJson(Map topJsonObject) { + ruleNode.parseFromJson(topJsonObject); + regexFilter.parseFromJson(topJsonObject); + } + + @Override + public boolean includes(String qualifiedName) { + return ruleNode.includes(qualifiedName) && regexFilter.includes(qualifiedName); + } + + public RuleNode getRuleNode() { + return ruleNode; + } +} 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..c8aad6ab9d83 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/ConfigurationFilter.java @@ -0,0 +1,60 @@ +/* + * 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.io.IOException; +import java.util.Map; + +import com.oracle.svm.configure.json.JsonWriter; + +public interface ConfigurationFilter { + + void printJson(JsonWriter writer) throws IOException; + + 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..c38ade2f9ebc 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,66 +26,23 @@ import java.io.IOException; import java.io.Reader; -import java.util.List; -import java.util.Map; 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")); + filter.parseFromJson(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) { - Map entry = asMap(entryObject, "Filter entries must be objects"); - Object qualified = null; - RuleNode.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) { - throw new JSONParserException(exactlyOneMessage); - } - qualified = pair.getValue(); - if ("includeClasses".equals(pair.getKey())) { - inclusion = RuleNode.Inclusion.Include; - } else if ("excludeClasses".equals(pair.getKey())) { - inclusion = RuleNode.Inclusion.Exclude; - } else { - throw new JSONParserException("Unknown attribute '" + pair.getKey() + "' (supported attributes: 'includeClasses', 'excludeClasses') in filter"); - } - } - if (qualified == null) { - throw new JSONParserException(exactlyOneMessage); - } - rootNode.addOrGetChildren(asString(qualified), inclusion); - } } 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..23ffa1b13ea4 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,7 +29,7 @@ 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 { 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..2b6e6f3cb56f --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RegexFilter.java @@ -0,0 +1,101 @@ +/* + * 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.HashMap; +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 Map> regexPatterns; + + public RegexFilter() { + regexPatterns = new HashMap<>(); + for (Inclusion inclusion : Inclusion.values()) { + regexPatterns.put(inclusion, new HashMap<>()); + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.quote("regexRules").append(": [").indent().newline(); + + boolean first = true; + for (Inclusion inclusion : Inclusion.values()) { + for (String pattern : regexPatterns.get(inclusion).keySet()) { + if (first) { + first = false; + } else { + writer.append(',').newline(); + } + writer.append("{").quote(inclusion == Inclusion.Include ? "includeClasses" : "excludeClasses").append(": ").quote(pattern).append("}"); + } + } + + writer.unindent().newline(); + writer.append("]"); + writer.unindent().newline(); + writer.append("}"); + } + + @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."); + for (Object patternObject : patternList) { + RuleNode.parseEntry(patternObject, (pattern, inclusion) -> regexPatterns.get(inclusion).computeIfAbsent(pattern, Pattern::compile)); + } + } + } + + private boolean matchesForInclusion(Inclusion inclusion, String qualifiedName) { + return regexPatterns.get(inclusion).values().stream().anyMatch(p -> p.matcher(qualifiedName).matches()); + } + + private boolean hasPatternsForInclusion(Inclusion inclusion) { + return regexPatterns.get(inclusion).size() != 0; + } + + @Override + public boolean includes(String qualifiedName) { + if (hasPatternsForInclusion(Inclusion.Include)) { + if (!matchesForInclusion(Inclusion.Include, qualifiedName)) { + return false; + } + } + if (hasPatternsForInclusion(Inclusion.Exclude)) { + return !matchesForInclusion(Inclusion.Exclude, qualifiedName); + } + return true; + } +} 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/RuleNode.java index 86037e030856..71ba5de361d7 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/RuleNode.java @@ -25,18 +25,23 @@ 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 java.util.function.BiConsumer; import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.core.util.json.JSONParserException; + +import static com.oracle.svm.core.configure.ConfigurationParser.asList; +import static com.oracle.svm.core.configure.ConfigurationParser.asMap; +import static com.oracle.svm.core.configure.ConfigurationParser.asString; /** Represents a rule that includes or excludes a set of Java classes. */ -public final class RuleNode { +public final class RuleNode implements ConfigurationFilter { /** Everything that is not included is considered excluded. */ private static final Inclusion DEFAULT_INCLUSION = Inclusion.Exclude; @@ -44,31 +49,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}. */ @@ -84,6 +68,12 @@ public static RuleNode createRoot() { return new RuleNode(""); } + public static RuleNode createRootWithIncludedChildren() { + RuleNode root = new RuleNode(""); + root.addOrGetChildren("**", ConfigurationFilter.Inclusion.Include); + return root; + } + private RuleNode(String unqualifiedName) { this.name = unqualifiedName; } @@ -226,17 +216,48 @@ 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) { + parseEntry(entryObject, this::addOrGetChildren); + } + } + } + + public static void parseEntry(Object entryObject, BiConsumer parsedEntryConsumer) { + Map entry = asMap(entryObject, "Filter entries must be objects"); + Object qualified = null; + RuleNode.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) { + throw new JSONParserException(exactlyOneMessage); + } + qualified = pair.getValue(); + if ("includeClasses".equals(pair.getKey())) { + inclusion = ConfigurationFilter.Inclusion.Include; + } else if ("excludeClasses".equals(pair.getKey())) { + inclusion = ConfigurationFilter.Inclusion.Exclude; + } else { + throw new JSONParserException("Unknown attribute '" + pair.getKey() + "' (supported attributes: 'includeClasses', 'excludeClasses') in filter"); + } + } + if (qualified == null) { + throw new JSONParserException(exactlyOneMessage); } + parsedEntryConsumer.accept(asString(qualified), inclusion); } private void printJsonEntries(JsonWriter writer, boolean[] isFirstRule, String parentQualified) throws IOException { @@ -280,7 +301,8 @@ private static void printJsonRule(JsonWriter writer, boolean[] isFirstRule, Stri writer.append("}"); } - public boolean treeIncludes(String qualifiedName) { + @Override + public boolean includes(String qualifiedName) { RuleNode current = this; Inclusion inheritedInclusion = DEFAULT_INCLUSION; StringTokenizer tokenizer = new StringTokenizer(qualifiedName, ".", false); 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..e9acc538b8db 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,6 +28,7 @@ import org.graalvm.compiler.phases.common.LazyValue; +import com.oracle.svm.configure.filters.ConfigurationFilter; import com.oracle.svm.configure.filters.RuleNode; /** @@ -55,58 +56,55 @@ public final class AccessAdvisor { private static final RuleNode 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 = RuleNode.createRootWithIncludedChildren(); + + 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 = RuleNode.createRootWithIncludedChildren(); 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 = RuleNode.createRootWithIncludedChildren(); // 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(); } @@ -117,10 +115,10 @@ public final class AccessAdvisor { * 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); + 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() { @@ -131,8 +129,8 @@ public static RuleNode 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 +139,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 +157,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.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; } From e40f3f82c9f51a4f7eb2a071ac0e695f60c8ba8f Mon Sep 17 00:00:00 2001 From: Aleksandar Gradinac Date: Wed, 9 Mar 2022 20:35:29 +0100 Subject: [PATCH 7/7] Cleanup --- substratevm/mx.substratevm/mx_substratevm.py | 15 +-- .../oracle/svm/agent/NativeImageAgent.java | 30 ++--- .../ConditionalConfigurationWriter.java | 11 +- .../agent/configwithorigins/ClassInfo.java | 19 ++-- .../ConfigurationWithOrigins.java | 45 +------- .../ConfigurationWithOriginsTracer.java | 4 +- .../ConfigurationWithOriginsWriter.java | 4 +- ...HumanReadableConfigurationWithOrigins.java | 49 +------- .../ignoredconfig/AgentMetaInfProcessor.java | 8 +- .../tracing/ConfigurationResultWriter.java | 6 +- .../ConfigurationGenerator.java | 6 + .../ConfigurationVerifier.java | 4 + .../test/config/OmitPreviousConfigTests.java | 3 - .../svm/configure/ConfigurationTool.java | 107 +++++++++--------- .../ConditionalConfigurationPredicate.java | 24 ++++ .../config/ConfigurationFileCollection.java | 1 - .../configure/config/ConfigurationSet.java | 32 +++--- .../config/SerializationConfiguration.java | 1 - .../svm/configure/filters/ComplexFilter.java | 16 +-- .../filters/ConfigurationFilter.java | 7 +- .../filters/FilterConfigurationParser.java | 53 +++++++++ ...RuleNode.java => HierarchyFilterNode.java} | 101 ++++------------- .../configure/filters/ModuleFilterTools.java | 4 +- .../svm/configure/filters/RegexFilter.java | 46 ++++---- .../svm/configure/trace/AccessAdvisor.java | 21 ++-- .../configure/trace/ReflectionProcessor.java | 2 +- .../oracle/svm/jvmtiagentbase/Support.java | 1 + 27 files changed, 285 insertions(+), 335 deletions(-) rename substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/{conditionalconfig => configwithorigins}/HumanReadableConfigurationWithOrigins.java (72%) rename substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/{RuleNode.java => HierarchyFilterNode.java} (75%) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 92348e511786..b75bfcd2b7ec 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -475,7 +475,6 @@ def help_stdout_check(output): def native_unittests_task(extra_build_args=None): - tests = ['com.oracle.svm.test', 'com.oracle.svm.configure.test.config'] if mx.is_windows(): # GR-24075 mx_unittest.add_global_ignore_glob('com.oracle.svm.test.ProcessPropertiesTest') @@ -491,7 +490,7 @@ def native_unittests_task(extra_build_args=None): if mx.is_windows(): mx_unittest.add_global_ignore_glob('com.oracle.svm.test.SecurityServiceTest') - native_unittest(['--builder-on-modulepath', '--build-args', _native_unittest_features] + additional_build_args + tests) + native_unittest(['--builder-on-modulepath', '--build-args', _native_unittest_features] + additional_build_args) def conditional_config_task(native_image): @@ -511,11 +510,13 @@ def conditional_config_task(native_image): ''' ) agent_opts = ['config-output-dir=' + config_dir, 'experimental-conditional-config-filter-file=' + conditional_config_filter_path] - jvm_unittest(['-agentpath:' + agent_path + '=' + ','.join(agent_opts)] - + ['com.oracle.svm.configure.test.conditionalconfig.ConfigurationGenerator']) + 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] - + ['com.oracle.svm.configure.test.conditionalconfig.ConfigurationVerifier']) + 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): @@ -605,7 +606,7 @@ def _native_unittest(native_image, cmdline_args): except IOError: mx.log('warning: could not read blacklist: ' + blacklist) - unittest_args = unmask(pargs.unittest_args) if unmask(pargs.unittest_args) else ['com.oracle.svm.test'] + unittest_args = unmask(pargs.unittest_args) if unmask(pargs.unittest_args) else ['com.oracle.svm.test', 'com.oracle.svm.configure.test'] builder_on_modulepath = pargs.builder_on_modulepath _native_junit(native_image, unittest_args, unmask(pargs.build_args), unmask(pargs.run_args), blacklist, whitelist, pargs.preserve_image, builder_on_modulepath) 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 01c07aed159c..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 @@ -71,7 +71,7 @@ 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; @@ -205,7 +205,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c configurationWithOrigins = true; } else if (token.startsWith("experimental-conditional-config-filter-file=")) { conditionalConfigUserPackageFilterFiles.add(getTokenValue(token)); - } else if (token.startsWith("conditional-config-class-name-filter-file=")) { + } else if (token.startsWith("conditional-config-class-filter-file=")) { conditionalConfigClassNameFilterFiles.add(getTokenValue(token)); } else if (token.equals("track-reflection-metadata")) { trackReflectionMetadata = true; @@ -235,16 +235,16 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } ComplexFilter callerFilter = null; - RuleNode callerFilterRuleNode = null; + HierarchyFilterNode callerFilterHierarchyFilterNode = null; if (!builtinCallerFilter) { - callerFilterRuleNode = RuleNode.createRootWithIncludedChildren(); - callerFilter = new ComplexFilter(callerFilterRuleNode); + callerFilterHierarchyFilterNode = HierarchyFilterNode.createInclusiveRoot(); + callerFilter = new ComplexFilter(callerFilterHierarchyFilterNode); } if (!callerFilterFiles.isEmpty()) { - if (callerFilterRuleNode == null) { - callerFilterRuleNode = AccessAdvisor.copyBuiltinCallerFilterTree(); - callerFilter = new ComplexFilter(callerFilterRuleNode); + if (callerFilterHierarchyFilterNode == null) { + callerFilterHierarchyFilterNode = AccessAdvisor.copyBuiltinCallerFilterTree(); + callerFilter = new ComplexFilter(callerFilterHierarchyFilterNode); } if (!parseFilterFiles(callerFilter, callerFilterFiles)) { return 1; @@ -309,18 +309,18 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c tracer = writer; tracingResultWriter = writer; } else if (!conditionalConfigUserPackageFilterFiles.isEmpty()) { - ComplexFilter userCodeFilter = new ComplexFilter(RuleNode.createRoot()); + ComplexFilter userCodeFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); if (!parseFilterFiles(userCodeFilter, conditionalConfigUserPackageFilterFiles)) { return 2; } ComplexFilter classNameFilter; if (!conditionalConfigClassNameFilterFiles.isEmpty()) { - classNameFilter = new ComplexFilter(RuleNode.createRoot()); - if(!parseFilterFiles(classNameFilter, conditionalConfigClassNameFilterFiles)) { + classNameFilter = new ComplexFilter(HierarchyFilterNode.createRoot()); + if (!parseFilterFiles(classNameFilter, conditionalConfigClassNameFilterFiles)) { return 3; } } else { - classNameFilter = new ComplexFilter(RuleNode.createRootWithIncludedChildren()); + classNameFilter = new ComplexFilter(HierarchyFilterNode.createInclusiveRoot()); } ConditionalConfigurationPredicate predicate = new ConditionalConfigurationPredicate(classNameFilter); @@ -432,7 +432,7 @@ private static boolean parseFilterFiles(ComplexFilter filter, List filte return error(false, "cannot parse filter file " + path + ": " + e); } } - filter.getRuleNode().removeRedundantNodes(); + filter.getHierarchyFilterNode().removeRedundantNodes(); return true; } @@ -458,7 +458,7 @@ private void setupExecutorServiceForPeriodicConfigurationCapture(int writePeriod initialDelay, writePeriod, TimeUnit.SECONDS); } - private static void ignoreConfigFromClasspath(JvmtiEnv jvmti, ConfigurationFileCollection 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) { @@ -472,7 +472,7 @@ private static void ignoreConfigFromClasspath(JvmtiEnv jvmti, ConfigurationFileC } } - 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 index ca2c3f60ec85..5fc1e969d5ed 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/ConditionalConfigurationWriter.java @@ -35,6 +35,7 @@ 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; @@ -86,7 +87,7 @@ private Map> mapMethodsToCallNodes() { /* This code is only ever executed by one thread. */ @SuppressWarnings("NonAtomicOperationOnVolatileField") - private Set maybePropagateConfiguration(List callNodes) { + 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 @@ -134,7 +135,7 @@ private Set maybePropagateConfiguration(List callNod return affectedNodes; } - private ConfigurationSet findCommonConfigurationForMethod(List callNodes) { + private static ConfigurationSet findCommonConfigurationForMethod(List callNodes) { ConfigurationSet config = null; for (MethodCallNode node : callNodes) { if (config == null) { @@ -159,7 +160,7 @@ private void deduceConditionalConfiguration(Map * 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 rootNode = methodCallNodes.remove(null).get(0); + MethodCallNode rootCallNode = methodCallNodes.remove(null).get(0); for (List value : methodCallNodes.values()) { for (MethodCallNode node : value) { @@ -170,7 +171,7 @@ private void deduceConditionalConfiguration(Map } } - addConfigurationWithCondition(rootNode.configuration, ConfigurationCondition.alwaysTrue()); + addConfigurationWithCondition(rootCallNode.configuration, ConfigurationCondition.alwaysTrue()); filterConfiguration(); } @@ -199,7 +200,7 @@ private void filterConfiguration() { configurationContainer = configurationContainer.filter(predicate); } - private void propagateConfiguration(Map> methodCallNodes) { + private static void propagateConfiguration(Map> methodCallNodes) { /* * Iteratively propagate configuration from children to parent calls until an iteration * doesn't produce any changes. diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java index b66e812e716c..a527a86e183c 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ClassInfo.java @@ -24,20 +24,21 @@ */ 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 com.oracle.svm.jvmtiagentbase.Support.checkPhase; -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 { 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 index ab377d4c50c9..0b1bdd39e2c9 100644 --- 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 @@ -27,14 +27,12 @@ import java.io.IOException; import java.util.Set; -import org.graalvm.nativeimage.impl.ConfigurationCondition; - -import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.json.JsonPrintable; import com.oracle.svm.configure.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; -public class ConfigurationWithOrigins extends ConfigurationBase { +public class ConfigurationWithOrigins implements JsonPrintable { private final MethodCallNode root; private final ConfigurationFile configFile; @@ -90,43 +88,4 @@ private void printJson(MethodCallNode node, JsonWriter writer, Set entry) { } else { stackTrace = filterStackTrace(stackTrace); if (stackTrace != null) { - dispatchTraceEntry(stackTrace, processor, entry); + dispatchTraceEntry(stackTrace, entry); } } } } - public void dispatchTraceEntry(MethodInfo[] stackTrace, TraceProcessor processor, Map entry) { + public void dispatchTraceEntry(MethodInfo[] stackTrace, Map entry) { MethodCallNode currentNode = rootNode; for (int i = stackTrace.length - 1; i >= 0; i--) { MethodInfo nextMethodInfo = stackTrace[i]; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java index 768125d00ed8..510c137cd992 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/ConfigurationWithOriginsWriter.java @@ -34,7 +34,7 @@ public class ConfigurationWithOriginsWriter extends ConfigurationWithOriginsTracer implements TracingResultWriter { - public static final String CONFIG_WITH_ORIGINS_SUFFIX = "-origins.json"; + public static final String CONFIG_WITH_ORIGINS_SUFFIX = "-origins.txt"; public ConfigurationWithOriginsWriter(TraceProcessor processor, MethodInfoRecordKeeper methodInfoRecordKeeper) { super(processor, methodInfoRecordKeeper); @@ -53,6 +53,6 @@ public boolean supportsOnUnloadTraceWriting() { @Override public List writeToDirectory(Path directoryPath) throws IOException { return ConfigurationSet.writeConfiguration(configFile -> directoryPath.resolve(configFile.getFileName(CONFIG_WITH_ORIGINS_SUFFIX)), - configFile -> new ConfigurationWithOrigins(rootNode, configFile)); + configFile -> new HumanReadableConfigurationWithOrigins(rootNode, configFile)); } } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/HumanReadableConfigurationWithOrigins.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java similarity index 72% rename from substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/HumanReadableConfigurationWithOrigins.java rename to substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java index 6e2c6dc22cf0..a0dc0219f65d 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/conditionalconfig/HumanReadableConfigurationWithOrigins.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/configwithorigins/HumanReadableConfigurationWithOrigins.java @@ -22,21 +22,18 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.agent.conditionalconfig; +package com.oracle.svm.agent.configwithorigins; import java.io.IOException; import java.io.StringWriter; import java.util.List; import java.util.Set; -import org.graalvm.nativeimage.impl.ConfigurationCondition; - -import com.oracle.svm.agent.configwithorigins.MethodCallNode; -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; -public class HumanReadableConfigurationWithOrigins extends ConfigurationBase { +public class HumanReadableConfigurationWithOrigins implements JsonPrintable { static final String CONNECTING_INDENT = "\u2502 "; // "| " static final String EMPTY_INDENT = " "; @@ -76,7 +73,6 @@ private void printNode(JsonWriter writer, String prefix, MethodCallNode node) th @Override public void printJson(JsonWriter writer) throws IOException { - Set nodesWithNonEmptyConfig = root.getNodesWithNonEmptyConfig(configFile); writer.append("root").newline(); List nodes = List.copyOf(root.calledMethods.values()); @@ -99,43 +95,4 @@ private void printChildNodes(JsonWriter writer, String prefix, MethodCallNode no } } } - - @Override - public boolean isEmpty() { - throw new UnsupportedOperationException(); - } - - @Override - public HumanReadableConfigurationWithOrigins copy() { - throw new UnsupportedOperationException(); - } - - @Override - protected void merge(HumanReadableConfigurationWithOrigins other) { - throw new UnsupportedOperationException(); - } - - @Override - protected void mergeConditional(ConfigurationCondition condition, HumanReadableConfigurationWithOrigins other) { - throw new UnsupportedOperationException(); - } - - @Override - protected void subtract(HumanReadableConfigurationWithOrigins other) { - throw new UnsupportedOperationException(); - } - - @Override - protected void intersect(HumanReadableConfigurationWithOrigins other) { - throw new UnsupportedOperationException(); - } - - @Override - protected void removeIf(DummyPredicate predicate) { - throw new UnsupportedOperationException(); - } - - interface DummyPredicate { - - } } 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 5d23ef9557aa..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 @@ -32,15 +32,15 @@ public class AgentMetaInfProcessor implements NativeImageMetaInfResourceProcessor { - private ConfigurationFileCollection ignoredConfigSet; + private final ConfigurationFileCollection ignoredConfigFileCollection; - public AgentMetaInfProcessor(ConfigurationFileCollection 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/tracing/ConfigurationResultWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/tracing/ConfigurationResultWriter.java index a79c7581a263..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 @@ -38,12 +38,12 @@ public class ConfigurationResultWriter extends Tracer implements TracingResultWriter { private final TraceProcessor processor; private final ConfigurationSet configuration; - private final ConfigurationSet omitedConfiguration; + private final ConfigurationSet omittedConfiguration; public ConfigurationResultWriter(TraceProcessor processor, ConfigurationSet configuration, ConfigurationSet omittedConfiguration) { this.processor = processor; this.configuration = configuration; - this.omitedConfiguration = omittedConfiguration; + this.omittedConfiguration = omittedConfiguration; } @Override @@ -83,7 +83,7 @@ public boolean supportsOnUnloadTraceWriting() { @Override public List writeToDirectory(Path directoryPath) throws IOException { - ConfigurationSet finalConfiguration = configuration.copyAndSubtract(omitedConfiguration); + 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 index fdd4931ae664..8194d0392de3 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationGenerator.java @@ -33,6 +33,10 @@ 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(); @@ -81,6 +85,7 @@ public static void runTest() { ClassUtil.createProxy(IA.class); } + @SuppressWarnings("unused") private static class A { } @@ -156,6 +161,7 @@ static void doWork() { } } + @SuppressWarnings("unused") private static class C { } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java index 138fff701488..3f836be78ed4 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/conditionalconfig/ConfigurationVerifier.java @@ -44,6 +44,10 @@ public class ConfigurationVerifier { @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(); 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 70946f9c891b..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 @@ -52,7 +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.core.util.VMError; public class OmitPreviousConfigTests { @@ -73,8 +72,6 @@ private static ConfigurationSet loadTraceProcessorFromResourceDirectory(String r } }); - AccessAdvisor unusedAdvisor = new AccessAdvisor(); - Function handler = e -> { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); 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 41c0c86a31db..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 @@ -55,7 +55,7 @@ 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; @@ -153,17 +153,17 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA boolean builtinHeuristicFilter = true; List callerFilters = new ArrayList<>(); - ConfigurationFileCollection omittedInputSet = new ConfigurationFileCollection(); - ConfigurationFileCollection inputSet = new ConfigurationFileCollection(); - ConfigurationFileCollection outputSet = new ConfigurationFileCollection(); + 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; - ConfigurationFileCollection 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); @@ -172,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": @@ -246,19 +246,18 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA break; } } - failIfAgentLockFilesPresent(inputSet, omittedInputSet, outputSet); + failIfAgentLockFilesPresent(inputCollection, omittedCollection, outputCollection); - RuleNode callersFilterRuleNode = null; + HierarchyFilterNode callersFilterHierarchyFilterNode = null; ComplexFilter callersFilter = null; if (!builtinCallerFilter) { - callersFilterRuleNode = RuleNode.createRoot(); - callersFilterRuleNode.addOrGetChildren("**", ConfigurationFilter.Inclusion.Include); - callersFilter = new ComplexFilter(callersFilterRuleNode); + callersFilterHierarchyFilterNode = HierarchyFilterNode.createInclusiveRoot(); + callersFilter = new ComplexFilter(callersFilterHierarchyFilterNode); } if (!callerFilters.isEmpty()) { - if (callersFilterRuleNode == null) { - callersFilterRuleNode = AccessAdvisor.copyBuiltinCallerFilterTree(); - callersFilter = new ComplexFilter(callersFilterRuleNode); + if (callersFilterHierarchyFilterNode == null) { + callersFilterHierarchyFilterNode = AccessAdvisor.copyBuiltinCallerFilterTree(); + callersFilter = new ComplexFilter(callersFilterHierarchyFilterNode); } for (URI uri : callerFilters) { try { @@ -268,28 +267,28 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA throw new UsageException("Cannot parse filter file " + uri + ": " + e); } } - callersFilter.getRuleNode().removeRedundantNodes(); + callersFilter.getHierarchyFilterNode().removeRedundantNodes(); } ConfigurationSet configurationSet; ConfigurationSet omittedConfigurationSet; try { - omittedConfigurationSet = omittedInputSet.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, null, 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 = omittedConfigurationSet.getPredefinedClassesConfiguration()::containsClassWithHash; - configurationSet = inputSet.loadConfigurationSet(ConfigurationFileCollection.FAIL_ON_EXCEPTION, predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash); + 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."); } @@ -308,45 +307,45 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA } } - 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))) { configurationSet.getReflectionConfiguration().printJson(writer); } } - for (URI uri : outputSet.getJniConfigPaths()) { + for (URI uri : outputCollection.getJniConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { configurationSet.getJniConfiguration().printJson(writer); } } - for (URI uri : outputSet.getProxyConfigPaths()) { + for (URI uri : outputCollection.getProxyConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { configurationSet.getProxyConfiguration().printJson(writer); } } - for (URI uri : outputSet.getResourceConfigPaths()) { + for (URI uri : outputCollection.getResourceConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { configurationSet.getResourceConfiguration().printJson(writer); } } - for (URI uri : outputSet.getSerializationConfigPaths()) { + for (URI uri : outputCollection.getSerializationConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { configurationSet.getSerializationConfiguration().printJson(writer); } } - for (URI uri : outputSet.getPredefinedClassesConfigPaths()) { + for (URI uri : outputCollection.getPredefinedClassesConfigPaths()) { try (JsonWriter writer = new JsonWriter(Paths.get(uri))) { configurationSet.getPredefinedClassesConfiguration().printJson(writer); } } } - private static void failIfAgentLockFilesPresent(ConfigurationFileCollection... sets) { + private static void failIfAgentLockFilesPresent(ConfigurationFileCollection... collections) { Set paths = null; - for (ConfigurationFileCollection set : sets) { - for (URI path : set.getDetectedAgentLockPaths()) { + for (ConfigurationFileCollection coll : collections) { + for (URI path : coll.getDetectedAgentLockPaths()) { if (paths == null) { paths = new HashSet<>(); } @@ -374,7 +373,7 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce args.add(arg); } } - RuleNode rootNode = RuleNode.createRoot(); + HierarchyFilterNode rootNode = HierarchyFilterNode.createRoot(); ComplexFilter filter = new ComplexFilter(rootNode); boolean filterModified = false; for (String arg : args) { @@ -391,9 +390,9 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce } filterModified = true; String[] moduleNames = (value != null) ? value.split(",") : new String[0]; - RuleNode.Inclusion exportedInclusion = current.startsWith("--include") ? ConfigurationFilter.Inclusion.Include : ConfigurationFilter.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 = ConfigurationFilter.Inclusion.Include; exportedInclusion = ConfigurationFilter.Inclusion.Include; @@ -430,24 +429,22 @@ private static void generateFilterRules(Iterator argsIter) throws IOExce } rootNode.removeRedundantNodes(); - OutputStream targetStream; if (outputPath != null) { try (FileOutputStream os = new FileOutputStream(outputPath.toFile())) { - targetStream = os; + printFilterToStream(filter, os); } } else { - targetStream = System.out; + printFilterToStream(filter, System.out); } + } + + private static void printFilterToStream(ConfigurationFilter filter, OutputStream targetStream) throws IOException { try (JsonWriter writer = new JsonWriter(new OutputStreamWriter(targetStream))) { filter.printJson(writer); } } - private static RuleNode maybeCreateRootNode(RuleNode rootNode) { - return (rootNode != null) ? rootNode : RuleNode.createRoot(); - } - - private static void addSingleRule(RuleNode root, String argName, String qualifiedPkg, RuleNode.Inclusion inclusion) { + 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); } 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 index e81d88288128..a4b9c2fa1ea8 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConditionalConfigurationPredicate.java @@ -1,3 +1,27 @@ +/* + * 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; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java index b6e5480c11c7..2f4e1df09b84 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -28,7 +28,6 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; 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 87c0ce43b502..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 @@ -30,8 +30,8 @@ import java.util.List; import java.util.function.Function; - 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.util.VMError; @@ -70,13 +70,13 @@ public ConfigurationSet() { } private ConfigurationSet mutate(ConfigurationSet other, Mutator mutator) { - TypeConfiguration reflectionConfiguration = mutator.apply(this.reflectionConfiguration, other.reflectionConfiguration); - TypeConfiguration jniConfiguration = mutator.apply(this.jniConfiguration, other.jniConfiguration); - ResourceConfiguration resourceConfiguration = mutator.apply(this.resourceConfiguration, other.resourceConfiguration); - ProxyConfiguration proxyConfiguration = mutator.apply(this.proxyConfiguration, other.proxyConfiguration); - SerializationConfiguration serializationConfiguration = mutator.apply(this.serializationConfiguration, other.serializationConfiguration); - PredefinedClassesConfiguration predefinedClassesConfiguration = mutator.apply(this.predefinedClassesConfiguration, other.predefinedClassesConfiguration); - return new ConfigurationSet(reflectionConfiguration, jniConfiguration, resourceConfiguration, proxyConfiguration, serializationConfiguration, predefinedClassesConfiguration); + 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 ConfigurationSet copyAndMerge(ConfigurationSet other) { @@ -92,13 +92,13 @@ public ConfigurationSet copyAndIntersectWith(ConfigurationSet other) { } public ConfigurationSet filter(ConditionalConfigurationPredicate filter) { - TypeConfiguration reflectionConfiguration = this.reflectionConfiguration.copyAndFilter(filter); - TypeConfiguration jniConfiguration = this.jniConfiguration.copyAndFilter(filter); - ResourceConfiguration resourceConfiguration = this.resourceConfiguration.copyAndFilter(filter); - ProxyConfiguration proxyConfiguration = this.proxyConfiguration.copyAndFilter(filter); - SerializationConfiguration serializationConfiguration = this.serializationConfiguration.copyAndFilter(filter); - PredefinedClassesConfiguration predefinedClassesConfiguration = this.predefinedClassesConfiguration.copyAndFilter(filter); - return new ConfigurationSet(reflectionConfiguration, jniConfiguration, resourceConfiguration, proxyConfiguration, serializationConfiguration, predefinedClassesConfiguration); + 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 TypeConfiguration getReflectionConfiguration() { @@ -144,7 +144,7 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { } } - public static List writeConfiguration(Function configFilePathResolver, Function> configSupplier) throws IOException { + public static List writeConfiguration(Function configFilePathResolver, Function configSupplier) throws IOException { List writtenFiles = new ArrayList<>(); for (ConfigurationFile configFile : ConfigurationFile.values()) { if (configFile.canBeGeneratedByAgent()) { 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 f014e3b94d39..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 @@ -168,7 +168,6 @@ private static SerializationConfigurationType createConfigurationType(Configurat return new SerializationConfigurationType(condition, convertedClassName, convertedCustomTargetConstructorClassName); } - private static SerializationConfigurationLambdaCapturingType createLambdaCapturingClassConfigurationType(ConfigurationCondition condition, String className) { String convertedClassName = SignatureUtil.toInternalClassName(className); return new SerializationConfigurationLambdaCapturingType(condition, convertedClassName); 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 index de1cc8d941f4..51c30b19f9be 100644 --- 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 @@ -30,18 +30,18 @@ import java.util.Map; public class ComplexFilter implements ConfigurationFilter { - private final RuleNode ruleNode; + private final HierarchyFilterNode hierarchyFilterNode; private final RegexFilter regexFilter = new RegexFilter(); - public ComplexFilter(RuleNode ruleNode) { - this.ruleNode = ruleNode; + public ComplexFilter(HierarchyFilterNode hierarchyFilterNode) { + this.hierarchyFilterNode = hierarchyFilterNode; } @Override public void printJson(JsonWriter writer) throws IOException { writer.append('{'); writer.indent().newline(); - ruleNode.printJson(writer); + hierarchyFilterNode.printJson(writer); regexFilter.printJson(writer); writer.unindent().newline(); writer.append('}').newline(); @@ -49,16 +49,16 @@ public void printJson(JsonWriter writer) throws IOException { @Override public void parseFromJson(Map topJsonObject) { - ruleNode.parseFromJson(topJsonObject); + hierarchyFilterNode.parseFromJson(topJsonObject); regexFilter.parseFromJson(topJsonObject); } @Override public boolean includes(String qualifiedName) { - return ruleNode.includes(qualifiedName) && regexFilter.includes(qualifiedName); + return hierarchyFilterNode.includes(qualifiedName) && regexFilter.includes(qualifiedName); } - public RuleNode getRuleNode() { - return ruleNode; + 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 index c8aad6ab9d83..99482ea03e3a 100644 --- 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 @@ -24,14 +24,11 @@ */ package com.oracle.svm.configure.filters; -import java.io.IOException; import java.util.Map; -import com.oracle.svm.configure.json.JsonWriter; +import com.oracle.svm.configure.json.JsonPrintable; -public interface ConfigurationFilter { - - void printJson(JsonWriter writer) throws IOException; +public interface ConfigurationFilter extends JsonPrintable { void parseFromJson(Map topJsonObject); 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 c38ade2f9ebc..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,9 +26,13 @@ import java.io.IOException; import java.io.Reader; +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 ConfigurationFilter filter; @@ -39,6 +43,55 @@ public FilterConfigurationParser(ConfigurationFilter filter) { this.filter = filter; } + static void parseEntry(Object entryObject, BiConsumer parsedEntryConsumer) { + Map entry = asMap(entryObject, "Filter entries must be objects"); + Object qualified = 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) { + throw new JSONParserException(exactlyOneMessage); + } + qualified = pair.getValue(); + if ("includeClasses".equals(pair.getKey())) { + inclusion = ConfigurationFilter.Inclusion.Include; + } else if ("excludeClasses".equals(pair.getKey())) { + inclusion = ConfigurationFilter.Inclusion.Exclude; + } else { + throw new JSONParserException("Unknown attribute '" + pair.getKey() + "' (supported attributes: 'includeClasses', 'excludeClasses') in filter"); + } + } + if (qualified == null) { + throw new JSONParserException(exactlyOneMessage); + } + 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(); 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 75% 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 71ba5de361d7..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 @@ -31,17 +31,13 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; -import java.util.function.BiConsumer; import com.oracle.svm.configure.json.JsonWriter; -import com.oracle.svm.core.util.json.JSONParserException; import static com.oracle.svm.core.configure.ConfigurationParser.asList; -import static com.oracle.svm.core.configure.ConfigurationParser.asMap; -import static com.oracle.svm.core.configure.ConfigurationParser.asString; /** Represents a rule that includes or excludes a set of Java classes. */ -public final class RuleNode implements ConfigurationFilter { +public final class HierarchyFilterNode implements ConfigurationFilter { /** Everything that is not included is considered excluded. */ private static final Inclusion DEFAULT_INCLUSION = Inclusion.Exclude; @@ -62,19 +58,19 @@ public final class RuleNode implements ConfigurationFilter { 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(""); } - public static RuleNode createRootWithIncludedChildren() { - RuleNode root = new RuleNode(""); + public static HierarchyFilterNode createInclusiveRoot() { + HierarchyFilterNode root = new HierarchyFilterNode(""); root.addOrGetChildren("**", ConfigurationFilter.Inclusion.Include); return root; } - private RuleNode(String unqualifiedName) { + private HierarchyFilterNode(String unqualifiedName) { this.name = unqualifiedName; } @@ -82,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. @@ -115,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 { @@ -126,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); } @@ -147,7 +143,7 @@ private boolean isLeafNode() { */ public void reduceExhaustiveTree() { if (children != null) { - for (RuleNode child : children.values()) { + for (HierarchyFilterNode child : children.values()) { child.reduceExhaustiveTree0(); } } @@ -160,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) { @@ -231,79 +227,30 @@ public void parseFromJson(Map top) { if (rulesObject != null) { List rulesList = asList(rulesObject, "Attribute 'list' must be a list of rule objects"); for (Object entryObject : rulesList) { - parseEntry(entryObject, this::addOrGetChildren); + FilterConfigurationParser.parseEntry(entryObject, this::addOrGetChildren); } } } - public static void parseEntry(Object entryObject, BiConsumer parsedEntryConsumer) { - Map entry = asMap(entryObject, "Filter entries must be objects"); - Object qualified = null; - RuleNode.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) { - throw new JSONParserException(exactlyOneMessage); - } - qualified = pair.getValue(); - if ("includeClasses".equals(pair.getKey())) { - inclusion = ConfigurationFilter.Inclusion.Include; - } else if ("excludeClasses".equals(pair.getKey())) { - inclusion = ConfigurationFilter.Inclusion.Exclude; - } else { - throw new JSONParserException("Unknown attribute '" + pair.getKey() + "' (supported attributes: 'includeClasses', 'excludeClasses') in filter"); - } - } - if (qualified == null) { - throw new JSONParserException(exactlyOneMessage); - } - parsedEntryConsumer.accept(asString(qualified), inclusion); - } - private void printJsonEntries(JsonWriter writer, boolean[] isFirstRule, String parentQualified) throws IOException { 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("}"); - } - @Override public boolean includes(String qualifiedName) { - RuleNode current = this; + HierarchyFilterNode current = this; Inclusion inheritedInclusion = DEFAULT_INCLUSION; StringTokenizer tokenizer = new StringTokenizer(qualifiedName, ".", false); while (tokenizer.hasMoreTokens()) { @@ -311,7 +258,7 @@ public boolean includes(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) { @@ -324,15 +271,15 @@ public boolean includes(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 23ffa1b13ea4..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 @@ -33,7 +33,7 @@ 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 index 2b6e6f3cb56f..a62d12274438 100644 --- 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 @@ -27,7 +27,8 @@ import static com.oracle.svm.core.configure.ConfigurationParser.asList; import java.io.IOException; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -36,12 +37,11 @@ public class RegexFilter implements ConfigurationFilter { - private final Map> regexPatterns; + private final Pattern[][] regexPatterns = new Pattern[Inclusion.values().length][]; public RegexFilter() { - regexPatterns = new HashMap<>(); for (Inclusion inclusion : Inclusion.values()) { - regexPatterns.put(inclusion, new HashMap<>()); + regexPatterns[inclusion.ordinal()] = new Pattern[0]; } } @@ -49,15 +49,10 @@ public RegexFilter() { public void printJson(JsonWriter writer) throws IOException { writer.quote("regexRules").append(": [").indent().newline(); - boolean first = true; + boolean[] first = {true}; for (Inclusion inclusion : Inclusion.values()) { - for (String pattern : regexPatterns.get(inclusion).keySet()) { - if (first) { - first = false; - } else { - writer.append(',').newline(); - } - writer.append("{").quote(inclusion == Inclusion.Include ? "includeClasses" : "excludeClasses").append(": ").quote(pattern).append("}"); + for (Pattern pattern : regexPatterns[inclusion.ordinal()]) { + FilterConfigurationParser.printEntry(writer, first, inclusion, pattern.pattern()); } } @@ -67,33 +62,44 @@ public void printJson(JsonWriter writer) throws IOException { 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) { - RuleNode.parseEntry(patternObject, (pattern, inclusion) -> regexPatterns.get(inclusion).computeIfAbsent(pattern, Pattern::compile)); + 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) { - return regexPatterns.get(inclusion).values().stream().anyMatch(p -> p.matcher(qualifiedName).matches()); - } - - private boolean hasPatternsForInclusion(Inclusion inclusion) { - return regexPatterns.get(inclusion).size() != 0; + for (Pattern p : regexPatterns[inclusion.ordinal()]) { + if (p.matcher(qualifiedName).matches()) { + return true; + } + } + return false; } @Override public boolean includes(String qualifiedName) { - if (hasPatternsForInclusion(Inclusion.Include)) { + if (regexPatterns[Inclusion.Include.ordinal()].length != 0) { if (!matchesForInclusion(Inclusion.Include, qualifiedName)) { return false; } } - if (hasPatternsForInclusion(Inclusion.Exclude)) { + 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/AccessAdvisor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java index e9acc538b8db..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 @@ -29,7 +29,7 @@ import org.graalvm.compiler.phases.common.LazyValue; import com.oracle.svm.configure.filters.ConfigurationFilter; -import com.oracle.svm.configure.filters.RuleNode; +import com.oracle.svm.configure.filters.HierarchyFilterNode; /** * Decides if a recorded access should be included in a configuration. Also advises the agent's @@ -44,19 +44,19 @@ 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.createRootWithIncludedChildren(); + internalCallerFilter = HierarchyFilterNode.createInclusiveRoot(); internalCallerFilter.addOrGetChildren("com.sun.crypto.provider.**", ConfigurationFilter.Inclusion.Exclude); internalCallerFilter.addOrGetChildren("com.sun.java.util.jar.pack.**", ConfigurationFilter.Inclusion.Exclude); @@ -98,11 +98,12 @@ public final class AccessAdvisor { internalCallerFilter.removeRedundantNodes(); - internalAccessFilter = RuleNode.createRootWithIncludedChildren(); + internalAccessFilter = HierarchyFilterNode.createInclusiveRoot(); excludeInaccessiblePackages(internalAccessFilter); internalAccessFilter.removeRedundantNodes(); - accessWithoutCallerFilter = RuleNode.createRootWithIncludedChildren(); // in addition to accessFilter + 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 @@ -114,18 +115,18 @@ 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) { + 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(); } 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 9d13fa687f90..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 @@ -261,7 +261,7 @@ public void processEntry(Map entry, ConfigurationSet configurationSet } } - private void addFullyQualifiedDeclaredMethod(String descriptor, TypeConfiguration configuration) { + private static void addFullyQualifiedDeclaredMethod(String descriptor, TypeConfiguration configuration) { int sigbegin = descriptor.indexOf('('); int classend = descriptor.lastIndexOf('.', sigbegin - 1); String qualifiedClass = descriptor.substring(0, classend); 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 a6fa6aa12296..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 @@ -444,5 +444,6 @@ private Support() { } public static class WrongPhaseException extends Exception { + private static final long serialVersionUID = 8503239518909756105L; } }