diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index dc702c150655..ad37e9ac67dd 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -18,6 +18,7 @@ At runtime, premain runtime options are set along with main class' arguments in * (GR-58914) `ActiveProcessorCount` must be set at isolate or VM creation time. * (GR-59326) Ensure builder ForkJoin commonPool parallelism always respects NativeImageOptions.NumberOfThreads. * (GR-60081) Native Image now targets `armv8.1-a` by default on AArch64. Use `-march=compatibility` for best compatibility or `-march=native` for best performance if the native executable is deployed on the same machine or on a machine with the same CPU features. To list all available machine types, use `-march=list`. +* (GR-54953) Add options `-H:IncludeAllMetadataForClassPathEntry`, `-H:IncludeAllForClassPath`, and `-H:IncludeAllMetadataForModule` for bulk inclusion of reachability metadata. ## GraalVM for JDK 23 (Internal Version 24.1.0) * (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect. 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 a8e014138b1f..6cf09305e1af 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 @@ -54,12 +54,17 @@ public void setBigBang(BigBang bb) { this.bb = bb; } + public static boolean isClassIncludedBase(Class cls) { + Class enclosingClass = cls.getEnclosingClass(); + return !Feature.class.isAssignableFrom(cls) && !AnnotationAccess.isAnnotationPresent(cls, TargetClass.class) && (enclosingClass == null || isClassIncludedBase(enclosingClass)); + } + /** * 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.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 33e3e3e347ab..4378601e7eea 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 @@ -1283,12 +1283,21 @@ public enum ReportingMode { @Option(help = "Include all classes, methods, and fields from given modules", type = OptionType.Debug) // public static final HostedOptionKey IncludeAllFromModule = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + @Option(help = "Include all classes, methods, fields, and resources from a given module for dynamic access", type = OptionType.Debug) // + public static final HostedOptionKey IncludeAllMetadataForModule = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + @Option(help = "Include all classes, methods, fields, and resources from given paths", type = OptionType.Debug) // public static final HostedOptionKey IncludeAllFromPath = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + @Option(help = "Include all classes, methods, fields, and resources from given paths for dynamic access", type = OptionType.Debug) // + public static final HostedOptionKey IncludeAllMetadataForClassPathEntry = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build()); + @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 = "Include all classes, methods, fields, and resources for dynamic access for the whole classpath", type = OptionType.Debug) // + public static final HostedOptionKey IncludeAllMetadataForClassPath = new HostedOptionKey<>(false); + public static boolean includeAll() { return IncludeAllFromModule.hasBeenSet() || IncludeAllFromPath.hasBeenSet() || IncludeAllFromClassPath.hasBeenSet(); } 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..045c7e0c7622 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.includeAllFromClassPath() || + classLoaderSupport.getPathsToIncludeMetadata().contains(classpathFile) || classLoaderSupport.isIncludeAllMetadataFromClassPath(); 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.getModuleNamesToInclude().contains(info.resolvedModule().name()) || + classLoaderSupport.getModuleNamesToIncludeMetadata().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 3a23d5551afa..d90b3c73a15e 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 @@ -27,6 +27,9 @@ import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromClassPath; import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromModule; import static com.oracle.svm.core.SubstrateOptions.IncludeAllFromPath; +import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForModule; +import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForClassPath; +import static com.oracle.svm.core.SubstrateOptions.IncludeAllMetadataForClassPathEntry; import static com.oracle.svm.core.util.VMError.guarantee; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; @@ -129,13 +132,20 @@ public final class NativeImageClassLoaderSupport { public final AnnotationExtractor annotationExtractor; private Set javaModuleNamesToInclude; + private Set moduleNamesToIncludeMetadata; + private Set javaPathsToInclude; + private Set pathsToIncludeMetadata; + private boolean includeAllFromClassPath; + private boolean includeAllMetadataFromClassPath; private Optional libGraalLoader; private List classLoaders; private final Set> classesToIncludeUnconditionally = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set> classesToIncludeMetadata = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set classNamesToIncludeMetadata = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Method implAddReadsAllUnnamed = ReflectionUtil.lookupMethod(Module.class, "implAddReadsAllUnnamed"); private final Method implAddEnableNativeAccess = ReflectionUtil.lookupMethod(Module.class, "implAddEnableNativeAccess"); @@ -245,25 +255,15 @@ private static Path stringToPath(String path) { public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) { guarantee(javaModuleNamesToInclude == null, "This method should be executed only once."); - javaModuleNamesToInclude = Collections.unmodifiableSet(new HashSet<>(IncludeAllFromModule.getValue(parsedHostedOptions).values())); - /* Verify all modules are present */ - final Set allModules = Stream.concat(modulepathModuleFinder.findAll().stream(), upgradeAndSystemModuleFinder.findAll().stream()) - .map(m -> m.descriptor().name()) - .collect(Collectors.toSet()); - javaModuleNamesToInclude.stream() - .filter(m -> !allModules.contains(m)) - .findAny().ifPresent(m -> missingFromSetOfEntriesError(m, allModules, "module-path", IncludeAllFromModule)); - javaPathsToInclude = IncludeAllFromPath.getValue(parsedHostedOptions).values().stream() - .map(NativeImageClassLoaderSupport::stringToPath) - .map(Path::toAbsolutePath) - .collect(Collectors.toUnmodifiableSet()); - /* Verify all paths are present */ - javaPathsToInclude.stream() - .filter(p -> !classpath().contains(p)) - .findAny().ifPresent(p -> missingFromSetOfEntriesError(p, classpath(), "classpath", IncludeAllFromPath)); + javaModuleNamesToInclude = collectAndVerifyModulesFromOption(IncludeAllFromModule); + moduleNamesToIncludeMetadata = collectAndVerifyModulesFromOption(IncludeAllMetadataForModule); + + javaPathsToInclude = collectAndVerifyPathsFromOption(IncludeAllFromPath); + pathsToIncludeMetadata = collectAndVerifyPathsFromOption(IncludeAllMetadataForClassPathEntry); includeAllFromClassPath = IncludeAllFromClassPath.getValue(parsedHostedOptions); + includeAllMetadataFromClassPath = IncludeAllMetadataForClassPath.getValue(parsedHostedOptions); new LoadClassHandler(executor, imageClassLoader).run(); @@ -281,6 +281,36 @@ public void loadAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoa } } + private Set collectAndVerifyModulesFromOption(HostedOptionKey option) { + var moduleNamesToInclude = Collections.unmodifiableSet(new HashSet<>(option.getValue(parsedHostedOptions).values())); + + /* Verify all modules are present */ + final Set allModules = Stream.concat(modulepathModuleFinder.findAll().stream(), upgradeAndSystemModuleFinder.findAll().stream()) + .map(m -> m.descriptor().name()) + .collect(Collectors.toSet()); + moduleNamesToInclude.stream() + .filter(m -> !allModules.contains(m)) + .findAny() + .ifPresent(m -> missingFromSetOfEntriesError(m, allModules, "module-path", option)); + + return moduleNamesToInclude; + } + + private Set collectAndVerifyPathsFromOption(HostedOptionKey option) { + var pathsToInclude = option.getValue(parsedHostedOptions).values().stream() + .map(NativeImageClassLoaderSupport::stringToPath) + .map(Path::toAbsolutePath) + .collect(Collectors.toUnmodifiableSet()); + + /* Verify all paths are present */ + pathsToInclude.stream() + .filter(p -> !classpath().contains(p)) + .findAny() + .ifPresent(p -> missingFromSetOfEntriesError(p, classpath(), "classpath", option)); + + return pathsToInclude; + } + private static void missingFromSetOfEntriesError(Object entry, Collection allEntries, String typeOfEntry, HostedOptionKey option) { String sortedEntries = allEntries.stream() @@ -590,6 +620,10 @@ public void setupLibGraalClassLoader() { } } + public Set getClassNamesToIncludeMetadata() { + return new HashSet<>(classNamesToIncludeMetadata); + } + private record AddExportsAndOpensAndReadsFormatValue(Module module, String packageName, List targetModules) { } @@ -729,7 +763,7 @@ private void run() { "org.graalvm.nativebridge")); Set additionalSystemModules = upgradeAndSystemModuleFinder.findAll().stream().map(v -> v.descriptor().name()).collect(Collectors.toSet()); - additionalSystemModules.retainAll(getJavaModuleNamesToInclude()); + additionalSystemModules.retainAll(getModuleNamesToIncludeMetadata()); requiresInit.addAll(additionalSystemModules); for (ModuleReference moduleReference : upgradeAndSystemModuleFinder.findAll()) { @@ -757,6 +791,7 @@ private void initModule(ModuleReference moduleReference) { try (ModuleReader moduleReader = moduleReference.open()) { Module module = optionalModule.get(); final boolean includeUnconditionally = javaModuleNamesToInclude.contains(module.getName()); + final boolean includeForReflection = moduleNamesToIncludeMetadata.contains(module.getName()); var container = moduleReference.location().orElseThrow(); if (ModuleLayer.boot().equals(module.getLayer())) { builderURILocations.add(container); @@ -766,7 +801,7 @@ private void initModule(ModuleReference moduleReference) { String className = extractClassName(moduleResource, fileSystemSeparatorChar); if (className != null) { currentlyProcessedEntry = moduleReferenceLocation + fileSystemSeparatorChar + moduleResource; - executor.execute(() -> handleClassFileName(container, module, className, includeUnconditionally)); + executor.execute(() -> handleClassFileName(container, module, className, includeUnconditionally, includeForReflection)); } entriesProcessed.increment(); }); @@ -777,6 +812,7 @@ private void initModule(ModuleReference moduleReference) { private void loadClassesFromPath(Path path) { final boolean includeUnconditionally = javaPathsToInclude.contains(path) || includeAllFromClassPath; + final boolean includeAllMetadata = pathsToIncludeMetadata.contains(path) || includeAllMetadataFromClassPath; if (ClasspathUtils.isJar(path)) { try { URI container = path.toAbsolutePath().toUri(); @@ -790,7 +826,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) { @@ -800,13 +836,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(", ")); @@ -832,7 +869,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)); + executor.execute(() -> handleClassFileName(container, null, className, includeUnconditionally, includeAllMetadata)); } entriesProcessed.increment(); return FileVisitResult.CONTINUE; @@ -916,7 +953,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) { + private void handleClassFileName(URI container, Module module, String className, boolean includeUnconditionally, boolean includeAllMetadata) { synchronized (classes) { EconomicSet classNames = classes.get(container); if (classNames == null) { @@ -942,12 +979,18 @@ private void handleClassFileName(URI container, Module module, String className, } catch (AssertionError error) { VMError.shouldNotReachHere(error); } catch (Throwable t) { + if (includeAllMetadata) { + classNamesToIncludeMetadata.add(className); + } ImageClassLoader.handleClassLoadingError(t); } if (clazz != null) { if (includeUnconditionally) { classesToIncludeUnconditionally.add(clazz); } + if (includeAllMetadata) { + classesToIncludeMetadata.add(clazz); + } imageClassLoader.handleClass(clazz); } imageClassLoader.watchdog.recordActivity(); @@ -987,21 +1030,39 @@ public void reportBuilderClassesInApplication() { } } - public Set getJavaModuleNamesToInclude() { + public Set getModuleNamesToInclude() { return javaModuleNamesToInclude; } + public Set getModuleNamesToIncludeMetadata() { + return moduleNamesToIncludeMetadata; + } + public Set getJavaPathsToInclude() { return javaPathsToInclude; } + public Set getPathsToIncludeMetadata() { + return pathsToIncludeMetadata; + } + public boolean includeAllFromClassPath() { return includeAllFromClassPath; } + public boolean isIncludeAllMetadataFromClassPath() { + return includeAllMetadataFromClassPath; + } + public List> getClassesToIncludeUnconditionally() { return classesToIncludeUnconditionally.stream() .sorted(Comparator.comparing(Class::getTypeName)) .collect(Collectors.toList()); } + + public List> getClassesToIncludeMetadata() { + return classesToIncludeMetadata.stream() + .sorted(Comparator.comparing(Class::getTypeName)) + .collect(Collectors.toList()); + } } 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 7f512b8eb3f0..e9271b73b741 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 @@ -70,6 +70,9 @@ 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.RuntimeJNIAccess; +import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.hosted.RuntimeSerialization; import org.graalvm.nativeimage.impl.AnnotationExtractor; import org.graalvm.nativeimage.impl.CConstantValueSupport; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; @@ -1066,6 +1069,12 @@ protected void setupNativeImage(String imageName, OptionValues options, Map bb.registerTypeForBaseImage(cls)); + loader.classLoaderSupport.getClassesToIncludeMetadata().stream() + .filter(ClassInclusionPolicy::isClassIncludedBase) + .forEach(NativeImageGenerator::registerClassFullyForReflection); + for (String className : loader.classLoaderSupport.getClassNamesToIncludeMetadata()) { + RuntimeReflection.registerClassLookup(className); + } registerEntryPointStubs(entryPoints); } @@ -1074,6 +1083,25 @@ protected void setupNativeImage(String imageName, OptionValues options, Map cls) { + RuntimeReflection.register(cls); + RuntimeJNIAccess.register(cls); + for (Method declaredMethod : cls.getDeclaredMethods()) { + RuntimeReflection.register(declaredMethod); + RuntimeJNIAccess.register(declaredMethod); + } + RuntimeReflection.registerAllDeclaredFields(cls); + RuntimeReflection.registerAllFields(cls); + RuntimeReflection.registerAllMethods(cls); + RuntimeReflection.registerAllDeclaredMethods(cls); + + for (var declaredField : cls.getDeclaredFields()) { + RuntimeReflection.register(declaredField); + RuntimeJNIAccess.register(declaredField); + } + RuntimeSerialization.register(cls); + } + protected void registerEntryPointStubs(Map entryPoints) { entryPoints.forEach((method, entryPointData) -> CEntryPointCallStubSupport.singleton().registerStubForMethod(method, () -> entryPointData)); }