diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java index 07ef05fedfff..a4b5adb949f3 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -221,7 +221,7 @@ public static void registerAllDeclaredConstructors(Class declaringClass) { * @since 23.0 */ public static void registerAllFields(Class declaringClass) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllFields(ConfigurationCondition.alwaysTrue(), declaringClass); } /** @@ -231,7 +231,7 @@ public static void registerAllFields(Class declaringClass) { * @since 23.0 */ public static void registerAllDeclaredFields(Class declaringClass) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFieldsQuery(ConfigurationCondition.alwaysTrue(), declaringClass); + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerAllDeclaredFields(ConfigurationCondition.alwaysTrue(), declaringClass); } /** diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java index 68e78b2c975b..ae5d3c78c48a 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -40,15 +40,23 @@ */ package org.graalvm.nativeimage.impl; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.graalvm.nativeimage.hosted.RuntimeJNIAccess; +import org.graalvm.nativeimage.hosted.RuntimeProxyCreation; + public interface RuntimeReflectionSupport extends ReflectionRegistry { // needed as reflection-specific ImageSingletons key void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); - void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz); + void registerAllFields(ConfigurationCondition condition, Class clazz); - void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz); + void registerAllDeclaredFields(ConfigurationCondition condition, Class clazz); void registerAllConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz); @@ -67,4 +75,49 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry { void registerAllSignersQuery(ConfigurationCondition condition, Class clazz); void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t); + + default void registerClassFully(ConfigurationCondition condition, Class clazz) { + boolean unsafeAllocated = !(clazz.isArray() || clazz.isInterface() || clazz.isPrimitive() || Modifier.isAbstract(clazz.getModifiers())); + register(condition, unsafeAllocated, clazz); + + // GR-62143 Register all fields is very slow. + // registerAllDeclaredFields(condition, clazz); + // registerAllFields(condition, clazz); + registerAllDeclaredMethodsQuery(condition, false, clazz); + registerAllMethodsQuery(condition, false, clazz); + registerAllDeclaredConstructorsQuery(condition, false, clazz); + registerAllConstructorsQuery(condition, false, clazz); + registerAllClassesQuery(condition, clazz); + registerAllDeclaredClassesQuery(condition, clazz); + registerAllNestMembersQuery(condition, clazz); + registerAllPermittedSubclassesQuery(condition, clazz); + registerAllRecordComponentsQuery(condition, clazz); + registerAllSignersQuery(condition, clazz); + + /* Register every single-interface proxy */ + // GR-62293 can't register proxies from jdk modules. + if (clazz.getModule() == null && clazz.isInterface()) { + RuntimeProxyCreation.register(clazz); + } + + RuntimeJNIAccess.register(clazz); + try { + for (Method declaredMethod : clazz.getDeclaredMethods()) { + RuntimeJNIAccess.register(declaredMethod); + } + for (Constructor declaredConstructor : clazz.getDeclaredConstructors()) { + RuntimeJNIAccess.register(declaredConstructor); + } + for (Field declaredField : clazz.getDeclaredFields()) { + RuntimeJNIAccess.register(declaredField); + // GR-62143 Registering all fields is very slow. + // RuntimeReflection.register(declaredField); + } + } catch (LinkageError e) { + /* If we can't link we can not register for JNI */ + } + + // GR-62143 Registering all fields is very slow. + // RuntimeSerialization.register(clazz); + } } diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index bd900af80a42..cb232bf1d334 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -4,11 +4,12 @@ This changelog summarizes major changes to GraalVM Native Image. ## GraalVM for JDK 25 * (GR-58668) Enabled [Whole-Program Sparse Conditional Constant Propagation (WP-SCCP)](https://github.com/oracle/graal/pull/9821) by default, improving the precision of points-to analysis in Native Image. This optimization enhances static analysis accuracy and scalability, potentially reducing the size of the final native binary. -* (GR-59313) Deprecated class-level metadata extraction using `native-image-inspect` and removed option `DumpMethodsData`. Use class-level SBOMs instead by passing `--enable-sbom=class-level,export` to the `native-image` builder. The default value of option `IncludeMethodData` was changed to `false`. +* (GR-59313) Deprecated class-level metadata extraction using `native-image-inspect` and removed option `DumpMethodsData`. Use class-level SBOMs instead by passing `--enable-sbom=class-level,export` to the `native-image` builder. The default value of option `IncludeMethodData` was changed to `false`. * (GR-52400) The build process now uses 85% of system memory in containers and CI environments. Otherwise, it tries to only use available memory. If less than 8GB of memory are available, it falls back to 85% of system memory. The reason for the selected memory limit is now also shown in the build resources section of the build output. * (GR-59864) Added JVM version check to the Native Image agent. The agent will abort execution if the JVM major version does not match the version it was built with, and warn if the full JVM version is different. * (GR-59135) Verify if hosted options passed to `native-image` exist prior to starting the builder. Provide suggestions how to fix unknown options early on. * (GR-61492) The experimental JDWP option is now present in standard GraalVM builds. +* (GR-54953) Add the experimental option `-H:Preserve` that makes the program work correctly without providing reachability metadata. Correctness is achieved by preserving all classes, resources, and reflection metadata in the image. Usage: `-H:Preserve=[all|none|module=|package=|package=|path=][,...]`. ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java index 74759c00ba71..9cd7db9945e0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ClassInclusionPolicy.java @@ -56,12 +56,28 @@ public void setBigBang(BigBang bb) { this.bb = bb; } + public static boolean isClassIncludedBase(Class cls) { + if (Feature.class.isAssignableFrom(cls)) { + return false; + } + + if (AnnotationAccess.isAnnotationPresent(cls, TargetClass.class)) { + return false; + } + try { + Class enclosingClass = cls.getEnclosingClass(); + return enclosingClass == null || isClassIncludedBase(enclosingClass); + } catch (LinkageError e) { + return true; + } + } + /** * Determine if the given class needs to be included in the image according to the policy. */ public boolean isClassIncluded(Class cls) { Class enclosingClass = cls.getEnclosingClass(); - return !Feature.class.isAssignableFrom(cls) && !AnnotationAccess.isAnnotationPresent(cls, TargetClass.class) && (enclosingClass == null || isClassIncluded(enclosingClass)); + return isClassIncludedBase(cls) && (enclosingClass == null || isClassIncluded(enclosingClass)); } /** diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java index dfc9d5df06e4..ccef46168d6a 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java @@ -666,7 +666,7 @@ public void onTypeInstantiated(AnalysisType type) { /* Register the type as instantiated with all its super types. */ assert type.isInstantiated() : type; - AnalysisError.guarantee(type.isArray() || (type.isInstanceClass() && !type.isAbstract())); + AnalysisError.guarantee(type.isArray() || (type.isInstanceClass() && !type.isAbstract()), "Type %s must be either an array, or a non abstract instance class", type.getName()); TypeState typeState = TypeState.forExactType(this, type, true); TypeState typeStateNonNull = TypeState.forExactType(this, type, false); 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 1eab692d7cfe..473dedbcfb75 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 @@ -1313,8 +1313,8 @@ public enum ReportingMode { @Option(help = "Deprecated, option no longer has any effect.", deprecated = true, deprecationMessage = "It no longer has any effect, and no replacement is available")// public static final HostedOptionKey UseOldMethodHandleIntrinsics = new HostedOptionKey<>(false); - @Option(help = "Include all classes, methods, fields, and resources from the class path", type = OptionType.Debug) // - public static final HostedOptionKey IncludeAllFromClassPath = new HostedOptionKey<>(false); + @Option(help = "file:doc-files/PreserveHelp.txt")// + public static final HostedOptionKey Preserve = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); @Option(help = "Force include include all public types and methods that can be reached using normal Java access rules.")// public static final HostedOptionKey UseBaseLayerInclusionPolicy = new HostedOptionKey<>(false); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/PreserveHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/PreserveHelp.txt new file mode 100644 index 000000000000..ccdd21addad5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/PreserveHelp.txt @@ -0,0 +1,17 @@ +Makes the program work correctly without providing reachability metadata. +This is achieved by preserving all class elements, resources, and reflection metadata in the image. +Usage of this options can cause a large increase in image size, and a small decrease in overall performance. + +Usage: -H:Preserve=[all|none|module=|package=|package=|path=][,...] + +The flag can be used in following ways: +1. -H:Preserve=all preserves all elements from the JDK, the classpath, and the module path +2. -H:Preserve=module= preserves all elements from a given module +3. -H:Preserve=module=ALL-UNNAMED preserves all elements from all class-path entries +4. -H:Preserve=package= preserves all elements from a given package +5. -H:Preserve=package= preserves all elements from packages captured by the wildcard. For example, -H:Preserve=package=my.app.* +6. -H:Preserve=path= preserves all elements from a given class-path entry +7. -H:Preserve=none disables all previous selections for preservation +8. A comma-separated list of the previous cases. For example, -H:Preserve=path=,module=,package= + +-H:Preserve is only allowed on the native-image command line and cannot be embedded in the native-image.properties files. \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 18a05ad417a1..4f1b5eea43a9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -26,6 +26,7 @@ import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors; +import java.lang.reflect.Modifier; import java.util.EnumSet; import java.util.Objects; @@ -159,7 +160,8 @@ public void registerNegativeQuery(ConfigurationCondition condition, String class @Platforms(Platform.HOSTED_ONLY.class) public void registerUnsafeAllocated(ConfigurationCondition condition, Class clazz) { - if (!clazz.isArray()) { + if (!clazz.isArray() && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) { + /* Otherwise, UNSAFE.allocateInstance results in InstantiationException */ var conditionSet = unsafeInstantiatedClasses.putIfAbsent(clazz, RuntimeConditionSet.createHosted(condition)); if (conditionSet != null) { conditionSet.addCondition(condition); 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 c7de7ab2ff97..fcbe39e5fd65 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 @@ -34,9 +34,9 @@ import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.driver.NativeImage.ArgumentQueue; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue; +import com.oracle.svm.hosted.driver.IncludeOptionsSupport; +import com.oracle.svm.hosted.driver.IncludeOptionsSupport.ExtendedOption; +import com.oracle.svm.hosted.driver.LayerOptionsSupport.LayerOption; import com.oracle.svm.util.LogUtils; class DefaultOptionHandler extends NativeImage.OptionHandler { @@ -151,7 +151,7 @@ public boolean consume(ArgumentQueue args) { List layerCreateValue = OptionUtils.resolveOptionValuesRedirection(SubstrateOptions.LayerCreate, rawLayerCreateValue, OptionOrigin.from(args.argumentOrigin)); LayerOption layerOption = LayerOption.parse(layerCreateValue); for (ExtendedOption option : layerOption.extendedOptions()) { - var packageOptionValue = PackageOptionValue.from(option); + var packageOptionValue = IncludeOptionsSupport.PackageOptionValue.from(option); if (packageOptionValue == null) { continue; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java index 2c87c1d0be94..91cc1192a352 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java @@ -116,7 +116,8 @@ public void collectResources(ResourceCollector resourceCollector) { /* Collect remaining resources from classpath */ classLoaderSupport.classpath().stream().parallel().forEach(classpathFile -> { - boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) || classLoaderSupport.includeAllFromClassPath(); + boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile) || + classLoaderSupport.getClassPathEntriesToPreserve().contains(classpathFile) || classLoaderSupport.preserveAll(); try { if (Files.isDirectory(classpathFile)) { scanDirectory(classpathFile, resourceCollector, includeCurrent); @@ -132,7 +133,8 @@ public void collectResources(ResourceCollector resourceCollector) { private void collectResourceFromModule(ResourceCollector resourceCollector, ResourceLookupInfo info) { ModuleReference moduleReference = info.resolvedModule.reference(); try (ModuleReader moduleReader = moduleReference.open()) { - boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name()); + boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name()) || + classLoaderSupport.getJavaModuleNamesToPreserve().contains(info.resolvedModule().name()); List resourcesFound = new ArrayList<>(); moduleReader.list().forEach(resourceName -> { var conditionsWithOrigins = shouldIncludeEntry(info.module, resourceCollector, resourceName, moduleReference.location().orElse(null), includeCurrent); 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 f907d3cefd37..cea7a80deed9 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 @@ -53,6 +53,7 @@ import java.util.Comparator; import java.util.Deque; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -66,6 +67,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import java.util.function.BiConsumer; +import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -79,6 +81,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue.ValueWithOrigin; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.SubstrateOptionsParser; @@ -87,8 +90,10 @@ import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; +import com.oracle.svm.hosted.driver.IncludeOptionsSupport; +import com.oracle.svm.hosted.driver.LayerOptionsSupport; +import com.oracle.svm.hosted.image.PreserveOptionsSupport; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue; import com.oracle.svm.hosted.option.HostedOptionParser; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.LogUtils; @@ -102,6 +107,8 @@ public final class NativeImageClassLoaderSupport { + public static final String ALL_UNNAMED = "ALL-UNNAMED"; + private final List imagecp; private final List buildcp; private final List imagemp; @@ -124,12 +131,26 @@ public final class NativeImageClassLoaderSupport { private Path layerFile; - private final Set javaModuleNamesToInclude; - private final Set javaPackagesToInclude; - private final Set javaPathsToInclude; + private final IncludeSelectors layerSelectors = new IncludeSelectors(SubstrateOptions.LayerCreate); + private final IncludeSelectors preserveSelectors = new IncludeSelectors(SubstrateOptions.Preserve); private boolean includeConfigSealed; + private boolean preserveAll; + + public void clearPreserveSelectors() { + preserveSelectors.clear(); + preserveAll = false; + } + + public IncludeSelectors getPreserveSelectors() { + return preserveSelectors; + } + + public IncludeSelectors getLayerSelectors() { + return layerSelectors; + } - private boolean includeAllFromClassPath; + private final Set> classesToPreserve = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set classNamesToPreserve = Collections.newSetFromMap(new ConcurrentHashMap<>()); private LoadClassHandler loadClassHandler; @@ -138,7 +159,6 @@ public final class NativeImageClassLoaderSupport { private final Set> classesToIncludeUnconditionally = ConcurrentHashMap.newKeySet(); private final Set includedJavaPackages = ConcurrentHashMap.newKeySet(); - private final Map failedJavaPackageInclusionRequests = new ConcurrentHashMap<>(); private final Method implAddReadsAllUnnamed = ReflectionUtil.lookupMethod(Module.class, "implAddReadsAllUnnamed"); private final Method implAddEnableNativeAccess = ReflectionUtil.lookupMethod(Module.class, "implAddEnableNativeAccess"); @@ -211,9 +231,6 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St annotationExtractor = new SubstrateAnnotationExtractor(); - javaModuleNamesToInclude = new LinkedHashSet<>(); - javaPackagesToInclude = new LinkedHashSet<>(); - javaPathsToInclude = new LinkedHashSet<>(); includeConfigSealed = false; } @@ -247,69 +264,28 @@ public List getClassLoaders() { return classLoaders; } - public void addJavaModuleToInclude(String moduleName) { - VMError.guarantee(!includeConfigSealed, "Class inclusion configuration is already sealed."); - javaModuleNamesToInclude.add(moduleName); - } - - public void addJavaPackageToInclude(PackageOptionValue packageOptionValue) { - VMError.guarantee(!includeConfigSealed, "Class inclusion configuration is already sealed."); - javaPackagesToInclude.add(packageOptionValue); - } - - public void addClassPathEntryToInclude(String cpEntry) { - VMError.guarantee(!includeConfigSealed, "Class inclusion configuration is already sealed."); - javaPathsToInclude.add(Path.of(cpEntry)); - } - public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) { VMError.guarantee(!includeConfigSealed, "This method should be executed only once."); - includeConfigSealed = true; - /* Verify all requested modules are present */ - List missingModules = javaModuleNamesToInclude.stream().filter(mn -> findModule(mn).isEmpty()).toList(); - if (!missingModules.isEmpty()) { - boolean plural = missingModules.size() > 1; - String pluralS = plural ? "s" : ""; - throw UserError.abort("Module request%s (module=...) %s %s could not find requested module%s. " + - "Provide a module-path that contains the specified module%s or remove %s from option.", - pluralS, String.join(", ", missingModules), layerCreateOptionStr(), pluralS, - pluralS, plural ? "entries" : "entry"); + layerSelectors.verifyAndResolve(); + preserveSelectors.verifyAndResolve(); + + if (preserveAll) { + String msg = """ + This image build includes all classes from the classpath and the JDK via the %s option. This will lead to noticeably bigger images and increased startup times. + If you notice '--initialize-at-build-time' related errors during the build, this is because unanticipated types ended up in the image heap.\ + The cause is one of the libraries on the classpath does not handle correctly when all elements are included in the image. + If this happens, please open an issue for the library whose field was containing forbidden types and correct the '--initialize-at-build-time' configuration for your build. + """ + .replaceAll("\n", System.lineSeparator()) + .formatted(SubstrateOptionsParser.commandArgument(SubstrateOptions.Preserve, "all")); + LogUtils.warning(msg); } - /* Verify all requested class-path entries are on the application class-path */ - Set resolvedJavaPathsToInclude = new HashSet<>(); - List missingClassPathEntries = new ArrayList<>(); - javaPathsToInclude.forEach(requestedCPEntry -> { - Optional optResolvedEntry = toRealPath(requestedCPEntry).findAny(); - if (optResolvedEntry.isPresent()) { - Path resolvedEntry = optResolvedEntry.get(); - if (applicationClassPath().contains(resolvedEntry)) { - resolvedJavaPathsToInclude.add(resolvedEntry); - return; - } - } - missingClassPathEntries.add(requestedCPEntry.toString()); - }); - - if (!missingClassPathEntries.isEmpty()) { - boolean plural = missingModules.size() > 1; - String pluralS = plural ? "s" : ""; - String pluralEntries = plural ? "entries" : "entry"; - throw UserError.abort("Class-path entry request%s (path=...) %s do not match %s on application class-path. " + - "Provide a class-path that contains the %s or remove %s from option.", - pluralS, String.join(", ", missingClassPathEntries), layerCreateOptionStr(), pluralEntries, - pluralEntries, pluralEntries); - } else { - /* - * Replace entries with resolved ones so that they are correctly matched in - * LoadClassHandler.loadClassesFromPath. - */ - javaPathsToInclude.clear(); - javaPathsToInclude.addAll(resolvedJavaPathsToInclude); + if (preserveAll) { + PreserveOptionsSupport.JDK_MODULES_TO_PRESERVE.forEach(moduleName -> preserveSelectors.addModule(moduleName, null)); } - - includeAllFromClassPath = SubstrateOptions.IncludeAllFromClassPath.getValue(parsedHostedOptions); + includeConfigSealed = true; loadClassHandler = new LoadClassHandler(executor, imageClassLoader); loadClassHandler.run(); @@ -328,9 +304,9 @@ public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoa } } - private String layerCreateOptionStr() { - ValueWithOrigin layerCreateValue = SubstrateOptions.LayerCreate.getValue(getParsedHostedOptions()).lastValueWithOrigin().orElseThrow(); - String layerCreateArgument = SubstrateOptionsParser.commandArgument(SubstrateOptions.LayerCreate, layerCreateValue.value()); + private String createOptionStr(HostedOptionKey option) { + ValueWithOrigin layerCreateValue = option.getValue(getParsedHostedOptions()).lastValueWithOrigin().orElseThrow(); + String layerCreateArgument = SubstrateOptionsParser.commandArgument(option, layerCreateValue.value()); return "specified with '%s' from %s".formatted(layerCreateArgument, layerCreateValue.origin()); } @@ -347,8 +323,10 @@ public void setupHostedOptionParser(List arguments) { * hostedOptionParser.getHostedValues(), so we want to affect the options map before it is * copied. */ - HostedImageLayerBuildingSupport.processLayerOptions(hostedOptionParser.getHostedValues(), this); - parsedHostedOptions = new OptionValues(hostedOptionParser.getHostedValues()); + EconomicMap, Object> hostedValues = hostedOptionParser.getHostedValues(); + HostedImageLayerBuildingSupport.processLayerOptions(hostedValues, this); + PreserveOptionsSupport.parsePreserveOption(hostedValues, this); + parsedHostedOptions = new OptionValues(hostedValues); } public HostedOptionParser getHostedOptionParser() { @@ -498,7 +476,7 @@ void processClassLoaderOptions() { } }); NativeImageClassLoaderOptions.EnableNativeAccess.getValue(parsedHostedOptions).values().stream().flatMap(m -> Arrays.stream(SubstrateUtil.split(m, ","))).forEach(moduleName -> { - if ("ALL-UNNAMED".equals(moduleName)) { + if (ALL_UNNAMED.equals(moduleName)) { ReflectionUtil.invokeMethod(implAddEnableNativeAccessToAllUnnamed, null); } else { Module module = findModule(moduleName).orElseThrow(() -> userWarningModuleNotFound(NativeImageClassLoaderOptions.EnableNativeAccess, moduleName)); @@ -627,7 +605,8 @@ public void setupLibGraalClassLoader() { public void allClassesLoaded() { if (loadClassHandler != null) { - loadClassHandler.validatePackageInclusionRequests(); + loadClassHandler.validatePackageInclusionRequests(loadClassHandler.includePackages, SubstrateOptions.LayerCreate); + loadClassHandler.validatePackageInclusionRequests(loadClassHandler.preservePackages, SubstrateOptions.Preserve); loadClassHandler = null; } reportBuilderClassesInApplication(); @@ -701,7 +680,7 @@ private AddExportsAndOpensAndReadsFormatValue asAddExportsAndOpensAndReadsFormat throw userWarningModuleNotFound(option, moduleName); }); List targetModules; - if (targetModuleNamesList.contains("ALL-UNNAMED")) { + if (targetModuleNamesList.contains(ALL_UNNAMED)) { targetModules = Collections.emptyList(); } else { targetModules = targetModuleNamesList.stream() @@ -751,8 +730,37 @@ private final class LoadClassHandler { LongAdder entriesProcessed; volatile String currentlyProcessedEntry; boolean initialReport; - Set requestedPackages; - List requestedPackageWildcards; + + record PackageRequest(Set requestedPackages, List requestedPackageWildcards) { + public static PackageRequest create(Set javaPackagesToInclude) { + Set tempRequestedPackages = new LinkedHashSet<>(); + List tempRequestedPackageWildcards = new ArrayList<>(); + for (LayerOptionsSupport.PackageOptionValue value : javaPackagesToInclude) { + if (value.isWildcard()) { + tempRequestedPackageWildcards.add(value); + } else { + tempRequestedPackages.add(value.name()); + } + } + return new PackageRequest(Collections.unmodifiableSet(tempRequestedPackages), List.copyOf(tempRequestedPackageWildcards)); + } + + public boolean shouldInclude(String packageName) { + if (requestedPackages.contains(packageName)) { + return true; + } + for (LayerOptionsSupport.PackageOptionValue requestedPackageWildcard : requestedPackageWildcards) { + if (packageName.startsWith(requestedPackageWildcard.name())) { + return true; + } + } + return false; + } + + } + + PackageRequest includePackages; + PackageRequest preservePackages; private LoadClassHandler(ForkJoinPool executor, ImageClassLoader imageClassLoader) { this.executor = executor; @@ -762,17 +770,8 @@ private LoadClassHandler(ForkJoinPool executor, ImageClassLoader imageClassLoade currentlyProcessedEntry = "Unknown Entry"; initialReport = true; - Set tempRequestedPackages = new LinkedHashSet<>(); - List tempRequestedPackageWildcards = new ArrayList<>(); - for (PackageOptionValue value : javaPackagesToInclude) { - if (value.isWildcard()) { - tempRequestedPackageWildcards.add(value); - } else { - tempRequestedPackages.add(value.name()); - } - } - requestedPackages = Collections.unmodifiableSet(tempRequestedPackages); - requestedPackageWildcards = List.copyOf(tempRequestedPackageWildcards); + includePackages = PackageRequest.create(layerSelectors.packages.keySet()); + preservePackages = PackageRequest.create(preserveSelectors.packages.keySet()); } private void run() { @@ -793,8 +792,8 @@ private void run() { Set additionalSystemModules = upgradeAndSystemModuleFinder.findAll().stream() .map(v -> v.descriptor().name()) + .filter(n -> getJavaModuleNamesToInclude().contains(n) || getJavaModuleNamesToPreserve().contains(n)) .collect(Collectors.toSet()); - additionalSystemModules.retainAll(getJavaModuleNamesToInclude()); requiresInit.addAll(additionalSystemModules); Set explicitlyAddedModules = ModuleSupport.parseModuleSetModifierProperty(ModuleSupport.PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES); @@ -817,16 +816,16 @@ private void run() { } /* Report package inclusion requests that did not have any effect. */ - void validatePackageInclusionRequests() { - List unusedRequests = new ArrayList<>(); - for (String requestedPackage : requestedPackages) { - if (!includedJavaPackages.contains(requestedPackage)) { - unusedRequests.add(new PackageOptionValue(requestedPackage, false)); + void validatePackageInclusionRequests(PackageRequest request, HostedOptionKey optionString) { + List unusedRequests = new ArrayList<>(); + for (String requestedPackage : request.requestedPackages) { + if (!NativeImageClassLoaderSupport.this.includedJavaPackages.contains(requestedPackage)) { + unusedRequests.add(new LayerOptionsSupport.PackageOptionValue(requestedPackage, false)); } } - var unusedWildcardRequests = new LinkedHashSet<>(requestedPackageWildcards); + var unusedWildcardRequests = new LinkedHashSet<>(request.requestedPackageWildcards); if (!unusedWildcardRequests.isEmpty()) { - for (String includedPackage : includedJavaPackages) { + for (String includedPackage : NativeImageClassLoaderSupport.this.includedJavaPackages) { unusedWildcardRequests.removeIf(wildcardRequest -> includedPackage.startsWith(wildcardRequest.name())); } } @@ -838,7 +837,7 @@ void validatePackageInclusionRequests() { String pluralS = plural ? "s" : ""; throw UserError.abort("Package request%s (package=...) %s %s could not find requested package%s. " + "Provide a class/module-path that contains the package%s or remove %s from option.", - pluralS, String.join(", ", requestsStrings), layerCreateOptionStr(), pluralS, + pluralS, String.join(", ", requestsStrings), createOptionStr(optionString), pluralS, pluralS, plural ? "entries" : "entry"); } } @@ -852,7 +851,8 @@ private void initModule(ModuleReference moduleReference, boolean moduleRequiresI } try (ModuleReader moduleReader = moduleReference.open()) { Module module = optionalModule.get(); - final boolean includeUnconditionally = javaModuleNamesToInclude.contains(module.getName()); + final boolean includeUnconditionally = layerSelectors.moduleNames().contains(module.getName()); + final boolean preserveModule = preserveSelectors.moduleNames().contains(module.getName()) || preserveAll; var container = moduleReference.location().orElseThrow(); if (ModuleLayer.boot().equals(module.getLayer())) { builderURILocations.add(container); @@ -862,7 +862,7 @@ private void initModule(ModuleReference moduleReference, boolean moduleRequiresI String className = extractClassName(moduleResource, fileSystemSeparatorChar); if (className != null) { currentlyProcessedEntry = moduleReferenceLocation + fileSystemSeparatorChar + moduleResource; - executor.execute(() -> handleClassFileName(container, module, className, includeUnconditionally, moduleRequiresInit)); + executor.execute(() -> handleClassFileName(container, module, className, includeUnconditionally, moduleRequiresInit, preserveModule)); } entriesProcessed.increment(); }); @@ -872,7 +872,8 @@ private void initModule(ModuleReference moduleReference, boolean moduleRequiresI } private void loadClassesFromPath(Path path) { - final boolean includeUnconditionally = javaPathsToInclude.contains(path) || includeAllFromClassPath; + final boolean includeUnconditionally = layerSelectors.classpathEntries().contains(path); + final boolean includeAllMetadata = preserveSelectors.classpathEntries().contains(path) || preserveAll; if (ClasspathUtils.isJar(path)) { try { URI container = path.toAbsolutePath().toUri(); @@ -886,7 +887,7 @@ private void loadClassesFromPath(Path path) { } if (probeJarFileSystem != null) { try (FileSystem jarFileSystem = probeJarFileSystem) { - loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet(), includeUnconditionally); + loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet(), includeUnconditionally, includeAllMetadata); } } } catch (ClosedByInterruptException ignored) { @@ -896,13 +897,14 @@ private void loadClassesFromPath(Path path) { } } else { URI container = path.toUri(); - loadClassesFromPath(container, path, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES, includeUnconditionally); + loadClassesFromPath(container, path, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES, includeUnconditionally, + includeAllMetadata); } } private static final String CLASS_EXTENSION = ".class"; - private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set excludes, boolean includeUnconditionally) { + private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set excludes, boolean includeUnconditionally, boolean includeAllMetadata) { boolean useFilter = root.equals(excludeRoot); if (useFilter) { String excludesStr = excludes.stream().map(Path::toString).collect(Collectors.joining(", ")); @@ -928,7 +930,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { String className = extractClassName(fileName, fileSystemSeparatorChar); if (className != null) { currentlyProcessedEntry = file.toUri().toString(); - executor.execute(() -> handleClassFileName(container, null, className, includeUnconditionally, true)); + executor.execute(() -> handleClassFileName(container, null, className, includeUnconditionally, true, includeAllMetadata)); } entriesProcessed.increment(); return FileVisitResult.CONTINUE; @@ -1012,7 +1014,7 @@ private String extractClassName(String fileName, char fileSystemSeparatorChar) { return strippedClassFileName.equals("module-info") ? null : strippedClassFileName.replace(fileSystemSeparatorChar, '.'); } - private void handleClassFileName(URI container, Module module, String className, boolean includeUnconditionally, boolean classRequiresInit) { + private void handleClassFileName(URI container, Module module, String className, boolean includeUnconditionally, boolean classRequiresInit, boolean preserveReflectionMetadata) { if (classRequiresInit) { synchronized (classes) { EconomicSet classNames = classes.get(container); @@ -1038,41 +1040,32 @@ private void handleClassFileName(URI container, Module module, String className, } catch (AssertionError error) { VMError.shouldNotReachHere(error); } catch (Throwable t) { - if (includePackage(packageName(className))) { - // Record unresolvable classes that are from requested packages - failedJavaPackageInclusionRequests.put(className, t); + if (preserveReflectionMetadata) { + classNamesToPreserve.add(className); } ImageClassLoader.handleClassLoadingError(t); } + if (clazz != null) { String packageName = clazz.getPackageName(); includedJavaPackages.add(packageName); - if (includeUnconditionally || includePackage(packageName)) { + if (includeUnconditionally || includePackages.shouldInclude(packageName)) { classesToIncludeUnconditionally.add(clazz); } if (classRequiresInit) { imageClassLoader.handleClass(clazz); } + if (preserveReflectionMetadata || preservePackages.shouldInclude(packageName)) { + classesToPreserve.add(clazz); + } } imageClassLoader.watchdog.recordActivity(); } + } - private static String packageName(String className) { - int packageSep = className.lastIndexOf('.'); - return packageSep > 0 ? className.substring(0, packageSep) : ""; - } - - private boolean includePackage(String packageName) { - if (requestedPackages.contains(packageName)) { - return true; - } - for (PackageOptionValue requestedPackageWildcard : requestedPackageWildcards) { - if (packageName.startsWith(requestedPackageWildcard.name())) { - return true; - } - } - return false; - } + private static String packageName(String className) { + int packageSep = className.lastIndexOf('.'); + return packageSep > 0 ? className.substring(0, packageSep) : ""; } public void reportBuilderClassesInApplication() { @@ -1109,20 +1102,178 @@ public void reportBuilderClassesInApplication() { } public Set getJavaModuleNamesToInclude() { - return javaModuleNamesToInclude; + return layerSelectors.moduleNames(); + } + + public Set getJavaModuleNamesToPreserve() { + return preserveSelectors.moduleNames(); } public Set getJavaPathsToInclude() { - return javaPathsToInclude; + return layerSelectors.classpathEntries(); + } + + public Set getClassPathEntriesToPreserve() { + return preserveSelectors.classpathEntries(); } - public boolean includeAllFromClassPath() { - return includeAllFromClassPath; + public Set getClassNamesToPreserve() { + return Collections.unmodifiableSet(classNamesToPreserve); } - public List> getClassesToIncludeUnconditionally() { + public boolean preserveAll() { + return preserveAll; + } + + public void setPreserveAll() { + preserveAll = true; + } + + public Stream> getClassesToIncludeUnconditionally() { return classesToIncludeUnconditionally.stream() - .sorted(Comparator.comparing(Class::getTypeName)) - .collect(Collectors.toList()); + .sorted(Comparator.comparing(Class::getTypeName)); + } + + public Stream> getClassesToPreserve() { + return classesToPreserve.stream() + .sorted(Comparator.comparing(Class::getTypeName)); + } + + public class IncludeSelectors { + private static final String CLASS_INCLUSION_SEALED_MSG = "Class inclusion configuration is already sealed."; + + private final Map moduleNames = new LinkedHashMap<>(); + private final Map packages = new LinkedHashMap<>(); + private final Map classpathEntries = new LinkedHashMap<>(); + private final HostedOptionKey option; + + public IncludeSelectors(HostedOptionKey option) { + this.option = option; + } + + public void verifyAndResolve() { + verifyAllRequestedModulesPresent(); + verifyClasspathEntriesPresentAndResolve(); + } + + /** + * Verify all requested class-path entries are on the application class-path and resolve + * them. + */ + private void verifyClasspathEntriesPresentAndResolve() { + Set resolvedJavaPathsToInclude = new HashSet<>(); + List missingClassPathEntries = new ArrayList<>(); + classpathEntries.keySet().forEach(requestedCPEntry -> { + Optional optResolvedEntry = toRealPath(requestedCPEntry).findAny(); + if (optResolvedEntry.isPresent()) { + Path resolvedEntry = optResolvedEntry.get(); + if (applicationClassPath().contains(resolvedEntry)) { + resolvedJavaPathsToInclude.add(resolvedEntry); + return; + } + } + missingClassPathEntries.add(requestedCPEntry.toString()); + }); + + if (!missingClassPathEntries.isEmpty()) { + boolean plural = missingClassPathEntries.size() > 1; + String pluralS = plural ? "s" : ""; + String pluralEntries = plural ? "entries" : "entry"; + String msg = String.format("Class-path entry request%s (path=...) %s do not match the application class-path %s. Provide a class-path that contains the %s or remove %s from option.", + pluralS, String.join(", ", missingClassPathEntries), pluralEntries, + pluralEntries, pluralEntries); + String listOfOptions = missingClassPathEntries.stream() + .map(Path::of) + .map(classpathEntries::get) + .map(this::originatingOptionString) + .distinct() + .collect(singleOrMultiLine(plural)); + msg += String.format(" The missing classpath entries were requested in the following option%s: %s", pluralS, listOfOptions); + throw UserError.abort(msg); + } else { + /* + * Replace entries with resolved ones so that they are correctly matched in + * LoadClassHandler.loadClassesFromPath. + */ + classpathEntries.clear(); + for (Path path : resolvedJavaPathsToInclude) { + /* ExtendedOptionWithOrigin of resolved entries are not needed anymore */ + classpathEntries.put(path, null); + } + } + } + + private static Collector singleOrMultiLine(boolean plural) { + if (plural) { + String indentation = " "; + return Collectors.joining(System.lineSeparator() + indentation, System.lineSeparator() + indentation, ""); + } else { + return Collectors.joining(", "); + } + } + + /* Verify all requested modules are present on the module path */ + private void verifyAllRequestedModulesPresent() { + List> missingModules = moduleNames.entrySet().stream() + .filter(e -> findModule(e.getKey()).isEmpty()) + .toList(); + if (!missingModules.isEmpty()) { + boolean plural = missingModules.size() > 1; + String pluralS = plural ? "s" : ""; + String listOfModules = missingModules.stream().map(Map.Entry::getKey).collect(Collectors.joining(", ")); + String msg = String.format("Module request%s (module=...) %s could not find requested module%s. " + + "Provide a module-path that contains the specified module%s or remove %s from option.", + pluralS, listOfModules, pluralS, + pluralS, plural ? "entries" : "entry"); + String listOfOptions = missingModules.stream() + .map(Map.Entry::getValue) + .map(this::originatingOptionString) + .distinct() + .collect(singleOrMultiLine(plural)); + msg += String.format(" The missing modules were requested in the following option%s: %s", pluralS, listOfOptions); + throw UserError.abort(msg); + } + } + + private String originatingOptionString(IncludeOptionsSupport.ExtendedOptionWithOrigin v) { + return SubstrateOptionsParser.commandArgument(option, v.valueWithOrigin().value().toString()) + " from " + v.valueWithOrigin().origin(); + } + + public void addModule(String moduleName, IncludeOptionsSupport.ExtendedOptionWithOrigin extendedOptionWithOrigin) { + VMError.guarantee(!includeConfigSealed, CLASS_INCLUSION_SEALED_MSG); + if (moduleName.equals(ALL_UNNAMED)) { + IncludeOptionsSupport.ExtendedOptionWithOrigin includeOptionsSupport = new IncludeOptionsSupport.ExtendedOptionWithOrigin(extendedOptionWithOrigin.option(), + extendedOptionWithOrigin.valueWithOrigin()); + for (Path path : applicationClassPath()) { + classpathEntries.put(path, includeOptionsSupport); + } + } else { + moduleNames.put(moduleName, extendedOptionWithOrigin); + } + } + + public void addPackage(LayerOptionsSupport.PackageOptionValue packageOptionValue) { + VMError.guarantee(!includeConfigSealed, CLASS_INCLUSION_SEALED_MSG); + packages.put(packageOptionValue, null); + } + + public void addClassPathEntry(String cpEntry, IncludeOptionsSupport.ExtendedOptionWithOrigin extendedOptionWithOrigin) { + VMError.guarantee(!includeConfigSealed, CLASS_INCLUSION_SEALED_MSG); + classpathEntries.put(Path.of(cpEntry), extendedOptionWithOrigin); + } + + public void clear() { + packages.clear(); + moduleNames.clear(); + classpathEntries.clear(); + } + + public Set classpathEntries() { + return classpathEntries.keySet(); + } + + public Set moduleNames() { + return moduleNames.keySet(); + } } } 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 eae4aa443200..bfe70d196b14 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 @@ -71,9 +71,12 @@ import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.Feature.OnAnalysisExitAccess; +import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.AnnotationExtractor; import org.graalvm.nativeimage.impl.CConstantValueSupport; +import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import org.graalvm.nativeimage.impl.SizeOfSupport; import org.graalvm.word.PointerBase; @@ -1077,6 +1080,14 @@ protected void setupNativeImage(String imageName, OptionValues options, Map bb.registerTypeForBaseImage(cls)); + var runtimeReflection = ImageSingletons.lookup(RuntimeReflectionSupport.class); + loader.classLoaderSupport.getClassesToPreserve().parallel() + .filter(ClassInclusionPolicy::isClassIncludedBase) + .forEach(c -> runtimeReflection.registerClassFully(ConfigurationCondition.alwaysTrue(), c)); + for (String className : loader.classLoaderSupport.getClassNamesToPreserve()) { + RuntimeReflection.registerClassLookup(className); + } + registerEntryPointStubs(entryPoints); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/IncludeOptionsSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/IncludeOptionsSupport.java new file mode 100644 index 000000000000..6942dfead11c --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/IncludeOptionsSupport.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025, 2025, 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.driver; + +import static com.oracle.svm.hosted.driver.IncludeOptionsSupport.ExtendedOption.MODULE_OPTION; +import static com.oracle.svm.hosted.driver.IncludeOptionsSupport.ExtendedOption.PACKAGE_OPTION; +import static com.oracle.svm.hosted.driver.IncludeOptionsSupport.ExtendedOption.PATH_OPTION; + +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.option.LocatableMultiOptionValue; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.NativeImageClassLoaderSupport; + +public class IncludeOptionsSupport { + public record ExtendedOptionWithOrigin(ExtendedOption option, LocatableMultiOptionValue.ValueWithOrigin valueWithOrigin) { + } + + public record ExtendedOption(String key, String value) { + + public static final String PACKAGE_OPTION = "package"; + public static final String MODULE_OPTION = "module"; + public static final String PATH_OPTION = "path"; + + public static ExtendedOption parse(String option) { + String[] optionParts = SubstrateUtil.split(option, "=", 2); + if (optionParts.length == 2) { + return new ExtendedOption(optionParts[0], optionParts[1]); + } else { + return new ExtendedOption(option, null); + } + } + } + + public record PackageOptionValue(String name, boolean isWildcard) { + + static final String PACKAGE_WILDCARD_SUFFIX = ".*"; + + public static PackageOptionValue from(ExtendedOption extendedOption) { + if (!extendedOption.key().equals(PACKAGE_OPTION)) { + return null; + } + String extendedOptionValue = extendedOption.value(); + if (extendedOptionValue.endsWith(PACKAGE_WILDCARD_SUFFIX)) { + return new PackageOptionValue(extendedOptionValue.substring(0, extendedOptionValue.length() - PACKAGE_WILDCARD_SUFFIX.length()), true); + } + return new PackageOptionValue(extendedOptionValue, false); + } + + @Override + public String toString() { + return name + (isWildcard ? PACKAGE_WILDCARD_SUFFIX : ""); + } + } + + public static String possibleExtendedOptions() { + return Stream.of(IncludeOptionsSupport.ExtendedOption.MODULE_OPTION, IncludeOptionsSupport.ExtendedOption.PACKAGE_OPTION, IncludeOptionsSupport.ExtendedOption.PATH_OPTION) + .map(option -> option + "=" + "<" + option + ">") + .collect(Collectors.joining(", ")); + } + + public static void parseIncludeSelector(String optionArg, LocatableMultiOptionValue.ValueWithOrigin valueWithOrigin, NativeImageClassLoaderSupport.IncludeSelectors includeSelectors, + ExtendedOption option, String possibleOptions) { + boolean validOption = option.value() != null && !option.value().isEmpty(); + switch (option.key()) { + case MODULE_OPTION -> { + UserError.guarantee(validOption, "Option %s specified with '%s' from %s requires a module name argument, e.g., %s=module-name.", + option.key(), optionArg, valueWithOrigin.origin(), option.key()); + includeSelectors.addModule(option.value, new ExtendedOptionWithOrigin(option, valueWithOrigin)); + + } + case PACKAGE_OPTION -> { + UserError.guarantee(validOption, "Option %s specified with '%s' from %s requires a package name argument, e.g., %s=package-name.", + option.key(), optionArg, valueWithOrigin.origin(), option.key()); + includeSelectors.addPackage(Objects.requireNonNull(PackageOptionValue.from(option))); + } + case PATH_OPTION -> { + UserError.guarantee(validOption, "Option %s specified with '%s' from %s requires a class-path entry, e.g., %s=path/to/cp-entry.", + option.key(), optionArg, valueWithOrigin.origin(), option.key()); + includeSelectors.addClassPathEntry(option.value(), new ExtendedOptionWithOrigin(option, valueWithOrigin)); + } + default -> + throw UserError.abort("Unknown option %s specified with '%s' from %s. The possible options are: " + possibleOptions, + option.key(), optionArg, valueWithOrigin.origin()); + } + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerOptionsSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/LayerOptionsSupport.java similarity index 64% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerOptionsSupport.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/LayerOptionsSupport.java index 02b0fc6b9d4b..c29ab711161d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerOptionsSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/LayerOptionsSupport.java @@ -22,15 +22,16 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted.imagelayer; +package com.oracle.svm.hosted.driver; import java.nio.file.Path; import java.util.List; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.imagelayer.LayerArchiveSupport; -public class LayerOptionsSupport { +public class LayerOptionsSupport extends IncludeOptionsSupport { public record LayerOption(Path fileName, ExtendedOption[] extendedOptions) { /** Split a layer option into its components. */ @@ -55,36 +56,4 @@ public static LayerOption parse(List options) { } } - public record ExtendedOption(String key, String value) { - - static ExtendedOption parse(String option) { - String[] optionParts = SubstrateUtil.split(option, "=", 2); - if (optionParts.length == 2) { - return new ExtendedOption(optionParts[0], optionParts[1]); - } else { - return new ExtendedOption(option, null); - } - } - } - - public record PackageOptionValue(String name, boolean isWildcard) { - - static final String PACKAGE_WILDCARD_SUFFIX = ".*"; - - public static PackageOptionValue from(ExtendedOption extendedOption) { - if (!extendedOption.key().equals(LayerArchiveSupport.PACKAGE_OPTION)) { - return null; - } - String extendedOptionValue = extendedOption.value(); - if (extendedOptionValue.endsWith(PACKAGE_WILDCARD_SUFFIX)) { - return new PackageOptionValue(extendedOptionValue.substring(0, extendedOptionValue.length() - PACKAGE_WILDCARD_SUFFIX.length()), true); - } - return new PackageOptionValue(extendedOptionValue, false); - } - - @Override - public String toString() { - return name + (isWildcard ? PACKAGE_WILDCARD_SUFFIX : ""); - } - } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/package-info.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/package-info.java new file mode 100644 index 000000000000..456ef2c091d0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/package-info.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025, 2025, 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. + */ + +/** + * This package is intentionally not marked as {@link org.graalvm.nativeimage.Platform.HOSTED_ONLY} + * as it is used in the driver (com.oracle.svm.driver.NativeImage) during the image + * build. Otherwise, classes in this package are effectively + * {@link org.graalvm.nativeimage.Platform.HOSTED_ONLY}. + */ +package com.oracle.svm.hosted.driver; \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java new file mode 100644 index 000000000000..ada19a0d7b90 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025, 2025, 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.image; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Stream; + +import org.graalvm.collections.EconomicMap; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import com.oracle.svm.core.option.LocatableMultiOptionValue; +import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.NativeImageClassLoaderSupport; +import com.oracle.svm.hosted.driver.IncludeOptionsSupport; + +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.options.OptionValues; + +public class PreserveOptionsSupport extends IncludeOptionsSupport { + + public static final String PRESERVE_ALL = "all"; + public static final String PRESERVE_NONE = "none"; + + /** + * All Java modules, except: + *
    + *
  • jdk.localedata that pulls in 250 MB of code into the image.
  • + *
  • All internal modules.
  • + *
  • All tooling modules such as the java.compiler.
  • + *
  • Modules that are currently not supported with Native Image (e.g., + * java.management.
  • + *
+ */ + public static final Set JDK_MODULES_TO_PRESERVE = Set.of( + "java.base", + "java.desktop", + "java.xml", + "java.xml.crypto", + "java.rmi", + "jdk.net", + "jdk.random", + "java.smartcardio", + "jdk.charsets", + "java.sql", + "java.sql.rowset", + "java.transaction.xa", + "java.datatransfer", + "java.security.sasl", + "jdk.security.jgss", + "jdk.security.auth", + "jdk.crypto.cryptoki", + "jdk.crypto.ec", + "java.logging", + "java.naming", + "jdk.naming.dns", + "jdk.httpserver", + "jdk.zipfs", + "jdk.nio.mapmode", + "java.instrument", + "java.prefs", + "jdk.unsupported", + "jdk.accessibility"); + + private static String preservePossibleOptions() { + return String.format("[%s, %s, %s]", + PRESERVE_ALL, PRESERVE_NONE, IncludeOptionsSupport.possibleExtendedOptions()); + + } + + public static void parsePreserveOption(EconomicMap, Object> hostedValues, NativeImageClassLoaderSupport classLoaderSupport) { + AccumulatingLocatableMultiOptionValue.Strings preserve = SubstrateOptions.Preserve.getValue(new OptionValues(hostedValues)); + Stream> valuesWithOrigins = preserve.getValuesWithOrigins(); + valuesWithOrigins.forEach(valueWithOrigin -> { + String optionArgument = SubstrateOptionsParser.commandArgument(SubstrateOptions.Preserve, valueWithOrigin.value(), true, false); + if (!valueWithOrigin.origin().commandLineLike()) { + throw UserError.abort("Using %s is only allowed on command line. The option was used from %s", optionArgument, valueWithOrigin.origin()); + } + + var options = Arrays.stream(valueWithOrigin.value().split(",")).toList(); + for (String option : options) { + UserError.guarantee(!option.isEmpty(), "Option %s from %s cannot be passed an empty string. The possible options are: %s", + optionArgument, valueWithOrigin.origin(), preservePossibleOptions()); + switch (option) { + case PRESERVE_ALL -> classLoaderSupport.setPreserveAll(); + case PRESERVE_NONE -> classLoaderSupport.clearPreserveSelectors(); + default -> parseIncludeSelector(optionArgument, valueWithOrigin, classLoaderSupport.getPreserveSelectors(), ExtendedOption.parse(option), preservePossibleOptions()); + } + } + }); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java index f8d97fa8ab1c..efd209d08a27 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java @@ -31,7 +31,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import org.capnproto.ReaderOptions; import org.capnproto.Serialize; @@ -50,21 +49,24 @@ import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.ArchiveSupport; -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.NativeImageClassLoaderSupport; import com.oracle.svm.hosted.NativeImageGenerator; import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.ExtendedOption; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.LayerOption; -import com.oracle.svm.hosted.imagelayer.LayerOptionsSupport.PackageOptionValue; +import com.oracle.svm.hosted.driver.IncludeOptionsSupport; +import com.oracle.svm.hosted.driver.LayerOptionsSupport.LayerOption; import jdk.graal.compiler.core.common.SuppressFBWarnings; import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionValues; public final class HostedImageLayerBuildingSupport extends ImageLayerBuildingSupport { + + private static String layerCreatePossibleOptions() { + return "[" + IncludeOptionsSupport.possibleExtendedOptions() + "]"; + } + private SVMImageLayerLoader loader; private SVMImageLayerWriter writer; private SVMImageLayerSingletonLoader singletonLoader; @@ -166,31 +168,9 @@ public static void processLayerOptions(EconomicMap, Object> values, classLoaderSupport.setLayerFile(layerOption.fileName()); String layerCreateArg = SubstrateOptionsParser.commandArgument(SubstrateOptions.LayerCreate, layerCreateValue); - for (ExtendedOption option : layerOption.extendedOptions()) { - switch (option.key()) { - case LayerArchiveSupport.MODULE_OPTION -> { - UserError.guarantee(option.value() != null || option.value().isEmpty(), - "Layer option %s specified with '%s' from %s requires a module name argument, e.g., %s=module-name.", - option.key(), layerCreateArg, valueWithOrigin.origin(), option.key()); - classLoaderSupport.addJavaModuleToInclude(option.value()); - - } - case LayerArchiveSupport.PACKAGE_OPTION -> { - UserError.guarantee(option.value() != null || option.value().isEmpty(), - "Layer option %s specified with '%s' from %s requires a package name argument, e.g., %s=package-name.", - option.key(), layerCreateArg, valueWithOrigin.origin(), option.key()); - classLoaderSupport.addJavaPackageToInclude(Objects.requireNonNull(PackageOptionValue.from(option))); - } - case LayerArchiveSupport.PATH_OPTION -> { - UserError.guarantee(option.value() != null || option.value().isEmpty(), - "Layer option %s specified with '%s' from %s requires a class-path entry, e.g., %s=path/to/cp-entry.", - option.key(), layerCreateArg, valueWithOrigin.origin(), option.key()); - classLoaderSupport.addClassPathEntryToInclude(option.value()); - } - default -> - throw UserError.abort("Unknown layer option %s specified with '%s' from %s. Use --help-extra for usage instructions.", - option.key(), layerCreateArg, valueWithOrigin.origin()); - } + NativeImageClassLoaderSupport.IncludeSelectors layerSelectors = classLoaderSupport.getLayerSelectors(); + for (IncludeOptionsSupport.ExtendedOption option : layerOption.extendedOptions()) { + IncludeOptionsSupport.parseIncludeSelector(layerCreateArg, valueWithOrigin, layerSelectors, option, layerCreatePossibleOptions()); } SubstrateOptions.UseBaseLayerInclusionPolicy.update(values, true); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java index 44c0e723034c..7e6be584c245 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java @@ -43,17 +43,13 @@ public class LayerArchiveSupport { - protected static final String MODULE_OPTION = "module"; - public static final String PACKAGE_OPTION = "package"; - protected static final String PATH_OPTION = "path"; - private static final int LAYER_FILE_FORMAT_VERSION_MAJOR = 0; private static final int LAYER_FILE_FORMAT_VERSION_MINOR = 1; protected static final String LAYER_INFO_MESSAGE_PREFIX = "Native Image Layers"; protected static final String LAYER_TEMP_DIR_PREFIX = "layerRoot-"; - protected static final String LAYER_FILE_EXTENSION = ".nil"; + public static final String LAYER_FILE_EXTENSION = ".nil"; protected final LayerProperties layerProperties; protected final ArchiveSupport archiveSupport; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/package-info.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/package-info.java new file mode 100644 index 000000000000..310108c01eee --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025, 2025, 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. + */ + +@Platforms(Platform.HOSTED_ONLY.class) +package com.oracle.svm.hosted.imagelayer; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java index 41f9c71b9ebe..6ad5fbd19640 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java @@ -152,6 +152,7 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("com.sun.security.sasl", JDK_CLASS_REASON); rci.initializeAtBuildTime("java.security", JDK_CLASS_REASON); + rci.initializeAtRunTime("sun.security.pkcs11.P11Util", "Cleaner reference"); rci.initializeAtBuildTime("javax.crypto", JDK_CLASS_REASON); rci.initializeAtBuildTime("javax.security.auth", JDK_CLASS_REASON); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 23d7fd83c313..ea776ca76ae0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -717,7 +717,7 @@ private static void finishFieldBeforeCompilation(String name, JNIAccessibleField assert hField.equals(hybridLayout.getArrayField()) : "JNI access to hybrid objects is implemented only for the array field"; offset = hybridLayout.getArrayBaseOffset(); } else { - assert hField.hasLocation(); + assert hField.hasLocation() : hField; offset = hField.getLocation(); } hidingSubclasses = findHidingSubclasses(hField.getDeclaringClass(), sub -> anyFieldMatches(sub, name)); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 3e9e09ea28e8..85a8a90e6b2b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -530,7 +530,7 @@ public void register(ConfigurationCondition condition, boolean finalIsWritable, } @Override - public void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz) { + public void registerAllFields(ConfigurationCondition condition, Class clazz) { registerAllFieldsQuery(condition, false, clazz); } @@ -549,7 +549,7 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, boolean que } @Override - public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz) { + public void registerAllDeclaredFields(ConfigurationCondition condition, Class clazz) { registerAllDeclaredFieldsQuery(condition, false, clazz); } @@ -582,13 +582,8 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel boolean exists = classFields.containsKey(analysisField); boolean shouldRegisterReachabilityHandler = classFields.isEmpty(); var cndValue = classFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectField)); - if (!queriedOnly) { - /* queryOnly methods are conditioned by the type itself */ - cndValue.getConditions().addCondition(cnd); - } - if (!exists) { - registerTypesForField(analysisField, reflectField, true); + registerTypesForField(analysisField, reflectField, queriedOnly); /* * The image needs to know about subtypes shadowing fields registered for reflection to @@ -620,6 +615,8 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel * registered as queried. */ if (!queriedOnly) { + /* queryOnly methods are conditioned on the type itself */ + cndValue.getConditions().addCondition(cnd); registerTypesForField(analysisField, reflectField, false); } } @@ -1067,11 +1064,11 @@ private boolean shouldExcludeClass(Class clazz) { private static T queryGenericInfo(Callable callable) { try { return callable.call(); - } catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError e) { + } catch (MalformedParameterizedTypeException | TypeNotPresentException | LinkageError | AssertionError e) { /* These are rethrown at run time, so we can simply ignore them when querying. */ return null; } catch (Throwable t) { - throw VMError.shouldNotReachHere(t); + throw VMError.shouldNotReachHere(callable.toString(), t); } } @@ -1327,7 +1324,9 @@ public AnnotationMemberValue getAnnotationDefaultData(AnnotatedElement element) @Override public int getReflectionMethodsCount() { - return registeredMethods.size(); + return registeredMethods.values().stream() + .map(Map::size) + .reduce(0, Integer::sum); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java index 8ae0b0b2225f..5e8c0e7e1b41 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.TargetClass; +import jdk.graal.compiler.api.replacements.Fold; import jdk.vm.ci.meta.ResolvedJavaType; /** @@ -74,6 +75,8 @@ public static boolean shouldExclude(Executable method, AnalysisMetaAccess metaAc return true; } else if (aMethod.isAnnotationPresent(Delete.class)) { return true; // accesses would fail at runtime + } else if (aMethod.isAnnotationPresent(Fold.class)) { + return true; // accesses can contain hosted elements } else if (aMethod.isSynthetic() && aMethod.getDeclaringClass().isAnnotationPresent(TargetClass.class)) { /* * Synthetic methods are usually methods injected by javac to provide access to diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java index 43f661063491..58addcbbdfbe 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ReflectionUtil.java @@ -122,7 +122,7 @@ public static Constructor lookupConstructor(boolean optional, Class de openModule(declaringClass); result.setAccessible(true); return result; - } catch (ReflectiveOperationException ex) { + } catch (ReflectiveOperationException | NoClassDefFoundError ex) { if (optional) { return null; } diff --git a/vm/mx.vm/mx_vm_benchmark.py b/vm/mx.vm/mx_vm_benchmark.py index 419ef750aa34..a0eadf2c6e40 100644 --- a/vm/mx.vm/mx_vm_benchmark.py +++ b/vm/mx.vm/mx_vm_benchmark.py @@ -238,10 +238,12 @@ def __init__(self, vm: NativeImageVM, bm_suite: BenchmarkSuite | NativeImageBenc base_image_build_args += ['--gc=' + vm.gc] + ['-H:+SpawnIsolates'] if vm.native_architecture: base_image_build_args += ['-march=native'] + if vm.preserve_all: + base_image_build_args += ['-H:Preserve=all'] + if vm.preserve_classpath: + base_image_build_args += ['-H:Preserve=module=ALL-UNNAMED'] if vm.analysis_context_sensitivity: base_image_build_args += ['-H:AnalysisContextSensitivity=' + vm.analysis_context_sensitivity, '-H:-RemoveSaturatedTypeFlows', '-H:+AliasArrayTypeFlows'] - if vm.no_inlining_before_analysis: - base_image_build_args += ['-H:-InlineBeforeAnalysis'] if vm.optimization_level: base_image_build_args += ['-' + vm.optimization_level] if vm.async_sampler: @@ -530,6 +532,8 @@ def __init__(self, name, config_name, extra_java_args=None, extra_launcher_args= self.is_llvm = False self.gc = None self.native_architecture = False + self.preserve_all = False + self.preserve_classpath = False self.use_upx = False self.use_open_type_world = False self.use_compacting_gc = False @@ -545,7 +549,6 @@ def __init__(self, name, config_name, extra_java_args=None, extra_launcher_args= self.force_profile_inference = False self.profile_inference_debug = False self.analysis_context_sensitivity = None - self.no_inlining_before_analysis = False self.optimization_level = None self._configure_comma_separated_configs(config_name) if ',' in config_name: @@ -567,6 +570,10 @@ def config_name(self): config = [] if self.native_architecture is True: config += ["native-architecture"] + if self.preserve_all is True: + config += ["preserve-all"] + if self.preserve_classpath is True: + config += ["preserve-classpath"] if self.use_string_inlining is True: config += ["string-inlining"] if self.use_open_type_world is True: @@ -601,8 +608,6 @@ def config_name(self): if sensitivity.startswith("_"): sensitivity = sensitivity[1:] config += [sensitivity] - if self.no_inlining_before_analysis is True: - config += ["no-inline"] if self.jdk_profiles_collect is True: config += ["jdk-profiles-collect"] if self.adopted_jdk_pgo is True: @@ -638,7 +643,8 @@ def _configure_from_name(self, config_name): mx.abort(f"config_name must be set. Use 'default' for the default {self.__class__.__name__} configuration.") # This defines the allowed config names for NativeImageVM. The ones registered will be available via --jvm-config - rule = r'^(?Pnative-architecture-)?(?Pstring-inlining-)?(?Potw-)?(?Pcompacting-gc-)?(?Pgate-)?(?Pupx-)?(?Pquickbuild-)?(?Pg1gc-)?' \ + rule = r'^(?Pnative-architecture-)?(?Pstring-inlining-)?(?Potw-)?(?Pcompacting-gc-)?(?Ppreserve-all-)?(?Ppreserve-classpath-)?' \ + r'(?Pgate-)?(?Pupx-)?(?Pquickbuild-)?(?Pg1gc-)?' \ r'(?Pllvm-)?(?Ppgo-|pgo-sampler-)?(?Pinline-)?' \ r'(?Pinsens-|allocsens-|1obj-|2obj1h-|3obj2h-|4obj3h-)?(?Pno-inline-)?(?Pjdk-profiles-collect-|adopted-jdk-pgo-)?' \ r'(?Pprofile-inference-feature-extraction-|profile-inference-pgo-|profile-inference-debug-)?(?Psafepoint-sampler-|async-sampler-)?(?PO0-|O1-|O2-|O3-|Os-)?(default-)?(?Pce-|ee-)?$' @@ -653,6 +659,14 @@ def _configure_from_name(self, config_name): mx.logv(f"'native-architecture' is enabled for {config_name}") self.native_architecture = True + if matching.group("preserve_all") is not None: + mx.logv(f"'preserve-all' is enabled for {config_name}") + self.preserve_all = True + + if matching.group("preserve_classpath") is not None: + mx.logv(f"'preserve-classpath' is enabled for {config_name}") + self.preserve_classpath = True + if matching.group("string_inlining") is not None: mx.logv(f"'string-inlining' is enabled for {config_name}") self.use_string_inlining = True @@ -788,14 +802,6 @@ def generate_profiling_package_prefixes(): else: mx.abort(f"Unknown configuration for optimization level: {olevel}") - if matching.group("no_inlining_before_analysis") is not None: - option = matching.group("no_inlining_before_analysis")[:-1] - if option == "no-inline": - mx.logv(f"not doing inlining before analysis for {config_name}") - self.no_inlining_before_analysis = True - else: - mx.abort(f"Unknown configuration for no inlining before analysis: {option}") - if matching.group("analysis_context_sensitivity") is not None: context_sensitivity = matching.group("analysis_context_sensitivity")[:-1] if context_sensitivity in ["insens", "allocsens"]: @@ -1936,18 +1942,15 @@ def register_graalvm_vms(): mx_polybenchmarks_benchmark.rules = polybenchmark_rules optimization_levels = ['O0', 'O1', 'O2', 'O3', 'Os'] - - # Inlining before analysis is done by default analysis_context_sensitivity = ['insens', 'allocsens', '1obj', '2obj1h', '3obj2h', '4obj3h'] - analysis_context_sensitivity_no_inline = [f"{analysis_component}-no-inline" for analysis_component in analysis_context_sensitivity] for short_name, config_suffix in [('niee', 'ee'), ('ni', 'ce')]: if any(component.short_name == short_name for component in mx_sdk_vm_impl.registered_graalvm_components(stage1=False)): - for main_config in ['default', 'gate', 'llvm', 'native-architecture'] + analysis_context_sensitivity + analysis_context_sensitivity_no_inline: + for main_config in ['default', 'gate', 'llvm', 'native-architecture', 'preserve-all', 'preserve-classpath'] + analysis_context_sensitivity: final_config_name = f'{main_config}-{config_suffix}' mx_benchmark.add_java_vm(NativeImageVM('native-image', final_config_name, ['--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED']), _suite, 10) # ' ' force the empty O<> configs as well - for main_config in ['llvm', 'native-architecture', 'g1gc', 'native-architecture-g1gc', ''] + analysis_context_sensitivity + analysis_context_sensitivity_no_inline: + for main_config in ['llvm', 'native-architecture', 'g1gc', 'native-architecture-g1gc', 'preserve-all', 'preserve-classpath'] + analysis_context_sensitivity: for optimization_level in optimization_levels: if len(main_config) > 0: final_config_name = f'{main_config}-{optimization_level}-{config_suffix}'