8386842: Preview files in root directory not recognized in system image

Reviewed-by: alanb, sherman, liach
This commit is contained in:
Du�an B�lek 2026-07-02 09:32:44 +00:00
parent 157cca7802
commit c042ad289d
2 changed files with 95 additions and 5 deletions

View File

@ -486,7 +486,7 @@ public final class ImageReader implements AutoCloseable {
ImageLocation loc = null;
if (isPreviewEnabled) {
// We must test preview location first (if in preview mode).
loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + resourcePath);
loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + "/" + resourcePath);
}
if (loc == null) {
loc = findLocation(moduleName, resourcePath);
@ -531,7 +531,7 @@ public final class ImageReader implements AutoCloseable {
return node.isResource();
}
}
loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + resourcePath);
loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + "/" + resourcePath);
}
if (loc == null) {
loc = findLocation(moduleName, resourcePath);
@ -561,7 +561,19 @@ public final class ImageReader implements AutoCloseable {
// Now try the non-prefixed resource name, but be careful to avoid false
// positives for names like "/modules/modules/xxx" which could return a
// location of a directory entry.
loc = findLocation(name.substring(MODULES_PREFIX.length()));
String resourceName = name.substring(MODULES_PREFIX.length());
if (isPreviewEnabled) {
// Root-level preview resources are not pre-cached when an image
// is opened, so check for them first.
int pathStart = resourceName.indexOf('/', 1);
if (pathStart > 1 && resourceName.indexOf('/', pathStart + 1) < 0) {
loc = findLocation(resourceName.substring(0, pathStart)
+ PREVIEW_INFIX + "/" + resourceName.substring(pathStart + 1));
}
}
if (loc == null) {
loc = findLocation(resourceName);
}
return loc != null && loc.getType() == RESOURCE
? ensureCached(newResource(name, loc))
: null;
@ -649,6 +661,36 @@ public final class ImageReader implements AutoCloseable {
private Directory completeModuleDirectory(Directory dir, ImageLocation loc) {
assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
List<Node> previewOnlyNodes = getPreviewNodesToMerge(dir);
if (isPreviewEnabled && previewOnlyNodes.isEmpty()) {
// When opening an image in preview mode, packages that have preview
// content are eagerly processed, caching preview resources and
// preview-only directories for direct lookup. Root-level preview
// resources are omitted during this process, since they have no
// package path and the empty package is not represented under
// "/packages", and must be processed separately.
int moduleStart = MODULES_PREFIX.length() + 1;
if (dir.getName().indexOf('/', moduleStart) < 0) {
ImageLocation previewLoc = findLocation(dir.getName() + PREVIEW_INFIX);
if (previewLoc != null) {
previewOnlyNodes = createChildNodes(previewLoc, 0, childLoc -> {
String baseName = getBaseName(childLoc);
String nonPreviewChildName = dir.getName() + "/" + baseName;
boolean isPreviewOnly = ImageLocation.isPreviewOnly(childLoc.getFlags());
LocationType type = childLoc.getType();
if (type == RESOURCE) {
Node childNode = nodes.computeIfAbsent(nonPreviewChildName, n -> newResource(n, childLoc));
return isPreviewOnly ? childNode : null;
} else {
assert type == MODULES_DIR : "Invalid location type: " + childLoc;
Node childNode = nodes.get(nonPreviewChildName);
assert !(isPreviewOnly && childNode == null) :
"Inconsistent child node: " + nonPreviewChildName;
return isPreviewOnly ? childNode : null;
}
});
}
}
}
// We hide preview names from direct lookup, but must also prevent
// the preview directory from appearing in any META-INF directories.
boolean parentIsMetaInfDir = isMetaInf(dir);

View File

@ -46,6 +46,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -59,7 +60,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
/*
* @test
* @bug 8385355
* @bug 8385355 8386842
* @summary Tests for ImageReader.
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
@ -86,6 +87,11 @@ public class ImageReaderTest {
"!META-INF/z",
"!META-INF/collision/child.properties",
"!META-INF/collision",
// Non-class resource in top level directory
"!fileA.txt",
"!fileB.txt",
"!META-INF/preview/fileA.txt",
"!META-INF/preview/fileB.txt",
// Replaces original class in preview mode.
"@com.foo.HasPreviewVersion",
// New class in existing package in preview mode.
@ -96,6 +102,11 @@ public class ImageReaderTest {
// Two new packages in preview mode (new symbolic links).
"@com.bar.preview.stuff.Foo",
"@com.bar.preview.stuff.Bar"),
"modbaz", Arrays.asList(
"!file.txt",
"!normal.txt",
"!META-INF/preview/file.txt",
"!META-INF/preview/previewOnly.txt"),
"modgus", Arrays.asList(
// A second module with a preview-only empty package (preview).
"@com.bar.preview.other.Gus"));
@ -270,6 +281,18 @@ public class ImageReaderTest {
assertAbsent(reader, "/modules/modfoo/com/foo/bar/IsPreviewOnly.class");
assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar");
assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class");
// Non-class resource in top level directory
assertResource(reader, "modfoo", "fileA.txt");
assertNonPreviewResourceVersion(reader, "modfoo", "fileA.txt");
assertNode(reader, "/modules/modfoo/fileB.txt");
assertNonPreviewResourceVersion(reader, "modfoo", "fileB.txt");
assertDirContents(reader, "/modules/modfoo", "META-INF", "module-info.class", "fileA.txt", "fileB.txt", "com");
assertAbsent(reader, "/modules/modbaz/previewOnly.txt");
assertDirContents(reader, "/modules/modbaz", "META-INF", "module-info.class", "file.txt", "normal.txt");
assertNonPreviewResourceVersion(reader, "modbaz", "file.txt");
assertNonPreviewResourceVersion(reader, "modbaz", "normal.txt");
}
}
@ -289,6 +312,20 @@ public class ImageReaderTest {
assertResource(reader, "modfoo", "com/foo/bar/IsPreviewOnly.class");
assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar");
assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class", "IsPreviewOnly.class");
// Non-class resource in top level directory
assertResource(reader, "modfoo", "fileA.txt");
assertPreviewResourceVersion(reader, "modfoo", "fileA.txt");
assertNode(reader, "/modules/modfoo/fileB.txt");
assertPreviewResourceVersion(reader, "modfoo", "fileB.txt");
assertDirContents(reader, "/modules/modfoo/com", "foo");
assertDirContents(reader, "/modules/modfoo", "META-INF", "module-info.class", "fileA.txt", "fileB.txt", "com");
assertNode(reader, "/modules/modbaz/previewOnly.txt");
assertDirContents(reader, "/modules/modbaz", "META-INF", "module-info.class", "file.txt", "normal.txt", "previewOnly.txt");
assertPreviewResourceVersion(reader, "modbaz", "file.txt");
assertNonPreviewResourceVersion(reader, "modbaz", "normal.txt");
assertPreviewResourceVersion(reader, "modbaz", "previewOnly.txt");
}
}
@ -405,6 +442,17 @@ public class ImageReaderTest {
assertSame(resNode, reader.findNode(nodeName));
}
private static void assertNonPreviewResourceVersion(ImageReader reader, String modName, String resPath) throws IOException {
Node resNode = reader.findResourceNode(modName, resPath);
assertArrayEquals(resPath.getBytes(StandardCharsets.UTF_8), reader.getResource(resNode));
}
private static void assertPreviewResourceVersion(ImageReader reader, String modName, String resPath) throws IOException {
Node resNode = reader.findResourceNode(modName, resPath);
String name = "META-INF/preview/" + resPath;
assertArrayEquals(name.getBytes(StandardCharsets.UTF_8), reader.getResource(resNode));
}
private static void assertNonPreviewVersion(ImageClassLoader loader, String module, String fqn) throws IOException {
assertEquals("Class: " + fqn, loader.loadAndGetToString(module, fqn));
}
@ -441,7 +489,7 @@ public class ImageReaderTest {
classes.forEach(fqn -> {
if (fqn.startsWith("!")) {
jar.addEntry(fqn.substring(1), "resource".getBytes(StandardCharsets.UTF_8));
jar.addEntry(fqn.substring(1), fqn.substring(1).getBytes(StandardCharsets.UTF_8));
return;
}
boolean isPreviewEntry = fqn.startsWith("@");