From 0544b64ba4111c650ddb25b036f9217eaee24b06 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Tue, 4 Nov 2025 10:36:52 -0800 Subject: [PATCH] 8370216: Fix of new File().exists() causes undefined behavior in libraries --- .../share/classes/java/io/FileSystem.java | 9 +- .../unix/classes/java/io/UnixFileSystem.java | 6 + .../classes/java/io/WinNTFileSystem.java | 6 + test/jdk/java/io/File/EmptyPath.java | 180 +++++++++++++----- 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/src/java.base/share/classes/java/io/FileSystem.java b/src/java.base/share/classes/java/io/FileSystem.java index 3596e0e842a..31bd5b123d7 100644 --- a/src/java.base/share/classes/java/io/FileSystem.java +++ b/src/java.base/share/classes/java/io/FileSystem.java @@ -32,8 +32,15 @@ import java.lang.annotation.Native; */ abstract class FileSystem { + /* -- Legacy empty path behavior -- */ - /* -- Current Working Directory --*/ + private static final String FAIL_IF_EMPTY_PATH_PROPERTY = + "jdk.io.File.failIfEmptyPath"; + + protected static final boolean FAIL_IF_EMPTY_PATH = + Boolean.getBoolean(FAIL_IF_EMPTY_PATH_PROPERTY); + + /* -- Current Working Directory -- */ /* lazy initialization of CWD object */ private static class CurrentWorkingDirectoryHolder { diff --git a/src/java.base/unix/classes/java/io/UnixFileSystem.java b/src/java.base/unix/classes/java/io/UnixFileSystem.java index 5f9edcd4356..2fb62fd1619 100644 --- a/src/java.base/unix/classes/java/io/UnixFileSystem.java +++ b/src/java.base/unix/classes/java/io/UnixFileSystem.java @@ -34,10 +34,16 @@ final class UnixFileSystem extends FileSystem { private final String userDir; private String getPathForSysCalls(String path) { + if (FAIL_IF_EMPTY_PATH) + return path; + return path.isEmpty() ? getCWD().getPath() : path; } private File getFileForSysCalls(File file) { + if (FAIL_IF_EMPTY_PATH) + return file; + return file.getPath().isEmpty() ? getCWD() : file; } diff --git a/src/java.base/windows/classes/java/io/WinNTFileSystem.java b/src/java.base/windows/classes/java/io/WinNTFileSystem.java index e053f7f004c..2f16fe25e08 100644 --- a/src/java.base/windows/classes/java/io/WinNTFileSystem.java +++ b/src/java.base/windows/classes/java/io/WinNTFileSystem.java @@ -83,10 +83,16 @@ final class WinNTFileSystem extends FileSystem { } private String getPathForWin32Calls(String path) { + if (FAIL_IF_EMPTY_PATH) + return path; + return (path != null && path.isEmpty()) ? getCWD().getPath() : path; } private File getFileForWin32Calls(File file) { + if (FAIL_IF_EMPTY_PATH) + return file; + return file.getPath().isEmpty() ? getCWD() : file; } diff --git a/test/jdk/java/io/File/EmptyPath.java b/test/jdk/java/io/File/EmptyPath.java index 374a69c7959..3bb525f6009 100644 --- a/test/jdk/java/io/File/EmptyPath.java +++ b/test/jdk/java/io/File/EmptyPath.java @@ -22,9 +22,10 @@ */ /* @test - * @bug 4842706 8024695 8361587 8362429 + * @bug 4842706 8024695 8361587 8362429 8370216 * @summary Test some file operations with empty path * @run junit EmptyPath + * @run junit/othervm -Djdk.io.File.failIfEmptyPath=true EmptyPath */ import java.io.File; @@ -47,6 +48,7 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; @@ -55,28 +57,35 @@ import static org.junit.jupiter.api.Assertions.*; public class EmptyPath { private static final String EMPTY_STRING = ""; + static boolean failIfEmptyPath; + static File f; static Path p; @BeforeAll public static void init() { + failIfEmptyPath = Boolean.getBoolean("jdk.io.File.failIfEmptyPath"); f = new File(EMPTY_STRING); p = Path.of(EMPTY_STRING); } + private boolean failIfEmptyPath() { + return failIfEmptyPath; + } + @Test public void canExecute() { - assertTrue(f.canExecute()); + assertNotEquals(failIfEmptyPath, f.canExecute()); } @Test public void canRead() { - assertTrue(f.canRead()); + assertNotEquals(failIfEmptyPath, f.canRead()); } @Test public void canWrite() { - assertTrue(f.canWrite()); + assertNotEquals(failIfEmptyPath, f.canWrite()); } @Test @@ -107,7 +116,7 @@ public class EmptyPath { @Test public void exists() { - assertTrue(f.exists()); + assertNotEquals(failIfEmptyPath, f.exists()); } @Test @@ -142,8 +151,12 @@ public class EmptyPath { @Test public void getFreeSpace() throws IOException { - FileStore fs = Files.getFileStore(f.toPath()); - checkSpace(fs.getUnallocatedSpace(), f.getFreeSpace()); + if (failIfEmptyPath) + assertEquals(0, f.getFreeSpace()); + else { + FileStore fs = Files.getFileStore(f.toPath()); + checkSpace(fs.getUnallocatedSpace(), f.getFreeSpace()); + } } @Test @@ -168,14 +181,22 @@ public class EmptyPath { @Test public void getTotalSpace() throws IOException { - FileStore fs = Files.getFileStore(f.toPath()); - checkSpace(fs.getTotalSpace(), f.getTotalSpace()); + if (failIfEmptyPath) + assertEquals(0, f.getTotalSpace()); + else { + FileStore fs = Files.getFileStore(f.toPath()); + checkSpace(fs.getTotalSpace(), f.getTotalSpace()); + } } @Test public void getUsableSpace() throws IOException { - FileStore fs = Files.getFileStore(f.toPath()); - checkSpace(fs.getUsableSpace(), f.getUsableSpace()); + if (failIfEmptyPath) + assertEquals(0, f.getTotalSpace()); + else { + FileStore fs = Files.getFileStore(f.toPath()); + checkSpace(fs.getUsableSpace(), f.getUsableSpace()); + } } @Test @@ -190,7 +211,7 @@ public class EmptyPath { @Test public void isDirectory() { - assertTrue(f.isDirectory()); + assertNotEquals(failIfEmptyPath, f.isDirectory()); } @Test @@ -205,22 +226,34 @@ public class EmptyPath { @Test public void lastModified() { - assertTrue(f.lastModified() > 0); + if (failIfEmptyPath) + assertEquals(0, f.lastModified()); + else + assertTrue(f.lastModified() > 0); } @Test public void length() throws IOException { - assertEquals(Files.size(f.toPath()), f.length()); + if (failIfEmptyPath) + assertEquals(0, f.length()); + else + assertEquals(Files.size(f.toPath()), f.length()); } @Test public void list() throws IOException { - list(f.list()); + if (failIfEmptyPath) + assertNull(f.list()); + else + list(f.list()); } @Test public void listFilenameFilter() throws IOException { - list(f.list((FilenameFilter)null)); + if (failIfEmptyPath) + assertNull(f.list((FilenameFilter)null)); + else + list(f.list((FilenameFilter)null)); } private void list(String[] files) throws IOException { @@ -233,7 +266,10 @@ public class EmptyPath { @Test public void listFiles() throws IOException { - listFiles(x -> x.listFiles()); + if (failIfEmptyPath) + assertNull(f.listFiles()); + else + listFiles(x -> x.listFiles()); } @Test @@ -241,12 +277,18 @@ public class EmptyPath { FileFilter ff = new FileFilter() { public boolean accept(File pathname) { return true; } }; - listFiles(x -> x.listFiles(ff)); + if (failIfEmptyPath) + assertNull(f.listFiles(ff)); + else + listFiles(x -> x.listFiles(ff)); } @Test public void listFilesNullFileFilter() throws IOException { - listFiles(x -> x.listFiles((FileFilter)null)); + if (failIfEmptyPath) + assertNull(f.listFiles((FileFilter)null)); + else + listFiles(x -> x.listFiles((FileFilter)null)); } @Test @@ -254,12 +296,18 @@ public class EmptyPath { FilenameFilter fnf = new FilenameFilter() { public boolean accept(File dir, String name) { return true; } }; - listFiles(x -> x.listFiles(fnf)); + if (failIfEmptyPath) + assertNull(f.listFiles(fnf)); + else + listFiles(x -> x.listFiles(fnf)); } @Test public void listFilesNullFilenameFilter() throws IOException { - listFiles(x -> x.listFiles((FilenameFilter)null)); + if (failIfEmptyPath) + assertNull(f.listFiles((FilenameFilter)null)); + else + listFiles(x -> x.listFiles((FilenameFilter)null)); } private void listFiles(Function func) throws IOException { @@ -317,13 +365,19 @@ public class EmptyPath { public void setLastModified() { long t0 = f.lastModified(); long t = System.currentTimeMillis(); - try { - assertTrue(f.setLastModified(t)); - assertEquals(t, f.lastModified()); - assertTrue(f.setLastModified(t0)); - assertEquals(t0, f.lastModified()); - } finally { - f.setLastModified(t0); + if (failIfEmptyPath) { + assertEquals(0, t0); + assertFalse(f.setLastModified(t)); + assertEquals(0, f.lastModified()); + } else { + try { + assertTrue(f.setLastModified(t)); + assertEquals(t, f.lastModified()); + assertTrue(f.setLastModified(t0)); + assertEquals(t0, f.lastModified()); + } finally { + f.setLastModified(t0); + } } } @@ -334,45 +388,72 @@ public class EmptyPath { @Test @DisabledOnOs({OS.WINDOWS}) public void setReadable() { - assertTrue(f.canRead()); - try { - assertTrue(f.setReadable(false)); + if (failIfEmptyPath) { assertFalse(f.canRead()); - assertTrue(f.setReadable(true)); + assertFalse(f.setReadable(false)); + assertFalse(f.canRead()); + assertFalse(f.setReadable(true)); + assertFalse(f.canRead()); + } else { assertTrue(f.canRead()); - } finally { - f.setReadable(true); + try { + assertTrue(f.setReadable(false)); + assertFalse(f.canRead()); + assertTrue(f.setReadable(true)); + assertTrue(f.canRead()); + } finally { + f.setReadable(true); + } } } @Test @DisabledOnOs({OS.WINDOWS}) public void setReadOnly() { - assertTrue(f.canExecute()); - assertTrue(f.canRead()); - assertTrue(f.canWrite()); - try { - assertTrue(f.setReadOnly()); - assertTrue(f.canRead()); + if (failIfEmptyPath) { + assertFalse(f.canExecute()); + assertFalse(f.canRead()); assertFalse(f.canWrite()); - assertTrue(f.setWritable(true, true)); + assertFalse(f.setReadOnly()); + assertFalse(f.canRead()); + assertFalse(f.canWrite()); + assertFalse(f.setWritable(true, true)); + assertFalse(f.canWrite()); + } else { + assertTrue(f.canExecute()); + assertTrue(f.canRead()); assertTrue(f.canWrite()); - } finally { - f.setWritable(true, true); + try { + assertTrue(f.setReadOnly()); + assertTrue(f.canRead()); + assertFalse(f.canWrite()); + assertTrue(f.setWritable(true, true)); + assertTrue(f.canWrite()); + } finally { + f.setWritable(true, true); + } } } @Test @DisabledOnOs({OS.WINDOWS}) public void setWritable() { - assertTrue(f.canWrite()); - try { - assertTrue(f.setWritable(false, true)); + if (failIfEmptyPath) { assertFalse(f.canWrite()); - assertTrue(f.setWritable(true, true)); + assertFalse(f.setWritable(false, true)); + assertFalse(f.canWrite()); + assertFalse(f.setWritable(true, true)); + assertFalse(f.canWrite()); + } else { assertTrue(f.canWrite()); - } finally { - f.setWritable(true, true); + try { + assertTrue(f.setWritable(false, true)); + assertFalse(f.canWrite()); + assertTrue(f.setWritable(true, true)); + assertTrue(f.canWrite()); + } finally { + f.setWritable(true, true); + } } } @@ -391,6 +472,7 @@ public class EmptyPath { assertEquals(f.toPath().toUri(), f.toURI()); } + @DisabledIf("failIfEmptyPath") @Test public void toURL() throws MalformedURLException { assertEquals(f.toPath().toUri().toURL(), f.toURL());