diff --git a/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java b/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java index d24cc77600c..eb3f25ceca7 100644 --- a/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java +++ b/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java @@ -225,6 +225,7 @@ public final class ModulePatcher { private final ModuleReference mref; private final URL delegateCodeSourceURL; private volatile ModuleReader delegate; + private volatile boolean closed; /** * Creates the ModuleReader to reads resources in a patched module. @@ -291,6 +292,15 @@ public final class ModulePatcher { return r; } + /** + * Throws an IOException if the ModuleReader is closed. + */ + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("ModuleReader is closed"); + } + } + /** * Finds a resources in the patch locations. Returns null if not found * or the name is "module-info.class" as that cannot be overridden. @@ -310,7 +320,7 @@ public final class ModulePatcher { * Finds a resource of the given name in the patched module. */ public Resource findResource(String name) throws IOException { - + assert !closed : "module reader is closed"; // patch locations Resource r = findResourceInPatch(name); if (r != null) @@ -354,6 +364,7 @@ public final class ModulePatcher { @Override public Optional find(String name) throws IOException { + ensureOpen(); Resource r = findResourceInPatch(name); if (r != null) { URI uri = URI.create(r.getURL().toString()); @@ -365,6 +376,7 @@ public final class ModulePatcher { @Override public Optional open(String name) throws IOException { + ensureOpen(); Resource r = findResourceInPatch(name); if (r != null) { return Optional.of(r.getInputStream()); @@ -375,6 +387,7 @@ public final class ModulePatcher { @Override public Optional read(String name) throws IOException { + ensureOpen(); Resource r = findResourceInPatch(name); if (r != null) { ByteBuffer bb = r.getByteBuffer(); @@ -398,6 +411,7 @@ public final class ModulePatcher { @Override public Stream list() throws IOException { + ensureOpen(); Stream s = delegate().list(); for (ResourceFinder finder : finders) { s = Stream.concat(s, finder.list()); @@ -407,6 +421,10 @@ public final class ModulePatcher { @Override public void close() throws IOException { + if (closed) { + return; + } + closed = true; closeAll(finders); delegate().close(); } diff --git a/test/jdk/java/lang/module/ModuleReader/patched/PatchedModuleReaderTest.java b/test/jdk/java/lang/module/ModuleReader/patched/PatchedModuleReaderTest.java new file mode 100644 index 00000000000..80f4e324627 --- /dev/null +++ b/test/jdk/java/lang/module/ModuleReader/patched/PatchedModuleReaderTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2025, 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. + * + * 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.io.IOException; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test + * @bug 8372787 + * @summary Test the behaviour of ModuleReader when using --patch-module + * @comment patch the java.base module with a test specific resource + * @compile/module=java.base java/lang/PatchedFoo.java + * @run junit/othervm ${test.main.class} + */ +class PatchedModuleReaderTest { + + private static ModuleReference patchedModuleRef; + + @BeforeAll + static void beforeAll() { + patchedModuleRef = ModuleFinder.ofSystem() + .find("java.base") + .orElseThrow(); + } + + /* + * Verifies that the resource that was patched into a module + * is found by the ModuleReader. + */ + @Test + void testResourceFound() throws Exception { + try (ModuleReader reader = patchedModuleRef.open()) { + String resourceName = "java/lang/PatchedFoo.class"; + Optional res = reader.find(resourceName); + assertTrue(res.isPresent(), resourceName + " is missing in " + + patchedModuleRef.descriptor().name() + " module"); + URI uri = res.get(); + assertEquals("file", uri.getScheme(), + "unexpected scheme in resource URI " + uri); + assertTrue(uri.getPath().endsWith(resourceName), + "unexpected path component " + uri.getPath() + + " in resource URI " + uri); + + } + } + + /* + * Verifies the ModuleReader against a resource which isn't + * expected to be part of the patched module. + */ + @Test + void testResourceNotFound() throws Exception { + try (ModuleReader reader = patchedModuleRef.open()) { + String nonExistentResource = "foo/bar/NonExistent.class"; + Optional res = reader.find(nonExistentResource); + assertTrue(res.isEmpty(), "unexpected resource " + nonExistentResource + + " in " + patchedModuleRef.descriptor().name() + " module"); + } + } + + /* + * This test opens a ModuleReader for a patched module, accumulates + * the Stream of resources from that ModuleReader and then closes that + * ModuleReader. It then verifies that the closed ModuleReader + * throws the specified IOException whenever it is used for subsequent + * operations on the Stream of resources. + */ + @Test + void testIOExceptionAfterClose() throws Exception { + ModuleReader reader; + Stream resources; + try (var _ = reader = patchedModuleRef.open()) { + // hold on to the available resources, to test them after the + // ModuleReader is closed + resources = reader.list(); + } // close the ModuleReader + + // verify IOException is thrown by the closed ModuleReader + + assertThrows(IOException.class, () -> reader.list(), + "ModuleReader.list()"); + + resources.forEach(rn -> { + assertThrows(IOException.class, () -> reader.read(rn), + "ModuleReader.read(String)"); + assertThrows(IOException.class, () -> reader.open(rn), + "ModuleReader.open(String)"); + assertThrows(IOException.class, () -> reader.find(rn), + "ModuleReader.find(String)"); + }); + + // repeat the test for a non-existent resource + String nonExistentResource = "foo/bar/NonExistent.class"; + assertThrows(IOException.class, () -> reader.read(nonExistentResource), + "ModuleReader.read(String)"); + assertThrows(IOException.class, () -> reader.open(nonExistentResource), + "ModuleReader.open(String)"); + assertThrows(IOException.class, () -> reader.find(nonExistentResource), + "ModuleReader.find(String)"); + } +} diff --git a/test/jdk/java/lang/module/ModuleReader/patched/java.base/java/lang/PatchedFoo.java b/test/jdk/java/lang/module/ModuleReader/patched/java.base/java/lang/PatchedFoo.java new file mode 100644 index 00000000000..814757134d6 --- /dev/null +++ b/test/jdk/java/lang/module/ModuleReader/patched/java.base/java/lang/PatchedFoo.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025, 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. + * + * 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 java.lang; + +public class PatchedFoo { +}