diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationSupport.java new file mode 100644 index 0000000000000..009389c4cc95b --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationSupport.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, 2023, 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; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.option.OptionClassFilter; + +public class MissingRegistrationSupport { + + private final OptionClassFilter classFilter; + + @Platforms(Platform.HOSTED_ONLY.class) + public MissingRegistrationSupport(OptionClassFilter classFilter) { + this.classFilter = classFilter; + } + + @Fold + public static MissingRegistrationSupport singleton() { + return ImageSingletons.lookup(MissingRegistrationSupport.class); + } + + public boolean reportMissingRegistrationErrors(StackTraceElement responsibleClass) { + return classFilter.isIncluded(responsibleClass.getModuleName(), getPackageName(responsibleClass.getClassName()), responsibleClass.getClassName()) != null; + } + + private static String getPackageName(String className) { + int lastDot = className.lastIndexOf('.'); + return (lastDot != -1) ? className.substring(0, lastDot) : ""; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 1ae2dcd9aa7e8..4d5dfeaff7357 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -906,7 +906,10 @@ public Boolean getValueOrDefault(UnmodifiableEconomicMap, Object> v @Option(help = "Instead of abort, only warn if image builder classes are found on the image class-path.", type = OptionType.Debug)// public static final HostedOptionKey AllowDeprecatedBuilderClassesOnImageClasspath = new HostedOptionKey<>(false); - @Option(help = "Throw Native Image-specific exceptions when encountering an unregistered reflection call.", type = OptionType.User)// - public static final HostedOptionKey ThrowMissingRegistrationErrors = new HostedOptionKey<>(false); + @Option(help = "file:doc-files/MissingRegistrationHelp.txt")// + public static final HostedOptionKey ThrowMissingRegistrationErrors = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); + + @Option(help = "file:doc-files/MissingRegistrationPathsHelp.txt")// + public static final HostedOptionKey ThrowMissingRegistrationErrorsPaths = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/MissingRegistrationHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/MissingRegistrationHelp.txt new file mode 100644 index 0000000000000..baad25ac45e25 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/MissingRegistrationHelp.txt @@ -0,0 +1,19 @@ +Throw Native Image-specific errors when trying to access an element that has not been registered. +This can happen on reflection and serialization queries, and resource access. +If used without args, the errors will be thrown when calling the corresponding query from any class in scope of the option. + +Using -H:ThrowMissingRegistrationErrors 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 trigger missing registration errors. +If used without arguments on command line all classes will trigger missing registration errors. + +Using -H:ThrowMissingRegistrationErrors 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 -H:ThrowMissingRegistrationErrors with arguments on command line does not have that restriction. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/MissingRegistrationPathsHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/MissingRegistrationPathsHelp.txt new file mode 100644 index 0000000000000..9d7022b9f8859 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/MissingRegistrationPathsHelp.txt @@ -0,0 +1,11 @@ +Trigger missing registration errors from all types in the given class or module-path entries. + +This option requires arguments that are of the same type as the +arguments passed via -p (--module-path) or -cp (--class-path): + + -H:ThrowMissingRegistrationErrorsPaths + +The given entries are searched and all classes inside are registered as -H:ThrowMissingRegistrationErrorsPaths 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.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index 667726526b6ba..b351a627b4796 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -97,6 +97,7 @@ import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.snippets.SnippetRuntime; import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor; import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; @@ -306,6 +307,9 @@ private static void instanceHubErrorStub(DynamicHub hub) throws InstantiationExc } else if (!hub.isInstanceClass() || LayoutEncoding.isSpecial(hub.getLayoutEncoding())) { throw new InstantiationException("Can only allocate instance objects for concrete classes."); } else if (!hub.isInstantiated()) { + if (MissingReflectionRegistrationUtils.throwMissingRegistrationErrors()) { + MissingReflectionRegistrationUtils.forClass(hub.getTypeName()); + } throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." + " Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + "."); } else if (LayoutEncoding.isHybrid(hub.getLayoutEncoding())) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionClassFilter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionClassFilter.java new file mode 100644 index 0000000000000..1b49242eb0e90 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/OptionClassFilter.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023, 2023, 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.util.Collections; +import java.util.Map; +import java.util.Set; + +public class OptionClassFilter { + private final Set reasonCommandLine = Collections.singleton(OptionOrigin.commandLineOptionOriginSingleton); + + private final Map> requireCompletePackageOrClass; + private final Set requireCompleteModules; + private boolean requireCompleteAll; + + public OptionClassFilter(Map> requireCompletePackageOrClass, Set requireCompleteModules, boolean requireCompleteAll) { + this.requireCompletePackageOrClass = requireCompletePackageOrClass; + this.requireCompleteModules = requireCompleteModules; + this.requireCompleteAll = requireCompleteAll; + } + + public Object isIncluded(Class clazz) { + Module module = clazz.getModule(); + return isIncluded(module.isNamed() ? module.getName() : null, clazz.getPackageName(), clazz.getName()); + } + + public Object isIncluded(String moduleName, String packageName, String className) { + if (requireCompleteAll) { + return reasonCommandLine; + } + + if (moduleName != null) { + for (Module module : requireCompleteModules) { + if (module.getName().equals(moduleName)) { + return module.toString(); + } + } + } + + Set origins = requireCompletePackageOrClass.get(className); + if (origins != null) { + return origins; + } + return requireCompletePackageOrClass.get(packageName); + } + + public void addPackageOrClass(String packageOrClass, Set reason) { + requireCompletePackageOrClass.put(packageOrClass, reason); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java index b35137d46fcc4..79fc405153740 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java @@ -25,9 +25,11 @@ package com.oracle.svm.core.reflect; import java.io.Serial; +import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Map; import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; @@ -35,7 +37,9 @@ import org.graalvm.compiler.options.Option; import org.graalvm.nativeimage.MissingReflectionRegistrationError; +import com.oracle.svm.core.MissingRegistrationSupport; import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.ExitStatus; @@ -57,7 +61,7 @@ public enum ReportingMode { } public static boolean throwMissingRegistrationErrors() { - return SubstrateOptions.ThrowMissingRegistrationErrors.getValue(); + return SubstrateOptions.ThrowMissingRegistrationErrors.hasBeenSet(); } public static ReportingMode missingRegistrationReportingMode() { @@ -117,6 +121,10 @@ private static String errorMessage(String failedAction, String elementDescriptor private static final Set seenOutputs = Options.MissingRegistrationReportingMode.getValue() == ReportingMode.Warn ? ConcurrentHashMap.newKeySet() : null; private static void report(MissingReflectionRegistrationError exception) { + StackTraceElement responsibleClass = getResponsibleClass(exception); + if (responsibleClass != null && !MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(responsibleClass)) { + return; + } switch (missingRegistrationReportingMode()) { case Throw -> { throw exception; @@ -175,6 +183,60 @@ private static void printLine(StringBuilder sb, Object object) { sb.append(" ").append(object).append(System.lineSeparator()); } + /* + * This is a list of all public JDK methods that end up potentially throwing missing + * registration errors. This should be implemented using wrapping substitutions once they are + * available. + */ + private static final Map> reflectionEntryPoints = Map.of( + Class.class.getTypeName(), Set.of( + "forName", + "getClasses", + "getDeclaredClasses", + "getConstructor", + "getConstructors", + "getDeclaredConstructor", + "getDeclaredConstructors", + "getField", + "getFields", + "getDeclaredField", + "getDeclaredFields", + "getMethod", + "getMethods", + "getDeclaredMethod", + "getDeclaredMethods", + "getNestMembers", + "getPermittedSubclasses", + "getRecordComponents", + "getSigners", + "arrayType", + "newInstance"), + Method.class.getTypeName(), Set.of("invoke"), + Constructor.class.getTypeName(), Set.of("newInstance"), + "java.lang.reflect.ReflectAccess", Set.of("newInstance"), + "jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"), + sun.misc.Unsafe.class.getName(), Set.of("allocateInstance"), + /* For jdk.internal.misc.Unsafe.allocateInstance(), which is intrinsified */ + SubstrateAllocationSnippets.class.getName(), Set.of("instanceHubErrorStub")); + + private static StackTraceElement getResponsibleClass(Throwable t) { + StackTraceElement[] stackTrace = t.getStackTrace(); + boolean returnNext = false; + for (StackTraceElement stackTraceElement : stackTrace) { + if (reflectionEntryPoints.getOrDefault(stackTraceElement.getClassName(), Set.of()).contains(stackTraceElement.getMethodName())) { + /* + * Multiple functions with the same name can be called in succession, like the + * Class.forName caller-sensitive adapters. We skip those until we find a method + * that is not a monitored reflection entry point. + */ + returnNext = true; + } else if (returnNext) { + return stackTraceElement; + } + } + return null; + } + public static final class ExitException extends Error { @Serial// private static final long serialVersionUID = -3638940737396726143L; 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 index 817862e35d316..ef597f5953391 100644 --- 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 @@ -25,33 +25,19 @@ 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.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 com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.svm.core.ClassLoaderSupport; -import com.oracle.svm.core.SubstrateUtil; 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.OptionClassFilter; 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 jdk.vm.ci.meta.ResolvedJavaType; @@ -67,26 +53,12 @@ static final class Options { public static final HostedOptionKey LinkAtBuildTimePaths = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); } - 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 final ClassLoaderSupport classLoaderSupport; - private final ImageClassLoader imageClassLoader; - private final Map uriModuleMap; + private final OptionClassFilter classFilter; public LinkAtBuildTimeSupport(ImageClassLoader imageClassLoader, ClassLoaderSupport classLoaderSupport) { this.classLoaderSupport = classLoaderSupport; - this.imageClassLoader = imageClassLoader; - - 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())); + this.classFilter = OptionClassFilterBuilder.createFilter(imageClassLoader, Options.LinkAtBuildTime, Options.LinkAtBuildTimePaths); /* * SerializationBuilder.newConstructorForSerialization() creates synthetic @@ -94,72 +66,13 @@ public LinkAtBuildTimeSupport(ImageClassLoader imageClassLoader, ClassLoaderSupp * 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); + classFilter.addPackageOrClass("jdk.internal.reflect", null); } public static LinkAtBuildTimeSupport singleton() { return ImageSingletons.lookup(LinkAtBuildTimeSupport.class); } - 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)); - } - } - } - public boolean linkAtBuildTime(ResolvedJavaType type) { Class clazz = ((OriginalClassProvider) type).getJavaClass(); if (clazz == null) { @@ -173,29 +86,15 @@ public boolean linkAtBuildTime(ResolvedJavaType type) { } public boolean linkAtBuildTime(Class clazz) { - return linkAtBuildTimeImpl(clazz) != null; + return isIncluded(clazz) != null; } - private Object linkAtBuildTimeImpl(Class clazz) { - if (requireCompleteAll) { - return reasonCommandLine; - } - + private Object isIncluded(Class clazz) { 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()); + return classFilter.isIncluded(clazz); } public String errorMessageFor(ResolvedJavaType type) { @@ -213,7 +112,7 @@ public String errorMessageFor(Class clazz) { @SuppressWarnings("unchecked") private String linkAtBuildTimeReason(Class clazz) { - Object reason = linkAtBuildTimeImpl(clazz); + Object reason = isIncluded(clazz); if (reason == null) { return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 6223703e5c416..7b793b650aa0c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -175,6 +175,7 @@ import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.JavaMainWrapper.JavaMainSupport; import com.oracle.svm.core.LinkerInvocation; +import com.oracle.svm.core.MissingRegistrationSupport; import com.oracle.svm.core.OS; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.SubstrateOptions; @@ -218,6 +219,7 @@ import com.oracle.svm.core.image.ImageHeapLayouter; import com.oracle.svm.core.jdk.ServiceCatalogSupport; import com.oracle.svm.core.option.HostedOptionValues; +import com.oracle.svm.core.option.OptionClassFilter; import com.oracle.svm.core.option.RuntimeOptionValues; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.riscv64.RISCV64CPUFeatureAccess; @@ -864,6 +866,10 @@ protected void setupNativeImage(OptionValues options, Map baseOption; + private final HostedOptionKey pathsOption; + private final Map uriModuleMap; + + protected final Map> requireCompletePackageOrClass = new HashMap<>(); + private final Set requireCompleteModules = new HashSet<>(); + private boolean requireCompleteAll; + + public static OptionClassFilter createFilter(ImageClassLoader imageClassLoader, HostedOptionKey baseOption, + HostedOptionKey pathsOption) { + OptionClassFilterBuilder builder = new OptionClassFilterBuilder(imageClassLoader, baseOption, pathsOption); + + baseOption.getValue().getValuesWithOrigins().forEach(builder::extractBaseOptionValue); + pathsOption.getValue().getValuesWithOrigins().forEach(builder::extractPathsOptionValue); + + return builder.build(); + } + + public OptionClassFilterBuilder(ImageClassLoader imageClassLoader, HostedOptionKey baseOption, + HostedOptionKey pathsOption) { + this.imageClassLoader = imageClassLoader; + this.baseOption = baseOption; + this.pathsOption = pathsOption; + + 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())); + } + + private void extractBaseOptionValue(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(baseOption, value), origin); + } else { + for (String entry : OptionUtils.resolveOptionValuesRedirection(baseOption, 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(baseOption, 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(baseOption, value), origin); + } + } + } + } + + private void extractPathsOptionValue(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(pathsOption, value), origin); + } + if (value.isEmpty()) { + throw UserError.abort("Using '%s' requires directory or jar-file path arguments.", + SubstrateOptionsParser.commandArgument(pathsOption, 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(pathsOption, value), origin, pathStr); + } + for (String pkg : packages) { + requireCompletePackageOrClass.put(pkg, Collections.singleton(origin)); + } + } + } + + private OptionClassFilter build() { + return new OptionClassFilter(requireCompletePackageOrClass, requireCompleteModules, requireCompleteAll); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java index 5e156abbea252..70f1e3d09ad14 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java @@ -535,7 +535,7 @@ private static void registerForSerialization(Class serializationTargetClass) * serialization class consistency, so need to register all constructors, methods and * fields. */ - if (SubstrateOptions.ThrowMissingRegistrationErrors.getValue()) { + if (SubstrateOptions.ThrowMissingRegistrationErrors.hasBeenSet()) { RuntimeReflection.registerAsQueried(serializationTargetClass.getDeclaredConstructors()); RuntimeReflection.registerAsQueried(serializationTargetClass.getDeclaredMethods()); } else {