diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 81a08d704688..ab1b963b3f34 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -506,7 +506,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'] _native_junit(native_image, unittest_args, unmask(pargs.build_args), unmask(pargs.run_args), blacklist, whitelist, pargs.preserve_image) @@ -848,7 +848,8 @@ def _native_image_launcher_extra_jvm_args(): 'substratevm:SVM_AGENT', ], build_args=[ - '--features=com.oracle.svm.agent.NativeImageAgent$RegistrationFeature' + '--features=com.oracle.svm.agent.NativeImageAgent$RegistrationFeature', + '--enable-url-protocols=jar', ], ), mx_sdk_vm.LibraryConfig( 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 5fb868d99eb4..b28deed92466 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,11 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; +import com.oracle.svm.agent.ignoredconfig.AgentMetaInfProcessor; import com.oracle.svm.agent.stackaccess.InterceptedState; import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess; import com.oracle.svm.agent.stackaccess.OnDemandJavaStackAccess; @@ -52,6 +54,8 @@ import com.oracle.svm.agent.tracing.core.Tracer; import com.oracle.svm.agent.tracing.core.TracingResultWriter; import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker; +import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.hosted.Feature; @@ -104,12 +108,14 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c String traceOutputFile = null; String configOutputDir = null; ConfigurationSet mergeConfigs = new ConfigurationSet(); + ConfigurationSet omittedConfigs = new ConfigurationSet(); boolean builtinCallerFilter = true; boolean builtinHeuristicFilter = true; List callerFilterFiles = new ArrayList<>(); List accessFilterFiles = new ArrayList<>(); boolean experimentalClassLoaderSupport = true; boolean experimentalClassDefineSupport = false; + boolean experimentalOmitClasspathConfig = false; boolean build = false; boolean configurationWithOrigins = false; int configWritePeriod = -1; // in seconds @@ -130,6 +136,14 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c if (token.startsWith("config-merge-dir=")) { mergeConfigs.addDirectory(Paths.get(configOutputDir)); } + } else if (token.startsWith("config-to-omit=")) { + String omittedConfigDir = getTokenValue(token); + omittedConfigDir = transformPath(omittedConfigDir); + omittedConfigs.addDirectory(Paths.get(omittedConfigDir)); + } else if (token.equals("experimental-omit-config-from-classpath")) { + experimentalOmitClasspathConfig = true; + } else if (token.startsWith("experimental-omit-config-from-classpath=")) { + experimentalOmitClasspathConfig = Boolean.parseBoolean(getTokenValue(token)); } else if (token.startsWith("restrict-all-dir") || token.equals("restrict") || token.startsWith("restrict=")) { warn("restrict mode is no longer supported, ignoring option: " + token); } else if (token.equals("no-builtin-caller-filter")) { @@ -235,7 +249,23 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } return e; // rethrow }; + if (experimentalOmitClasspathConfig) { + ignoreConfigFromClasspath(jvmti, omittedConfigs); + } AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter); + TraceProcessor omittedConfigProcessor = null; + 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; + } + Path[] predefinedClassDestinationDirs = {configOutputDirPath.resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR)}; if (configurationWithOrigins) { ConfigurationWithOriginsResultWriter writer = new ConfigurationWithOriginsResultWriter(advisor, recordKeeper); @@ -244,7 +274,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } else { TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler), mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler), - mergeConfigs.loadPredefinedClassesConfig(predefinedClassDestinationDirs, handler)); + mergeConfigs.loadPredefinedClassesConfig(predefinedClassDestinationDirs, shouldExcludeClassesWithHash, handler), omittedConfigProcessor); ConfigurationResultWriter writer = new ConfigurationResultWriter(processor); tracer = writer; tracingResultWriter = writer; @@ -360,6 +390,30 @@ private void setupExecutorServiceForPeriodicConfigurationCapture(int writePeriod initialDelay, writePeriod, TimeUnit.SECONDS); } + private static void ignoreConfigFromClasspath(JvmtiEnv jvmti, ConfigurationSet ignoredConfigSet) { + String classpath = Support.getSystemProperty(jvmti, "java.class.path"); + String sep = Support.getSystemProperty(jvmti, "path.separator"); + if (sep == null) { + if (Platform.includedIn(Platform.LINUX.class) || Platform.includedIn(Platform.DARWIN.class)) { + sep = ":"; + } else if (Platform.includedIn(Platform.WINDOWS.class)) { + sep = "[:;]"; + } else { + warn("Running on unknown platform. Not omitting existing config from classpath."); + return; + } + } + + AgentMetaInfProcessor processor = new AgentMetaInfProcessor(ignoredConfigSet); + for (String cpEntry : classpath.split(sep)) { + try { + NativeImageMetaInfWalker.walkMetaInfForCPEntry(Paths.get(cpEntry), processor); + } catch (NativeImageMetaInfWalker.MetaInfWalkException e) { + warn("Failed to walk the classpath entry: " + cpEntry + " Reason: " + e); + } + } + } + private static final Pattern propertyBlacklist = Pattern.compile("(java\\..*)|(sun\\..*)|(jvmci\\..*)"); private static final Pattern propertyWhitelist = Pattern.compile("(java\\.library\\.path)|(java\\.io\\.tmpdir)"); 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 new file mode 100644 index 000000000000..e2d288865fd8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ignoredconfig/AgentMetaInfProcessor.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.ignoredconfig; + +import java.nio.file.Path; + +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.driver.metainf.MetaInfFileType; +import com.oracle.svm.driver.metainf.NativeImageMetaInfResourceProcessor; + +public class AgentMetaInfProcessor implements NativeImageMetaInfResourceProcessor { + + private ConfigurationSet ignoredConfigSet; + + public AgentMetaInfProcessor(ConfigurationSet ignoredConfigSet) { + this.ignoredConfigSet = ignoredConfigSet; + } + + @Override + public void processMetaInfResource(Path classpathEntry, Path resourceRoot, Path resourcePath, MetaInfFileType type) throws Exception { + ignoredConfigSet.addDirectory(resourcePath.getParent()); + } + + @Override + public void showWarning(String message) { + } + + @Override + public void showVerboseMessage(String message) { + } + + @Override + public boolean isExcluded(Path resourcePath, Path classpathEntry) { + return false; + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConfigurationWithOriginsResultWriter.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/predicatedconfig/ConfigurationWithOriginsResultWriter.java index 0c8e680e64a6..be8807dd39d4 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/predicatedconfig/ConfigurationWithOriginsResultWriter.java @@ -98,8 +98,8 @@ private TraceProcessor createNewTraceProcessor() { ProxyConfiguration proxyConfig = new ProxyConfiguration(); ResourceConfiguration resourceConfig = new ResourceConfiguration(); SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); - PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(new Path[0]); - return new TraceProcessor(advisor, jniConfig, reflectConfig, proxyConfig, resourceConfig, serializationConfiguration, predefinedClassesConfiguration); + PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(new Path[0], null); + return new TraceProcessor(advisor, jniConfig, reflectConfig, proxyConfig, resourceConfig, serializationConfiguration, predefinedClassesConfiguration, null); } private static final class MethodCallNode { diff --git a/substratevm/src/com.oracle.svm.configure.test/src/META-INF/native-image/native-image.properties b/substratevm/src/com.oracle.svm.configure.test/src/META-INF/native-image/native-image.properties new file mode 100644 index 000000000000..d9c3e83c8ff0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/META-INF/native-image/native-image.properties @@ -0,0 +1 @@ +Args = -H:IncludeResources=com/oracle/svm/configure/test/config/.*json 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 new file mode 100644 index 000000000000..54a2766847c6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -0,0 +1,314 @@ +/* + * 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.configure.test.config; + +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +import com.oracle.svm.configure.config.PredefinedClassesConfiguration; +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.configure.config.ConfigurationMemberKind; +import com.oracle.svm.configure.config.ConfigurationMethod; +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.ProxyConfiguration; +import com.oracle.svm.configure.config.ResourceConfiguration; +import com.oracle.svm.configure.config.SerializationConfiguration; +import com.oracle.svm.configure.config.TypeConfiguration; +import com.oracle.svm.configure.trace.AccessAdvisor; +import com.oracle.svm.configure.trace.TraceProcessor; +import com.oracle.svm.core.util.UserError; + +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) { + try { + ConfigurationSet configurationSet = new ConfigurationSet(); + configurationSet.addDirectory(resourceFileName -> { + try { + String resourceName = resourceDirectory + "/" + resourceFileName; + URL resourceURL = OmitPreviousConfigTests.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 UserError.abort(e, "Unexpected error while locating the configuration files."); + } + }); + + AccessAdvisor unusedAdvisor = new AccessAdvisor(); + + Function handler = e -> { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + Assert.fail("Exception occurred while loading configuration: " + e + System.lineSeparator() + sw); + return e; + }; + Predicate shouldExcludeClassesWithHash = null; + 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); + } catch (Exception e) { + throw UserError.abort(e, "Unexpected error while loading the configuration files."); + } + } + + @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()); + } + + @Test + public void testConfigDifference() { + TraceProcessor previousConfigProcessor = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, null); + TraceProcessor currentConfigProcessor = loadTraceProcessorFromResourceDirectory(CURRENT_CONFIG_DIR_NAME, previousConfigProcessor); + + doTestGeneratedTypeConfig(); + doTestTypeConfig(currentConfigProcessor.getJniConfiguration()); + + doTestProxyConfig(currentConfigProcessor.getProxyConfiguration()); + + doTestResourceConfig(currentConfigProcessor.getResourceConfiguration()); + + doTestSerializationConfig(currentConfigProcessor.getSerializationConfiguration()); + + doTestPredefinedClassesConfig(currentConfigProcessor.getPredefinedClassesConfiguration()); + } + + private static void doTestGeneratedTypeConfig() { + TypeMethodsWithFlagsTest typeMethodsWithFlagsTestDeclared = new TypeMethodsWithFlagsTest(ConfigurationMemberKind.DECLARED); + typeMethodsWithFlagsTestDeclared.doTest(); + + TypeMethodsWithFlagsTest typeMethodsWithFlagsTestPublic = new TypeMethodsWithFlagsTest(ConfigurationMemberKind.PUBLIC); + typeMethodsWithFlagsTestPublic.doTest(); + + TypeMethodsWithFlagsTest typeMethodsWithFlagsTestDeclaredPublic = new TypeMethodsWithFlagsTest(ConfigurationMemberKind.DECLARED_AND_PUBLIC); + typeMethodsWithFlagsTestDeclaredPublic.doTest(); + } + + private static void doTestTypeConfig(TypeConfiguration typeConfig) { + doTestExpectedMissingTypes(typeConfig); + + doTestTypeFlags(typeConfig); + + doTestFields(typeConfig); + + doTestMethods(typeConfig); + } + + private static void doTestExpectedMissingTypes(TypeConfiguration typeConfig) { + Assert.assertNull(typeConfig.get("FlagTestA")); + Assert.assertNull(typeConfig.get("FlagTestB")); + } + + private static void doTestTypeFlags(TypeConfiguration typeConfig) { + ConfigurationType flagTestHasDeclaredType = getConfigTypeOrFail(typeConfig, "FlagTestC"); + Assert.assertTrue(flagTestHasDeclaredType.haveAllDeclaredClasses() || flagTestHasDeclaredType.haveAllDeclaredFields() || flagTestHasDeclaredType.haveAllDeclaredConstructors()); + + ConfigurationType flagTestHasPublicType = getConfigTypeOrFail(typeConfig, "FlagTestD"); + Assert.assertTrue(flagTestHasPublicType.haveAllPublicClasses() || flagTestHasPublicType.haveAllPublicFields() || flagTestHasPublicType.haveAllPublicConstructors()); + } + + private static void doTestFields(TypeConfiguration typeConfig) { + ConfigurationType fieldTestType = getConfigTypeOrFail(typeConfig, "MethodAndFieldTest"); + + Assert.assertNull(fieldTestType.getFieldInfoIfPresent("SimpleField")); + Assert.assertNull(fieldTestType.getFieldInfoIfPresent("AllowWriteField")); + + FieldInfo newField = fieldTestType.getFieldInfoIfPresent("NewField"); + Assert.assertFalse(newField.isFinalButWritable()); + + FieldInfo newWritableField = getFieldInfoOrFail(fieldTestType, "NewAllowWriteField"); + Assert.assertTrue(newWritableField.isFinalButWritable()); + + FieldInfo newlyWritableField = getFieldInfoOrFail(fieldTestType, "NewNowWritableField"); + Assert.assertTrue(newlyWritableField.isFinalButWritable()); + } + + private static void doTestMethods(TypeConfiguration typeConfig) { + ConfigurationType methodTestType = getConfigTypeOrFail(typeConfig, "MethodAndFieldTest"); + + Assert.assertNull(methodTestType.getMethodKindIfPresent(new ConfigurationMethod("", "(I)V"))); + Assert.assertNotNull(methodTestType.getMethodKindIfPresent(new ConfigurationMethod("method", "()V"))); + } + + private static void doTestProxyConfig(ProxyConfiguration proxyConfig) { + Assert.assertFalse(proxyConfig.contains("testProxySeenA", "testProxySeenB", "testProxySeenC")); + Assert.assertTrue(proxyConfig.contains("testProxyUnseen")); + } + + private static void doTestResourceConfig(ResourceConfiguration resourceConfig) { + Assert.assertFalse(resourceConfig.anyResourceMatches("seenResource.txt")); + Assert.assertTrue(resourceConfig.anyResourceMatches("unseenResource.txt")); + + Assert.assertFalse(resourceConfig.anyBundleMatches("seenBundle")); + Assert.assertTrue(resourceConfig.anyBundleMatches("unseenBundle")); + } + + private static void doTestSerializationConfig(SerializationConfiguration serializationConfig) { + Assert.assertFalse(serializationConfig.contains("seenType", null)); + Assert.assertTrue(serializationConfig.contains("unseenType", null)); + } + + private static ConfigurationType getConfigTypeOrFail(TypeConfiguration typeConfig, String typeName) { + ConfigurationType type = typeConfig.get(typeName); + Assert.assertNotNull(type); + return type; + } + + private static FieldInfo getFieldInfoOrFail(ConfigurationType type, String field) { + FieldInfo fieldInfo = type.getFieldInfoIfPresent(field); + Assert.assertNotNull(fieldInfo); + return fieldInfo; + } + + private static void doTestPredefinedClassesConfig(PredefinedClassesConfiguration predefinedClassesConfig) { + Assert.assertFalse("Must not contain a previously seen class.", predefinedClassesConfig.containsClassWithName("previouslySeenClass")); + Assert.assertTrue("Must contain a newly seen class.", predefinedClassesConfig.containsClassWithName("unseenClass")); + } +} + +class TypeMethodsWithFlagsTest { + + static final String TEST_CLASS_NAME_PREFIX = "MethodsWithFlagsType"; + static final String INTERNAL_SIGNATURE_ONE = "([Ljava/lang/String;)V"; + static final String INTERNAL_SIGNATURE_TWO = "([Ljava/lang/String;Ljava/lang/String;)V"; + + final ConfigurationMemberKind methodKind; + + final Map methodsThatMustExist = new HashMap<>(); + final Map methodsThatMustNotExist = new HashMap<>(); + + final TypeConfiguration previousConfig = new TypeConfiguration(); + final TypeConfiguration currentConfig = new TypeConfiguration(); + + TypeMethodsWithFlagsTest(ConfigurationMemberKind methodKind) { + this.methodKind = methodKind; + generateTestMethods(); + populateConfig(); + currentConfig.removeAll(previousConfig); + } + + void generateTestMethods() { + Map targetMap; + + targetMap = getMethodsMap(ConfigurationMemberKind.DECLARED); + targetMap.put(new ConfigurationMethod("", INTERNAL_SIGNATURE_ONE), ConfigurationMemberKind.DECLARED); + targetMap.put(new ConfigurationMethod("testMethodDeclaredSpecificSignature", INTERNAL_SIGNATURE_ONE), ConfigurationMemberKind.DECLARED); + targetMap.put(new ConfigurationMethod("testMethodDeclaredMatchesAllSignature", null), ConfigurationMemberKind.DECLARED); + + targetMap = getMethodsMap(ConfigurationMemberKind.PUBLIC); + targetMap.put(new ConfigurationMethod("", INTERNAL_SIGNATURE_TWO), ConfigurationMemberKind.PUBLIC); + targetMap.put(new ConfigurationMethod("testMethodPublicSpecificSignature", INTERNAL_SIGNATURE_ONE), ConfigurationMemberKind.PUBLIC); + targetMap.put(new ConfigurationMethod("testMethodPublicMatchesAllSignature", null), ConfigurationMemberKind.PUBLIC); + } + + Map getMethodsMap(ConfigurationMemberKind otherKind) { + if (methodKind.equals(otherKind) || methodKind.equals(ConfigurationMemberKind.DECLARED_AND_PUBLIC)) { + return methodsThatMustNotExist; + } + return methodsThatMustExist; + } + + void populateConfig() { + ConfigurationType oldType = new ConfigurationType(getTypeName()); + setFlags(oldType); + previousConfig.add(oldType); + + ConfigurationType newType = new ConfigurationType(getTypeName()); + for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { + newType.addMethod(methodEntry.getKey().getName(), methodEntry.getKey().getInternalSignature(), methodEntry.getValue()); + } + for (Map.Entry methodEntry : methodsThatMustNotExist.entrySet()) { + newType.addMethod(methodEntry.getKey().getName(), methodEntry.getKey().getInternalSignature(), methodEntry.getValue()); + } + currentConfig.add(newType); + } + + void setFlags(ConfigurationType config) { + if (methodKind.equals(ConfigurationMemberKind.DECLARED) || methodKind.equals(ConfigurationMemberKind.DECLARED_AND_PUBLIC)) { + config.setAllDeclaredClasses(); + config.setAllDeclaredConstructors(); + config.setAllDeclaredMethods(); + config.setAllDeclaredFields(); + } + if (methodKind.equals(ConfigurationMemberKind.PUBLIC) || methodKind.equals(ConfigurationMemberKind.DECLARED_AND_PUBLIC)) { + config.setAllPublicClasses(); + config.setAllPublicConstructors(); + config.setAllPublicMethods(); + config.setAllPublicFields(); + } + } + + String getTypeName() { + return TEST_CLASS_NAME_PREFIX + "_" + methodKind.name(); + } + + void doTest() { + String name = getTypeName(); + ConfigurationType configurationType = currentConfig.get(name); + if (methodsThatMustExist.size() == 0) { + Assert.assertNull("Generated configuration type " + name + " exists. Expected it to be cleared as it is empty.", configurationType); + } else { + Assert.assertNotNull("Generated configuration type " + name + " does not exist. Has the test code changed?", configurationType); + + for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { + ConfigurationMemberKind kind = configurationType.getMethodKindIfPresent(methodEntry.getKey()); + Assert.assertNotNull("Method " + methodEntry.getKey() + " unexpectedly NOT found in the new configuration.", kind); + Assert.assertEquals("Method " + methodEntry.getKey() + " contains a different kind than expected in the new configuration.", kind, methodEntry.getValue()); + } + for (Map.Entry methodEntry : methodsThatMustNotExist.entrySet()) { + ConfigurationMemberKind kind = configurationType.getMethodKindIfPresent(methodEntry.getKey()); + Assert.assertNull("Method " + methodEntry.getKey() + " unexpectedly found in the new configuration.", kind); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/jni-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/jni-config.json new file mode 100644 index 000000000000..cbd1bab6e395 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/jni-config.json @@ -0,0 +1,61 @@ +[ + { + "name": "FlagTestA", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "FlagTestB", + "allPublicMethods": true, + "allPublicFields": true, + "allPublicConstructors": true + }, + { + "name": "FlagTestC", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "FlagTestD", + "allPublicMethods": true, + "allPublicFields": true, + "allPublicConstructors": true + }, + { + "name": "MethodAndFieldTest", + "fields": [ + { + "name": "SimpleField" + }, + { + "name": "AllowWriteField", + "allowWrite": true + }, + { + "name": "NewField" + }, + { + "name": "NewAllowWriteField", + "allowWrite": true + }, + { + "name": "NewNowWritableField", + "allowWrite": true + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "int" + ] + }, + { + "name": "method", + "parameterTypes": [] + } + ] + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/predefined-classes-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/predefined-classes-config.json new file mode 100644 index 000000000000..418c2ff8c44f --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/predefined-classes-config.json @@ -0,0 +1,15 @@ +[ + { + "type": "agent-extracted", + "classes": [ + { + "nameInfo": "previouslySeenClass", + "hash": "95327129d8806d2964e53ac792cbe1fc7cddb32ac70367f992755ff580225b80" + }, + { + "nameInfo": "unseenClass", + "hash": "81696cdc703b9e4f17c9c43551567a638ff265233460e52504e5c7862ba1ef39" + } + ] + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/proxy-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/proxy-config.json new file mode 100644 index 000000000000..d19bbcd17feb --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/proxy-config.json @@ -0,0 +1,11 @@ +[ + [], + [ + "testProxySeenA", + "testProxySeenB", + "testProxySeenC" + ], + [ + "testProxyUnseen" + ] +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/reflect-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/reflect-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/config/config-dir/reflect-config.json @@ -0,0 +1,2 @@ +[ +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/resource-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/resource-config.json new file mode 100644 index 000000000000..a755bdc5dc9b --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/resource-config.json @@ -0,0 +1,18 @@ +{ + "resources": [ + { + "pattern": "seenResource.txt" + }, + { + "pattern": "unseenResource.txt" + } + ], + "bundles": [ + { + "name": "seenBundle" + }, + { + "name": "unseenBundle" + } + ] +} diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/serialization-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/serialization-config.json new file mode 100644 index 000000000000..95bb2e2f8a3e --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/config-dir/serialization-config.json @@ -0,0 +1,8 @@ +[ + { + "name": "seenType" + }, + { + "name": "unseenType" + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/jni-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/jni-config.json new file mode 100644 index 000000000000..1ec4eaa4962d --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/jni-config.json @@ -0,0 +1,55 @@ +[ + { + "name": "FlagTestA", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allPublicFields": true, + "allDeclaredConstructors": true, + "allPublicConstructors": true + }, + { + "name": "FlagTestB", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allPublicFields": true, + "allDeclaredConstructors": true, + "allPublicConstructors": true + }, + { + "name": "FlagTestC", + "allPublicMethods": true, + "allPublicFields": true, + "allPublicConstructors": true + }, + { + "name": "FlagTestD", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "MethodAndFieldTest", + "fields": [ + { + "name": "SimpleField" + }, + { + "name": "AllowWriteField", + "allowWrite": true + }, + { + "name": "NewNowWritableField" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "int" + ] + } + ] + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/predefined-classes-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/predefined-classes-config.json new file mode 100644 index 000000000000..c171d216a619 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/predefined-classes-config.json @@ -0,0 +1,11 @@ +[ + { + "type": "agent-extracted", + "classes": [ + { + "nameInfo": "previouslySeenClass", + "hash": "95327129d8806d2964e53ac792cbe1fc7cddb32ac70367f992755ff580225b80" + } + ] + } +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/proxy-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/proxy-config.json new file mode 100644 index 000000000000..4f82d8b9b5f0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/proxy-config.json @@ -0,0 +1,8 @@ +[ + [], + [ + "testProxySeenA", + "testProxySeenB", + "testProxySeenC" + ] +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/reflect-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/reflect-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/config/prev-config-dir/reflect-config.json @@ -0,0 +1,2 @@ +[ +] diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/resource-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/resource-config.json new file mode 100644 index 000000000000..2b854f560f0e --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/resource-config.json @@ -0,0 +1,12 @@ +{ + "resources": [ + { + "pattern": "seenResource.txt" + } + ], + "bundles": [ + { + "name": "seenBundle" + } + ] +} diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/serialization-config.json b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/serialization-config.json new file mode 100644 index 000000000000..77f5f3862016 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/prev-config-dir/serialization-config.json @@ -0,0 +1,5 @@ +[ + { + "name": "seenType" + } +] 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 6295bac49563..9c00ca455c3a 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 @@ -40,6 +40,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Collectors; import com.oracle.svm.configure.config.ConfigurationSet; @@ -144,6 +145,7 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA boolean builtinHeuristicFilter = true; List callerFilterFiles = new ArrayList<>(); + ConfigurationSet omittedInputSet = new ConfigurationSet(); ConfigurationSet inputSet = new ConfigurationSet(); ConfigurationSet outputSet = new ConfigurationSet(); while (argsIter.hasNext()) { @@ -165,6 +167,10 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA outputSet.addDirectory(directory); break; + case "--omit-from-input-dir": + omittedInputSet.addDirectory(requirePath(current, value)); + break; + case "--reflect-input": set = inputSet; // fall through case "--reflect-output": @@ -259,15 +265,23 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA advisor.setCallerFilterTree(callersFilter); } 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), + null); List predefinedClassDestDirs = new ArrayList<>(); for (URI pathUri : outputSet.getPredefinedClassesConfigPaths()) { predefinedClassDestDirs.add(Paths.get(pathUri).getParent().resolve(ConfigurationFile.PREDEFINED_CLASSES_AGENT_EXTRACTED_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]), ConfigurationSet.FAIL_ON_EXCEPTION)); + inputSet.loadPredefinedClassesConfig(predefinedClassDestDirs.toArray(new Path[0]), shouldExcludeClassesWithHash, ConfigurationSet.FAIL_ON_EXCEPTION), + omittedInputTraceProcessor); } catch (IOException e) { throw e; } catch (Throwable t) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java index 2e4dea2b1a5c..7788a887e2af 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMethod.java @@ -52,7 +52,7 @@ public static String toInternalParamsSignature(List types) { private final String name; private final String internalSignature; - private int hash; + private final int hash; public ConfigurationMethod(String name, String internalSignature) { this.name = name; @@ -62,6 +62,7 @@ public ConfigurationMethod(String name, String internalSignature) { paramsOnlySignature = paramsOnlySignature.substring(0, paramsEnd + 1); } this.internalSignature = paramsOnlySignature; + this.hash = name.hashCode() * 31 + (this.internalSignature == null ? 0 : this.internalSignature.hashCode()); } public String getName() { @@ -96,9 +97,6 @@ public void printJson(JsonWriter writer) throws IOException { @Override public int hashCode() { - if (hash == 0) { - hash = name.hashCode() * 31 + (internalSignature == null ? 0 : internalSignature.hashCode()); - } return hash; } @@ -110,4 +108,13 @@ public boolean equals(Object obj) { } return (obj == this); } + + @Override + public String toString() { + return "ConfigurationMethod{" + + "name='" + name + '\'' + + ", internalSignature='" + internalSignature + '\'' + + ", hash=" + hash + + '}'; + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationPredefinedClass.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationPredefinedClass.java index 391ade505de4..497be032a647 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationPredefinedClass.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationPredefinedClass.java @@ -40,6 +40,10 @@ public ConfigurationPredefinedClass(String nameInfo, String hash) { this.hash = hash; } + public String getNameInfo() { + return nameInfo; + } + @Override public void printJson(JsonWriter writer) throws IOException { writer.append("{ "); 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 15e935ff59bb..e6464c320b6b 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,14 +25,16 @@ package com.oracle.svm.configure.config; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.URI; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; 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; @@ -61,6 +63,15 @@ public void addDirectory(Path path) { predefinedClassesConfigPaths.add(path.resolve(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()).toUri()); } + 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())); + } + public boolean isEmpty() { return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() && predefinedClassesConfigPaths.isEmpty(); @@ -104,8 +115,9 @@ public ProxyConfiguration loadProxyConfig(Function excep return proxyConfiguration; } - public PredefinedClassesConfiguration loadPredefinedClassesConfig(Path[] classDestinationDirs, Function exceptionHandler) throws Exception { - PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(classDestinationDirs); + public PredefinedClassesConfiguration loadPredefinedClassesConfig(Path[] classDestinationDirs, Predicate shouldExcludeClassesWithHash, + Function exceptionHandler) throws Exception { + PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(classDestinationDirs, shouldExcludeClassesWithHash); loadConfig(predefinedClassesConfigPaths, new PredefinedClassesConfigurationParser(predefinedClassesConfiguration::add), exceptionHandler); return predefinedClassesConfiguration; } @@ -129,10 +141,9 @@ private static TypeConfiguration loadTypeConfig(Collection uris, Function configPaths, ConfigurationParser configurationParser, Function exceptionHandler) throws Exception { - for (URI uri : configPaths) { - Path path = Paths.get(uri); - try { - configurationParser.parseAndRegister(path); + for (URI path : configPaths) { + try (Reader reader = new InputStreamReader(path.toURL().openStream())) { + configurationParser.parseAndRegister(reader); } catch (IOException ioe) { Exception e = ioe; if (exceptionHandler != null) { 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 f973e9358a8f..2f1ee8fb9ebe 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 @@ -28,6 +28,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; +import java.util.function.BiPredicate; import java.util.function.Consumer; import com.oracle.svm.configure.json.JsonPrintable; @@ -55,6 +56,135 @@ public ConfigurationType(String qualifiedJavaName) { this.qualifiedJavaName = qualifiedJavaName; } + public ConfigurationType(ConfigurationType other) { + qualifiedJavaName = other.qualifiedJavaName; + mergeWith(other); + } + + public void mergeWith(ConfigurationType other) { + assert qualifiedJavaName.equals(other.qualifiedJavaName); + mergeFlagsWith(other); + mergeFieldsWith(other); + mergeMethodsWith(other); + } + + private void mergeFlagsWith(ConfigurationType other) { + setFlagsFromOtherUsingPredicate(other, (our, their) -> our || their); + } + + private void mergeFieldsWith(ConfigurationType other) { + if (other.fields != null) { + if (fields == null) { + fields = new HashMap<>(); + } + for (Map.Entry fieldInfoEntry : other.fields.entrySet()) { + fields.compute(fieldInfoEntry.getKey(), (key, value) -> { + if (value == null) { + return fieldInfoEntry.getValue(); + } else { + return value.newMergedWith(fieldInfoEntry.getValue()); + } + }); + } + } + maybeRemoveFields(allDeclaredFields, allPublicFields); + } + + private void maybeRemoveFields(boolean hasAllDeclaredFields, boolean hasAllPublicFields) { + if (hasAllDeclaredFields) { + removeFields(ConfigurationMemberKind.DECLARED); + } + if (hasAllPublicFields) { + removeFields(ConfigurationMemberKind.PUBLIC); + } + } + + private void mergeMethodsWith(ConfigurationType other) { + if (other.methods != null) { + if (methods == null) { + methods = new HashMap<>(); + } + for (Map.Entry methodEntry : other.methods.entrySet()) { + methods.compute(methodEntry.getKey(), (key, value) -> { + if (value != null) { + return value.equals(ConfigurationMemberKind.PRESENT) ? methodEntry.getValue() : value; + } else { + return methodEntry.getValue(); + } + }); + } + } + maybeRemoveMethods(allDeclaredMethods, allPublicMethods, allDeclaredConstructors, allPublicConstructors); + } + + private void maybeRemoveMethods(boolean hasAllDeclaredMethods, boolean hasAllPublicMethods, boolean hasAllDeclaredConstructors, boolean hasAllPublicConstructors) { + if (hasAllDeclaredMethods) { + removeMethods(ConfigurationMemberKind.DECLARED, false); + } + if (hasAllDeclaredConstructors) { + removeMethods(ConfigurationMemberKind.DECLARED, true); + } + + if (hasAllPublicMethods) { + removeMethods(ConfigurationMemberKind.PUBLIC, false); + } + if (hasAllPublicConstructors) { + removeMethods(ConfigurationMemberKind.PUBLIC, true); + } + } + + public void removeAll(ConfigurationType other) { + removeFlags(other); + removeFields(other); + removeMethods(other); + } + + private void removeFlags(ConfigurationType other) { + setFlagsFromOtherUsingPredicate(other, (our, their) -> our && !their); + } + + private void removeFields(ConfigurationType other) { + maybeRemoveFields(allDeclaredFields || other.allDeclaredFields, allPublicFields || other.allPublicFields); + if (fields != null && other.fields != null) { + for (Map.Entry fieldInfoEntry : other.fields.entrySet()) { + fields.computeIfPresent(fieldInfoEntry.getKey(), (key, value) -> value.newWithDifferencesFrom(fieldInfoEntry.getValue())); + } + if (fields.isEmpty()) { + fields = null; + } + } + } + + private void removeMethods(ConfigurationType other) { + maybeRemoveMethods(allDeclaredMethods || other.allDeclaredMethods, allPublicMethods || other.allPublicMethods, + allDeclaredConstructors || other.allDeclaredConstructors, allPublicConstructors || other.allPublicConstructors); + if (methods != null && other.methods != null) { + methods.entrySet().removeAll(other.methods.entrySet()); + if (methods.isEmpty()) { + methods = null; + } + } + } + + private void setFlagsFromOtherUsingPredicate(ConfigurationType other, BiPredicate predicate) { + allDeclaredClasses = predicate.test(allDeclaredClasses, other.allDeclaredClasses); + allPublicClasses = predicate.test(allPublicClasses, other.allPublicClasses); + allDeclaredFields = predicate.test(allDeclaredFields, other.allDeclaredFields); + allPublicFields = predicate.test(allPublicFields, other.allPublicFields); + allDeclaredMethods = predicate.test(allDeclaredMethods, other.allDeclaredMethods); + allPublicMethods = predicate.test(allPublicMethods, other.allPublicMethods); + allDeclaredConstructors = predicate.test(allDeclaredConstructors, other.allDeclaredConstructors); + allPublicConstructors = predicate.test(allPublicConstructors, other.allPublicConstructors); + } + + public boolean isEmpty() { + return methods == null && fields == null && allFlagsFalse(); + } + + private boolean allFlagsFalse() { + return !(allDeclaredClasses || allPublicClasses || allDeclaredFields || allPublicFields || allDeclaredMethods || allPublicMethods || allDeclaredConstructors || allPublicConstructors); + } + public String getQualifiedJavaName() { return qualifiedJavaName; } @@ -97,15 +227,23 @@ public void addMethod(String name, String internalSignature, ConfigurationMember } ConfigurationMethod method = new ConfigurationMethod(name, internalSignature); if (matchesAllSignatures) { // remove any methods that the new entry matches - methods.compute(method, (k, v) -> memberKind.union(v)); + methods.compute(method, (k, v) -> v != null ? memberKind.union(v) : memberKind); methods = maybeRemove(methods, map -> map.entrySet().removeIf(entry -> name.equals(entry.getKey().getName()) && memberKind.includes(entry.getValue()) && !method.equals(entry.getKey()))); } else { - methods.compute(method, (k, v) -> memberKind.intersect(v)); + methods.compute(method, (k, v) -> v != null ? memberKind.intersect(v) : memberKind); } assert methods.containsKey(method); } + public ConfigurationMemberKind getMethodKindIfPresent(ConfigurationMethod method) { + return methods == null ? null : methods.get(method); + } + + public FieldInfo getFieldInfoIfPresent(String field) { + return fields == null ? null : fields.get(field); + } + public boolean haveAllDeclaredClasses() { return allDeclaredClasses; } 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 853e7716a1c6..9bf58e60cd9d 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 @@ -24,7 +24,7 @@ */ package com.oracle.svm.configure.config; -final class FieldInfo { +public final class FieldInfo { private static final FieldInfo[] FINAL_NOT_WRITABLE_CACHE; static { ConfigurationMemberKind[] values = ConfigurationMemberKind.values(); @@ -49,6 +49,23 @@ private FieldInfo(ConfigurationMemberKind kind, boolean finalButWritable) { this.finalButWritable = finalButWritable; } + public FieldInfo newMergedWith(FieldInfo other) { + assert kind.equals(other.kind); + if (finalButWritable == other.finalButWritable) { + return this; + } + return get(kind, finalButWritable || other.finalButWritable); + } + + public FieldInfo newWithDifferencesFrom(FieldInfo other) { + assert kind.equals(other.kind); + boolean newFinalButWritable = finalButWritable && !other.finalButWritable; + if (!newFinalButWritable) { + return null; + } + return get(kind, newFinalButWritable); + } + public ConfigurationMemberKind 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 26819ff13d85..08f930f925a9 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 @@ -30,6 +30,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.json.JsonWriter; @@ -39,14 +40,19 @@ public class PredefinedClassesConfiguration implements ConfigurationBase { private final Path[] classDestinationDirs; private final ConcurrentHashMap classes = new ConcurrentHashMap<>(); + private final Predicate shouldExcludeClassWithHash; - public PredefinedClassesConfiguration(Path[] classDestinationDirs) { + public PredefinedClassesConfiguration(Path[] classDestinationDirs, Predicate shouldExcludeClassWithHash) { this.classDestinationDirs = classDestinationDirs; + this.shouldExcludeClassWithHash = shouldExcludeClassWithHash; } public void add(String nameInfo, byte[] classData) { ensureDestinationDirsExist(); String hash = PredefinedClassesSupport.hash(classData, 0, classData.length); + if (shouldExcludeClassWithHash != null && shouldExcludeClassWithHash.test(hash)) { + return; + } if (classDestinationDirs != null) { for (Path dir : classDestinationDirs) { try { @@ -61,6 +67,9 @@ public void add(String nameInfo, byte[] classData) { } public void add(String nameInfo, String hash, Path directory) { + if (shouldExcludeClassWithHash != null && shouldExcludeClassWithHash.test(hash)) { + return; + } if (classDestinationDirs != null) { ensureDestinationDirsExist(); for (Path dir : classDestinationDirs) { @@ -119,4 +128,12 @@ public void printJson(JsonWriter writer) throws IOException { public boolean isEmpty() { return classes.isEmpty(); } + + public boolean containsClassWithName(String className) { + return classes.values().stream().anyMatch(clazz -> clazz.getNameInfo().equals(className)); + } + + public boolean containsClassWithHash(String hash) { + return classes.containsKey(hash); + } } 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 3abfaac941a0..db0f73893c1e 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 @@ -26,6 +26,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -35,6 +36,15 @@ public class ProxyConfiguration implements ConfigurationBase { private final ConcurrentHashMap.KeySetView, Boolean> interfaceLists = ConcurrentHashMap.newKeySet(); + public ProxyConfiguration() { + } + + public ProxyConfiguration(ProxyConfiguration other) { + for (List interfaceList : other.interfaceLists) { + interfaceLists.add(new ArrayList<>(interfaceList)); + } + } + public void add(List interfaceList) { interfaceLists.add(interfaceList); } @@ -43,6 +53,14 @@ public boolean contains(List interfaceList) { return interfaceLists.contains(interfaceList); } + public boolean contains(String... interfaces) { + return contains(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()); 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 788077b9c060..602f1fa5d61e 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 @@ -64,6 +64,21 @@ public void addResourceBundles(String name) { private final ConcurrentMap ignoredResources = new ConcurrentHashMap<>(); private final ConcurrentHashMap.KeySetView bundles = ConcurrentHashMap.newKeySet(); + public ResourceConfiguration() { + } + + public ResourceConfiguration(ResourceConfiguration other) { + addedResources.putAll(other.addedResources); + ignoredResources.putAll(other.ignoredResources); + bundles.addAll(other.bundles); + } + + public void removeAll(ResourceConfiguration other) { + addedResources.keySet().removeAll(other.addedResources.keySet()); + ignoredResources.keySet().removeAll(other.ignoredResources.keySet()); + bundles.removeAll(other.bundles); + } + public void addResourcePattern(String pattern) { addedResources.computeIfAbsent(pattern, Pattern::compile); } 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 15cdc6f6465d..12bcd960d4c2 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,8 +40,27 @@ public class SerializationConfiguration implements ConfigurationBase { private final Set serializations = ConcurrentHashMap.newKeySet(); + public SerializationConfiguration() { + } + + public SerializationConfiguration(SerializationConfiguration other) { + this.serializations.addAll(other.serializations); + } + + public void removeAll(SerializationConfiguration other) { + serializations.removeAll(other.serializations); + } + public void add(String serializationTargetClass, String customTargetConstructorClass) { - serializations.add(serializationTargetClass + (customTargetConstructorClass != null ? KEY_SEPARATOR + customTargetConstructorClass : "")); + serializations.add(mapNameAndConstructor(serializationTargetClass, customTargetConstructorClass)); + } + + public boolean contains(String serializationTargetClass, String customTargetConstructorClass) { + return serializations.contains(mapNameAndConstructor(serializationTargetClass, customTargetConstructorClass)); + } + + private static String mapNameAndConstructor(String serializationTargetClass, String customTargetConstructorClass) { + return serializationTargetClass + (customTargetConstructorClass != null ? KEY_SEPARATOR + customTargetConstructorClass : ""); } @Override 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 f428071bf984..93304534c763 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.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -40,6 +41,28 @@ public class TypeConfiguration implements ConfigurationBase { private final ConcurrentMap types = new ConcurrentHashMap<>(); + public TypeConfiguration() { + } + + public TypeConfiguration(TypeConfiguration other) { + for (ConfigurationType configurationType : other.types.values()) { + types.put(configurationType.getQualifiedJavaName(), new ConfigurationType(configurationType)); + } + } + + public void removeAll(TypeConfiguration other) { + for (Map.Entry typeEntry : other.types.entrySet()) { + types.computeIfPresent(typeEntry.getKey(), (key, value) -> { + if (value.equals(typeEntry.getValue())) { + return null; + } + assert value.getQualifiedJavaName().equals(typeEntry.getValue().getQualifiedJavaName()); + value.removeAll(typeEntry.getValue()); + return value.isEmpty() ? null : value; + }); + } + } + public ConfigurationType get(String qualifiedJavaName) { return types.get(qualifiedJavaName); } 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 4fe87b9733f0..e1e257042460 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 @@ -45,34 +45,62 @@ 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) { + PredefinedClassesConfiguration predefinedClassesConfiguration, TraceProcessor omittedConfigProcessor) { 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() { - return jniProcessor.getConfiguration(); + TypeConfiguration result = jniProcessor.getConfiguration(); + if (omittedConfigProcessor != null) { + result = new TypeConfiguration(result); + result.removeAll(omittedConfigProcessor.jniProcessor.getConfiguration()); + } + return result; } public TypeConfiguration getReflectionConfiguration() { - return reflectionProcessor.getConfiguration(); + TypeConfiguration result = reflectionProcessor.getConfiguration(); + if (omittedConfigProcessor != null) { + result = new TypeConfiguration(result); + result.removeAll(omittedConfigProcessor.reflectionProcessor.getConfiguration()); + } + return result; } public ProxyConfiguration getProxyConfiguration() { - return reflectionProcessor.getProxyConfiguration(); + ProxyConfiguration result = reflectionProcessor.getProxyConfiguration(); + if (omittedConfigProcessor != null) { + result = new ProxyConfiguration(result); + result.removeAll(omittedConfigProcessor.reflectionProcessor.getProxyConfiguration()); + } + return result; } public ResourceConfiguration getResourceConfiguration() { - return reflectionProcessor.getResourceConfiguration(); + ResourceConfiguration result = reflectionProcessor.getResourceConfiguration(); + if (omittedConfigProcessor != null) { + result = new ResourceConfiguration(result); + result.removeAll(omittedConfigProcessor.reflectionProcessor.getResourceConfiguration()); + } + return result; } public SerializationConfiguration getSerializationConfiguration() { - return serializationProcessor.getSerializationConfiguration(); + SerializationConfiguration result = serializationProcessor.getSerializationConfiguration(); + if (omittedConfigProcessor != null) { + result = new SerializationConfiguration(result); + result.removeAll(omittedConfigProcessor.serializationProcessor.getSerializationConfiguration()); + } + return result; } public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java index e634cdb665b6..1057f6b286b3 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOption.java @@ -43,6 +43,7 @@ import java.util.stream.Stream; import com.oracle.svm.driver.NativeImage.BuildConfiguration; +import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker; final class MacroOption { enum MacroOptionKind { @@ -227,7 +228,7 @@ private static Map> collectMacroOption Map collectedOptions = Collections.emptyMap(); if (Files.isDirectory(optionsDir)) { collectedOptions = Files.list(optionsDir).filter(Files::isDirectory) - .filter(optionDir -> Files.isReadable(optionDir.resolve(NativeImage.nativeImagePropertiesFilename))) + .filter(optionDir -> Files.isReadable(optionDir.resolve(NativeImageMetaInfWalker.nativeImagePropertiesFilename))) .map(MacroOption::create).filter(Objects::nonNull) .collect(Collectors.toMap(MacroOption::getOptionName, Function.identity())); } @@ -425,7 +426,7 @@ private MacroOption(Path optionDirectory) { this.kind = MacroOptionKind.fromSubdir(optionDirectory.getParent().getFileName().toString()); this.optionName = optionDirectory.getFileName().toString(); this.optionDirectory = optionDirectory; - this.properties = NativeImage.loadProperties(optionDirectory.resolve(NativeImage.nativeImagePropertiesFilename)); + this.properties = NativeImage.loadProperties(optionDirectory.resolve(NativeImageMetaInfWalker.nativeImagePropertiesFilename)); } @Override diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 61bb2237782b..686af3012d67 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -34,15 +34,10 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.net.URI; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayDeque; @@ -72,7 +67,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.driver.metainf.MetaInfFileType; +import com.oracle.svm.driver.metainf.NativeImageMetaInfResourceProcessor; +import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker; import org.graalvm.compiler.options.OptionKey; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; @@ -84,7 +81,6 @@ import com.oracle.svm.core.OS; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.core.util.UserError; @@ -100,7 +96,7 @@ public class NativeImage { /* Used to enable native-image JPMS support (WIP) - will become default once completed */ - static final boolean USE_NI_JPMS = System.getenv().getOrDefault("USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM", "false").toLowerCase().equals("true"); + static final boolean USE_NI_JPMS = System.getenv().getOrDefault("USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM", "false").equalsIgnoreCase("true"); private static final String DEFAULT_GENERATOR_CLASS_NAME = NativeImageGeneratorRunner.class.getName(); private static final String DEFAULT_GENERATOR_MODULE_NAME = ModuleSupport.getModuleName(NativeImageGeneratorRunner.class); @@ -148,7 +144,7 @@ static String getResource(String resourceName) { private static final String usageText = getResource("/Usage.txt"); - class ArgumentQueue { + static class ArgumentQueue { private final ArrayDeque queue; public final String argumentOrigin; @@ -266,9 +262,6 @@ private static String oR(OptionKey option) { private final List excludedConfigs = new ArrayList<>(); - public static final String nativeImagePropertiesFilename = "native-image.properties"; - public static final String nativeImageMetaInf = "META-INF/native-image"; - public interface BuildConfiguration { /** * @return the name of the image generator main class. @@ -652,6 +645,55 @@ public Optional getResourcesJar() { } } + class DriverMetaInfProcessor implements NativeImageMetaInfResourceProcessor { + @Override + public void processMetaInfResource(Path classpathEntry, Path resourceRoot, Path resourcePath, MetaInfFileType type) throws IOException { + NativeImageArgsProcessor args = NativeImage.this.new NativeImageArgsProcessor(resourcePath.toUri().toString()); + Path componentDirectory = resourceRoot.relativize(resourcePath).getParent(); + Function resolver = str -> { + int nameCount = componentDirectory.getNameCount(); + String optionArg = null; + if (nameCount > 2) { + String optionArgKey = componentDirectory.subpath(2, nameCount).toString(); + optionArg = propertyFileSubstitutionValues.get(optionArgKey); + } + return resolvePropertyValue(str, optionArg, componentDirectory, config); + }; + + if (type == MetaInfFileType.Properties) { + Map properties = loadProperties(Files.newInputStream(resourcePath)); + String imageNameValue = properties.get("ImageName"); + if (imageNameValue != null) { + addCustomImageBuilderArgs(injectHostedOptionOrigin(oHName + resolver.apply(imageNameValue), resourcePath.toUri().toString())); + } + forEachPropertyValue(properties.get("JavaArgs"), NativeImage.this::addImageBuilderJavaArgs, resolver); + forEachPropertyValue(properties.get("Args"), args, resolver); + } else { + args.accept(oH(type.optionKey) + resourceRoot.relativize(resourcePath)); + } + args.apply(true); + } + + @Override + public void showWarning(String message) { + if (isVerbose()) { + NativeImage.showWarning(message); + } + } + + @Override + public void showVerboseMessage(String message) { + NativeImage.this.showVerboseMessage(isVerbose(), message); + } + + @Override + public boolean isExcluded(Path resourcePath, Path classpathEntry) { + return excludedConfigs.stream() + .filter(e -> e.jarPattern.matcher(classpathEntry.toString()).find()) + .anyMatch(e -> e.resourcePattern.matcher(resourcePath.toString()).find()); + } + } + private ArrayList createFallbackBuildArgs() { ArrayList buildArgs = new ArrayList<>(); Collection fallbackSystemProperties = customJavaArgs.stream() @@ -749,8 +791,11 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } } + private final DriverMetaInfProcessor metaInfProcessor; + protected NativeImage(BuildConfiguration config) { this.config = config; + this.metaInfProcessor = new DriverMetaInfProcessor(); String configFileEnvVarKey = "NATIVE_IMAGE_CONFIG_FILE"; String configFile = System.getenv(configFileEnvVarKey); @@ -946,107 +991,11 @@ private static void consolidateListArgs(Collection args, String argPrefi } } - enum MetaInfFileType { - Properties(null, nativeImagePropertiesFilename), - JniConfiguration(ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()), - ReflectConfiguration(ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()), - ResourceConfiguration(ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFile.RESOURCES.getFileName()), - ProxyConfiguration(ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFile.DYNAMIC_PROXY.getFileName()), - SerializationConfiguration(ConfigurationFiles.Options.SerializationConfigurationResources, ConfigurationFile.SERIALIZATION.getFileName()), - SerializationDenyConfiguration(ConfigurationFiles.Options.SerializationDenyConfigurationResources, ConfigurationFile.SERIALIZATION_DENY.getFileName()), - PredefinedClassesConfiguration(ConfigurationFiles.Options.PredefinedClassesConfigurationResources, ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()); - - final OptionKey optionKey; - final String fileName; - - MetaInfFileType(OptionKey optionKey, String fileName) { - this.optionKey = optionKey; - this.fileName = fileName; - } - } - - interface NativeImageMetaInfResourceProcessor { - void processMetaInfResource(Path classpathEntry, Path resourceRoot, Path resourcePath, MetaInfFileType type, Function resolver) throws IOException; - } - private void processClasspathNativeImageMetaInf(Path classpathEntry) { - processClasspathNativeImageMetaInf(classpathEntry, this::processNativeImageMetaInf); - } - - private void processClasspathNativeImageMetaInf(Path classpathEntry, NativeImageMetaInfResourceProcessor metaInfProcessor) { try { - if (Files.isDirectory(classpathEntry)) { - Path nativeImageMetaInfBase = classpathEntry.resolve(Paths.get(nativeImageMetaInf)); - processNativeImageMetaInf(classpathEntry, nativeImageMetaInfBase, metaInfProcessor); - } else { - List jarFileMatches = Collections.emptyList(); - if (classpathEntry.endsWith(ClasspathUtils.cpWildcardSubstitute)) { - try { - jarFileMatches = Files.list(classpathEntry.getParent()) - .filter(ClasspathUtils::isJar) - .collect(Collectors.toList()); - } catch (NoSuchFileException e) { - /* Fallthrough */ - } - } else if (ClasspathUtils.isJar(classpathEntry)) { - jarFileMatches = Collections.singletonList(classpathEntry); - } - - for (Path jarFile : jarFileMatches) { - URI jarFileURI = URI.create("jar:" + jarFile.toUri()); - FileSystem probeJarFS; - try { - probeJarFS = FileSystems.newFileSystem(jarFileURI, Collections.emptyMap()); - } catch (UnsupportedOperationException e) { - probeJarFS = null; - if (isVerbose()) { - showWarning(ClasspathUtils.classpathToString(classpathEntry) + " does not describe valid jarfile" + (jarFileMatches.size() > 1 ? "s" : "")); - } - } - if (probeJarFS != null) { - try (FileSystem jarFS = probeJarFS) { - Path nativeImageMetaInfBase = jarFS.getPath("/" + nativeImageMetaInf); - processNativeImageMetaInf(jarFile, nativeImageMetaInfBase, metaInfProcessor); - } - } - } - } - } catch (IOException | FileSystemNotFoundException e) { - throw showError("Invalid classpath entry " + ClasspathUtils.classpathToString(classpathEntry), e); - } - } - - private void processNativeImageMetaInf(Path classpathEntry, Path nativeImageMetaInfBase, NativeImageMetaInfResourceProcessor metaInfProcessor) throws IOException { - if (Files.isDirectory(nativeImageMetaInfBase)) { - for (MetaInfFileType fileType : MetaInfFileType.values()) { - List nativeImageMetaInfFiles = Files.walk(nativeImageMetaInfBase) - .filter(p -> p.endsWith(fileType.fileName)) - .collect(Collectors.toList()); - for (Path nativeImageMetaInfFile : nativeImageMetaInfFiles) { - boolean excluded = isExcluded(nativeImageMetaInfFile, classpathEntry); - if (excluded) { - continue; - } - - Path resourceRoot = nativeImageMetaInfBase.getParent().getParent(); - Function resolver = str -> { - Path componentDirectory = resourceRoot.relativize(nativeImageMetaInfFile).getParent(); - int nameCount = componentDirectory.getNameCount(); - String optionArg = null; - if (nameCount > 2) { - String optionArgKey = componentDirectory.subpath(2, nameCount).toString(); - optionArg = propertyFileSubstitutionValues.get(optionArgKey); - } - return resolvePropertyValue(str, optionArg, componentDirectory, config); - }; - showVerboseMessage(isVerbose(), "Apply " + nativeImageMetaInfFile.toUri()); - try { - metaInfProcessor.processMetaInfResource(classpathEntry, resourceRoot, nativeImageMetaInfFile, fileType, resolver); - } catch (NativeImageError err) { - showError("Processing " + nativeImageMetaInfFile.toUri() + " failed", err); - } - } - } + NativeImageMetaInfWalker.walkMetaInfForCPEntry(classpathEntry, metaInfProcessor); + } catch (NativeImageMetaInfWalker.MetaInfWalkException e) { + throw showError(e.getMessage(), e.cause); } } @@ -1054,12 +1003,6 @@ public void addExcludeConfig(Pattern jarPattern, Pattern resourcePattern) { excludedConfigs.add(new ExcludeConfig(jarPattern, resourcePattern)); } - private boolean isExcluded(Path resourcePath, Path classpathEntry) { - return excludedConfigs.stream() - .filter(e -> e.jarPattern.matcher(classpathEntry.toString()).find()) - .anyMatch(e -> e.resourcePattern.matcher(resourcePath.toString()).find()); - } - static String injectHostedOptionOrigin(String option, String origin) { if (origin != null && option.startsWith(oH)) { String optionOriginSeparator = "@"; @@ -1082,24 +1025,6 @@ static String injectHostedOptionOrigin(String option, String origin) { return option; } - private void processNativeImageMetaInf(@SuppressWarnings("unused") Path classpathEntry, Path resourceRoot, - Path resourcePath, MetaInfFileType resourceType, Function resolver) throws IOException { - - NativeImageArgsProcessor args = new NativeImageArgsProcessor(resourcePath.toUri().toString()); - if (resourceType == MetaInfFileType.Properties) { - Map properties = loadProperties(Files.newInputStream(resourcePath)); - String imageNameValue = properties.get("ImageName"); - if (imageNameValue != null) { - addCustomImageBuilderArgs(injectHostedOptionOrigin(oHName + resolver.apply(imageNameValue), resourcePath.toUri().toString())); - } - forEachPropertyValue(properties.get("JavaArgs"), this::addImageBuilderJavaArgs, resolver); - forEachPropertyValue(properties.get("Args"), args, resolver); - } else { - args.accept(oH(resourceType.optionKey) + resourceRoot.relativize(resourcePath)); - } - args.apply(true); - } - static void processManifestMainAttributes(Path path, BiConsumer manifestConsumer) { if (path.endsWith(ClasspathUtils.cpWildcardSubstitute)) { if (!Files.isDirectory(path.getParent())) { @@ -1388,7 +1313,7 @@ private List getAgentArguments() { if (!agentOptions.isEmpty()) { args.add("-agentlib:native-image-diagnostics-agent=" + agentOptions); } - args.add("-javaagent:" + config.getAgentJAR().toAbsolutePath().toString() + (agentOptions.isEmpty() ? "" : "=" + agentOptions)); + args.add("-javaagent:" + config.getAgentJAR().toAbsolutePath() + (agentOptions.isEmpty() ? "" : "=" + agentOptions)); return args; } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/MetaInfFileType.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/MetaInfFileType.java new file mode 100644 index 000000000000..963d3b7e552a --- /dev/null +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/MetaInfFileType.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016, 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.driver.metainf; + +import org.graalvm.compiler.options.OptionKey; + +import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.core.configure.ConfigurationFiles; + +public enum MetaInfFileType { + Properties(null, NativeImageMetaInfWalker.nativeImagePropertiesFilename), + JniConfiguration(ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()), + ReflectConfiguration(ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()), + ResourceConfiguration(ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFile.RESOURCES.getFileName()), + ProxyConfiguration(ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFile.DYNAMIC_PROXY.getFileName()), + SerializationConfiguration(ConfigurationFiles.Options.SerializationConfigurationResources, ConfigurationFile.SERIALIZATION.getFileName()), + SerializationDenyConfiguration(ConfigurationFiles.Options.SerializationDenyConfigurationResources, ConfigurationFile.SERIALIZATION_DENY.getFileName()), + PredefinedClassesConfiguration(ConfigurationFiles.Options.PredefinedClassesConfigurationResources, ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()); + + public final OptionKey optionKey; + public final String fileName; + + MetaInfFileType(OptionKey optionKey, String fileName) { + this.optionKey = optionKey; + this.fileName = fileName; + } +} diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfResourceProcessor.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfResourceProcessor.java new file mode 100644 index 000000000000..6a44b8e60f14 --- /dev/null +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfResourceProcessor.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016, 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.driver.metainf; + +import java.nio.file.Path; + +public interface NativeImageMetaInfResourceProcessor { + /** + * Process a single file located under the native-image META-INF directory. + * + * @param classpathEntry Classpath entry from which the file originated. + * @param resourceRoot Parent of the META-INF/native-image directory from which the file + * originated. + * @param resourcePath Path to the file. + * @param type Type of the file. + */ + void processMetaInfResource(Path classpathEntry, Path resourceRoot, Path resourcePath, MetaInfFileType type) throws Exception; + + void showWarning(String message); + + void showVerboseMessage(String message); + + boolean isExcluded(Path resourcePath, Path classpathEntry); +} diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfWalker.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfWalker.java new file mode 100644 index 000000000000..df5d2335411d --- /dev/null +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/NativeImageMetaInfWalker.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016, 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.driver.metainf; + +import com.oracle.svm.core.util.ClasspathUtils; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class NativeImageMetaInfWalker { + + public static final String nativeImageMetaInf = "META-INF/native-image"; + public static final String nativeImagePropertiesFilename = "native-image.properties"; + + public static class MetaInfWalkException extends Exception { + private static final long serialVersionUID = 7185681203564964445L; + + public final Throwable cause; + + public MetaInfWalkException(String message, Throwable cause) { + super(message); + this.cause = cause; + } + } + + public static void walkMetaInfForCPEntry(Path classpathEntry, NativeImageMetaInfResourceProcessor metaInfProcessor) throws MetaInfWalkException { + try { + if (Files.isDirectory(classpathEntry)) { + Path nativeImageMetaInfBase = classpathEntry.resolve(Paths.get(nativeImageMetaInf)); + processNativeImageMetaInf(classpathEntry, nativeImageMetaInfBase, metaInfProcessor); + } else { + List jarFileMatches = Collections.emptyList(); + if (classpathEntry.endsWith(ClasspathUtils.cpWildcardSubstitute)) { + try { + jarFileMatches = Files.list(classpathEntry.getParent()) + .filter(ClasspathUtils::isJar) + .collect(Collectors.toList()); + } catch (NoSuchFileException e) { + /* Fallthrough */ + } + } else if (ClasspathUtils.isJar(classpathEntry)) { + jarFileMatches = Collections.singletonList(classpathEntry); + } + + for (Path jarFile : jarFileMatches) { + URI jarFileURI = URI.create("jar:" + jarFile.toUri()); + FileSystem probeJarFS; + try { + probeJarFS = FileSystems.newFileSystem(jarFileURI, Collections.emptyMap()); + } catch (UnsupportedOperationException e) { + probeJarFS = null; + metaInfProcessor.showWarning(ClasspathUtils.classpathToString(classpathEntry) + " does not describe valid jarfile" + (jarFileMatches.size() > 1 ? "s" : "")); + } + if (probeJarFS != null) { + try (FileSystem jarFS = probeJarFS) { + Path nativeImageMetaInfBase = jarFS.getPath("/" + nativeImageMetaInf); + processNativeImageMetaInf(jarFile, nativeImageMetaInfBase, metaInfProcessor); + } + } + } + } + } catch (IOException | FileSystemNotFoundException e) { + throw new MetaInfWalkException("Invalid classpath entry " + ClasspathUtils.classpathToString(classpathEntry), e); + } + } + + private static void processNativeImageMetaInf(Path classpathEntry, Path nativeImageMetaInfBase, NativeImageMetaInfResourceProcessor metaInfProcessor) throws MetaInfWalkException { + if (Files.isDirectory(nativeImageMetaInfBase)) { + for (MetaInfFileType fileType : MetaInfFileType.values()) { + List nativeImageMetaInfFiles; + try { + nativeImageMetaInfFiles = Files.walk(nativeImageMetaInfBase) + .filter(p -> p.endsWith(fileType.fileName)) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new MetaInfWalkException("Processing " + nativeImageMetaInfBase.toUri() + " failed.", e); + } + for (Path nativeImageMetaInfFile : nativeImageMetaInfFiles) { + boolean excluded = metaInfProcessor.isExcluded(nativeImageMetaInfFile, classpathEntry); + if (excluded) { + continue; + } + + Path resourceRoot = nativeImageMetaInfBase.getParent().getParent(); + metaInfProcessor.showVerboseMessage("Apply " + nativeImageMetaInfFile.toUri()); + try { + metaInfProcessor.processMetaInfResource(classpathEntry, resourceRoot, nativeImageMetaInfFile, fileType); + } catch (Throwable err) { + throw new MetaInfWalkException("Processing " + nativeImageMetaInfFile.toUri() + " failed", err); + } + } + } + } + } +}