@@ -207,7 +207,7 @@ files whose output is deterministic (`api`, `bitwise`, `calls`, `closure`,
207207** byte for byte** . Five more (` cstack ` , ` gc ` , ` literals ` , ` locals ` ,
208208` constructs ` ) print things that vary with the implementation or with a random
209209seed, so they are only asserted to run to completion and print ` OK ` — the reason
210- for each is listed under * Phase 10* below. 848 tests green, JaCoCo line coverage
210+ for each is listed under * Phase 10* below. 850 tests green, JaCoCo line coverage
2112110.90 against a 0.89 gate. (The upstream-suite gate needs ` LUA54_TESTS ` or
212212` target/lua54-tests ` ; without a test directory that whole class ** skips itself** ,
213213so a green run on a machine without it is 25 tests weaker than it looks.)
@@ -1601,11 +1601,17 @@ seek offset"*, which falls through `errnoOf`'s catch-all into `EIO`. Seeking
16011601**past** EOF is legal and returns the new position — easy to block by accident
16021602while fixing this.
16031603
1604- Two things are deliberately absent. `fopen` on a **directory** succeeds on both
1605- macOS and glibc but then behaves differently on read, and the JVM will not open a
1606- directory as a stream at all. And `print` writes to the C-level `stdout`, so it
1607- is *not* redirected by `io.output` — a script that assumes otherwise produces
1608- two different wrong answers.
1604+ One thing is deliberately absent: `print` writes to the C-level `stdout`, so it
1605+ is *not* redirected by `io.output` — a script that assumes otherwise produces two
1606+ different wrong answers.
1607+
1608+ `fopen` on a **directory** used to be listed here as absent too, and that was a
1609+ mistake: it is a single POSIX rule (`open(dir, O_RDONLY)` succeeds, `O_WRONLY`
1610+ gives `EISDIR`, and the first read fails with `EISDIR`), it is the same on macOS
1611+ and glibc, and the JVM simply refused to model it. It is now modelled — see
1612+ *The directory bug* below. The one part that is genuinely not comparable is
1613+ `seek("end")` on a directory, which returns the directory's own size (64 here),
1614+ a filesystem fact rather than a Lua one.
16091615
16101616#### The `os.time` sweep: `lua_tointegerx` converts strings
16111617
@@ -1791,10 +1797,15 @@ It also found one place where C and Java simply disagree about a path:
17911797` cannot open : No such file or directory ` ; Java resolves the empty path to the
17921798current directory and fails while * reading* , giving ` Is a directory ` — which we
17931799turned into ` Input/output error ` . The empty path is the only case where the two
1794- judge a path differently, so it gets ` ENOENT ` by C's rule. ** Directories
1795- themselves are fine and must not be "fixed":** ` loadfile("/tmp") ` succeeds at
1796- ` fopen ` in C and fails at * read* , so it takes the other branch —
1797- ` cannot read /tmp: Is a directory ` .
1800+ judge a path differently, so it gets ` ENOENT ` by C's rule.
1801+
1802+ Directories were claimed here as "fine, must not be fixed" — and that claim was
1803+ wrong, which is the point worth keeping: ** it was written from reading the C
1804+ source, not from running the reference.** ` loadfile("/tmp") ` does succeed at
1805+ ` fopen ` in C and fail at * read* — but this implementation was reporting
1806+ ` cannot open /tmp: Input/output error ` , i.e. the * other* branch, because Java's
1807+ ` Files.readAllBytes ` throws at open. Measuring it (see * The directory bug* ) turned
1808+ one wrong sentence into a real bug across five entry points.
17981809
17991810#### A sweep that came back clean: ` load ` 's reader contract
18001811
@@ -2160,6 +2171,41 @@ All 39 came back byte-identical — those branches were **correct but never
21602171executed** . Their value is as a regression net: change ` read("n") ` 's prefix
21612172handling or the write-buffer threshold and this goes red immediately.
21622173
2174+ #### The directory bug: one POSIX rule, five wrong answers
2175+
2176+ Reading ` PackageLib ` 's uncovered lines and asking "can a Lua script reach this?"
2177+ led to ` searcher_C ` 's fallback and, from there, to a module name that collides
2178+ with a directory. That turned out to be ** one rule mis-modelled in five places** :
2179+
2180+ POSIX lets ` open(dir, O_RDONLY) ` succeed. ` fopen(dir, "r") ` therefore succeeds;
2181+ ` fopen(dir, "w") ` gives ` EISDIR ` ; and the first ` fgetc ` / ` fread ` fails with
2182+ ` EISDIR ` . The JVM refuses to open a directory at all, so this implementation got
2183+ all five entry points wrong in one consistent way:
2184+
2185+ | | reference | before |
2186+ | --- | --- | --- |
2187+ | ` loadfile(dir) ` | ` nil, "cannot read <p>: Is a directory" ` | ` nil, "cannot open <p>: Input/output error" ` |
2188+ | ` io.open(dir, "r") ` | a file handle | ` nil, "<p>: No such file or directory", 2 ` |
2189+ | ` io.open(dir, "w") ` | ` nil, "<p>: Is a directory", 21 ` | ` nil, "<p>: No such file or directory", 2 ` |
2190+ | ` package.searchpath ` | finds it (returns the path) | ` nil, "no file '<p>'" ` |
2191+ | ` require(name) ` | ` error loading module … cannot read … ` | ` module '…' not found ` |
2192+
2193+ The fix is one idea applied at four sites: ` openInto ` lets a read-only open of a
2194+ directory through (and reports ` EISDIR ` for anything writable), ` LuaFile.getc ` /
2195+ ` read ` fail with 21, ` seek ` / ` tell ` treat it as a zero-length file (a directory
2196+ descriptor * is* seekable — ` f:seek("set", 5) ` returns 5, and ` f:seek("set", -1) `
2197+ is still ` EINVAL ` ), ` PackageLib.readable ` accepts directories, and ` loadfile ` /
2198+ ` searcher_Lua ` take the ` cannot read ` branch instead of ` cannot open ` .
2199+
2200+ 18 lines, byte-identical after the fix. Two things about how this was found are
2201+ worth more than the bug:
2202+
2203+ * The README previously said directories were "fine and must not be fixed" —
2204+ written from reading ` lauxlib.c ` , not from running the reference binary. It was
2205+ wrong. Measure, then write.
2206+ * The same sentence in the * code* comment (` BaseLib ` ) had asserted the behaviour
2207+ was already correct. A comment agreeing with the code is not evidence.
2208+
21632209### What is still missing
21642210
21652211Nothing in the language or the ten standard libraries, and every upstream file
0 commit comments