diff --git a/sdk/mx.sdk/mx_sdk_vm.py b/sdk/mx.sdk/mx_sdk_vm.py index 54c5aa5534b9..a9c95545fbd1 100644 --- a/sdk/mx.sdk/mx_sdk_vm.py +++ b/sdk/mx.sdk/mx_sdk_vm.py @@ -159,7 +159,7 @@ def add_relative_home_path(self, language, path): class LauncherConfig(AbstractNativeImageConfig): def __init__(self, destination, jar_distributions, main_class, build_args, is_main_launcher=True, default_symlinks=True, is_sdk_launcher=False, custom_launcher_script=None, extra_jvm_args=None, - use_modules=None, main_module=None, option_vars=None, home_finder=True, **kwargs): + use_modules=None, main_module=None, link_at_build_time=True, option_vars=None, home_finder=True, **kwargs): """ :param str main_class :param bool is_main_launcher @@ -174,6 +174,7 @@ def __init__(self, destination, jar_distributions, main_class, build_args, is_ma self.main_module = main_module assert self.use_modules is None or self.main_module self.main_class = main_class + self.link_at_build_time = link_at_build_time self.is_main_launcher = is_main_launcher self.default_symlinks = default_symlinks self.is_sdk_launcher = is_sdk_launcher diff --git a/sdk/mx.sdk/mx_sdk_vm_impl.py b/sdk/mx.sdk/mx_sdk_vm_impl.py index 41c32ec8243e..c1ffd29fd85d 100644 --- a/sdk/mx.sdk/mx_sdk_vm_impl.py +++ b/sdk/mx.sdk/mx_sdk_vm_impl.py @@ -1256,6 +1256,8 @@ def contents(self): build_args += ['-ea', '-H:-AOTInline', '-H:+PreserveFramePointer', '-H:-DeleteLocalSymbols'] if _get_svm_support().is_debug_supported(): build_args += ['-g'] + if getattr(image_config, 'link_at_build_time', True): + build_args += ['--link-at-build-time'] graalvm_dist = get_final_graalvm_distribution() graalvm_location = dirname(graalvm_dist.find_single_source_location('dependency:' + self.subject.name)) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 54062dd502c7..82ec788db4ee 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -8,6 +8,7 @@ This changelog summarizes major changes to GraalVM Native Image. * Remove support for JDK8. As a result, `JDK8OrEarlier` and `JDK11OrLater` have been deprecated and will be removed in a future release. * (GR-26814) (GR-37018) (GR-37038) Red Hat added support for the following JFR events: `SafepointBegin`, `SafepointEnd`, `GarbageCollection`, `GCPhasePause`, and `GCPhasePauseLevel*`. All GC-related JFR events are currently limited to the serial GC. * (GR-35721) Deprecate `-H:±BuildOutputUseNewStyle` option. The old build output style will be removed in a future release. +* (GR-36905) Allow incomplete classes at build-time is now default. Add --link-at-build-time option and @ support for native-image.properties. Add --link-at-build-time-paths option. ## Version 22.0.0 * (GR-33930) Decouple HostedOptionParser setup from classpath/modulepath scanning (use ServiceLoader for collecting options). diff --git a/substratevm/mx.substratevm/macro-junit.properties b/substratevm/mx.substratevm/macro-junit.properties index 6f802d0e00a0..dec7f4d637fe 100644 --- a/substratevm/mx.substratevm/macro-junit.properties +++ b/substratevm/mx.substratevm/macro-junit.properties @@ -8,4 +8,5 @@ ImageClasspath = ${.}/junit-tool.jar:${.}/junit.jar:${.}/hamcrest.jar Args = -H:Features=com.oracle.svm.junit.JUnitFeature \ -H:Class=com.oracle.svm.junit.SVMJUnitRunner \ -H:TestFile=${*} \ - --initialize-at-build-time=org.junit,com.oracle.mxtool.junit.MxJUnitRequest + --initialize-at-build-time=org.junit,com.oracle.mxtool.junit.MxJUnitRequest \ + --link-at-build-time=@svm-junit.packages \ No newline at end of file diff --git a/substratevm/mx.substratevm/macro-junitcp.properties b/substratevm/mx.substratevm/macro-junitcp.properties index 3fb002782ec9..b5ed54d7cf28 100644 --- a/substratevm/mx.substratevm/macro-junitcp.properties +++ b/substratevm/mx.substratevm/macro-junitcp.properties @@ -7,4 +7,5 @@ ImageClasspath = ${.}/junit-support.jar:${.}/junit-tool.jar:${.}/junit.jar:${.}/ Args = -H:Features=com.oracle.svm.junit.JUnitFeature \ -H:Class=com.oracle.svm.junit.SVMJUnitRunner \ -H:TestFile=${*} \ - --initialize-at-build-time=org.junit,com.oracle.mxtool.junit.MxJUnitRequest + --initialize-at-build-time=org.junit,com.oracle.mxtool.junit.MxJUnitRequest \ + --link-at-build-time=@svm-junit.packages \ No newline at end of file diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index f35a483e8e35..dde0dd16cf7b 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1603,6 +1603,7 @@ "description" : "Native-image based junit testing support", "layout" : { "native-image.properties" : "file:mx.substratevm/macro-junit.properties", + "svm-junit.packages" : "file:mx.substratevm/svm-junit.packages", }, }, @@ -1611,6 +1612,7 @@ "description" : "Native-image based junit testing support but with running image-builder on classpath", "layout" : { "native-image.properties" : "file:mx.substratevm/macro-junitcp.properties", + "svm-junit.packages" : "file:mx.substratevm/svm-junit.packages", }, }, diff --git a/substratevm/mx.substratevm/svm-junit.packages b/substratevm/mx.substratevm/svm-junit.packages new file mode 100644 index 000000000000..1da0ff3a3cd6 --- /dev/null +++ b/substratevm/mx.substratevm/svm-junit.packages @@ -0,0 +1,18 @@ +com.oracle.mxtool.junit +junit.framework +junit.runner +org.hamcrest +org.hamcrest.core +org.hamcrest.internal +org.junit +org.junit.internal +org.junit.internal.builders +org.junit.internal.runners.model +org.junit.internal.runners.statements +org.junit.rules +org.junit.runner +org.junit.runner.manipulation +org.junit.runner.notification +org.junit.runners +org.junit.runners.model +org.junit.validator \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 61a4c37a04d9..f7aefb9edc2b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -47,20 +47,18 @@ public final class ReflectionConfigurationParser extends ConfigurationParser private static final String CONSTRUCTOR_NAME = ""; private final ReflectionConfigurationParserDelegate delegate; - private final boolean allowIncompleteClasspath; private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", "allDeclaredClasses", "allPermittedSubclasses", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods"); public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { - this(delegate, false, true); + this(delegate, true); } - public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate, boolean allowIncompleteClasspath, boolean strictConfiguration) { + public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration) { super(strictConfiguration); this.delegate = delegate; - this.allowIncompleteClasspath = allowIncompleteClasspath; } @Override @@ -282,19 +280,15 @@ private String formatMethod(T clazz, String methodName, List paramTypes) { return delegate.getTypeName(clazz) + '.' + methodName + '(' + parameterTypeNames + ')'; } - private void handleError(String message) { + private static void handleError(String message) { handleError(message, null); } - private void handleError(String msg, Throwable cause) { + private static void handleError(String msg, Throwable cause) { String message = msg; if (cause != null) { message += " Reason: " + formatError(cause) + '.'; } - if (allowIncompleteClasspath) { - System.err.println("Warning: " + message); - } else { - throw new JSONParserException(message + " To allow unresolvable reflection configuration, use option --allow-incomplete-classpath"); - } + System.err.println("Warning: " + message); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java index 176087b6115f..fa3607a57726 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/LocatableMultiOptionValue.java @@ -29,10 +29,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.common.option.LocatableOption; -import com.oracle.svm.common.option.MultiOptionValue; import org.graalvm.collections.Pair; +import com.oracle.svm.common.option.LocatableOption; +import com.oracle.svm.common.option.MultiOptionValue; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ClassUtil; @@ -76,8 +76,8 @@ public List values() { return values.stream().map(Pair::getLeft).collect(Collectors.toList()); } - public Stream> getValuesWithOrigins() { - return values.stream(); + public Stream> getValuesWithOrigins() { + return values.stream().map(pair -> Pair.create(pair.getLeft(), OptionOrigin.from(pair.getRight()))); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionOrigin.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionOrigin.java new file mode 100644 index 000000000000..9dcb2627f8ff --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionOrigin.java @@ -0,0 +1,288 @@ +/* + * 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.core.option; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import com.oracle.svm.core.util.VMError; + +public abstract class OptionOrigin { + + public static final OptionOrigin commandLineOptionOriginSingleton = new CommandLineOptionOrigin(); + + public URI container() { + return null; + } + + public Path location() { + return null; + } + + public boolean commandLineLike() { + return false; + } + + /** + * Return the option values contained in the redirection file specified by the given path. + * Depending on the specific kind of OptionOrigin the method to retrieve those values can + * require different implementations. + */ + public List getRedirectionValues(@SuppressWarnings("unused") Path valuesFile) throws IOException { + throw new IOException(new UnsupportedOperationException()); + } + + public static OptionOrigin from(String origin) { + + if (origin == null) { + return commandLineOptionOriginSingleton; + } + + URI originURI = originURI(origin); + if (originURI == null) { + var macroOption = MacroOptionOrigin.from(origin); + if (macroOption != null) { + return macroOption; + } + throw VMError.shouldNotReachHere("Unsupported OptionOrigin: " + origin); + } + switch (originURI.getScheme()) { + case "jar": + return new JarOptionOrigin(originURI); + case "file": + Path originPath = Path.of(originURI); + if (!Files.isReadable(originPath)) { + VMError.shouldNotReachHere("Directory origin with path that cannot be read: " + originPath); + } + return new DirectoryOptionOrigin(originPath); + default: + throw VMError.shouldNotReachHere("OptionOrigin of unsupported scheme: " + originURI); + } + } + + protected static URI originURI(String origin) { + try { + return new URI(origin); + } catch (URISyntaxException x) { + return null; + } + } + + static List getRedirectionValuesFromPath(Path normalizedRedirPath) throws IOException { + if (Files.isReadable(normalizedRedirPath)) { + return Files.readAllLines(normalizedRedirPath); + } + throw new FileNotFoundException("Unable to read file from " + normalizedRedirPath.toUri()); + } +} + +final class CommandLineOptionOrigin extends OptionOrigin { + + CommandLineOptionOrigin() { + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof CommandLineOptionOrigin; + } + + @Override + public boolean commandLineLike() { + return true; + } + + @Override + public String toString() { + return "command line"; + } +} + +final class MacroOptionOrigin extends OptionOrigin { + + public final OptionUtils.MacroOptionKind kind; + public final String name; + public final Path optionDirectory; + + private MacroOptionOrigin(OptionUtils.MacroOptionKind kind, String name, URI optionDirectory) { + this.kind = kind; + this.name = name; + VMError.guarantee(optionDirectory != null, "Invalid optionDirectory origin"); + this.optionDirectory = Path.of(optionDirectory); + } + + @Override + public int hashCode() { + return Objects.hash(kind, name); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof MacroOptionOrigin) { + var that = (MacroOptionOrigin) obj; + return Objects.equals(this.kind, that.kind) && + Objects.equals(this.name, that.name); + } + return false; + } + + public static MacroOptionOrigin from(String rawOrigin) { + for (OptionUtils.MacroOptionKind kind : OptionUtils.MacroOptionKind.values()) { + String prefix = kind.getDescriptionPrefix(true); + if (rawOrigin.startsWith(prefix)) { + int optionDirectorySep = rawOrigin.indexOf('@'); + VMError.guarantee(optionDirectorySep > 0, "Missing macro-option optionDirectory origin"); + String optionDirectory = rawOrigin.substring(optionDirectorySep + 1); + int argumentOriginSep = optionDirectory.indexOf('@'); + if (argumentOriginSep > 0) { + /* Strip optional trailing argumentOrigin */ + optionDirectory = optionDirectory.substring(0, argumentOriginSep); + } + return new MacroOptionOrigin(kind, rawOrigin.substring(prefix.length()), originURI(optionDirectory)); + } + } + return null; + } + + @Override + public boolean commandLineLike() { + return OptionUtils.MacroOptionKind.Macro.equals(kind); + } + + @Override + public String toString() { + return kind + " option '" + name + "'"; + } + + @Override + public List getRedirectionValues(Path valuesFile) throws IOException { + var normalizedRedirPath = optionDirectory.resolve(valuesFile).normalize(); + return getRedirectionValuesFromPath(normalizedRedirPath); + } +} + +abstract class URIOptionOrigin extends OptionOrigin { + + protected URI container; + + @Override + public URI container() { + return container; + } + + protected Path location; + + @Override + public Path location() { + return location; + } + + @Override + public int hashCode() { + return Objects.hash(container, location); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof URIOptionOrigin) { + var that = (URIOptionOrigin) obj; + return Objects.equals(this.container, that.container) && + Objects.equals(this.location, that.location); + } + return false; + } + + @Override + public String toString() { + return String.format("'%s' in '%s'", location(), container()); + } +} + +final class JarOptionOrigin extends URIOptionOrigin { + protected JarOptionOrigin(URI rawOrigin) { + var specific = rawOrigin.getSchemeSpecificPart(); + int sep = specific.lastIndexOf('!'); + VMError.guarantee(sep > 0, "Invalid jar origin"); + var origin = specific.substring(0, sep); + container = URIOptionOrigin.originURI(origin); + location = Path.of(specific.substring(sep + 2)); + } + + @SuppressWarnings("try") + @Override + public List getRedirectionValues(Path valuesFile) throws IOException { + URI jarFileURI = URI.create("jar:" + container()); + FileSystem probeJarFS; + try { + probeJarFS = FileSystems.newFileSystem(jarFileURI, Collections.emptyMap()); + } catch (UnsupportedOperationException e) { + probeJarFS = null; + } + if (probeJarFS == null) { + throw new IOException("Unable to create jar file system for " + jarFileURI); + } + try (FileSystem fs = probeJarFS) { + var normalizedRedirPath = location().getParent().resolve(valuesFile).normalize(); + return getRedirectionValuesFromPath(normalizedRedirPath); + } + } +} + +final class DirectoryOptionOrigin extends URIOptionOrigin { + protected DirectoryOptionOrigin(Path originPath) { + int pathPos = 0; + int metaInfPos = -1; + for (Path entry : originPath) { + if ("META-INF".equals(entry.toString())) { + metaInfPos = pathPos; + break; + } + ++pathPos; + } + VMError.guarantee(metaInfPos > 0, "Invalid directory origin"); + container = originPath.getRoot().resolve(originPath.subpath(0, metaInfPos)).toUri(); + location = originPath.subpath(metaInfPos, originPath.getNameCount()); + } + + @Override + public List getRedirectionValues(Path valuesFile) throws IOException { + var normalizedRedirPath = Path.of(container()).resolve(location()).getParent().resolve(valuesFile).normalize(); + return getRedirectionValuesFromPath(normalizedRedirPath); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java index 4fe1d767c9c8..f446c89d1e27 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionUtils.java @@ -24,12 +24,19 @@ */ package com.oracle.svm.core.option; +import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.compiler.options.OptionKey; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.util.UserError; /** * This class contains static helper methods related to options. @@ -67,4 +74,84 @@ public static List flatten(String delimiter, List values) { } return result; } + + public static List resolveOptionValuesRedirection(OptionKey option, String optionValue, OptionOrigin origin) { + return Arrays.asList(SubstrateUtil.split(optionValue, ",")).stream() + .flatMap(entry -> resolveOptionValueRedirection(option, optionValue, origin, entry)) + .collect(Collectors.toList()); + } + + private static Stream resolveOptionValueRedirection(OptionKey option, String optionValue, OptionOrigin origin, String entry) { + if (entry.trim().startsWith("@")) { + Path valuesFile = Path.of(entry.substring(1)); + if (valuesFile.isAbsolute()) { + throw UserError.abort("Option '%s' provided by %s contains value redirection file '%s' that is an absolute path.", + SubstrateOptionsParser.commandArgument(option, optionValue), origin, valuesFile); + } + try { + return origin.getRedirectionValues(valuesFile).stream(); + } catch (IOException e) { + throw UserError.abort(e, "Option '%s' provided by %s contains invalid option value redirection.", + SubstrateOptionsParser.commandArgument(option, optionValue), origin); + } + } else { + return Stream.of(entry); + } + } + + public enum MacroOptionKind { + + Language("languages", true), + Tool("tools", true), + Macro("macros", false); + + public static final String macroOptionPrefix = "--"; + + public final String subdir; + public final boolean allowAll; + + MacroOptionKind(String subdir, boolean allowAll) { + this.subdir = subdir; + this.allowAll = allowAll; + } + + public static MacroOptionKind fromSubdir(String subdir) { + for (MacroOptionKind kind : MacroOptionKind.values()) { + if (kind.subdir.equals(subdir)) { + return kind; + } + } + throw new InvalidMacroException("No MacroOptionKind for subDir: " + subdir); + } + + public static MacroOptionKind fromString(String kindName) { + for (MacroOptionKind kind : MacroOptionKind.values()) { + if (kind.toString().equals(kindName)) { + return kind; + } + } + throw new InvalidMacroException("No MacroOptionKind for kindName: " + kindName); + } + + public String getDescriptionPrefix(boolean commandLineStyle) { + StringBuilder sb = new StringBuilder(); + if (commandLineStyle) { + sb.append(macroOptionPrefix); + } + sb.append(this).append(":"); + return sb.toString(); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } + + @SuppressWarnings("serial") + public static final class InvalidMacroException extends RuntimeException { + public InvalidMacroException(String arg0) { + super(arg0); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java index 226e7205d3a0..62fada7890e8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java @@ -208,7 +208,13 @@ public static String commandArgument(OptionKey option, String value, String a if (apiOption.fixedValue().length == 0) { if (apiOptionWithValue == null) { /* First APIOption that accepts value is selected as fallback */ - apiOptionWithValue = optionName + apiOption.valueSeparator()[0] + value; + if (Arrays.equals(apiOption.defaultValue(), new String[]{value})) { + /* Option with default value. Use short form */ + apiOptionWithValue = optionName; + } else { + /* Option with custom value. Use form with valueSeparator */ + apiOptionWithValue = optionName + apiOption.valueSeparator()[0] + value; + } } } else if (apiOption.fixedValue()[0].equals(value)) { /* Return requested option expressed as fixed-value APIOption */ diff --git a/substratevm/src/com.oracle.svm.driver/resources/META-INF/native-image/com.oracle.substratevm/svm-driver/native-image.properties b/substratevm/src/com.oracle.svm.driver/resources/META-INF/native-image/com.oracle.substratevm/svm-driver/native-image.properties index a3883325fe9c..a597237df010 100644 --- a/substratevm/src/com.oracle.svm.driver/resources/META-INF/native-image/com.oracle.substratevm/svm-driver/native-image.properties +++ b/substratevm/src/com.oracle.svm.driver/resources/META-INF/native-image/com.oracle.substratevm/svm-driver/native-image.properties @@ -1,2 +1,4 @@ ImageName = native-image -Args = -H:-ParseRuntimeOptions --initialize-at-build-time=com.oracle.svm.driver +Args = -H:-ParseRuntimeOptions \ + --initialize-at-build-time=com.oracle.svm.driver \ + --link-at-build-time=com.oracle.svm.driver,com.oracle.svm.driver.metainf \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java index 3401ef093644..ec48cae2ab39 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java @@ -36,7 +36,7 @@ import org.graalvm.compiler.options.OptionType; -import com.oracle.svm.driver.MacroOption.MacroOptionKind; +import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.driver.NativeImage.ArgumentQueue; class DefaultOptionHandler extends NativeImage.OptionHandler { @@ -105,7 +105,7 @@ public boolean consume(ArgumentQueue args) { nativeImage.showMessage(helpExtraText); nativeImage.apiOptionHandler.printOptions(nativeImage::showMessage, true); nativeImage.showNewline(); - nativeImage.optionRegistry.showOptions(MacroOptionKind.Macro, true, nativeImage::showMessage); + nativeImage.optionRegistry.showOptions(OptionUtils.MacroOptionKind.Macro, true, nativeImage::showMessage); nativeImage.showNewline(); System.exit(0); return true; 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 1057f6b286b3..89068c6fab6d 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 @@ -42,46 +42,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.driver.NativeImage.BuildConfiguration; import com.oracle.svm.driver.metainf.NativeImageMetaInfWalker; final class MacroOption { - enum MacroOptionKind { - Language("languages", true), - Tool("tools", true), - Macro("macros", false); - - final String subdir; - final boolean allowAll; - - MacroOptionKind(String subdir, boolean allowAll) { - this.subdir = subdir; - this.allowAll = allowAll; - } - - static MacroOptionKind fromSubdir(String subdir) { - for (MacroOptionKind kind : MacroOptionKind.values()) { - if (kind.subdir.equals(subdir)) { - return kind; - } - } - throw new InvalidMacroException("No MacroOptionKind for subDir: " + subdir); - } - - static MacroOptionKind fromString(String kindName) { - for (MacroOptionKind kind : MacroOptionKind.values()) { - if (kind.toString().equals(kindName)) { - return kind; - } - } - throw new InvalidMacroException("No MacroOptionKind for kindName: " + kindName); - } - - @Override - public String toString() { - return name().toLowerCase(); - } - } Path getOptionDirectory() { return optionDirectory; @@ -91,34 +56,20 @@ String getOptionName() { return optionName; } - private static final String macroOptionPrefix = "--"; - String getDescription(boolean commandLineStyle) { - StringBuilder sb = new StringBuilder(); - if (commandLineStyle) { - sb.append(macroOptionPrefix); - } - sb.append(kind.toString()).append(":").append(getOptionName()); - return sb.toString(); - } - - @SuppressWarnings("serial") - static final class InvalidMacroException extends RuntimeException { - InvalidMacroException(String arg0) { - super(arg0); - } + return kind.getDescriptionPrefix(commandLineStyle) + getOptionName(); } @SuppressWarnings("serial") static final class VerboseInvalidMacroException extends RuntimeException { - private final MacroOptionKind forKind; + private final OptionUtils.MacroOptionKind forKind; private final MacroOption context; VerboseInvalidMacroException(String arg0, MacroOption context) { this(arg0, null, context); } - VerboseInvalidMacroException(String arg0, MacroOptionKind forKind, MacroOption context) { + VerboseInvalidMacroException(String arg0, OptionUtils.MacroOptionKind forKind, MacroOption context) { super(arg0); this.forKind = forKind; this.context = context; @@ -218,12 +169,12 @@ boolean isEnabledFromCommandline() { } static final class Registry { - private final Map> supported = new HashMap<>(); + private final Map> supported = new HashMap<>(); private final LinkedHashSet enabled = new LinkedHashSet<>(); - private static Map> collectMacroOptions(Path rootDir) throws IOException { - Map> result = new HashMap<>(); - for (MacroOptionKind kind : MacroOptionKind.values()) { + private static Map> collectMacroOptions(Path rootDir) throws IOException { + Map> result = new HashMap<>(); + for (OptionUtils.MacroOptionKind kind : OptionUtils.MacroOptionKind.values()) { Path optionsDir = rootDir.resolve(kind.subdir); Map collectedOptions = Collections.emptyMap(); if (Files.isDirectory(optionsDir)) { @@ -238,7 +189,7 @@ private static Map> collectMacroOption } Registry() { - for (MacroOptionKind kind : MacroOptionKind.values()) { + for (OptionUtils.MacroOptionKind kind : OptionUtils.MacroOptionKind.values()) { supported.put(kind, new HashMap<>()); } } @@ -250,21 +201,21 @@ void addMacroOptionRoot(Path rootDir) { supported.get(optionKind).putAll(optionMap); }); } catch (IOException e) { - throw new InvalidMacroException("Error while discovering supported MacroOptions in " + rootDir + ": " + e.getMessage()); + throw new OptionUtils.InvalidMacroException("Error while discovering supported MacroOptions in " + rootDir + ": " + e.getMessage()); } } - Set getAvailableOptions(MacroOptionKind forKind) { + Set getAvailableOptions(OptionUtils.MacroOptionKind forKind) { return supported.get(forKind).keySet(); } - void showOptions(MacroOptionKind forKind, boolean commandLineStyle, Consumer lineOut) { + void showOptions(OptionUtils.MacroOptionKind forKind, boolean commandLineStyle, Consumer lineOut) { List optionsToShow = new ArrayList<>(); - for (MacroOptionKind kind : MacroOptionKind.values()) { + for (OptionUtils.MacroOptionKind kind : OptionUtils.MacroOptionKind.values()) { if (forKind != null && !kind.equals(forKind)) { continue; } - if (forKind == null && kind == MacroOptionKind.Macro) { + if (forKind == null && kind == OptionUtils.MacroOptionKind.Macro) { // skip non-API macro options by default continue; } @@ -272,7 +223,7 @@ void showOptions(MacroOptionKind forKind, boolean commandLineStyle, Consumer addedCheck, MacroOption context, Consumer enabler) { String specString; if (context == null) { - if (optionString.startsWith(macroOptionPrefix)) { - specString = optionString.substring(macroOptionPrefix.length()); + if (optionString.startsWith(OptionUtils.MacroOptionKind.macroOptionPrefix)) { + specString = optionString.substring(OptionUtils.MacroOptionKind.macroOptionPrefix.length()); } else { return false; } @@ -315,9 +266,9 @@ boolean enableOption(BuildConfiguration config, String optionString, HashSet getEnabledOptions(MacroOptionKind kind) { + LinkedHashSet getEnabledOptions(OptionUtils.MacroOptionKind kind) { return enabled.stream().filter(eo -> kind.equals(eo.option.kind)).collect(Collectors.toCollection(LinkedHashSet::new)); } - Stream getEnabledOptionsStream(MacroOptionKind kind, MacroOptionKind... otherKinds) { - EnumSet kindSet = EnumSet.of(kind, otherKinds); + Stream getEnabledOptionsStream(OptionUtils.MacroOptionKind kind, OptionUtils.MacroOptionKind... otherKinds) { + EnumSet kindSet = EnumSet.of(kind, otherKinds); return enabled.stream().filter(eo -> kindSet.contains(eo.option.kind)); } @@ -411,7 +362,7 @@ EnabledOption getEnabledOption(MacroOption option) { private final String optionName; private final Path optionDirectory; - final MacroOptionKind kind; + final OptionUtils.MacroOptionKind kind; private final Map properties; private static MacroOption create(Path macroOptionDirectory) { @@ -423,7 +374,7 @@ private static MacroOption create(Path macroOptionDirectory) { } private MacroOption(Path optionDirectory) { - this.kind = MacroOptionKind.fromSubdir(optionDirectory.getParent().getFileName().toString()); + this.kind = OptionUtils.MacroOptionKind.fromSubdir(optionDirectory.getParent().getFileName().toString()); this.optionName = optionDirectory.getFileName().toString(); this.optionDirectory = optionDirectory; this.properties = NativeImage.loadProperties(optionDirectory.resolve(NativeImageMetaInfWalker.nativeImagePropertiesFilename)); diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java index 7be4b92c6627..d38cd4f2f8af 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/MacroOptionHandler.java @@ -29,9 +29,9 @@ import java.util.HashSet; import com.oracle.svm.core.OS; +import com.oracle.svm.core.option.OptionUtils.InvalidMacroException; import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.driver.MacroOption.AddedTwiceException; -import com.oracle.svm.driver.MacroOption.InvalidMacroException; import com.oracle.svm.driver.MacroOption.VerboseInvalidMacroException; import com.oracle.svm.driver.NativeImage.ArgumentQueue; import com.oracle.svm.driver.NativeImage.BuildConfiguration; @@ -116,8 +116,9 @@ private void applyEnabled(MacroOption.EnabledOption enabledOption, String argume enabledOption.forEachPropertyValue(config, "JavaArgs", nativeImage::addImageBuilderJavaArgs); String origin = enabledOption.getOption().getDescription(true); + origin += "@" + enabledOption.getOption().getOptionDirectory().toUri(); if (argumentOrigin != null) { - origin += " from " + argumentOrigin; + origin += "@" + argumentOrigin; } NativeImage.NativeImageArgsProcessor args = nativeImage.new NativeImageArgsProcessor(origin); enabledOption.forEachPropertyValue(config, "Args", args); 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 bc2c934c5ec8..76d05fae9c25 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 @@ -77,11 +77,11 @@ import com.oracle.svm.core.OS; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.core.util.VMError; import com.oracle.svm.driver.MacroOption.EnabledOption; -import com.oracle.svm.driver.MacroOption.MacroOptionKind; import com.oracle.svm.driver.MacroOption.Registry; import com.oracle.svm.driver.metainf.MetaInfFileType; import com.oracle.svm.driver.metainf.NativeImageMetaInfResourceProcessor; @@ -821,7 +821,7 @@ private void completeOptionArgs() { } /* Determine if truffle is needed- any MacroOption of kind Language counts */ - enabledLanguages = optionRegistry.getEnabledOptions(MacroOptionKind.Language); + enabledLanguages = optionRegistry.getEnabledOptions(OptionUtils.MacroOptionKind.Language); /* Provide more memory for image building if we have more than one language. */ if (enabledLanguages.size() > 1) { @@ -1190,7 +1190,7 @@ private boolean shouldAddCWDToCP() { return false; } - Optional explicitMacroOption = optionRegistry.getEnabledOptions(MacroOptionKind.Macro).stream().filter(EnabledOption::isEnabledFromCommandline).findAny(); + Optional explicitMacroOption = optionRegistry.getEnabledOptions(OptionUtils.MacroOptionKind.Macro).stream().filter(EnabledOption::isEnabledFromCommandline).findAny(); /* If we have any explicit macro options, we do not put "." on classpath */ if (explicitMacroOption.isPresent()) { return false; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AbstractNativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AbstractNativeImageClassLoaderSupport.java index 0902bc345c2c..ef6c26e20125 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AbstractNativeImageClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AbstractNativeImageClassLoaderSupport.java @@ -55,6 +55,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; import org.graalvm.compiler.options.OptionValues; import com.oracle.svm.core.SubstrateOptions; @@ -68,11 +70,18 @@ public abstract class AbstractNativeImageClassLoaderSupport { final List imagecp; private final List buildcp; + private final EconomicMap> classes; + private final EconomicMap> packages; + private final EconomicSet emptySet; protected final URLClassLoader classPathClassLoader; protected AbstractNativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, String[] classpath) { + classes = EconomicMap.create(); + packages = EconomicMap.create(); + emptySet = EconomicSet.create(); + classPathClassLoader = new URLClassLoader(Util.verifyClassPathAndConvertToURLs(classpath), defaultSystemClassLoader); imagecp = Collections.unmodifiableList(Arrays.stream(classPathClassLoader.getURLs()).map(Util::urlToPath).collect(Collectors.toList())); @@ -108,7 +117,7 @@ public ClassLoader getClassLoader() { protected abstract List applicationModulePath(); - protected abstract Optional findModule(String moduleName); + protected abstract Optional findModule(String moduleName); private HostedOptionParser hostedOptionParser; private OptionValues parsedHostedOptions; @@ -132,6 +141,18 @@ public OptionValues getParsedHostedOptions() { return parsedHostedOptions; } + public EconomicSet classes(URI container) { + return classes.get(container, emptySet); + } + + public EconomicSet packages(URI container) { + return packages.get(container, emptySet); + } + + public boolean noEntryForURI(EconomicSet set) { + return set == emptySet; + } + protected abstract void processClassLoaderOptions(); public abstract void propagateQualifiedExports(String fromTargetModule, String toTargetModule); @@ -210,7 +231,8 @@ private Set getExcludeDirectories() { private void loadClassesFromPath(Path path) { if (ClasspathUtils.isJar(path)) { try { - URI jarURI = new URI("jar:" + path.toAbsolutePath().toUri()); + URI container = path.toAbsolutePath().toUri(); + URI jarURI = new URI("jar:" + container); FileSystem probeJarFileSystem; try { probeJarFileSystem = FileSystems.newFileSystem(jarURI, Collections.emptyMap()); @@ -220,7 +242,7 @@ private void loadClassesFromPath(Path path) { } if (probeJarFileSystem != null) { try (FileSystem jarFileSystem = probeJarFileSystem) { - loadClassesFromPath(jarFileSystem.getPath("/"), Collections.emptySet()); + loadClassesFromPath(container, jarFileSystem.getPath("/"), Collections.emptySet()); } } } catch (ClosedByInterruptException ignored) { @@ -229,13 +251,14 @@ private void loadClassesFromPath(Path path) { throw shouldNotReachHere(e); } } else { - loadClassesFromPath(path, excludeDirectories); + URI container = path.toUri(); + loadClassesFromPath(container, path, excludeDirectories); } } protected static final String CLASS_EXTENSION = ".class"; - private void loadClassesFromPath(Path root, Set excludes) { + private void loadClassesFromPath(URI container, Path root, Set excludes) { FileVisitor visitor = new SimpleFileVisitor<>() { private final char fileSystemSeparatorChar = root.getFileSystem().getSeparator().charAt(0); @@ -252,7 +275,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { assert !excludes.contains(file.getParent()) : "Visiting file '" + file + "' with excluded parent directory"; String fileName = root.relativize(file).toString(); if (fileName.endsWith(CLASS_EXTENSION)) { - executor.execute(() -> handleClassFileName(null, fileName, fileSystemSeparatorChar)); + executor.execute(() -> handleClassFileName(container, null, fileName, fileSystemSeparatorChar)); } return FileVisitResult.CONTINUE; } @@ -295,12 +318,32 @@ private String strippedClassFileName(String fileName) { return result.substring(0, result.length() - CLASS_EXTENSION.length()); } - protected void handleClassFileName(Object module, String fileName, char fileSystemSeparatorChar) { + protected void handleClassFileName(URI container, Object module, String fileName, char fileSystemSeparatorChar) { String strippedClassFileName = strippedClassFileName(fileName); if (strippedClassFileName.equals("module-info")) { return; } + String className = strippedClassFileName.replace(fileSystemSeparatorChar, '.'); + synchronized (classes) { + EconomicSet classNames = classes.get(container); + if (classNames == null) { + classNames = EconomicSet.create(); + classes.put(container, classNames); + } + classNames.add(className); + } + int packageSep = className.lastIndexOf('.'); + String packageName = packageSep > 0 ? className.substring(0, packageSep) : ""; + synchronized (packages) { + EconomicSet packageNames = packages.get(container); + if (packageNames == null) { + packageNames = EconomicSet.create(); + packages.put(container, packageNames); + } + packageNames.add(packageName); + } + Class clazz = null; try { clazz = imageClassLoader.forName(className, module); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConfigurationTypeResolver.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConfigurationTypeResolver.java index e4a323443b0b..b749d9bfbcb1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConfigurationTypeResolver.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConfigurationTypeResolver.java @@ -25,19 +25,16 @@ package com.oracle.svm.hosted; import com.oracle.svm.core.TypeResult; -import com.oracle.svm.core.util.json.JSONParserException; import jdk.vm.ci.meta.MetaUtil; public final class ConfigurationTypeResolver { private final String configurationType; private final ImageClassLoader classLoader; - private final boolean allowIncompleteClasspath; - public ConfigurationTypeResolver(String configurationType, ImageClassLoader classLoader, boolean allowIncompleteClasspath) { + public ConfigurationTypeResolver(String configurationType, ImageClassLoader classLoader) { this.configurationType = configurationType; this.classLoader = classLoader; - this.allowIncompleteClasspath = allowIncompleteClasspath; } public Class resolveType(String typeName) { @@ -48,16 +45,8 @@ public Class resolveType(String typeName) { } TypeResult> typeResult = classLoader.findClass(name); if (!typeResult.isPresent()) { - handleError("Could not resolve " + name + " for " + configurationType + "."); + System.err.println("Warning: Could not resolve " + name + " for " + configurationType + "."); } return typeResult.get(); } - - private void handleError(String message) { - if (allowIncompleteClasspath) { - System.err.println("Warning: " + message); - } else { - throw new JSONParserException(message + " To allow unresolvable " + configurationType + ", use option --allow-incomplete-classpath"); - } - } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java index 8e8375fa8c0f..4ad048da5bd3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FallbackFeature.java @@ -293,7 +293,6 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { public void afterAnalysis(AfterAnalysisAccess a) { if (SubstrateOptions.FallbackThreshold.getValue() == SubstrateOptions.NoFallback || NativeImageOptions.ReportUnsupportedElementsAtRuntime.getValue() || - NativeImageOptions.AllowIncompleteClasspath.getValue() || SubstrateOptions.SharedLibrary.getValue()) { /* * Any of the above ensures we unconditionally allow stand-alone image to be generated. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java index ebb8d2fb3dbf..98c3c6c6d97e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java @@ -33,6 +33,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.net.URI; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; @@ -428,7 +429,7 @@ public Optional getMainClassFromModule(Object module) { return classLoaderSupport.getMainClassFromModule(module); } - public Optional findModule(String moduleName) { + public Optional findModule(String moduleName) { return classLoaderSupport.findModule(moduleName); } @@ -443,4 +444,16 @@ public String getMainClassNotFoundErrorMessage(String className) { private static String pathsToString(List paths) { return paths.stream().map(n -> String.valueOf(n)).collect(Collectors.joining(File.pathSeparator)); } + + public EconomicSet classes(URI container) { + return classLoaderSupport.classes(container); + } + + public EconomicSet packages(URI container) { + return classLoaderSupport.packages(container); + } + + public boolean noEntryForURI(EconomicSet set) { + return classLoaderSupport.noEntryForURI(set); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeFeature.java new file mode 100644 index 000000000000..bdf6e7a17340 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeFeature.java @@ -0,0 +1,207 @@ +/* + * 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.hosted; + +import java.io.File; +import java.lang.module.ModuleFinder; +import java.net.URI; +import java.nio.file.Path; +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 java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.graalvm.collections.EconomicSet; +import org.graalvm.collections.Pair; +import org.graalvm.compiler.options.Option; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.ClassLoaderSupport; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.option.APIOption; +import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.LocatableMultiOptionValue; +import com.oracle.svm.core.option.OptionOrigin; +import com.oracle.svm.core.option.OptionUtils; +import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.jdk.ClassLoaderSupportFeatureJDK11OrLater; + +@AutomaticFeature +public final class LinkAtBuildTimeFeature implements Feature { + + static final class Options { + @APIOption(name = "link-at-build-time", defaultValue = "")// + @Option(help = "file:doc-files/LinkAtBuildTimeHelp.txt")// + public static final HostedOptionKey LinkAtBuildTime = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + + @APIOption(name = "link-at-build-time-paths")// + @Option(help = "file:doc-files/LinkAtBuildTimePathsHelp.txt")// + public static final HostedOptionKey LinkAtBuildTimePaths = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); + } + + private final String javaIdentifier = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + private final Pattern validOptionValue = Pattern.compile(javaIdentifier + "(\\." + javaIdentifier + ")*"); + + private final Set reasonCommandLine = Collections.singleton(OptionOrigin.commandLineOptionOriginSingleton); + + private final Map> requireCompletePackageOrClass = new HashMap<>(); + private final Set requireCompleteModules = new HashSet<>(); + private boolean requireCompleteAll; + + private ClassLoaderSupport classLoaderSupport; + private ImageClassLoader imageClassLoader; + private Map uriModuleMap; + + @Override + public List> getRequiredFeatures() { + return Collections.singletonList(ClassLoaderSupportFeatureJDK11OrLater.class); + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + classLoaderSupport = ImageSingletons.lookup(ClassLoaderSupport.class); + + imageClassLoader = ((FeatureImpl.AfterRegistrationAccessImpl) access).getImageClassLoader(); + uriModuleMap = ModuleFinder.of(imageClassLoader.applicationModulePath().toArray(Path[]::new)).findAll().stream() + .filter(mRef -> mRef.location().isPresent()) + .collect(Collectors.toUnmodifiableMap(mRef -> mRef.location().get(), mRef -> imageClassLoader.findModule(mRef.descriptor().name()).get())); + + /* + * SerializationBuilder.newConstructorForSerialization() creates synthetic + * jdk/internal/reflect/GeneratedSerializationConstructorAccessor* classes that do not have + * the synthetic modifier set (clazz.isSynthetic() returns false for such classes). Any + * class with package-name jdk.internal.reflect should be treated as link-at-build-time. + */ + requireCompletePackageOrClass.put("jdk.internal.reflect", null); + + Options.LinkAtBuildTime.getValue().getValuesWithOrigins().forEach(this::extractLinkAtBuildTimeOptionValue); + Options.LinkAtBuildTimePaths.getValue().getValuesWithOrigins().forEach(this::extractLinkAtBuildTimePathsOptionValue); + + ImageSingletons.add(LinkAtBuildTimeSupport.class, new LinkAtBuildTimeSupport(this)); + } + + private void extractLinkAtBuildTimeOptionValue(Pair valueOrigin) { + var value = valueOrigin.getLeft(); + OptionOrigin origin = valueOrigin.getRight(); + URI container = origin.container(); + if (value.isEmpty()) { + if (origin.commandLineLike()) { + requireCompleteAll = true; + return; + } + var originModule = uriModuleMap.get(container); + if (originModule != null) { + requireCompleteModules.add(originModule); + return; + } + throw UserError.abort("Using '%s' without args only allowed on module-path. %s not part of module-path.", + SubstrateOptionsParser.commandArgument(Options.LinkAtBuildTime, value), origin); + } else { + for (String entry : OptionUtils.resolveOptionValuesRedirection(Options.LinkAtBuildTime, value, origin)) { + if (validOptionValue.matcher(entry).matches()) { + if (!origin.commandLineLike() && !imageClassLoader.classes(container).contains(entry) && !imageClassLoader.packages(container).contains(entry)) { + throw UserError.abort("Option '%s' provided by %s contains '%s'. No such package or class name found in '%s'.", + SubstrateOptionsParser.commandArgument(Options.LinkAtBuildTime, value), origin, entry, container); + } + requireCompletePackageOrClass.computeIfAbsent(entry, unused -> new HashSet<>()).add(origin); + } else { + throw UserError.abort("Entry '%s' in option '%s' provided by %s is neither a package nor a fully qualified classname.", + entry, SubstrateOptionsParser.commandArgument(Options.LinkAtBuildTime, value), origin); + } + } + } + } + + private void extractLinkAtBuildTimePathsOptionValue(Pair valueOrigin) { + var value = valueOrigin.getLeft(); + OptionOrigin origin = valueOrigin.getRight(); + if (!origin.commandLineLike()) { + throw UserError.abort("Using '%s' is only allowed on command line.", + SubstrateOptionsParser.commandArgument(Options.LinkAtBuildTimePaths, value), origin); + } + if (value.isEmpty()) { + throw UserError.abort("Using '%s' requires directory or jar-file path arguments.", + SubstrateOptionsParser.commandArgument(Options.LinkAtBuildTimePaths, value), origin); + } + for (String pathStr : SubstrateUtil.split(value, File.pathSeparator)) { + Path path = Path.of(pathStr); + EconomicSet packages = imageClassLoader.packages(path.toAbsolutePath().normalize().toUri()); + if (imageClassLoader.noEntryForURI(packages)) { + throw UserError.abort("Option '%s' provided by %s contains entry '%s'. No such entry exists on class or module-path.", + SubstrateOptionsParser.commandArgument(Options.LinkAtBuildTimePaths, value), origin, pathStr); + } + for (String pkg : packages) { + requireCompletePackageOrClass.put(pkg, Collections.singleton(origin)); + } + } + } + + boolean linkAtBuildTime(Class clazz) { + return linkAtBuildTimeImpl(clazz) != null; + } + + @SuppressWarnings("unchecked") + String linkAtBuildTimeReason(Class clazz) { + Object reason = linkAtBuildTimeImpl(clazz); + if (reason == null) { + return null; + } + if (reason instanceof String) { + return (String) reason; + } + Set origins = (Set) reason; + return origins.stream().map(OptionOrigin::toString).collect(Collectors.joining(" and ")); + } + + private Object linkAtBuildTimeImpl(Class clazz) { + if (requireCompleteAll) { + return reasonCommandLine; + } + + if (clazz.isArray() || !classLoaderSupport.isNativeImageClassLoader(clazz.getClassLoader())) { + return "system default"; + } + assert !clazz.isPrimitive() : "Primitive classes are not loaded via NativeImageClassLoader"; + + var module = clazz.getModule(); + if (module.isNamed() && (requireCompleteModules.contains(module))) { + return module.toString(); + } + + Set origins = requireCompletePackageOrClass.get(clazz.getName()); + if (origins != null) { + return origins; + } + return requireCompletePackageOrClass.get(clazz.getPackageName()); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeSupport.java new file mode 100644 index 000000000000..403ba68be543 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeSupport.java @@ -0,0 +1,74 @@ +/* + * 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.hosted; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; + +import jdk.vm.ci.meta.ResolvedJavaType; + +public final class LinkAtBuildTimeSupport { + + private final LinkAtBuildTimeFeature feature; + + LinkAtBuildTimeSupport(LinkAtBuildTimeFeature feature) { + this.feature = feature; + } + + public static LinkAtBuildTimeSupport singleton() { + return ImageSingletons.lookup(LinkAtBuildTimeSupport.class); + } + + public boolean linkAtBuildTime(ResolvedJavaType type) { + Class clazz = ((OriginalClassProvider) type).getJavaClass(); + if (clazz == null) { + /* + * Some kind of synthetic class coming from a substitution. We assume all such classes + * are linked at build time. + */ + return true; + } + return linkAtBuildTime(clazz); + } + + public boolean linkAtBuildTime(Class clazz) { + return feature.linkAtBuildTime(clazz); + } + + public String errorMessageFor(ResolvedJavaType type) { + Class clazz = ((OriginalClassProvider) type).getJavaClass(); + if (clazz == null) { + return "This error is reported at image build time because class " + type.toJavaName(true) + " is registered for linking at image build time."; + } + return errorMessageFor(clazz); + } + + public String errorMessageFor(Class clazz) { + assert feature.linkAtBuildTime(clazz); + return "This error is reported at image build time because class " + clazz.getTypeName() + " is registered for linking at image build time by " + feature.linkAtBuildTimeReason(clazz); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java index 26ab03dc48aa..a53678ae14c7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java @@ -158,7 +158,7 @@ public void afterAnalysis(AfterAnalysisAccess access) { Set analysisReachableSyntheticModules = analysisReachableNamedModules .stream() - .filter(ModuleLayerFeatureUtils::isModuleSynthetic) + .filter(ModuleLayerFeature::isModuleSynthetic) .collect(Collectors.toSet()); Set allReachableModules = analysisReachableNamedModules @@ -251,7 +251,7 @@ private void replicateVisibilityModifications(ModuleLayer runtimeBootLayer, Imag continue; } Module runtimeTo = e2.getValue().runtimeModule; - if (ModuleLayerFeatureUtils.isModuleSynthetic(hostedFrom) || hostedFrom.canRead(hostedTo)) { + if (isModuleSynthetic(hostedFrom) || hostedFrom.canRead(hostedTo)) { moduleLayerFeatureUtils.addReads(runtimeFrom, runtimeTo); if (hostedFrom == builderModule) { for (Module appModule : applicationModules) { @@ -260,7 +260,7 @@ private void replicateVisibilityModifications(ModuleLayer runtimeBootLayer, Imag } } for (String pn : runtimeFrom.getPackages()) { - if (ModuleLayerFeatureUtils.isModuleSynthetic(hostedFrom) || hostedFrom.isOpen(pn, hostedTo)) { + if (isModuleSynthetic(hostedFrom) || hostedFrom.isOpen(pn, hostedTo)) { moduleLayerFeatureUtils.addOpens(runtimeFrom, pn, runtimeTo); if (hostedTo == builderModule) { for (Module appModule : applicationModules) { @@ -268,7 +268,7 @@ private void replicateVisibilityModifications(ModuleLayer runtimeBootLayer, Imag } } } - if (ModuleLayerFeatureUtils.isModuleSynthetic(hostedFrom) || hostedFrom.isExported(pn, hostedTo)) { + if (isModuleSynthetic(hostedFrom) || hostedFrom.isExported(pn, hostedTo)) { moduleLayerFeatureUtils.addExports(runtimeFrom, pn, runtimeTo); if (hostedTo == builderModule) { for (Module appModule : applicationModules) { @@ -284,6 +284,10 @@ private void replicateVisibilityModifications(ModuleLayer runtimeBootLayer, Imag } } + private static boolean isModuleSynthetic(Module m) { + return m.getDescriptor().modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC); + } + private static List findApplicationModules(ModuleLayer runtimeBootLayer, List applicationModulePath) { List applicationModules = new ArrayList<>(); List applicationModuleNames; @@ -420,10 +424,6 @@ private static Field findFieldByName(Field[] fields, String name) { return Arrays.stream(fields).filter(f -> f.getName().equals(name)).findAny().orElseThrow(VMError::shouldNotReachHere); } - static boolean isModuleSynthetic(Module m) { - return m.getDescriptor().modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC); - } - /** * This method creates Module instances that will populate the runtime boot module layer of * the image. This implementation is copy-pasted from Module#defineModules(Configuration, diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java index 3e6d0f93f392..07dfd5a3b54d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderSupport.java @@ -47,7 +47,7 @@ protected List applicationModulePath() { } @Override - protected Optional findModule(String moduleName) { + protected Optional findModule(String moduleName) { return Optional.empty(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java index 49a4abddaf51..14c4a831967b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageOptions.java @@ -35,7 +35,6 @@ import org.graalvm.compiler.options.OptionKey; import org.graalvm.compiler.options.OptionValues; -import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.reports.ReportUtils; import com.oracle.graal.pointsto.util.CompletionExecutor; import com.oracle.svm.core.SubstrateOptions; @@ -111,14 +110,9 @@ public class NativeImageOptions { @Option(help = "Report usage of unsupported methods and fields at run time when they are accessed the first time, instead of as an error during image building", type = User)// public static final HostedOptionKey ReportUnsupportedElementsAtRuntime = new HostedOptionKey<>(false); - @APIOption(name = "allow-incomplete-classpath")// - @Option(help = "Allow image building with an incomplete class path: report type resolution errors at run time when they are accessed the first time, instead of during image building", type = User)// - public static final HostedOptionKey AllowIncompleteClasspath = new HostedOptionKey<>(false) { - @Override - protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { - PointstoOptions.UnresolvedIsError.update(values, !newValue); - } - }; + @APIOption(name = "allow-incomplete-classpath", deprecated = "Allowing an incomplete classpath is now the default. Use --link-at-build-time to report linking errors at image build time for a class or package.")// + @Option(help = "Deprecated", type = User)// + static final HostedOptionKey AllowIncompleteClasspath = new HostedOptionKey<>(false); @SuppressWarnings("all") private static boolean areAssertionsEnabled() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index 7d1252f684d7..49cf6fd31173 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -174,7 +174,7 @@ public void afterRegistration(AfterRegistrationAccess a) { FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; imageClassLoader = access.getImageClassLoader(); ImageSingletons.add(ResourcesRegistry.class, - new ResourcesRegistryImpl(new ConfigurationTypeResolver("resource configuration", imageClassLoader, NativeImageOptions.AllowIncompleteClasspath.getValue()))); + new ResourcesRegistryImpl(new ConfigurationTypeResolver("resource configuration", imageClassLoader))); } private static ResourcesRegistryImpl resourceRegistryImpl() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index dae4db125f77..80079be95792 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -123,6 +123,7 @@ public class SVMHost extends HostVM { private final Map> forbiddenTypes; private final Platform platform; private final ClassInitializationSupport classInitializationSupport; + private final LinkAtBuildTimeSupport linkAtBuildTimeSupport; private final HostedStringDeduplication stringTable; private final UnsafeAutomaticSubstitutionProcessor automaticSubstitutions; private final SnippetReflectionProvider originalSnippetReflection; @@ -151,6 +152,7 @@ public SVMHost(OptionValues options, ClassLoader classLoader, ClassInitializatio this.forbiddenTypes = setupForbiddenTypes(options); this.automaticSubstitutions = automaticSubstitutions; this.platform = platform; + this.linkAtBuildTimeSupport = LinkAtBuildTimeSupport.singleton(); } private static Map> setupForbiddenTypes(OptionValues options) { @@ -289,7 +291,8 @@ public boolean isInitialized(AnalysisType type) { @Override public GraphBuilderConfiguration updateGraphBuilderConfiguration(GraphBuilderConfiguration config, AnalysisMethod method) { - return config.withRetainLocalVariables(retainLocalVariables()); + return config.withRetainLocalVariables(retainLocalVariables()) + .withUnresolvedIsError(linkAtBuildTimeSupport.linkAtBuildTime(method.getDeclaringClass())); } private boolean retainLocalVariables() { @@ -389,13 +392,13 @@ private DynamicHub createHub(AnalysisType type) { return dynamicHub; } - private static Object isLocalClass(Class javaClass) { + private Object isLocalClass(Class javaClass) { try { return javaClass.isLocalClass(); } catch (InternalError e) { return e; } catch (LinkageError e) { - if (NativeImageOptions.AllowIncompleteClasspath.getValue()) { + if (!linkAtBuildTimeSupport.linkAtBuildTime(javaClass)) { return e; } else { return unsupportedMethod(javaClass, "isLocalClass"); @@ -407,13 +410,13 @@ private static Object isLocalClass(Class javaClass) { * @return boolean if class is available or LinkageError if class' parents are not on the * classpath or InternalError if the class is invalid. */ - private static Object isAnonymousClass(Class javaClass) { + private Object isAnonymousClass(Class javaClass) { try { return javaClass.isAnonymousClass(); } catch (InternalError e) { return e; } catch (LinkageError e) { - if (NativeImageOptions.AllowIncompleteClasspath.getValue()) { + if (!linkAtBuildTimeSupport.linkAtBuildTime(javaClass)) { return e; } else { return unsupportedMethod(javaClass, "isAnonymousClass"); @@ -421,11 +424,9 @@ private static Object isAnonymousClass(Class javaClass) { } } - private static Object unsupportedMethod(Class javaClass, String methodName) { - String message = "Discovered a type for which " + methodName + " can't be called: " + javaClass.getTypeName() + - ". To avoid this issue at build time use the " + - SubstrateOptionsParser.commandArgument(NativeImageOptions.AllowIncompleteClasspath, "+") + - " option. The LinkageError will then be reported at run time when this method is called for the first time."; + private Object unsupportedMethod(Class javaClass, String methodName) { + String message = "Discovered a type for which " + methodName + " cannot be called: " + javaClass.getTypeName() + ". " + + linkAtBuildTimeSupport.errorMessageFor(javaClass); throw new UnsupportedFeatureException(message); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java index 2f9872d24203..07e77635581d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java @@ -61,6 +61,7 @@ import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.snippets.SnippetRuntime; import com.oracle.svm.core.util.UserError; @@ -81,7 +82,7 @@ public static void processClassInitializationOptions(ClassInitializationSupport ClassInitializationOptions.ClassInitialization.getValue().getValuesWithOrigins().forEach(entry -> { for (String info : entry.getLeft().split(",")) { boolean noMatches = Arrays.stream(InitKind.values()).noneMatch(v -> info.endsWith(v.suffix())); - String origin = entry.getRight(); + OptionOrigin origin = entry.getRight(); if (noMatches) { throw UserError.abort("Element in class initialization configuration must end in %s, %s, or %s. Found: %s (from %s)", RUN_TIME.suffix(), RERUN.suffix(), BUILD_TIME.suffix(), info, origin); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializerGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializerGraphBuilderPhase.java index d69f1a86881e..ef3f598940db 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializerGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializerGraphBuilderPhase.java @@ -54,7 +54,7 @@ protected BytecodeParser createBytecodeParser(StructuredGraph graph, BytecodePar static class ClassInitializerBytecodeParser extends SharedBytecodeParser { ClassInitializerBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) { - super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext, true, true); + super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext, true, false); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ConfigurableClassInitialization.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ConfigurableClassInitialization.java index 7555c61554cf..09e3d014b293 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ConfigurableClassInitialization.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ConfigurableClassInitialization.java @@ -50,7 +50,7 @@ import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.NativeImageOptions; +import com.oracle.svm.hosted.LinkAtBuildTimeSupport; import jdk.internal.misc.Unsafe; import jdk.vm.ci.meta.MetaAccessProvider; @@ -177,40 +177,40 @@ private InitKind ensureClassInitialized(Class clazz, boolean allowErrors) { Unsafe.getUnsafe().ensureClassInitialized(clazz); return InitKind.BUILD_TIME; } catch (NoClassDefFoundError ex) { - if (NativeImageOptions.AllowIncompleteClasspath.getValue()) { - if (!allowErrors) { - System.out.println("Warning: class initialization of class " + clazz.getTypeName() + " failed with exception " + - ex.getClass().getTypeName() + (ex.getMessage() == null ? "" : ": " + ex.getMessage()) + ". This class will be initialized at run time because option " + - SubstrateOptionsParser.commandArgument(NativeImageOptions.AllowIncompleteClasspath, "+") + " is used for image building. " + - instructionsToInitializeAtRuntime(clazz)); - } + if (allowErrors) { + return InitKind.RUN_TIME; + } else if (!LinkAtBuildTimeSupport.singleton().linkAtBuildTime(clazz)) { + System.out.println("Warning: class initialization of class " + clazz.getTypeName() + " failed with exception " + + ex.getClass().getTypeName() + (ex.getMessage() == null ? "" : ": " + ex.getMessage()) + ". This class will be initialized at run time. " + + instructionsToInitializeAtRuntime(clazz)); return InitKind.RUN_TIME; } else { - return reportInitializationError(allowErrors, clazz, ex); - + return reportInitializationError("Class initialization of " + clazz.getTypeName() + " failed. " + + LinkAtBuildTimeSupport.singleton().errorMessageFor(clazz) + " " + + instructionsToInitializeAtRuntime(clazz), clazz, ex); } } catch (Throwable t) { - return reportInitializationError(allowErrors, clazz, t); + if (allowErrors) { + return InitKind.RUN_TIME; + } else { + return reportInitializationError("Class initialization of " + clazz.getTypeName() + " failed. " + + instructionsToInitializeAtRuntime(clazz), clazz, t); + } } } - private InitKind reportInitializationError(boolean allowErrors, Class clazz, Throwable t) { - if (allowErrors) { + private InitKind reportInitializationError(String msg, Class clazz, Throwable t) { + if (unsupportedFeatures != null) { + /* + * Report an unsupported feature during static analysis, so that we can collect multiple + * error messages without aborting analysis immediately. Returning InitKind.RUN_TIME + * ensures that analysis can continue, even though eventually an error is reported (so + * no image will be created). + */ + unsupportedFeatures.addMessage(clazz.getTypeName(), null, msg, null, t); return InitKind.RUN_TIME; } else { - String msg = String.format("Class initialization of %s failed. %s", clazz.getTypeName(), instructionsToInitializeAtRuntime(clazz)); - if (unsupportedFeatures != null) { - /* - * Report an unsupported feature during static analysis, so that we can collect - * multiple error messages without aborting analysis immediately. Returning - * InitKind.RUN_TIME ensures that analysis can continue, even though eventually an - * error is reported (so no image will be created). - */ - unsupportedFeatures.addMessage(clazz.getTypeName(), null, msg, null, t); - return InitKind.RUN_TIME; - } else { - throw UserError.abort(t, "%s", msg); - } + throw UserError.abort(t, "%s", msg); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java index f7db249faa24..e876be3a1b9f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java @@ -32,6 +32,8 @@ import org.graalvm.collections.Pair; +import com.oracle.svm.core.option.OptionOrigin; + /** * The initialization kind for a class. The order of the enum values matters, {@link #max} depends * on it. @@ -62,7 +64,7 @@ String suffix() { return SEPARATOR + name().toLowerCase(); } - Consumer stringConsumer(ClassInitializationSupport support, String origin) { + Consumer stringConsumer(ClassInitializationSupport support, OptionOrigin origin) { if (this == RUN_TIME) { return name -> support.initializeAtRunTime(name, reason(origin, name)); } else if (this == RERUN) { @@ -81,9 +83,8 @@ Consumer stringConsumer(ClassInitializationSupport support, String origi } } - private static String reason(String origin, String name) { - String prefix = "from "; - return (origin == null ? prefix + "the command line" : prefix + origin) + " with '" + name + "'"; + private static String reason(OptionOrigin origin, String name) { + return "from " + origin + " with '" + name + "'"; } static Pair strip(String input) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java index 944df94ab0e3..731b2a8196b9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java @@ -53,13 +53,12 @@ import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.json.JSONParserException; import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.NativeImageOptions; public final class ConfigurationParserUtils { public static ReflectionConfigurationParser>> create(ReflectionRegistry registry, ImageClassLoader imageClassLoader) { return new ReflectionConfigurationParser<>(new ReflectionRegistryAdapter(registry, imageClassLoader), - NativeImageOptions.AllowIncompleteClasspath.getValue(), ConfigurationFiles.Options.StrictConfiguration.getValue()); + ConfigurationFiles.Options.StrictConfiguration.getValue()); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/doc-files/LinkAtBuildTimeHelp.txt b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/doc-files/LinkAtBuildTimeHelp.txt new file mode 100644 index 000000000000..2ed511ce962a --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/doc-files/LinkAtBuildTimeHelp.txt @@ -0,0 +1,19 @@ +Require types to be fully defined at image build-time. If used without args, all classes in scope of the option are required to be fully defined. + +Using --link-at-build-time without arguments is only allowed on command line or when embedded in a +native-image.properties file of some zip/jar file on the module-path (but not on class-path). + +In the module path case, the option will cause all classes of the module to be required to be +fully defined at image build-time. If used without arguments on command line all classes are +required to be fully defined at image build-time. + +Using --link-at-build-time with arguments is allowed in every scope: + + 1. On command line + 2. Embedded in a native-image.properties file of some zip/jar file on module-path + 3. Embedded in a native-image.properties file of some zip/jar file on class-path + +If the option is embedded in native-image.properties file in some zip/jar file all class-names +and package-names passed to the option have to be found in the zip/jar files the option is embedded +in. Using --link-at-build-time with arguments on command line does not have that restriction. + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/doc-files/LinkAtBuildTimePathsHelp.txt b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/doc-files/LinkAtBuildTimePathsHelp.txt new file mode 100644 index 000000000000..d8188a550ed2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/doc-files/LinkAtBuildTimePathsHelp.txt @@ -0,0 +1,11 @@ +Require all types in given class or module-path entries to be fully defined at image build-time. + +This option requires arguments that are of the same type as the +arguments passed via -p (--module-path) or -cp (--class-path): + + --link-at-build-time-paths + +The given entries are searched and all classes inside are registered as --link-at-build-time classes. + +This option is only allowed to be used on command line. I.e. the option will be rejected if it is provided +by Args of a native-image.properties file embedded in a zip/jar file. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportImplJDK11OrLater.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportFeatureJDK11OrLater.java similarity index 97% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportImplJDK11OrLater.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportFeatureJDK11OrLater.java index ac77b1d2147c..c021061f41f6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportImplJDK11OrLater.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportFeatureJDK11OrLater.java @@ -52,7 +52,7 @@ import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.util.ModuleSupport; -public final class ClassLoaderSupportImplJDK11OrLater extends ClassLoaderSupportImpl { +final class ClassLoaderSupportImplJDK11OrLater extends ClassLoaderSupportImpl { private final NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport; private final Map> packageToModules; @@ -165,7 +165,7 @@ private void addToPackageNameModules(Module moduleName, String packageName) { } @AutomaticFeature -class ClassLoaderSupportFeatureJDK11OrLater implements Feature { +public class ClassLoaderSupportFeatureJDK11OrLater implements Feature { @Override public void afterRegistration(AfterRegistrationAccess a) { FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/NativeImageClassLoaderSupportJDK11OrLater.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/NativeImageClassLoaderSupportJDK11OrLater.java index 1a02c225c494..c774451ca8af 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/NativeImageClassLoaderSupportJDK11OrLater.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/NativeImageClassLoaderSupportJDK11OrLater.java @@ -54,6 +54,7 @@ import org.graalvm.compiler.options.OptionValues; import com.oracle.svm.core.option.LocatableMultiOptionValue; +import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; @@ -285,7 +286,7 @@ static List allLayers(ModuleLayer moduleLayer) { } private Stream processOption(OptionValues parsedHostedOptions, OptionKey specificOption) { - Stream> valuesWithOrigins = specificOption.getValue(parsedHostedOptions).getValuesWithOrigins(); + Stream> valuesWithOrigins = specificOption.getValue(parsedHostedOptions).getValuesWithOrigins(); Stream parsedOptions = valuesWithOrigins.flatMap(valWithOrig -> { try { return Stream.of(asAddExportsAndOpensAndReadsFormatValue(specificOption, valWithOrig)); @@ -318,8 +319,8 @@ private AddExportsAndOpensAndReadsFormatValue(Module module, String packageName, } } - private AddExportsAndOpensAndReadsFormatValue asAddExportsAndOpensAndReadsFormatValue(OptionKey option, Pair valueOrigin) { - String optionOrigin = valueOrigin.getRight(); + private AddExportsAndOpensAndReadsFormatValue asAddExportsAndOpensAndReadsFormatValue(OptionKey option, Pair valueOrigin) { + OptionOrigin optionOrigin = valueOrigin.getRight(); String optionValue = valueOrigin.getLeft(); boolean reads = option.equals(NativeImageClassLoaderOptions.AddReads); @@ -361,7 +362,7 @@ private AddExportsAndOpensAndReadsFormatValue asAddExportsAndOpensAndReadsFormat return new AddExportsAndOpensAndReadsFormatValue(module, packageName, targetModules); } - private static UserError.UserException userErrorAddExportsAndOpensAndReads(OptionKey option, String origin, String value, String detailMessage) { + private static UserError.UserException userErrorAddExportsAndOpensAndReads(OptionKey option, OptionOrigin origin, String value, String detailMessage) { Objects.requireNonNull(detailMessage, "missing detailMessage"); return UserError.abort("Invalid option %s provided by %s.%s", SubstrateOptionsParser.commandArgument(option, value), origin, detailMessage); } @@ -429,7 +430,7 @@ private void initModule(ModuleReference moduleReference) { Module module = optionalModule.get(); moduleReader.list().forEach(moduleResource -> { if (moduleResource.endsWith(CLASS_EXTENSION)) { - executor.execute(() -> handleClassFileName(module, moduleResource, '/')); + executor.execute(() -> handleClassFileName(moduleReference.location().get(), module, moduleResource, '/')); } }); } catch (IOException e) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index d23d4fe15bcb..ee291935020d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -51,12 +51,11 @@ import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.nodes.SubstrateMethodCallTargetNode; -import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.UserError.UserException; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ExceptionSynthesizer; -import com.oracle.svm.hosted.NativeImageOptions; +import com.oracle.svm.hosted.LinkAtBuildTimeSupport; import jdk.vm.ci.meta.JavaField; import jdk.vm.ci.meta.JavaKind; @@ -84,18 +83,18 @@ protected void run(StructuredGraph graph) { public abstract static class SharedBytecodeParser extends BytecodeParser { private final boolean explicitExceptionEdges; - private final boolean allowIncompleteClassPath; + private final boolean linkAtBuildTime; protected SharedBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext, boolean explicitExceptionEdges) { - this(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext, explicitExceptionEdges, NativeImageOptions.AllowIncompleteClasspath.getValue()); + this(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext, explicitExceptionEdges, LinkAtBuildTimeSupport.singleton().linkAtBuildTime(method.getDeclaringClass())); } protected SharedBytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, - IntrinsicContext intrinsicContext, boolean explicitExceptionEdges, boolean allowIncompleteClasspath) { + IntrinsicContext intrinsicContext, boolean explicitExceptionEdges, boolean linkAtBuildTime) { super(graphBuilderInstance, graph, parent, method, entryBCI, intrinsicContext); this.explicitExceptionEdges = explicitExceptionEdges; - this.allowIncompleteClassPath = allowIncompleteClasspath; + this.linkAtBuildTime = linkAtBuildTime; } @Override @@ -183,16 +182,15 @@ protected JavaType maybeEagerlyResolve(JavaType type, ResolvedJavaType accessing @Override protected void handleIllegalNewInstance(JavaType type) { /* - * If --allow-incomplete-classpath is set defer the error reporting to runtime, - * otherwise report the error during image building. + * If linkAtBuildTime was set for type, report the error during image building, + * otherwise defer the error reporting to runtime. */ - if (allowIncompleteClassPath) { - ExceptionSynthesizer.throwException(this, InstantiationError.class, type.toJavaName()); - } else { - String message = "Cannot instantiate " + type.toJavaName() + - ". To diagnose the issue you can use the " + allowIncompleteClassPathOption() + - " option. The instantiation error is then reported at run time."; + if (linkAtBuildTime) { + String message = "Cannot instantiate " + type.toJavaName() + ". " + + LinkAtBuildTimeSupport.singleton().errorMessageFor(method.getDeclaringClass()); throw new TypeInstantiationException(message); + } else { + ExceptionSynthesizer.throwException(this, InstantiationError.class, type.toJavaName()); } } @@ -248,13 +246,13 @@ protected void handleUnresolvedInvoke(JavaMethod javaMethod, InvokeKind invokeKi private void handleUnresolvedType(JavaType type) { /* - * If --allow-incomplete-classpath is set defer the error reporting to runtime, - * otherwise report the error during image building. + * If linkAtBuildTime was set for type, report the error during image building, + * otherwise defer the error reporting to runtime. */ - if (allowIncompleteClassPath) { - ExceptionSynthesizer.throwException(this, NoClassDefFoundError.class, type.toJavaName()); - } else { + if (linkAtBuildTime) { reportUnresolvedElement("type", type.toJavaName()); + } else { + ExceptionSynthesizer.throwException(this, NoClassDefFoundError.class, type.toJavaName()); } } @@ -265,13 +263,13 @@ private void handleUnresolvedField(JavaField field) { handleUnresolvedType(declaringClass); } else { /* - * If --allow-incomplete-classpath is set defer the error reporting to runtime, - * otherwise report the error during image building. + * If linkAtBuildTime was set for type, report the error during image building, + * otherwise defer the error reporting to runtime. */ - if (allowIncompleteClassPath) { - ExceptionSynthesizer.throwException(this, NoSuchFieldError.class, field.format("%H.%n")); - } else { + if (linkAtBuildTime) { reportUnresolvedElement("field", field.format("%H.%n")); + } else { + ExceptionSynthesizer.throwException(this, NoSuchFieldError.class, field.format("%H.%n")); } } } @@ -283,28 +281,23 @@ private void handleUnresolvedMethod(JavaMethod javaMethod) { handleUnresolvedType(declaringClass); } else { /* - * If --allow-incomplete-classpath is set defer the error reporting to runtime, - * otherwise report the error during image building. + * If linkAtBuildTime was set for type, report the error during image building, + * otherwise defer the error reporting to runtime. */ - if (allowIncompleteClassPath) { - ExceptionSynthesizer.throwException(this, NoSuchMethodError.class, javaMethod.format("%H.%n(%P)")); - } else { + if (linkAtBuildTime) { reportUnresolvedElement("method", javaMethod.format("%H.%n(%P)")); + } else { + ExceptionSynthesizer.throwException(this, NoSuchMethodError.class, javaMethod.format("%H.%n(%P)")); } } } - private static void reportUnresolvedElement(String elementKind, String elementAsString) { - String message = "Discovered unresolved " + elementKind + " during parsing: " + elementAsString + - ". To diagnose the issue you can use the " + allowIncompleteClassPathOption() + - " option. The missing " + elementKind + " is then reported at run time when it is accessed the first time."; + private void reportUnresolvedElement(String elementKind, String elementAsString) { + String message = "Discovered unresolved " + elementKind + " during parsing: " + elementAsString + ". " + + LinkAtBuildTimeSupport.singleton().errorMessageFor(method.getDeclaringClass()); throw new UnresolvedElementException(message); } - private static String allowIncompleteClassPathOption() { - return SubstrateOptionsParser.commandArgument(NativeImageOptions.AllowIncompleteClasspath, "+"); - } - @Override protected void emitCheckForInvokeSuperSpecial(ValueNode[] args) { /* Not implemented in SVM (GR-4854) */ diff --git a/substratevm/src/com.oracle.svm.junit/resources/META-INF/native-image/org.graalvm.nativeimage.junitsupport/com.oracle.svm.junit/native-image.properties b/substratevm/src/com.oracle.svm.junit/resources/META-INF/native-image/org.graalvm.nativeimage.junitsupport/com.oracle.svm.junit/native-image.properties index 14446be07331..d06f997c45b8 100644 --- a/substratevm/src/com.oracle.svm.junit/resources/META-INF/native-image/org.graalvm.nativeimage.junitsupport/com.oracle.svm.junit/native-image.properties +++ b/substratevm/src/com.oracle.svm.junit/resources/META-INF/native-image/org.graalvm.nativeimage.junitsupport/com.oracle.svm.junit/native-image.properties @@ -1 +1,2 @@ -Args = --add-reads=org.graalvm.nativeimage.junitsupport=ALL-UNNAMED +Args = --add-reads=org.graalvm.nativeimage.junitsupport=ALL-UNNAMED \ + --link-at-build-time=com.oracle.svm.junit \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java index 0038bd48f8f4..488832341a76 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java @@ -40,7 +40,6 @@ import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ReflectionUtil; @@ -100,11 +99,7 @@ public void addProxyClass(Class... interfaces) { try { clazz = getJdkProxyClass(classLoader, intfs); } catch (Throwable e) { - if (NativeImageOptions.AllowIncompleteClasspath.getValue()) { - return e; - } else { - throw e; - } + return e; } /* diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java index e25e8d11c795..c265f3d901c4 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java @@ -41,7 +41,6 @@ import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.hosted.config.ConfigurationParserUtils; import com.oracle.svm.reflect.hosted.ReflectionFeature; import com.oracle.svm.reflect.proxy.DynamicProxySupport; @@ -63,7 +62,7 @@ public void duringSetup(DuringSetupAccess a) { ImageClassLoader imageClassLoader = access.getImageClassLoader(); DynamicProxySupport dynamicProxySupport = new DynamicProxySupport(imageClassLoader.getClassLoader()); ImageSingletons.add(DynamicProxyRegistry.class, dynamicProxySupport); - ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("resource configuration", imageClassLoader, NativeImageOptions.AllowIncompleteClasspath.getValue()); + ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("resource configuration", imageClassLoader); ProxyRegistry proxyRegistry = new ProxyRegistry(typeResolver, dynamicProxySupport, imageClassLoader); ImageSingletons.add(ProxyRegistry.class, proxyRegistry); ProxyConfigurationParser parser = new ProxyConfigurationParser(proxyRegistry, ConfigurationFiles.Options.StrictConfiguration.getValue()); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java index 90d1f738b42c..3f17f3fcd21e 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java @@ -49,15 +49,6 @@ import java.util.Map; import java.util.Set; -import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; -import com.oracle.graal.pointsto.util.GraalAccess; -import jdk.vm.ci.hotspot.HotSpotObjectConstant; -import jdk.vm.ci.meta.Constant; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; import org.graalvm.compiler.debug.DebugContext; import org.graalvm.compiler.graph.iterators.NodeIterable; import org.graalvm.compiler.java.GraphBuilderPhase; @@ -75,6 +66,8 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; +import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; +import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; @@ -88,7 +81,6 @@ import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.hosted.config.ConfigurationParserUtils; import com.oracle.svm.reflect.hosted.ReflectionFeature; import com.oracle.svm.reflect.serialize.SerializationRegistry; @@ -96,6 +88,13 @@ import com.oracle.svm.util.ReflectionUtil; import jdk.internal.reflect.ReflectionFactory; +import jdk.vm.ci.hotspot.HotSpotObjectConstant; +import jdk.vm.ci.meta.Constant; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; @AutomaticFeature public class SerializationFeature implements Feature { @@ -112,7 +111,7 @@ public List> getRequiredFeatures() { public void duringSetup(DuringSetupAccess a) { FeatureImpl.DuringSetupAccessImpl access = (FeatureImpl.DuringSetupAccessImpl) a; ImageClassLoader imageClassLoader = access.getImageClassLoader(); - ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("serialization configuration", imageClassLoader, NativeImageOptions.AllowIncompleteClasspath.getValue()); + ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("serialization configuration", imageClassLoader); SerializationDenyRegistry serializationDenyRegistry = new SerializationDenyRegistry(typeResolver); serializationBuilder = new SerializationBuilder(serializationDenyRegistry, access, typeResolver); ImageSingletons.add(RuntimeSerializationSupport.class, serializationBuilder); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java index d9345e90d191..f56e3a90520f 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java @@ -41,9 +41,8 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.NativeImageOptions; +import com.oracle.svm.hosted.LinkAtBuildTimeSupport; import com.oracle.svm.reflect.hosted.ReflectionObjectReplacer; import jdk.vm.ci.meta.MetaAccessProvider; @@ -253,7 +252,7 @@ public RecomputeFieldValue.ValueAvailability valueAvailability() { @Override public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver) { Executable executable = (Executable) receiver; - return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedReceiverType, receiver); + return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedReceiverType, executable); } } @@ -266,7 +265,7 @@ public RecomputeFieldValue.ValueAvailability valueAvailability() { @Override public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver) { Executable executable = (Executable) receiver; - return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedParameterTypes, receiver); + return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedParameterTypes, executable); } } @@ -279,7 +278,7 @@ public RecomputeFieldValue.ValueAvailability valueAvailability() { @Override public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver) { Executable executable = (Executable) receiver; - return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedReturnType, receiver); + return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedReturnType, executable); } } @@ -292,24 +291,22 @@ public RecomputeFieldValue.ValueAvailability valueAvailability() { @Override public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver) { Executable executable = (Executable) receiver; - return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedExceptionTypes, receiver); + return AnnotatedTypeEncoder.encodeAnnotationTypes(executable::getAnnotatedExceptionTypes, executable); } } private static final class AnnotatedTypeEncoder { - static Object encodeAnnotationTypes(Supplier supplier, Object receiver) { + static Object encodeAnnotationTypes(Supplier supplier, Executable executable) { try { return supplier.get(); } catch (InternalError e) { return e; } catch (LinkageError e) { - if (NativeImageOptions.AllowIncompleteClasspath.getValue()) { + if (!LinkAtBuildTimeSupport.singleton().linkAtBuildTime(executable.getDeclaringClass())) { return e; } - Executable culprit = (Executable) receiver; - String message = "Encountered an error while processing annotated types for type: " + culprit.getDeclaringClass().getName() + ", executable: " + culprit.getName() + ". " + - "To avoid the issue at build time, use " + SubstrateOptionsParser.commandArgument(NativeImageOptions.AllowIncompleteClasspath, "+") + ". " + - "The error is then reported at runtime when these annotated types are first accessed."; + String message = "Encountered an error while processing annotated types for type: " + executable.getDeclaringClass().getName() + ", executable: " + executable.getName() + ". " + + LinkAtBuildTimeSupport.singleton().errorMessageFor(executable.getDeclaringClass()); throw new UnsupportedOperationException(message, e); } } diff --git a/vm/mx.vm/mx_vm.py b/vm/mx.vm/mx_vm.py index d7082f2c0007..ffe6dbfcb854 100644 --- a/vm/mx.vm/mx_vm.py +++ b/vm/mx.vm/mx_vm.py @@ -64,6 +64,7 @@ ], dir_jars=True, main_class="org.graalvm.component.installer.ComponentInstaller", + link_at_build_time=False, build_args=[], # Please see META-INF/native-image in the project for custom build options for native-image is_sdk_launcher=True,