8385355: NullPointerException in jdk.tools.jlink.internal.ImageResourcesTree after JDK-8377070

Reviewed-by: alanb
Backport-of: 92298786486daf092c8eb8f278818441df1f6b51
This commit is contained in:
Xueming Shen 2026-06-05 17:44:55 +00:00
parent f8f7ad28ba
commit cce810ed30
2 changed files with 62 additions and 10 deletions

View File

@ -235,22 +235,46 @@ public final class ImageResourcesTree {
return;
}
String modName = fullPath.substring(1, modEnd);
String pkgPath = fullPath.substring(modEnd + 1);
String resPath = fullPath.substring(modEnd + 1);
int pathEnd = resPath.lastIndexOf('/');
Node parentNode = getDirectoryNode(modName, modulesRoot);
boolean isPreviewPath = false;
if (pkgPath.startsWith(PREVIEW_PREFIX)) {
if (resPath.startsWith("META-INF/")) {
parentNode = getDirectoryNode("META-INF", parentNode);
if (!resPath.startsWith(PREVIEW_PREFIX)) {
// Non-preview META-INF paths are resources, not package
// hierarchies, so directory and file names may contain dots.
for (int i = "META-INF".length(), j; i != pathEnd; i = j) {
j = resPath.indexOf('/', i + 1);
parentNode = getDirectoryNode(resPath.substring(i + 1, j), parentNode);
}
String resourceName = resPath.substring(pathEnd + 1);
Node resourceNode = parentNode.getChildren(resourceName);
if (resourceNode == null) {
new ResourceNode(resourceName, parentNode);
} else if (!(resourceNode instanceof ResourceNode)) {
throw new InvalidTreeException(resourceNode);
}
return;
}
// For preview paths, process nodes relative to the preview directory.
pkgPath = pkgPath.substring(PREVIEW_PREFIX.length());
Node metaInf = getDirectoryNode("META-INF", parentNode);
parentNode = getDirectoryNode("preview", metaInf);
parentNode = getDirectoryNode("preview", parentNode);
resPath = resPath.substring(PREVIEW_PREFIX.length());
pathEnd -= PREVIEW_PREFIX.length();
isPreviewPath = true;
}
int pathEnd = pkgPath.lastIndexOf('/');
// From invariants tested above, this must now be well-formed.
String fullPkgName = (pathEnd == -1) ? "" : pkgPath.substring(0, pathEnd).replace('/', '.');
String resourceName = pkgPath.substring(pathEnd + 1);
String pkgPath = (pathEnd == -1) ? "" : resPath.substring(0, pathEnd);
if (pkgPath.contains(".")) {
// Non META-INF entries are package paths. Dots in path segment
// names would otherwise be confused with package separators.
System.err.println("Invalid package path, skipping " + pkgPath);
return;
}
String fullPkgName = pkgPath.replace('/', '.');
String resourceName = resPath.substring(pathEnd + 1);
// Intermediate packages are marked "empty" (no resources). This might
// later be merged with a non-empty link for the same package.
ModuleLink emptyLink = ModuleLink.forEmptyPackage(modName, isPreviewPath);

View File

@ -37,6 +37,7 @@ import tests.Helper;
import tests.JImageGenerator;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
@ -58,6 +59,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
/*
* @test
* @bug 8385355
* @summary Tests for ImageReader.
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
@ -72,13 +74,18 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
/// There is no mutable test instance state to worry about.
@TestInstance(PER_CLASS)
public class ImageReaderTest {
// The '@' prefix marks the entry as a preview entry which will be placed in
// the '/modules/<module>/META-INF/preview/...' namespace.
// The '@' prefix marks the entry as a preview class entry which will be placed in
// the '/modules/<module>/META-INF/preview/...' namespace. The '!' prefix marks
// the entry as a non-class resource path.
private static final Map<String, List<String>> IMAGE_ENTRIES = Map.of(
"modfoo", Arrays.asList(
"com.foo.HasPreviewVersion",
"com.foo.NormalFoo",
"com.foo.bar.NormalBar",
"!META-INF/maven/com.google.code.findbugs/jsr305/pom.properties",
"!META-INF/z",
"!META-INF/collision/child.properties",
"!META-INF/collision",
// Replaces original class in preview mode.
"@com.foo.HasPreviewVersion",
// New class in existing package in preview mode.
@ -138,6 +145,23 @@ public class ImageReaderTest {
}
}
@Test
public void testMetaInfResourcesAreNotPackagePaths() throws IOException {
for (PreviewMode mode : List.of(PreviewMode.ENABLED, PreviewMode.DISABLED)) {
try (ImageReader reader = ImageReader.open(image, mode)) {
assertResource(reader, "modfoo", "META-INF/maven/com.google.code.findbugs/jsr305/pom.properties");
assertResource(reader, "modfoo", "META-INF/z");
assertResource(reader, "modfoo", "META-INF/collision/child.properties");
assertDirContents(reader, "/modules/modfoo/META-INF", "MANIFEST.MF", "collision", "maven", "z");
assertDirContents(reader, "/modules/modfoo/META-INF/collision", "child.properties");
assertDirContents(reader, "/modules/modfoo/META-INF/maven", "com.google.code.findbugs");
assertDirContents(reader, "/modules/modfoo/META-INF/maven/com.google.code.findbugs", "jsr305");
assertDirContents(reader, "/modules/modfoo/META-INF/maven/com.google.code.findbugs/jsr305", "pom.properties");
assertAbsent(reader, "/modules/modfoo/META-INF/maven/com/google/code/findbugs/jsr305/pom.properties");
}
}
}
@ParameterizedTest
@CsvSource(delimiter = ':', value = {
"modfoo:com/foo/HasPreviewVersion.class",
@ -416,6 +440,10 @@ public class ImageReaderTest {
jar.addEntry("module-info.class", InMemoryJavaCompiler.compile("module-info", moduleInfo));
classes.forEach(fqn -> {
if (fqn.startsWith("!")) {
jar.addEntry(fqn.substring(1), "resource".getBytes(StandardCharsets.UTF_8));
return;
}
boolean isPreviewEntry = fqn.startsWith("@");
if (isPreviewEntry) {
fqn = fqn.substring(1);