diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AbstractNativeImageClassLoaderSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AbstractNativeImageClassLoaderSupport.java deleted file mode 100644 index 3110de940bcb..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/AbstractNativeImageClassLoaderSupport.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted; - -import static com.oracle.svm.core.util.VMError.shouldNotReachHere; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.channels.ClosedByInterruptException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ForkJoinPool; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; -import org.graalvm.compiler.options.OptionValues; - -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.util.ClasspathUtils; -import com.oracle.svm.core.util.InterruptImageBuilding; -import com.oracle.svm.core.util.UserError; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.option.HostedOptionParser; - -public abstract class AbstractNativeImageClassLoaderSupport { - - final List imagecp; - private final List buildcp; - private final EconomicMap> classes; - private final EconomicMap> packages; - private final EconomicSet emptySet; - - protected final URLClassLoader classPathClassLoader; - - protected AbstractNativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, String[] classpath) { - - classes = EconomicMap.create(); - packages = EconomicMap.create(); - emptySet = EconomicSet.create(); - - classPathClassLoader = new URLClassLoader(Util.verifyClassPathAndConvertToURLs(classpath), defaultSystemClassLoader); - - imagecp = Arrays.stream(classPathClassLoader.getURLs()) - .map(Util::urlToPath) - .collect(Collectors.toUnmodifiableList()); - String builderClassPathString = System.getProperty("java.class.path"); - String[] builderClassPathEntries = builderClassPathString.isEmpty() ? new String[0] : builderClassPathString.split(File.pathSeparator); - if (Arrays.asList(builderClassPathEntries).contains(".")) { - VMError.shouldNotReachHere("The classpath of " + NativeImageGeneratorRunner.class.getName() + - " must not contain \".\". This can happen implicitly if the builder runs exclusively on the --module-path" + - " but specifies the " + NativeImageGeneratorRunner.class.getName() + " main class without --module."); - } - buildcp = Arrays.stream(builderClassPathEntries) - .map(Paths::get) - .map(Path::toAbsolutePath) - .collect(Collectors.toUnmodifiableList()); - } - - List classpath() { - return Stream.concat(imagecp.stream(), buildcp.stream()).distinct().collect(Collectors.toList()); - } - - List applicationClassPath() { - return imagecp; - } - - public ClassLoader getClassLoader() { - return classPathClassLoader; - } - - protected abstract Class loadClassFromModule(Object module, String className) throws ClassNotFoundException; - - protected abstract Optional getMainClassFromModule(Object module); - - protected abstract List modulepath(); - - protected abstract List applicationModulePath(); - - protected abstract Optional findModule(String moduleName); - - private HostedOptionParser hostedOptionParser; - private OptionValues parsedHostedOptions; - private List remainingArguments; - - public void setupHostedOptionParser(List arguments) { - hostedOptionParser = new HostedOptionParser(getClassLoader()); - remainingArguments = Collections.unmodifiableList((hostedOptionParser.parse(arguments))); - parsedHostedOptions = new OptionValues(hostedOptionParser.getHostedValues()); - } - - public HostedOptionParser getHostedOptionParser() { - return hostedOptionParser; - } - - public List getRemainingArguments() { - return remainingArguments; - } - - public OptionValues getParsedHostedOptions() { - return parsedHostedOptions; - } - - public EconomicSet classes(URI container) { - return classes.get(container, emptySet); - } - - public EconomicSet packages(URI container) { - return packages.get(container, emptySet); - } - - public boolean noEntryForURI(EconomicSet set) { - return set == emptySet; - } - - protected abstract void processClassLoaderOptions(); - - public abstract void propagateQualifiedExports(String fromTargetModule, String toTargetModule); - - protected abstract void initAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader); - - protected static class Util { - - static URL[] verifyClassPathAndConvertToURLs(String[] classpath) { - Stream pathStream = new LinkedHashSet<>(Arrays.asList(classpath)).stream().flatMap(Util::toClassPathEntries); - return pathStream.map(v -> { - try { - return v.toAbsolutePath().toUri().toURL(); - } catch (MalformedURLException e) { - throw UserError.abort("Invalid classpath element '%s'. Make sure that all paths provided with '%s' are correct.", v, SubstrateOptions.IMAGE_CLASSPATH_PREFIX); - } - }).toArray(URL[]::new); - } - - static Stream toClassPathEntries(String classPathEntry) { - Path entry = ClasspathUtils.stringToClasspath(classPathEntry); - if (entry.endsWith(ClasspathUtils.cpWildcardSubstitute)) { - try { - return Files.list(entry.getParent()).filter(ClasspathUtils::isJar); - } catch (IOException e) { - return Stream.empty(); - } - } - if (Files.isReadable(entry)) { - return Stream.of(entry); - } - return Stream.empty(); - } - - static Path urlToPath(URL url) { - try { - return Paths.get(url.toURI()); - } catch (URISyntaxException e) { - throw VMError.shouldNotReachHere(); - } - } - } - - final Path excludeDirectoriesRoot = Paths.get("/"); - final Set excludeDirectories = getExcludeDirectories(); - - private Set getExcludeDirectories() { - return Stream.of("dev", "sys", "proc", "etc", "var", "tmp", "boot", "lost+found") - .map(excludeDirectoriesRoot::resolve).collect(Collectors.toUnmodifiableSet()); - } - - protected class ClassInit { - - protected final ForkJoinPool executor; - protected final ImageClassLoader imageClassLoader; - - protected ClassInit(ForkJoinPool executor, ImageClassLoader imageClassLoader) { - this.executor = executor; - this.imageClassLoader = imageClassLoader; - } - - protected void init() { - classpath().parallelStream().forEach(this::loadClassesFromPath); - } - - private void loadClassesFromPath(Path path) { - if (ClasspathUtils.isJar(path)) { - try { - URI container = path.toAbsolutePath().toUri(); - URI jarURI = new URI("jar:" + container); - FileSystem probeJarFileSystem; - try { - probeJarFileSystem = FileSystems.newFileSystem(jarURI, Collections.emptyMap()); - } catch (UnsupportedOperationException e) { - /* Silently ignore invalid jar-files on image-classpath */ - probeJarFileSystem = null; - } - if (probeJarFileSystem != null) { - try (FileSystem jarFileSystem = probeJarFileSystem) { - loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet()); - } - } - } catch (ClosedByInterruptException ignored) { - throw new InterruptImageBuilding(); - } catch (IOException | URISyntaxException e) { - throw shouldNotReachHere(e); - } - } else { - URI container = path.toUri(); - loadClassesFromPath(container, path, excludeDirectoriesRoot, excludeDirectories); - } - } - - protected static final String CLASS_EXTENSION = ".class"; - - private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set excludes) { - boolean useFilter = root.equals(excludeRoot); - if (useFilter) { - String excludesStr = excludes.stream().map(Path::toString).collect(Collectors.joining(", ")); - System.err.println("Warning: Using directory " + excludeRoot + " on classpath is discouraged." + - " Reading classes/resources from directories " + excludesStr + " will be suppressed."); - } - FileVisitor visitor = new SimpleFileVisitor<>() { - private final char fileSystemSeparatorChar = root.getFileSystem().getSeparator().charAt(0); - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - if (useFilter && excludes.contains(dir)) { - return FileVisitResult.SKIP_SUBTREE; - } - return super.preVisitDirectory(dir, attrs); - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - assert !excludes.contains(file.getParent()) : "Visiting file '" + file + "' with excluded parent directory"; - String fileName = root.relativize(file).toString(); - if (fileName.endsWith(CLASS_EXTENSION)) { - executor.execute(() -> handleClassFileName(container, null, fileName, fileSystemSeparatorChar)); - } - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - /* Silently ignore inaccessible files or directories. */ - return FileVisitResult.CONTINUE; - } - }; - - try { - Files.walkFileTree(root, visitor); - } catch (IOException ex) { - throw shouldNotReachHere(ex); - } - } - - /** - * Take a file name from a possibly-multi-versioned jar file and remove the versioning - * information. See https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html for - * the specification of the versioning strings. - * - * Then, depend on the JDK class loading mechanism to prefer the appropriately-versioned - * class when the class is loaded. The same class name be loaded multiple times, but each - * request will return the same appropriately-versioned class. If a higher-versioned class - * is not available in a lower-versioned JDK, a ClassNotFoundException will be thrown, which - * will be handled appropriately. - */ - private String strippedClassFileName(String fileName) { - final String versionedPrefix = "META-INF/versions/"; - final String versionedSuffix = "/"; - String result = fileName; - if (fileName.startsWith(versionedPrefix)) { - final int versionedSuffixIndex = fileName.indexOf(versionedSuffix, versionedPrefix.length()); - if (versionedSuffixIndex >= 0) { - result = fileName.substring(versionedSuffixIndex + versionedSuffix.length()); - } - } - return result.substring(0, result.length() - CLASS_EXTENSION.length()); - } - - protected void handleClassFileName(URI container, Object module, String fileName, char fileSystemSeparatorChar) { - String strippedClassFileName = strippedClassFileName(fileName); - if (strippedClassFileName.equals("module-info")) { - return; - } - - String className = strippedClassFileName.replace(fileSystemSeparatorChar, '.'); - synchronized (classes) { - EconomicSet classNames = classes.get(container); - if (classNames == null) { - classNames = EconomicSet.create(); - classes.put(container, classNames); - } - classNames.add(className); - } - int packageSep = className.lastIndexOf('.'); - String packageName = packageSep > 0 ? className.substring(0, packageSep) : ""; - synchronized (packages) { - EconomicSet packageNames = packages.get(container); - if (packageNames == null) { - packageNames = EconomicSet.create(); - packages.put(container, packageNames); - } - packageNames.add(packageName); - } - - Class clazz = null; - try { - clazz = imageClassLoader.forName(className, module); - } catch (AssertionError error) { - VMError.shouldNotReachHere(error); - } catch (Throwable t) { - ImageClassLoader.handleClassLoadingError(t); - } - if (clazz != null) { - imageClassLoader.handleClass(clazz); - } - } - } -} 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/ClassLoaderSupportFeature.java similarity index 54% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportFeature.java index e4dabf038e49..981eec9d9bb6 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/ClassLoaderSupportFeature.java @@ -30,6 +30,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayDeque; @@ -42,25 +45,37 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; import java.util.function.Predicate; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + import com.oracle.svm.core.ClassLoaderSupport; +import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ModuleSupport; -public class ClassLoaderSupportImpl extends ClassLoaderSupport { +class ClassLoaderSupportImpl extends ClassLoaderSupport { - private final AbstractNativeImageClassLoaderSupport classLoaderSupport; + private final NativeImageClassLoaderSupport classLoaderSupport; private final ClassLoader imageClassLoader; - protected ClassLoaderSupportImpl(AbstractNativeImageClassLoaderSupport classLoaderSupport) { + private final Map> packageToModules; + + protected ClassLoaderSupportImpl(NativeImageClassLoaderSupport classLoaderSupport) { this.classLoaderSupport = classLoaderSupport; this.imageClassLoader = classLoaderSupport.getClassLoader(); + packageToModules = new HashMap<>(); + buildPackageToModulesMap(classLoaderSupport); } @Override @@ -70,6 +85,12 @@ protected boolean isNativeImageClassLoaderImpl(ClassLoader loader) { @Override public void collectResources(ResourceCollector resourceCollector) { + /* Collect resources from modules */ + NativeImageClassLoaderSupport.allLayers(classLoaderSupport.moduleLayerForImageBuild).stream() + .flatMap(moduleLayer -> moduleLayer.configuration().modules().stream()) + .forEach(resolvedModule -> collectResourceFromModule(resourceCollector, resolvedModule)); + + /* Collect remaining resources from classpath */ for (Path classpathFile : classLoaderSupport.classpath()) { try { if (Files.isDirectory(classpathFile)) { @@ -83,7 +104,29 @@ public void collectResources(ResourceCollector resourceCollector) { } } - private static void scanDirectory(Path root, ResourceCollector collector, AbstractNativeImageClassLoaderSupport support) throws IOException { + private static void collectResourceFromModule(ResourceCollector resourceCollector, ResolvedModule resolvedModule) { + ModuleReference moduleReference = resolvedModule.reference(); + try (ModuleReader moduleReader = moduleReference.open()) { + String moduleName = resolvedModule.name(); + List foundResources = moduleReader.list() + .filter(resourceName -> resourceCollector.isIncluded(moduleName, resourceName)) + .collect(Collectors.toList()); + + for (String resName : foundResources) { + Optional content = moduleReader.open(resName); + if (content.isEmpty()) { + continue; + } + try (InputStream is = content.get()) { + resourceCollector.addResource(moduleName, resName, is, false); + } + } + } catch (IOException e) { + throw VMError.shouldNotReachHere(e); + } + } + + private static void scanDirectory(Path root, ResourceCollector collector, NativeImageClassLoaderSupport support) throws IOException { Map> matchedDirectoryResources = new HashMap<>(); Set allEntries = new HashSet<>(); @@ -159,7 +202,77 @@ private static void scanJar(Path jarPath, ResourceCollector collector) throws IO } @Override - public List getResourceBundle(String bundleName, Locale locale) { - return Collections.singletonList(ResourceBundle.getBundle(bundleName, locale, imageClassLoader)); + public List getResourceBundle(String bundleSpec, Locale locale) { + String[] specParts = bundleSpec.split(":", 2); + String moduleName; + String bundleName; + if (specParts.length > 1) { + moduleName = specParts[0]; + bundleName = specParts[1]; + } else { + moduleName = null; + bundleName = specParts[0]; + } + String packageName = packageName(bundleName); + Set modules; + if (moduleName != null) { + modules = classLoaderSupport.findModule(moduleName).stream().collect(Collectors.toSet()); + } else { + modules = packageToModules.getOrDefault(packageName, Collections.emptySet()); + } + if (modules.isEmpty()) { + /* If bundle is not located in any module get it via classloader (from ALL_UNNAMED) */ + return Collections.singletonList(ResourceBundle.getBundle(bundleName, locale, imageClassLoader)); + } + ArrayList resourceBundles = new ArrayList<>(); + for (Module module : modules) { + ModuleSupport.exportAndOpenPackageToClass(module.getName(), packageName, false, ClassLoaderSupportImpl.class); + resourceBundles.add(ResourceBundle.getBundle(bundleName, locale, module)); + } + return resourceBundles; + } + + private static String packageName(String bundleName) { + int classSep = bundleName.replace('/', '.').lastIndexOf('.'); + if (classSep == -1) { + return ""; /* unnamed package */ + } + return bundleName.substring(0, classSep); + } + + private void buildPackageToModulesMap(NativeImageClassLoaderSupport cls) { + for (ModuleLayer layer : NativeImageClassLoaderSupport.allLayers(cls.moduleLayerForImageBuild)) { + for (Module module : layer.modules()) { + for (String packageName : module.getDescriptor().packages()) { + addToPackageNameModules(module, packageName); + } + } + } + } + + private void addToPackageNameModules(Module moduleName, String packageName) { + Set prevValue = packageToModules.get(packageName); + if (prevValue == null) { + /* Mostly packageName is only used in a single module */ + packageToModules.put(packageName, Collections.singleton(moduleName)); + } else if (prevValue.size() == 1) { + /* Transition to HashSet - happens rarely */ + HashSet newValue = new HashSet<>(); + newValue.add(prevValue.iterator().next()); + newValue.add(moduleName); + packageToModules.put(packageName, newValue); + } else if (prevValue.size() > 1) { + /* Add to exiting HashSet - happens rarely */ + prevValue.add(moduleName); + } + } +} + +@AutomaticFeature +public class ClassLoaderSupportFeature implements Feature { + @Override + public void afterRegistration(AfterRegistrationAccess a) { + FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; + ImageSingletons.add(ClassLoaderSupport.class, new ClassLoaderSupportImpl(access.getImageClassLoader().classLoaderSupport)); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java index 98c3c6c6d97e..c45dee0ff2a3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java @@ -70,14 +70,14 @@ public final class ImageClassLoader { } public final Platform platform; - public final AbstractNativeImageClassLoaderSupport classLoaderSupport; + public final NativeImageClassLoaderSupport classLoaderSupport; private final EconomicSet> applicationClasses = EconomicSet.create(); private final EconomicSet> hostedOnlyClasses = EconomicSet.create(); private final EconomicSet systemMethods = EconomicSet.create(); private final EconomicSet systemFields = EconomicSet.create(); - ImageClassLoader(Platform platform, AbstractNativeImageClassLoaderSupport classLoaderSupport) { + ImageClassLoader(Platform platform, NativeImageClassLoaderSupport classLoaderSupport) { this.platform = platform; this.classLoaderSupport = classLoaderSupport; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeFeature.java index bdf6e7a17340..09f401d4ab45 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LinkAtBuildTimeFeature.java @@ -54,7 +54,6 @@ import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; -import com.oracle.svm.hosted.jdk.ClassLoaderSupportFeatureJDK11OrLater; @AutomaticFeature public final class LinkAtBuildTimeFeature implements Feature { @@ -84,7 +83,7 @@ static final class Options { @Override public List> getRequiredFeatures() { - return Collections.singletonList(ClassLoaderSupportFeatureJDK11OrLater.class); + return Collections.singletonList(ClassLoaderSupportFeature.class); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java index 5a76b041cbad..48c9cdb21b55 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java @@ -59,7 +59,6 @@ import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.jdk.BootModuleLayerSupport; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.jdk.NativeImageClassLoaderSupportJDK11OrLater; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; @@ -190,9 +189,9 @@ private static Stream extractRequiredModuleNames(Module m) { private ModuleLayer synthesizeRuntimeBootLayer(ImageClassLoader cl, Set reachableModules, Set syntheticModules) { /** * For consistent module lookup we reuse the {@link ModuleFinder}s defined and used in - * {@link NativeImageClassLoaderSupportJDK11OrLater}. + * {@link NativeImageClassLoaderSupport}. */ - NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport = (NativeImageClassLoaderSupportJDK11OrLater) cl.classLoaderSupport; + NativeImageClassLoaderSupport classLoaderSupport = cl.classLoaderSupport; ModuleFinder beforeFinder = classLoaderSupport.modulepathModuleFinder; ModuleFinder afterFinder = classLoaderSupport.upgradeAndSystemModuleFinder; Configuration cf = synthesizeRuntimeBootLayerConfiguration(beforeFinder, afterFinder, reachableModules); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java index dfc73222cd7e..a1cf23481efe 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderOptions.java @@ -50,7 +50,7 @@ public class NativeImageClassLoaderOptions { public static class ApplyNativeImageClassLoaderOptions implements NativeImageClassLoaderPostProcessing { @Override - public void apply(AbstractNativeImageClassLoaderSupport support) { + public void apply(NativeImageClassLoaderSupport support) { support.processClassLoaderOptions(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderPostProcessing.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderPostProcessing.java index c9a4b94871de..0c3fb0271b9d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderPostProcessing.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageClassLoaderPostProcessing.java @@ -27,12 +27,12 @@ /** * ServiceLoader interface to allow post-processing tasks that should be performed right after - * {@link AbstractNativeImageClassLoaderSupport} is created. For example, this is used to apply the + * {@link NativeImageClassLoaderSupport} is created. For example, this is used to apply the * native-image classloader options after hosted options are accessible but before * {@link com.oracle.svm.hosted.ImageClassLoader#initAllClasses()} gets called. */ public interface NativeImageClassLoaderPostProcessing { - void apply(AbstractNativeImageClassLoaderSupport support); + void apply(NativeImageClassLoaderSupport support); } 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 07dfd5a3b54d..1c264368b80c 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 @@ -24,55 +24,688 @@ */ package com.oracle.svm.hosted; +import static com.oracle.svm.core.util.VMError.shouldNotReachHere; + +import java.io.File; +import java.io.IOException; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.channels.ClosedByInterruptException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ForkJoinPool; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; +import org.graalvm.collections.Pair; +import org.graalvm.compiler.options.OptionKey; +import org.graalvm.compiler.options.OptionValues; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.option.LocatableMultiOptionValue; +import com.oracle.svm.core.option.OptionOrigin; +import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.ClasspathUtils; +import com.oracle.svm.core.util.InterruptImageBuilding; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.option.HostedOptionParser; +import com.oracle.svm.util.ModuleSupport; + +import jdk.internal.module.Modules; + +public class NativeImageClassLoaderSupport { + + private final List imagecp; + private final List buildcp; + private final List imagemp; + private final List buildmp; + + private final EconomicMap> classes; + private final EconomicMap> packages; + private final EconomicSet emptySet; + + private final URLClassLoader classPathClassLoader; + private final ClassLoader modulePathClassLoader; + + public final ModuleFinder upgradeAndSystemModuleFinder; + public final ModuleLayer moduleLayerForImageBuild; + public final ModuleFinder modulepathModuleFinder; + + protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, String[] classpath, String[] modulePath) { + + classes = EconomicMap.create(); + packages = EconomicMap.create(); + emptySet = EconomicSet.create(); + + classPathClassLoader = new URLClassLoader(Util.verifyClassPathAndConvertToURLs(classpath), defaultSystemClassLoader); + + imagecp = Arrays.stream(classPathClassLoader.getURLs()) + .map(Util::urlToPath) + .collect(Collectors.toUnmodifiableList()); + + String builderClassPathString = System.getProperty("java.class.path"); + String[] builderClassPathEntries = builderClassPathString.isEmpty() ? new String[0] : builderClassPathString.split(File.pathSeparator); + if (Arrays.asList(builderClassPathEntries).contains(".")) { + VMError.shouldNotReachHere("The classpath of " + NativeImageGeneratorRunner.class.getName() + + " must not contain \".\". This can happen implicitly if the builder runs exclusively on the --module-path" + + " but specifies the " + NativeImageGeneratorRunner.class.getName() + " main class without --module."); + } + buildcp = Arrays.stream(builderClassPathEntries) + .map(Path::of) + .map(Path::toAbsolutePath) + .collect(Collectors.toUnmodifiableList()); + + imagemp = Arrays.stream(modulePath) + .map(Path::of) + .collect(Collectors.toUnmodifiableList()); + + buildmp = Optional.ofNullable(System.getProperty("jdk.module.path")).stream() + .flatMap(s -> Arrays.stream(s.split(File.pathSeparator))) + .map(Path::of) + .collect(Collectors.toUnmodifiableList()); + + upgradeAndSystemModuleFinder = createUpgradeAndSystemModuleFinder(); + ModuleLayer moduleLayer = createModuleLayer(imagemp.toArray(Path[]::new), classPathClassLoader); + adjustBootLayerQualifiedExports(moduleLayer); + moduleLayerForImageBuild = moduleLayer; + + modulePathClassLoader = getSingleClassloader(moduleLayer); + + modulepathModuleFinder = ModuleFinder.of(modulepath().toArray(Path[]::new)); + } + + List classpath() { + return Stream.concat(imagecp.stream(), buildcp.stream()).distinct().collect(Collectors.toList()); + } + + List applicationClassPath() { + return imagecp; + } + + public ClassLoader getClassLoader() { + return modulePathClassLoader; + } -public class NativeImageClassLoaderSupport extends AbstractNativeImageClassLoaderSupport { + public void initAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) { + new ClassInit(executor, imageClassLoader).init(); + } + + private HostedOptionParser hostedOptionParser; + private OptionValues parsedHostedOptions; + private List remainingArguments; + + public void setupHostedOptionParser(List arguments) { + hostedOptionParser = new HostedOptionParser(getClassLoader()); + remainingArguments = Collections.unmodifiableList((hostedOptionParser.parse(arguments))); + parsedHostedOptions = new OptionValues(hostedOptionParser.getHostedValues()); + } + + public HostedOptionParser getHostedOptionParser() { + return hostedOptionParser; + } + + public List getRemainingArguments() { + return remainingArguments; + } + + public OptionValues getParsedHostedOptions() { + return parsedHostedOptions; + } + + public EconomicSet classes(URI container) { + return classes.get(container, emptySet); + } + + public EconomicSet packages(URI container) { + return packages.get(container, emptySet); + } + + public boolean noEntryForURI(EconomicSet set) { + return set == emptySet; + } + + protected static class Util { + + static URL[] verifyClassPathAndConvertToURLs(String[] classpath) { + Stream pathStream = new LinkedHashSet<>(Arrays.asList(classpath)).stream().flatMap(Util::toClassPathEntries); + return pathStream.map(v -> { + try { + return v.toAbsolutePath().toUri().toURL(); + } catch (MalformedURLException e) { + throw UserError.abort("Invalid classpath element '%s'. Make sure that all paths provided with '%s' are correct.", v, SubstrateOptions.IMAGE_CLASSPATH_PREFIX); + } + }).toArray(URL[]::new); + } + + static Stream toClassPathEntries(String classPathEntry) { + Path entry = ClasspathUtils.stringToClasspath(classPathEntry); + if (entry.endsWith(ClasspathUtils.cpWildcardSubstitute)) { + try { + return Files.list(entry.getParent()).filter(ClasspathUtils::isJar); + } catch (IOException e) { + return Stream.empty(); + } + } + if (Files.isReadable(entry)) { + return Stream.of(entry); + } + return Stream.empty(); + } + + static Path urlToPath(URL url) { + try { + return Paths.get(url.toURI()); + } catch (URISyntaxException e) { + throw VMError.shouldNotReachHere(); + } + } + } + + private ModuleLayer createModuleLayer(Path[] modulePaths, ClassLoader parent) { + ModuleFinder modulePathsFinder = ModuleFinder.of(modulePaths); + Set moduleNames = modulePathsFinder.findAll().stream().map(moduleReference -> moduleReference.descriptor().name()).collect(Collectors.toSet()); + + /** + * When building a moduleLayer for the module-path passed to native-image we need to be able + * to resolve against system modules that are not used by the moduleLayer in which the + * image-builder got loaded into. To do so we use {@link upgradeAndSystemModuleFinder} as + * {@code ModuleFinder after} in + * {@link Configuration#resolve(ModuleFinder, ModuleFinder, Collection)}. + */ + Configuration configuration = ModuleLayer.boot().configuration().resolve(modulePathsFinder, upgradeAndSystemModuleFinder, moduleNames); + /** + * For the modules we want to build an image for, a ModuleLayer is needed that can be + * accessed with a single classloader so we can use it for {@link ImageClassLoader}. + */ + return ModuleLayer.defineModulesWithOneLoader(configuration, List.of(ModuleLayer.boot()), parent).layer(); + } - NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, String[] classpath, @SuppressWarnings("unused") String[] modulePath) { - super(defaultSystemClassLoader, classpath); + /** + * Gets a finder that locates the upgrade modules and the system modules, in that order. Upgrade + * modules are used when mx environment variable {@code MX_BUILD_EXPLODED=true} is used. + */ + private static ModuleFinder createUpgradeAndSystemModuleFinder() { + ModuleFinder finder = ModuleFinder.ofSystem(); + ModuleFinder upgradeModulePath = finderFor("jdk.module.upgrade.path"); + if (upgradeModulePath != null) { + finder = ModuleFinder.compose(upgradeModulePath, finder); + } + return finder; + } + + /** + * Creates a finder from a module path specified by the {@code prop} system property. + */ + private static ModuleFinder finderFor(String prop) { + String s = System.getProperty(prop); + if (s == null || s.isEmpty()) { + return null; + } else { + String[] dirs = s.split(File.pathSeparator); + Path[] paths = new Path[dirs.length]; + int i = 0; + for (String dir : dirs) { + paths[i++] = Path.of(dir); + } + return ModuleFinder.of(paths); + } + } + + private static void adjustBootLayerQualifiedExports(ModuleLayer layer) { + /* + * For all qualified exports packages of modules in the the boot layer we check if layer + * contains modules that satisfy such qualified exports. If we find a match we perform a + * addExports. + */ + for (Module module : ModuleLayer.boot().modules()) { + for (ModuleDescriptor.Exports export : module.getDescriptor().exports()) { + for (String target : export.targets()) { + Optional optExportTargetModule = layer.findModule(target); + if (optExportTargetModule.isEmpty()) { + continue; + } + Module exportTargetModule = optExportTargetModule.get(); + if (module.isExported(export.source(), exportTargetModule)) { + continue; + } + Modules.addExports(module, export.source(), exportTargetModule); + } + } + } + } + + private ClassLoader getSingleClassloader(ModuleLayer moduleLayer) { + ClassLoader singleClassloader = classPathClassLoader; + for (Module module : moduleLayer.modules()) { + ClassLoader moduleClassLoader = module.getClassLoader(); + if (singleClassloader == classPathClassLoader) { + singleClassloader = moduleClassLoader; + } else { + VMError.guarantee(singleClassloader == moduleClassLoader); + } + } + return singleClassloader; + } + + private static void implAddReadsAllUnnamed(Module module) { + try { + Method implAddReadsAllUnnamed = Module.class.getDeclaredMethod("implAddReadsAllUnnamed"); + ModuleSupport.openModuleByClass(Module.class, NativeImageClassLoaderSupport.class); + implAddReadsAllUnnamed.setAccessible(true); + implAddReadsAllUnnamed.invoke(module); + } catch (ReflectiveOperationException | NoSuchElementException e) { + VMError.shouldNotReachHere("Could reflectively call Module.implAddReadsAllUnnamed", e); + } } - @Override protected List modulepath() { - return Collections.emptyList(); + return Stream.concat(imagemp.stream(), buildmp.stream()).collect(Collectors.toUnmodifiableList()); } - @Override protected List applicationModulePath() { - return Collections.emptyList(); + return imagemp; } - @Override - protected Optional findModule(String moduleName) { - return Optional.empty(); + public Optional findModule(String moduleName) { + return moduleLayerForImageBuild.findModule(moduleName); } - @Override - protected void processClassLoaderOptions() { - /* Nothing to do for Java 8 */ + void processClassLoaderOptions() { + processOption(NativeImageClassLoaderOptions.AddExports).forEach(val -> { + if (val.targetModules.isEmpty()) { + Modules.addExportsToAllUnnamed(val.module, val.packageName); + } else { + for (Module targetModule : val.targetModules) { + Modules.addExports(val.module, val.packageName, targetModule); + } + } + }); + processOption(NativeImageClassLoaderOptions.AddOpens).forEach(val -> { + if (val.targetModules.isEmpty()) { + Modules.addOpensToAllUnnamed(val.module, val.packageName); + } else { + for (Module targetModule : val.targetModules) { + Modules.addOpens(val.module, val.packageName, targetModule); + } + } + }); + processOption(NativeImageClassLoaderOptions.AddReads).forEach(val -> { + if (val.targetModules.isEmpty()) { + implAddReadsAllUnnamed(val.module); + } else { + for (Module targetModule : val.targetModules) { + Modules.addReads(val.module, targetModule); + } + } + }); } - @Override public void propagateQualifiedExports(String fromTargetModule, String toTargetModule) { - /* Nothing to do for Java 8 */ + Optional optFromTarget = moduleLayerForImageBuild.findModule(fromTargetModule); + Optional optToTarget = moduleLayerForImageBuild.findModule(toTargetModule); + VMError.guarantee(optFromTarget.isPresent() && optToTarget.isPresent()); + Module toTarget = optToTarget.get(); + Module fromTarget = optFromTarget.get(); + + allLayers(moduleLayerForImageBuild).stream().flatMap(layer -> layer.modules().stream()).forEach(m -> { + if (!m.equals(toTarget)) { + for (String p : m.getPackages()) { + if (m.isExported(p, fromTarget)) { + Modules.addExports(m, p, toTarget); + } + if (m.isOpen(p, fromTarget)) { + Modules.addOpens(m, p, toTarget); + } + } + } + }); } - @Override - protected Class loadClassFromModule(Object module, String className) throws ClassNotFoundException { - throw new UnsupportedOperationException("NativeImageClassLoader for Java 8 does not support modules"); + public static List allLayers(ModuleLayer moduleLayer) { + /** Implementation taken from {@link ModuleLayer#layers()} */ + List allLayers = new ArrayList<>(); + Set visited = new HashSet<>(); + Deque stack = new ArrayDeque<>(); + visited.add(moduleLayer); + stack.push(moduleLayer); + + while (!stack.isEmpty()) { + ModuleLayer layer = stack.pop(); + allLayers.add(layer); + + // push in reverse order + for (int i = layer.parents().size() - 1; i >= 0; i--) { + ModuleLayer parent = layer.parents().get(i); + if (!visited.contains(parent)) { + visited.add(parent); + stack.push(parent); + } + } + } + return allLayers; } - @Override - protected Optional getMainClassFromModule(Object module) { - return Optional.empty(); + private Stream processOption(OptionKey specificOption) { + Stream> valuesWithOrigins = specificOption.getValue(parsedHostedOptions).getValuesWithOrigins(); + Stream parsedOptions = valuesWithOrigins.flatMap(valWithOrig -> { + try { + return Stream.of(asAddExportsAndOpensAndReadsFormatValue(specificOption, valWithOrig)); + } catch (UserError.UserException e) { + if (ModuleSupport.modulePathBuild) { + throw e; + } else { + /* + * Until we switch to always running the image-builder on module-path we have to + * be tolerant if invalid --add-exports -add-opens or --add-reads options are + * used. GR-30433 + */ + System.out.println("Warning: " + e.getMessage()); + return Stream.empty(); + } + } + }); + return parsedOptions; } - @Override - public void initAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) { - new ClassInit(executor, imageClassLoader).init(); + private static final class AddExportsAndOpensAndReadsFormatValue { + private final Module module; + private final String packageName; + private final List targetModules; + + private AddExportsAndOpensAndReadsFormatValue(Module module, String packageName, List targetModules) { + this.module = module; + this.packageName = packageName; + this.targetModules = targetModules; + } + } + + private AddExportsAndOpensAndReadsFormatValue asAddExportsAndOpensAndReadsFormatValue(OptionKey option, Pair valueOrigin) { + OptionOrigin optionOrigin = valueOrigin.getRight(); + String optionValue = valueOrigin.getLeft(); + + boolean reads = option.equals(NativeImageClassLoaderOptions.AddReads); + String format = reads ? NativeImageClassLoaderOptions.AddReadsFormat : NativeImageClassLoaderOptions.AddExportsAndOpensFormat; + String syntaxErrorMessage = " Allowed value format: " + format; + + String[] modulePackageAndTargetModules = optionValue.split("=", 2); + if (modulePackageAndTargetModules.length != 2) { + throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, syntaxErrorMessage); + } + String modulePackage = modulePackageAndTargetModules[0]; + String targetModuleNames = modulePackageAndTargetModules[1]; + + String[] moduleAndPackage = modulePackage.split("/"); + if (moduleAndPackage.length > 1 + (reads ? 0 : 1)) { + throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, syntaxErrorMessage); + } + String moduleName = moduleAndPackage[0]; + String packageName = moduleAndPackage.length > 1 ? moduleAndPackage[1] : null; + + List targetModuleNamesList = Arrays.asList(targetModuleNames.split(",")); + if (targetModuleNamesList.isEmpty()) { + throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, syntaxErrorMessage); + } + + Module module = findModule(moduleName).orElseThrow(() -> { + return userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, " Specified module '" + moduleName + "' is unknown."); + }); + List targetModules; + if (targetModuleNamesList.contains("ALL-UNNAMED")) { + targetModules = Collections.emptyList(); + } else { + targetModules = targetModuleNamesList.stream().map(mn -> { + return findModule(mn).orElseThrow(() -> { + throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, " Specified target-module '" + mn + "' is unknown."); + }); + }).collect(Collectors.toList()); + } + return new AddExportsAndOpensAndReadsFormatValue(module, packageName, targetModules); + } + + private static UserError.UserException userErrorAddExportsAndOpensAndReads(OptionKey option, OptionOrigin origin, String value, String detailMessage) { + Objects.requireNonNull(detailMessage, "missing detailMessage"); + return UserError.abort("Invalid option %s provided by %s.%s", SubstrateOptionsParser.commandArgument(option, value), origin, detailMessage); + } + + Class loadClassFromModule(Object module, String className) { + assert module instanceof Module : "Argument `module` is not an instance of java.lang.Module"; + Module m = (Module) module; + assert isModuleClassLoader(modulePathClassLoader, m.getClassLoader()) : "Argument `module` is java.lang.Module from unknown ClassLoader"; + return Class.forName(m, className); + } + + private static boolean isModuleClassLoader(ClassLoader loader, ClassLoader moduleClassLoader) { + if (moduleClassLoader == loader) { + return true; + } else { + if (loader == null) { + return false; + } + return isModuleClassLoader(loader.getParent(), moduleClassLoader); + } + } + + Optional getMainClassFromModule(Object module) { + assert module instanceof Module : "Argument `module` is not an instance of java.lang.Module"; + return ((Module) module).getDescriptor().mainClass(); + } + + final Path excludeDirectoriesRoot = Paths.get("/"); + final Set excludeDirectories = getExcludeDirectories(); + + private Set getExcludeDirectories() { + return Stream.of("dev", "sys", "proc", "etc", "var", "tmp", "boot", "lost+found") + .map(excludeDirectoriesRoot::resolve).collect(Collectors.toUnmodifiableSet()); + } + + private class ClassInit { + + protected final ForkJoinPool executor; + protected final ImageClassLoader imageClassLoader; + + protected ClassInit(ForkJoinPool executor, ImageClassLoader imageClassLoader) { + this.executor = executor; + this.imageClassLoader = imageClassLoader; + } + + protected void init() { + List requiresInit = Arrays.asList( + "jdk.internal.vm.ci", "jdk.internal.vm.compiler", "com.oracle.graal.graal_enterprise", + "org.graalvm.sdk", "org.graalvm.truffle"); + + for (ModuleReference moduleReference : upgradeAndSystemModuleFinder.findAll()) { + if (requiresInit.contains(moduleReference.descriptor().name())) { + initModule(moduleReference); + } + } + for (ModuleReference moduleReference : modulepathModuleFinder.findAll()) { + initModule(moduleReference); + } + + classpath().parallelStream().forEach(this::loadClassesFromPath); + } + + private void initModule(ModuleReference moduleReference) { + Optional optionalModule = findModule(moduleReference.descriptor().name()); + if (optionalModule.isEmpty()) { + return; + } + try (ModuleReader moduleReader = moduleReference.open()) { + Module module = optionalModule.get(); + moduleReader.list().forEach(moduleResource -> { + if (moduleResource.endsWith(CLASS_EXTENSION)) { + executor.execute(() -> handleClassFileName(moduleReference.location().orElseThrow(), module, moduleResource, '/')); + } + }); + } catch (IOException e) { + throw new RuntimeException("Unable get list of resources in module" + moduleReference.descriptor().name(), e); + } + } + + private void loadClassesFromPath(Path path) { + if (ClasspathUtils.isJar(path)) { + try { + URI container = path.toAbsolutePath().toUri(); + URI jarURI = new URI("jar:" + container); + FileSystem probeJarFileSystem; + try { + probeJarFileSystem = FileSystems.newFileSystem(jarURI, Collections.emptyMap()); + } catch (UnsupportedOperationException e) { + /* Silently ignore invalid jar-files on image-classpath */ + probeJarFileSystem = null; + } + if (probeJarFileSystem != null) { + try (FileSystem jarFileSystem = probeJarFileSystem) { + loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet()); + } + } + } catch (ClosedByInterruptException ignored) { + throw new InterruptImageBuilding(); + } catch (IOException | URISyntaxException e) { + throw shouldNotReachHere(e); + } + } else { + URI container = path.toUri(); + loadClassesFromPath(container, path, excludeDirectoriesRoot, excludeDirectories); + } + } + + protected static final String CLASS_EXTENSION = ".class"; + + private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set excludes) { + boolean useFilter = root.equals(excludeRoot); + if (useFilter) { + String excludesStr = excludes.stream().map(Path::toString).collect(Collectors.joining(", ")); + System.err.println("Warning: Using directory " + excludeRoot + " on classpath is discouraged." + + " Reading classes/resources from directories " + excludesStr + " will be suppressed."); + } + FileVisitor visitor = new SimpleFileVisitor<>() { + private final char fileSystemSeparatorChar = root.getFileSystem().getSeparator().charAt(0); + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (useFilter && excludes.contains(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return super.preVisitDirectory(dir, attrs); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + assert !excludes.contains(file.getParent()) : "Visiting file '" + file + "' with excluded parent directory"; + String fileName = root.relativize(file).toString(); + if (fileName.endsWith(CLASS_EXTENSION)) { + executor.execute(() -> handleClassFileName(container, null, fileName, fileSystemSeparatorChar)); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + /* Silently ignore inaccessible files or directories. */ + return FileVisitResult.CONTINUE; + } + }; + + try { + Files.walkFileTree(root, visitor); + } catch (IOException ex) { + throw shouldNotReachHere(ex); + } + } + + /** + * Take a file name from a possibly-multi-versioned jar file and remove the versioning + * information. See https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html for + * the specification of the versioning strings. + * + * Then, depend on the JDK class loading mechanism to prefer the appropriately-versioned + * class when the class is loaded. The same class name be loaded multiple times, but each + * request will return the same appropriately-versioned class. If a higher-versioned class + * is not available in a lower-versioned JDK, a ClassNotFoundException will be thrown, which + * will be handled appropriately. + */ + private String strippedClassFileName(String fileName) { + final String versionedPrefix = "META-INF/versions/"; + final String versionedSuffix = "/"; + String result = fileName; + if (fileName.startsWith(versionedPrefix)) { + final int versionedSuffixIndex = fileName.indexOf(versionedSuffix, versionedPrefix.length()); + if (versionedSuffixIndex >= 0) { + result = fileName.substring(versionedSuffixIndex + versionedSuffix.length()); + } + } + return result.substring(0, result.length() - CLASS_EXTENSION.length()); + } + + protected void handleClassFileName(URI container, Object module, String fileName, char fileSystemSeparatorChar) { + String strippedClassFileName = strippedClassFileName(fileName); + if (strippedClassFileName.equals("module-info")) { + return; + } + + String className = strippedClassFileName.replace(fileSystemSeparatorChar, '.'); + synchronized (classes) { + EconomicSet classNames = classes.get(container); + if (classNames == null) { + classNames = EconomicSet.create(); + classes.put(container, classNames); + } + classNames.add(className); + } + int packageSep = className.lastIndexOf('.'); + String packageName = packageSep > 0 ? className.substring(0, packageSep) : ""; + synchronized (packages) { + EconomicSet packageNames = packages.get(container); + if (packageNames == null) { + packageNames = EconomicSet.create(); + packages.put(container, packageNames); + } + packageNames.add(packageName); + } + + Class clazz = null; + try { + clazz = imageClassLoader.forName(className, module); + } catch (AssertionError error) { + VMError.shouldNotReachHere(error); + } catch (Throwable t) { + ImageClassLoader.handleClassLoadingError(t); + } + if (clazz != null) { + imageClassLoader.handleClass(clazz); + } + } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java index 08af81b72649..f1f70a69599a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGeneratorRunner.java @@ -72,7 +72,6 @@ import com.oracle.svm.hosted.analysis.NativeImagePointsToAnalysis; import com.oracle.svm.hosted.code.CEntryPointData; import com.oracle.svm.hosted.image.AbstractImage.NativeImageKind; -import com.oracle.svm.hosted.jdk.NativeImageClassLoaderSupportJDK11OrLater; import com.oracle.svm.hosted.option.HostedOptionParser; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ModuleSupport; @@ -178,8 +177,7 @@ public static void uninstallNativeImageClassLoader() { */ public static ImageClassLoader installNativeImageClassLoader(String[] classpath, String[] modulepath, List arguments) { NativeImageSystemClassLoader nativeImageSystemClassLoader = NativeImageSystemClassLoader.singleton(); - AbstractNativeImageClassLoaderSupport nativeImageClassLoaderSupport = new NativeImageClassLoaderSupportJDK11OrLater(nativeImageSystemClassLoader.defaultSystemClassLoader, classpath, - modulepath); + NativeImageClassLoaderSupport nativeImageClassLoaderSupport = new NativeImageClassLoaderSupport(nativeImageSystemClassLoader.defaultSystemClassLoader, classpath, modulepath); nativeImageClassLoaderSupport.setupHostedOptionParser(arguments); /* Perform additional post-processing with the created nativeImageClassLoaderSupport */ for (NativeImageClassLoaderPostProcessing postProcessing : ServiceLoader.load(NativeImageClassLoaderPostProcessing.class)) { @@ -194,10 +192,6 @@ public static ImageClassLoader installNativeImageClassLoader(String[] classpath, */ nativeImageSystemClassLoader.setNativeImageClassLoader(nativeImageClassLoader); - if (!nativeImageClassLoaderSupport.imagecp.isEmpty()) { - ModuleSupport.openModuleByClass(JavaVersionUtil.class, null); - } - /* * Iterating all classes can already trigger class initialization: We need annotation * information, which triggers class initialization of annotation classes and enum classes diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java index b4c32b7f7b16..f914ed444e95 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java @@ -24,13 +24,6 @@ */ package com.oracle.svm.hosted.jdk; -import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.core.jdk.AccessControllerUtil; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.ReflectionUtil; -import org.graalvm.compiler.serviceprovider.JavaVersionUtil; -import org.graalvm.nativeimage.hosted.Feature; - import java.security.AccessControlContext; import java.security.DomainCombiner; import java.security.Permission; @@ -38,6 +31,14 @@ import java.util.HashMap; import java.util.Map; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.jdk.AccessControllerUtil; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; + @AutomaticFeature @SuppressWarnings({"unused"}) class AccessControlContextReplacerFeature implements Feature { @@ -81,9 +82,6 @@ public void duringSetup(DuringSetupAccess access) { allowContextIfExists("java.util.Calendar$CalendarAccessControlContext", "INSTANCE"); allowContextIfExists("javax.management.monitor.Monitor", "noPermissionsACC"); - if (JavaVersionUtil.JAVA_SPEC <= 8) { - allowContextIfExists("sun.misc.InnocuousThread", "ACC"); - } if (JavaVersionUtil.JAVA_SPEC >= 11) { allowContextIfExists("java.security.AccessController$AccHolder", "innocuousAcc"); allowContextIfExists("java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory", "ACC"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportFeatureJDK11OrLater.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportFeatureJDK11OrLater.java deleted file mode 100644 index 7ff6056679a6..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportFeatureJDK11OrLater.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.svm.hosted.jdk; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.module.ModuleReader; -import java.lang.module.ModuleReference; -import java.lang.module.ResolvedModule; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.stream.Collectors; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; - -import com.oracle.svm.core.ClassLoaderSupport; -import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.ClassLoaderSupportImpl; -import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.util.ModuleSupport; - -final class ClassLoaderSupportImplJDK11OrLater extends ClassLoaderSupportImpl { - - private final NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport; - private final Map> packageToModules; - - ClassLoaderSupportImplJDK11OrLater(NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport) { - super(classLoaderSupport); - this.classLoaderSupport = classLoaderSupport; - packageToModules = new HashMap<>(); - buildPackageToModulesMap(classLoaderSupport); - } - - @Override - public void collectResources(ResourceCollector resourceCollector) { - /* Collect resources from modules */ - NativeImageClassLoaderSupportJDK11OrLater.allLayers(classLoaderSupport.moduleLayerForImageBuild).stream() - .flatMap(moduleLayer -> moduleLayer.configuration().modules().stream()) - .forEach(resolvedModule -> collectResourceFromModule(resourceCollector, resolvedModule)); - - /* Collect remaining resources from classpath */ - super.collectResources(resourceCollector); - } - - private static void collectResourceFromModule(ResourceCollector resourceCollector, ResolvedModule resolvedModule) { - ModuleReference moduleReference = resolvedModule.reference(); - try (ModuleReader moduleReader = moduleReference.open()) { - String moduleName = resolvedModule.name(); - List foundResources = moduleReader.list() - .filter(resourceName -> resourceCollector.isIncluded(moduleName, resourceName)) - .collect(Collectors.toList()); - - for (String resName : foundResources) { - Optional content = moduleReader.open(resName); - if (content.isEmpty()) { - continue; - } - try (InputStream is = content.get()) { - resourceCollector.addResource(moduleName, resName, is, false); - } - } - } catch (IOException e) { - throw VMError.shouldNotReachHere(e); - } - } - - @Override - public List getResourceBundle(String bundleSpec, Locale locale) { - String[] specParts = bundleSpec.split(":", 2); - String moduleName; - String bundleName; - if (specParts.length > 1) { - moduleName = specParts[0]; - bundleName = specParts[1]; - } else { - moduleName = null; - bundleName = specParts[0]; - } - String packageName = packageName(bundleName); - Set modules; - if (moduleName != null) { - modules = classLoaderSupport.findModule(moduleName).stream().collect(Collectors.toSet()); - } else { - modules = packageToModules.getOrDefault(packageName, Collections.emptySet()); - } - if (modules.isEmpty()) { - /* If bundle is not located in any module get it via classloader (from ALL_UNNAMED) */ - return super.getResourceBundle(bundleName, locale); - } - ArrayList resourceBundles = new ArrayList<>(); - for (Module module : modules) { - ModuleSupport.exportAndOpenPackageToClass(module.getName(), packageName, false, ClassLoaderSupportImplJDK11OrLater.class); - resourceBundles.add(ResourceBundle.getBundle(bundleName, locale, module)); - } - return resourceBundles; - } - - private static String packageName(String bundleName) { - int classSep = bundleName.replace('/', '.').lastIndexOf('.'); - if (classSep == -1) { - return ""; /* unnamed package */ - } - return bundleName.substring(0, classSep); - } - - private void buildPackageToModulesMap(NativeImageClassLoaderSupportJDK11OrLater cls) { - for (ModuleLayer layer : NativeImageClassLoaderSupportJDK11OrLater.allLayers(cls.moduleLayerForImageBuild)) { - for (Module module : layer.modules()) { - for (String packageName : module.getDescriptor().packages()) { - addToPackageNameModules(module, packageName); - } - } - } - } - - private void addToPackageNameModules(Module moduleName, String packageName) { - Set prevValue = packageToModules.get(packageName); - if (prevValue == null) { - /* Mostly packageName is only used in a single module */ - packageToModules.put(packageName, Collections.singleton(moduleName)); - } else if (prevValue.size() == 1) { - /* Transition to HashSet - happens rarely */ - HashSet newValue = new HashSet<>(); - newValue.add(prevValue.iterator().next()); - newValue.add(moduleName); - packageToModules.put(packageName, newValue); - } else if (prevValue.size() > 1) { - /* Add to exiting HashSet - happens rarely */ - prevValue.add(moduleName); - } - } -} - -@AutomaticFeature -public class ClassLoaderSupportFeatureJDK11OrLater implements Feature { - @Override - public void afterRegistration(AfterRegistrationAccess a) { - FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; - ImageSingletons.add(ClassLoaderSupport.class, new ClassLoaderSupportImplJDK11OrLater((NativeImageClassLoaderSupportJDK11OrLater) access.getImageClassLoader().classLoaderSupport)); - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/NativeImageClassLoaderSupportJDK11OrLater.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/NativeImageClassLoaderSupportJDK11OrLater.java deleted file mode 100644 index c774451ca8af..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/NativeImageClassLoaderSupportJDK11OrLater.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) 2020, 2020, 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.jdk; - -import java.io.File; -import java.io.IOException; -import java.lang.module.Configuration; -import java.lang.module.ModuleDescriptor; -import java.lang.module.ModuleFinder; -import java.lang.module.ModuleReader; -import java.lang.module.ModuleReference; -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Deque; -import java.util.HashSet; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ForkJoinPool; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.graalvm.collections.Pair; -import org.graalvm.compiler.options.OptionKey; -import org.graalvm.compiler.options.OptionValues; - -import com.oracle.svm.core.option.LocatableMultiOptionValue; -import com.oracle.svm.core.option.OptionOrigin; -import com.oracle.svm.core.option.SubstrateOptionsParser; -import com.oracle.svm.core.util.UserError; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.AbstractNativeImageClassLoaderSupport; -import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.NativeImageClassLoaderOptions; -import com.oracle.svm.util.ModuleSupport; - -import jdk.internal.module.Modules; - -public class NativeImageClassLoaderSupportJDK11OrLater extends AbstractNativeImageClassLoaderSupport { - - private final List imagemp; - private final List buildmp; - - private final ClassLoader classLoader; - - public final ModuleFinder upgradeAndSystemModuleFinder; - public final ModuleLayer moduleLayerForImageBuild; - - public final ModuleFinder modulepathModuleFinder; - - public NativeImageClassLoaderSupportJDK11OrLater(ClassLoader defaultSystemClassLoader, String[] classpath, String[] modulePath) { - super(defaultSystemClassLoader, classpath); - - imagemp = Arrays.stream(modulePath).map(Paths::get).collect(Collectors.toUnmodifiableList()); - buildmp = Optional.ofNullable(System.getProperty("jdk.module.path")).stream() - .flatMap(s -> Arrays.stream(s.split(File.pathSeparator))).map(Paths::get).collect(Collectors.toUnmodifiableList()); - - upgradeAndSystemModuleFinder = createUpgradeAndSystemModuleFinder(); - ModuleLayer moduleLayer = createModuleLayer(imagemp.toArray(Path[]::new), classPathClassLoader); - adjustBootLayerQualifiedExports(moduleLayer); - moduleLayerForImageBuild = moduleLayer; - - classLoader = getSingleClassloader(moduleLayer); - - modulepathModuleFinder = ModuleFinder.of(modulepath().toArray(Path[]::new)); - } - - private ModuleLayer createModuleLayer(Path[] modulePaths, ClassLoader parent) { - ModuleFinder modulePathsFinder = ModuleFinder.of(modulePaths); - Set moduleNames = modulePathsFinder.findAll().stream().map(moduleReference -> moduleReference.descriptor().name()).collect(Collectors.toSet()); - - /** - * When building a moduleLayer for the module-path passed to native-image we need to be able - * to resolve against system modules that are not used by the moduleLayer in which the - * image-builder got loaded into. To do so we use {@link upgradeAndSystemModuleFinder} as - * {@code ModuleFinder after} in - * {@link Configuration#resolve(ModuleFinder, ModuleFinder, Collection)}. - */ - Configuration configuration = ModuleLayer.boot().configuration().resolve(modulePathsFinder, upgradeAndSystemModuleFinder, moduleNames); - /** - * For the modules we want to build an image for, a ModuleLayer is needed that can be - * accessed with a single classloader so we can use it for {@link ImageClassLoader}. - */ - return ModuleLayer.defineModulesWithOneLoader(configuration, List.of(ModuleLayer.boot()), parent).layer(); - } - - /** - * Gets a finder that locates the upgrade modules and the system modules, in that order. Upgrade - * modules are used when mx environment variable {@code MX_BUILD_EXPLODED=true} is used. - */ - private static ModuleFinder createUpgradeAndSystemModuleFinder() { - ModuleFinder finder = ModuleFinder.ofSystem(); - ModuleFinder upgradeModulePath = finderFor("jdk.module.upgrade.path"); - if (upgradeModulePath != null) { - finder = ModuleFinder.compose(upgradeModulePath, finder); - } - return finder; - } - - /** - * Creates a finder from a module path specified by the {@code prop} system property. - */ - private static ModuleFinder finderFor(String prop) { - String s = System.getProperty(prop); - if (s == null || s.isEmpty()) { - return null; - } else { - String[] dirs = s.split(File.pathSeparator); - Path[] paths = new Path[dirs.length]; - int i = 0; - for (String dir : dirs) { - paths[i++] = Path.of(dir); - } - return ModuleFinder.of(paths); - } - } - - private static void adjustBootLayerQualifiedExports(ModuleLayer layer) { - /* - * For all qualified exports packages of modules in the the boot layer we check if layer - * contains modules that satisfy such qualified exports. If we find a match we perform a - * addExports. - */ - for (Module module : ModuleLayer.boot().modules()) { - for (ModuleDescriptor.Exports export : module.getDescriptor().exports()) { - for (String target : export.targets()) { - Optional optExportTargetModule = layer.findModule(target); - if (optExportTargetModule.isEmpty()) { - continue; - } - Module exportTargetModule = optExportTargetModule.get(); - if (module.isExported(export.source(), exportTargetModule)) { - continue; - } - Modules.addExports(module, export.source(), exportTargetModule); - } - } - } - } - - private ClassLoader getSingleClassloader(ModuleLayer moduleLayer) { - ClassLoader singleClassloader = classPathClassLoader; - for (Module module : moduleLayer.modules()) { - ClassLoader moduleClassLoader = module.getClassLoader(); - if (singleClassloader == classPathClassLoader) { - singleClassloader = moduleClassLoader; - } else { - VMError.guarantee(singleClassloader == moduleClassLoader); - } - } - return singleClassloader; - } - - private static void implAddReadsAllUnnamed(Module module) { - try { - Method implAddReadsAllUnnamed = Module.class.getDeclaredMethod("implAddReadsAllUnnamed"); - ModuleSupport.openModuleByClass(Module.class, NativeImageClassLoaderSupportJDK11OrLater.class); - implAddReadsAllUnnamed.setAccessible(true); - implAddReadsAllUnnamed.invoke(module); - } catch (ReflectiveOperationException | NoSuchElementException e) { - VMError.shouldNotReachHere("Could reflectively call Module.implAddReadsAllUnnamed", e); - } - } - - @Override - protected List modulepath() { - return Stream.concat(imagemp.stream(), buildmp.stream()).collect(Collectors.toList()); - } - - @Override - protected List applicationModulePath() { - return imagemp; - } - - @Override - protected Optional findModule(String moduleName) { - return moduleLayerForImageBuild.findModule(moduleName); - } - - @Override - protected void processClassLoaderOptions() { - OptionValues optionValues = getParsedHostedOptions(); - - processOption(optionValues, NativeImageClassLoaderOptions.AddExports).forEach(val -> { - if (val.targetModules.isEmpty()) { - Modules.addExportsToAllUnnamed(val.module, val.packageName); - } else { - for (Module targetModule : val.targetModules) { - Modules.addExports(val.module, val.packageName, targetModule); - } - } - }); - processOption(optionValues, NativeImageClassLoaderOptions.AddOpens).forEach(val -> { - if (val.targetModules.isEmpty()) { - Modules.addOpensToAllUnnamed(val.module, val.packageName); - } else { - for (Module targetModule : val.targetModules) { - Modules.addOpens(val.module, val.packageName, targetModule); - } - } - }); - processOption(optionValues, NativeImageClassLoaderOptions.AddReads).forEach(val -> { - if (val.targetModules.isEmpty()) { - implAddReadsAllUnnamed(val.module); - } else { - for (Module targetModule : val.targetModules) { - Modules.addReads(val.module, targetModule); - } - } - }); - } - - @Override - public void propagateQualifiedExports(String fromTargetModule, String toTargetModule) { - Optional optFromTarget = moduleLayerForImageBuild.findModule(fromTargetModule); - Optional optToTarget = moduleLayerForImageBuild.findModule(toTargetModule); - VMError.guarantee(!optFromTarget.isEmpty() && !optToTarget.isEmpty()); - Module toTarget = optToTarget.get(); - Module fromTarget = optFromTarget.get(); - - allLayers(moduleLayerForImageBuild).stream().flatMap(layer -> layer.modules().stream()).forEach(m -> { - if (!m.equals(toTarget)) { - for (String p : m.getPackages()) { - if (m.isExported(p, fromTarget)) { - Modules.addExports(m, p, toTarget); - } - if (m.isOpen(p, fromTarget)) { - Modules.addOpens(m, p, toTarget); - } - } - } - }); - } - - static List allLayers(ModuleLayer moduleLayer) { - /** Implementation taken from {@link ModuleLayer#layers()} */ - List allLayers = new ArrayList<>(); - Set visited = new HashSet<>(); - Deque stack = new ArrayDeque<>(); - visited.add(moduleLayer); - stack.push(moduleLayer); - - while (!stack.isEmpty()) { - ModuleLayer layer = stack.pop(); - allLayers.add(layer); - - // push in reverse order - for (int i = layer.parents().size() - 1; i >= 0; i--) { - ModuleLayer parent = layer.parents().get(i); - if (!visited.contains(parent)) { - visited.add(parent); - stack.push(parent); - } - } - } - return allLayers; - } - - private Stream processOption(OptionValues parsedHostedOptions, OptionKey specificOption) { - Stream> valuesWithOrigins = specificOption.getValue(parsedHostedOptions).getValuesWithOrigins(); - Stream parsedOptions = valuesWithOrigins.flatMap(valWithOrig -> { - try { - return Stream.of(asAddExportsAndOpensAndReadsFormatValue(specificOption, valWithOrig)); - } catch (UserError.UserException e) { - if (ModuleSupport.modulePathBuild) { - throw e; - } else { - /* - * Until we switch to always running the image-builder on module-path we have to - * be tolerant if invalid --add-exports -add-opens or --add-reads options are - * used. GR-30433 - */ - System.out.println("Warning: " + e.getMessage()); - return Stream.empty(); - } - } - }); - return parsedOptions; - } - - private static final class AddExportsAndOpensAndReadsFormatValue { - private final Module module; - private final String packageName; - private final List targetModules; - - private AddExportsAndOpensAndReadsFormatValue(Module module, String packageName, List targetModules) { - this.module = module; - this.packageName = packageName; - this.targetModules = targetModules; - } - } - - private AddExportsAndOpensAndReadsFormatValue asAddExportsAndOpensAndReadsFormatValue(OptionKey option, Pair valueOrigin) { - OptionOrigin optionOrigin = valueOrigin.getRight(); - String optionValue = valueOrigin.getLeft(); - - boolean reads = option.equals(NativeImageClassLoaderOptions.AddReads); - String format = reads ? NativeImageClassLoaderOptions.AddReadsFormat : NativeImageClassLoaderOptions.AddExportsAndOpensFormat; - String syntaxErrorMessage = " Allowed value format: " + format; - - String[] modulePackageAndTargetModules = optionValue.split("=", 2); - if (modulePackageAndTargetModules.length != 2) { - throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, syntaxErrorMessage); - } - String modulePackage = modulePackageAndTargetModules[0]; - String targetModuleNames = modulePackageAndTargetModules[1]; - - String[] moduleAndPackage = modulePackage.split("/"); - if (moduleAndPackage.length > 1 + (reads ? 0 : 1)) { - throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, syntaxErrorMessage); - } - String moduleName = moduleAndPackage[0]; - String packageName = moduleAndPackage.length > 1 ? moduleAndPackage[1] : null; - - List targetModuleNamesList = Arrays.asList(targetModuleNames.split(",")); - if (targetModuleNamesList.isEmpty()) { - throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, syntaxErrorMessage); - } - - Module module = findModule(moduleName).orElseThrow(() -> { - return userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, " Specified module '" + moduleName + "' is unknown."); - }); - List targetModules; - if (targetModuleNamesList.contains("ALL-UNNAMED")) { - targetModules = Collections.emptyList(); - } else { - targetModules = targetModuleNamesList.stream().map(mn -> { - return findModule(mn).orElseThrow(() -> { - throw userErrorAddExportsAndOpensAndReads(option, optionOrigin, optionValue, " Specified target-module '" + mn + "' is unknown."); - }); - }).collect(Collectors.toList()); - } - return new AddExportsAndOpensAndReadsFormatValue(module, packageName, targetModules); - } - - private static UserError.UserException userErrorAddExportsAndOpensAndReads(OptionKey option, OptionOrigin origin, String value, String detailMessage) { - Objects.requireNonNull(detailMessage, "missing detailMessage"); - return UserError.abort("Invalid option %s provided by %s.%s", SubstrateOptionsParser.commandArgument(option, value), origin, detailMessage); - } - - @Override - protected Class loadClassFromModule(Object module, String className) throws ClassNotFoundException { - assert module instanceof Module : "Argument `module` is not an instance of java.lang.Module"; - Module m = (Module) module; - assert isModuleClassLoader(classLoader, m.getClassLoader()) : "Argument `module` is java.lang.Module from unknown ClassLoader"; - return Class.forName(m, className); - } - - private static boolean isModuleClassLoader(ClassLoader loader, ClassLoader moduleClassLoader) { - if (moduleClassLoader == loader) { - return true; - } else { - if (loader == null) { - return false; - } - return isModuleClassLoader(loader.getParent(), moduleClassLoader); - } - } - - @Override - protected Optional getMainClassFromModule(Object module) { - assert module instanceof Module : "Argument `module` is not an instance of java.lang.Module"; - return ((Module) module).getDescriptor().mainClass(); - } - - @Override - public ClassLoader getClassLoader() { - return classLoader; - } - - private class ClassInitWithModules extends ClassInit { - - ClassInitWithModules(ForkJoinPool executor, ImageClassLoader imageClassLoader) { - super(executor, imageClassLoader); - } - - @Override - protected void init() { - List requiresInit = Arrays.asList( - "jdk.internal.vm.ci", "jdk.internal.vm.compiler", "com.oracle.graal.graal_enterprise", - "org.graalvm.sdk", "org.graalvm.truffle"); - - for (ModuleReference moduleReference : upgradeAndSystemModuleFinder.findAll()) { - if (requiresInit.contains(moduleReference.descriptor().name())) { - initModule(moduleReference); - } - } - for (ModuleReference moduleReference : modulepathModuleFinder.findAll()) { - initModule(moduleReference); - } - - super.init(); - } - - private void initModule(ModuleReference moduleReference) { - Optional optionalModule = findModule(moduleReference.descriptor().name()); - if (optionalModule.isEmpty()) { - return; - } - try (ModuleReader moduleReader = moduleReference.open()) { - Module module = optionalModule.get(); - moduleReader.list().forEach(moduleResource -> { - if (moduleResource.endsWith(CLASS_EXTENSION)) { - executor.execute(() -> handleClassFileName(moduleReference.location().get(), module, moduleResource, '/')); - } - }); - } catch (IOException e) { - throw new RuntimeException("Unable get list of resources in module" + moduleReference.descriptor().name(), e); - } - } - } - - @Override - public void initAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader) { - new ClassInitWithModules(executor, imageClassLoader).init(); - } -} diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties index dfcb80a207a4..d6f7981996ce 100644 --- a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties @@ -5,6 +5,7 @@ Args= \ --features=com.oracle.svm.test.AbstractServiceLoaderTest$TestFeature \ --features=com.oracle.svm.test.NoProviderConstructorServiceLoaderTest$TestFeature \ --features=com.oracle.svm.test.NativeImageResourceUtils$TestFeature \ + --features=com.oracle.svm.test.jfr.JFRTestFeature \ --add-opens=java.base/java.lang=ALL-UNNAMED \ -H:+AllowVMInspection \ --add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.containers=ALL-UNNAMED diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java index 5e8c40a4264f..56b5a2205519 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrTest.java @@ -28,18 +28,21 @@ import static org.junit.Assume.assumeTrue; -import com.oracle.svm.core.jfr.HasJfrSupport; +import java.util.HashSet; + import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.nativeimage.hosted.Feature; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import com.oracle.svm.core.jfr.HasJfrSupport; import com.oracle.svm.test.jfr.utils.Jfr; import com.oracle.svm.test.jfr.utils.JfrFileParser; import com.oracle.svm.test.jfr.utils.LocalJfr; +import com.oracle.svm.util.ModuleSupport; -import java.util.HashSet; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordingFile; @@ -125,3 +128,14 @@ protected void checkRecording() throws AssertionError { } } } + +class JFRTestFeature implements Feature { + @Override + public void afterRegistration(AfterRegistrationAccess access) { + /* + * Use of org.graalvm.compiler.serviceprovider.JavaVersionUtil.JAVA_SPEC in + * com.oracle.svm.test.jfr.utils.poolparsers.ClassConstantPoolParser.parse + */ + ModuleSupport.exportAndOpenPackageToClass("jdk.internal.vm.compiler", "org.graalvm.compiler.serviceprovider", false, JFRTestFeature.class); + } +}