Skip to content

Commit

Permalink
Fix provider verification when some JMODs are present
Browse files Browse the repository at this point in the history
In some configurations, e.g. when java.base is missing from the packaged
modules, but another JDK module is present as JMOD that is linked into
an image, then provider verification can fail. Thus, the run-time image
link fails. Verify that this doesn't happen.

The fix is to return Platform.runtime() for run-time image based links
as well. Otherwise this code would return the wrong result.
  • Loading branch information
jerboaa committed Oct 24, 2024
1 parent e52f6db commit 2d97af9
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ private static ImageHelper createImageProvider(JlinkConfiguration config,
Map<String, Path> mods = cf.modules().stream()
.collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation));
// determine the target platform of the image being created
Platform targetPlatform = targetPlatform(cf, mods);
Platform targetPlatform = targetPlatform(cf, mods, config.linkFromRuntimeImage());
// if the user specified any --endian, then it must match the target platform's native
// endianness
if (endian != null && endian != targetPlatform.arch().byteOrder()) {
Expand Down Expand Up @@ -903,10 +903,12 @@ public Set<ModuleReference> findAll() {
};
}

private static Platform targetPlatform(Configuration cf, Map<String, Path> modsPaths) throws IOException {
private static Platform targetPlatform(Configuration cf,
Map<String, Path> modsPaths,
boolean runtimeImageLink) throws IOException {
Path javaBasePath = modsPaths.get("java.base");
assert javaBasePath != null : "java.base module path is missing";
if (isJavaBaseFromDefaultModulePath(javaBasePath)) {
if (runtimeImageLink || isJavaBaseFromDefaultModulePath(javaBasePath)) {
// this implies that the java.base module used for the target image
// will correspond to the current platform. So this isn't an attempt to
// build a cross-platform image. We use the current platform's endianness
Expand Down
162 changes: 128 additions & 34 deletions test/jdk/tools/jlink/JmodLess/AbstractLinkableRuntimeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
Expand All @@ -34,6 +35,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -105,8 +107,12 @@ protected OutputAnalyzer runJavaCmd(Path image, List<String> options) throws Exc
}

protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec) throws Exception {
return createJavaImageRuntimeLink(baseSpec, Collections.emptySet() /* exclude all jmods */);
}

protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec, Set<String> excludedJmods) throws Exception {
// create a base image only containing the jdk.jlink module and its transitive closure
Path runtimeJlinkImage = createRuntimeLinkImage(baseSpec);
Path runtimeJlinkImage = createRuntimeLinkImage(baseSpec, excludedJmods);

// On Windows jvm.dll is in 'bin' after the jlink
Path libjvm = Path.of((isWindows() ? "bin" : "lib"), "server", System.mapLibraryName("jvm"));
Expand Down Expand Up @@ -196,53 +202,65 @@ protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler, Pr

/**
* Prepares the test for execution. This assumes the current jimage is
* runtime-linkable. However, since the 'jmods' dir is present (default jmods
* module path), it needs to get removed to provoke a runtime link.
* runtime-linkable. However, since the 'jmods' dir might be present
* (default jmods module path), it needs to get removed to provoke a runtime
* link.
*
* @param baseSpec
* @return A path to a JDK image which is prepared for runtime linking.
* @throws Exception
*/
protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec) throws Exception {
return createRuntimeLinkImage(baseSpec, Collections.emptySet() /* exclude all jmods */);
}

/**
* Prepares the test for execution. This assumes the current jimage is
* runtime-linkable. However, since the 'jmods' dir might be present
* (default jmods module path), it needs to get removed to provoke a runtime
* link.
*
* @param baseSpec The modules to jlink
* @param excludedJmods The set of jmod files to exclude in the base JDK. Empty set if
* all JMODs should be removed.
* @return A path to a JDK image which is prepared for runtime linking.
* @throws Exception
*/
protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec,
Set<String> excludedJmodFiles) throws Exception {
Path runtimeJlinkImage = baseSpec.getHelper().createNewImageDir(baseSpec.getName() + "-jlink");
copyJDKTreeWithoutJmods(runtimeJlinkImage);
// Verify the base image is actually without packaged modules
if (Files.exists(runtimeJlinkImage.resolve("jmods"))) {
throw new AssertionError("Must not contain 'jmods' directory");
copyJDKTreeWithoutSpecificJmods(runtimeJlinkImage, excludedJmodFiles);
// Verify the base image is actually without desired packaged modules
if (excludedJmodFiles.isEmpty()) {
if (Files.exists(runtimeJlinkImage.resolve("jmods"))) {
throw new AssertionError("Must not contain 'jmods' directory");
}
} else {
Path basePath = runtimeJlinkImage.resolve("jmods");
for (String jmodFile: excludedJmodFiles) {
Path unexpectedFile = basePath.resolve(Path.of(jmodFile));
if (Files.exists(unexpectedFile)) {
throw new AssertionError("Must not contain jmod: " + unexpectedFile);
}
}
}
return runtimeJlinkImage;
}

private void copyJDKTreeWithoutJmods(Path runtimeJlinkImage) throws Exception {
private void copyJDKTreeWithoutSpecificJmods(Path runtimeJlinkImage,
Set<String> excludedJmods) throws Exception {
Files.createDirectory(runtimeJlinkImage);
String javaHome = System.getProperty("java.home");
Path root = Path.of(javaHome);
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
Objects.requireNonNull(dir);
Path relative = root.relativize(dir);
if (relative.getFileName().equals(Path.of("jmods"))) {
return FileVisitResult.SKIP_SUBTREE;
}
// Create dir in destination location
Path targetDir = runtimeJlinkImage.resolve(relative);
if (!Files.exists(targetDir)) {
Files.createDirectory(targetDir);
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Path relative = root.relativize(file);
Files.copy(file, runtimeJlinkImage.resolve(relative), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});

FileVisitor<Path> fileVisitor = null;
if (excludedJmods.isEmpty()) {
fileVisitor = new ExcludeAllJmodsFileVisitor(root, runtimeJlinkImage);
} else {
fileVisitor = new FileExcludingFileVisitor(excludedJmods,
root,
runtimeJlinkImage);
}
Files.walkFileTree(root, fileVisitor);
}

private List<String> parseListMods(String output) throws Exception {
Expand Down Expand Up @@ -274,6 +292,82 @@ protected static boolean isWindows() {
return System.getProperty("os.name").startsWith("Windows");
}

static class ExcludeAllJmodsFileVisitor extends SimpleFileVisitor<Path> {
private final Path root;
private final Path destination;

private ExcludeAllJmodsFileVisitor(Path root,
Path destination) {
this.destination = destination;
this.root = root;
}

@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
Objects.requireNonNull(dir);
Path relative = root.relativize(dir);
if (relative.getFileName().equals(Path.of("jmods"))) {
return FileVisitResult.SKIP_SUBTREE;
}
// Create dir in destination location
Path targetDir = destination.resolve(relative);
if (!Files.exists(targetDir)) {
Files.createDirectory(targetDir);
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Path relative = root.relativize(file);
Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
}

static class FileExcludingFileVisitor extends SimpleFileVisitor<Path> {

private final Set<String> filesToExclude;
private final Path root;
private final Path destination;

private FileExcludingFileVisitor(Set<String> filesToExclude,
Path root,
Path destination) {
this.filesToExclude = filesToExclude;
this.destination = destination;
this.root = root;
}

@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
Objects.requireNonNull(dir);
Path relative = root.relativize(dir);
// Create dir in destination location
Path targetDir = destination.resolve(relative);
if (!Files.exists(targetDir)) {
Files.createDirectory(targetDir);
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Path relative = root.relativize(file);
// Skip files as determined by the exclude set
String fileName = file.getFileName().toString();
if (!filesToExclude.contains(fileName)) {
Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING);
}
return FileVisitResult.CONTINUE;
}

}

static class BaseJlinkSpec {
final Helper helper;
final String name;
Expand Down
69 changes: 69 additions & 0 deletions test/jdk/tools/jlink/JmodLess/BasicJlinkMissingJavaBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* 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.
*
* 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.
*/

import java.nio.file.Path;
import java.util.List;
import java.util.Set;

import tests.Helper;


/*
* @test
* @summary Test basic runtime-image-based jlinking with java.base.jmod missing
* but java.xml.jmod present. It should use a run-time image based link.
* @requires (jlink.packagedModules & jlink.runtime.linkable & vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g BasicJlinkMissingJavaBase
*/
public class BasicJlinkMissingJavaBase extends AbstractLinkableRuntimeTest {

@Override
public void runTest(Helper helper) throws Exception {
Path finalImage = createJavaXMLRuntimeLink(helper, "java-xml");
verifyListModules(finalImage, List.of("java.base", "java.xml"));
}

private Path createJavaXMLRuntimeLink(Helper helper, String name) throws Exception {
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder();
builder.helper(helper)
.name(name)
.addModule("java.xml")
.validatingModule("java.xml");
Set<String> excludedJmods = Set.of("java.base.jmod");
return createJavaImageRuntimeLink(builder.build(), excludedJmods);
}

public static void main(String[] args) throws Exception {
BasicJlinkMissingJavaBase test = new BasicJlinkMissingJavaBase();
test.run();
}

}

0 comments on commit 2d97af9

Please sign in to comment.