RFR: 8365467: Fix multiple issues in ExplodedImage

David Beaumont duke at openjdk.org
Wed Aug 13 12:40:17 UTC 2025


On Wed, 13 Aug 2025 12:20:23 GMT, David Beaumont <duke at openjdk.org> wrote:

> This PR addresses several issues found while adding unit tests for ExplodedImage.
> I have added review comments for changes (some of which are a little preferential, but bring the code into line with things like ImageReader in terms of the name choices for variables).
> Later it is likely that this code will be adapted for the up-coming preview mode support in Valhalla, so having it unit-testable is important.

Adding explanatory notes for the non-test changes/fixes.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 59:

> 57:     private static final String PACKAGES = "/packages/";
> 58: 
> 59:     private final Path modulesDir;

Needed to make this class actually testable and avoid just using the static field from SystemImage.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 65:

> 63: 
> 64:     ExplodedImage(Path modulesDir) throws IOException {
> 65:         this.modulesDir = modulesDir;

Avoid unnecessary assumptions about file systems.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 95:

> 93:         }
> 94: 
> 95:         @Override

Discovered this method was missing during testing. I *think* this logic is what's needed here, but I would like someone to just double check.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 132:

> 130:                 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
> 131:                     for (Path p : stream) {
> 132:                         p = modulesDir.relativize(p);

Avoid using static field.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 158:

> 156: 
> 157:     @Override
> 158:     public synchronized void close() throws IOException {

The nodes map is always accessed synchronised except here, so by synchronizing this we can stop using ConcurrentHashMap.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 171:

> 169:             return node;
> 170:         }
> 171:         // lazily created for paths like /packages/<package>/<module>/xyz

This code is simply wrong. The lookup of 'packages/java.lang/java.base/java/lang' should fail, since there is no node for it. It's the wrapping file system's job to test for symbolic links in the path and resolve this sort of thing.
Only /packages, /packages/xxx and /packages/xxx/yyy nodes need to exist in SystemImage.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 216:

> 214:         } catch (IOException x) {
> 215:             // Since the path reference a file, any errors should not be ignored.
> 216:             throw new UncheckedIOException(x);

Silently ignoring IOException was both a risk, and a possible performance issue, since it was being used for normal code flow whenever a non-existent node was being asked for. Now, an exception here is unconditionally a problem, since the given path does exist.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 226:

> 224:      */
> 225:     Path underlyingModulesPath(String name) {
> 226:         if (name.startsWith(MODULES) && name.length() > MODULES.length()) {

The extra length test avoids issues when "/modules/" is given, since that *should* be invalid but otherwise gets turned into a path to the parent dir.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 258:

> 256:                     String moduleName = module.getFileName().toString();
> 257:                     // make sure "/modules/<moduleName>" is created
> 258:                     Objects.requireNonNull(createModulesNode(MODULES + moduleName, module));

This happens once per module, so it's where we create the root dirs (see below).

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 276:

> 274:         // create "/modules" directory
> 275:         // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
> 276:         PathNode modulesRootNode = new PathNode("/modules", new ArrayList<>(nodes.values()));

Renamed because there's already "modulesDir" elsewhere.

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java line 286:

> 284:             List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());
> 285:             for (String moduleName : moduleNameList) {
> 286:                 Node moduleNode = Objects.requireNonNull(nodes.get(MODULES + moduleName));

There's no need to call code that "creates" the module directory node here, we did it above, and here we can just guarantee it's in the cache.

src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java line 81:

> 79:     }
> 80: 
> 81:     private static final String RUNTIME_HOME;

Hiding these prevents unwanted use by other classes (which would make them effectively untestable).

test/jdk/jdk/internal/jrtfs/whitebox/ExplodedImageTestDriver.java line 30:

> 28:  * @run junit/othervm java.base/jdk.internal.jrtfs.ExplodedImageTest
> 29:  */
> 30: public class ExplodedImageTestDriver {}

Not 100% sure if a class declaration is needed here. It seems to work fine, but I've seen a case where there wasn't one (but that felt odd). Happy to remove if not wanted.

test/jdk/jdk/internal/jrtfs/whitebox/TEST.properties line 1:

> 1: modules = \

Cargo-culted without a true understand of what's going on. This seems to be what's needed for module access.

-------------

PR Review: https://git.openjdk.org/jdk/pull/26757#pullrequestreview-3115737360
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273278111
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273280133
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273285948
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273289787
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273293717
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273300351
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273308453
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273325040
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273310217
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273313715
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273312772
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273315520
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273317906
PR Review Comment: https://git.openjdk.org/jdk/pull/26757#discussion_r2273319482


More information about the core-libs-dev mailing list