Files
reverse-engineering-anything/src
吴佳浩Alben 172f248884 fix(javascript): emit no fs acquisition for a rejected literal mode (#8624)
* fix(javascript): emit no fs acquisition for a rejected literal mode

`fs.open`, `fs.openSync` and `fs/promises.open` validate the `mode` in argument 2 before
touching the filesystem, so a mode Node rejects throws synchronously and no descriptor
exists. The guard read only the flag, so a rejected mode kept its acquisition and any
linked release.

Measured on Node 24.22.1:

    fs.openSync(p, "r", -1)          -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", 1.5)         -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", NaN)         -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", 1e21)        -> THREW ERR_OUT_OF_RANGE
    fs.openSync(p, "r", true)        -> THREW ERR_INVALID_ARG_TYPE
    fs.openSync(p, "r", 0n)          -> THREW ERR_INVALID_ARG_TYPE
    fs.openSync(p, "r", {})          -> THREW ERR_INVALID_ARG_TYPE
    fs.openSync(p, "r", "8")         -> THREW ERR_INVALID_ARG_VALUE
    fs.openSync(p, "r", "")          -> THREW ERR_INVALID_ARG_VALUE
    fs.openSync(p, "r", "0o644")     -> THREW ERR_INVALID_ARG_VALUE
    --- accepted ---
    fs.openSync(p, "r", 0o644)       -> ok
    fs.openSync(p, "r", 2147483647)  -> ok
    fs.openSync(p, "r", -0)          -> ok
    fs.openSync(p, "r", "0644")      -> ok
    fs.openSync(p, "r", null)        -> ok
    fs.openSync(p, "r", void 0)      -> ok

The guard decides only exactly-readable literals. A numeric mode is validated as an
unsigned 32-bit integer; a string mode must be a canonical octal digit string, padded
leading zeros allowed, and its value must stay inside uint32; `null`, `undefined` and
`void <expr>` default the mode and are kept, as is a computed value.

Three boundaries are deliberate:

- Values in `[2^31, 2^32-1]` **abort the Node process** rather than throwing
  (`Assertion failed: args[2]->IsInt32()`), so they stay undecided instead of being
  claimed as a synchronous throw.
- A spread before the mode slot shifts the runtime position.
- A unary chain is resolved through its innermost readable operand, and a nested
  boolean or string result is left undecided rather than applied as a number.

Regressions cover both directions. Full suite here: 5466 passed | 7 skipped.

* fix(javascript): remove the superseded string mode check

Review finding. `rejectsOpenModeLiteral` builds `octal` from a string literal or a
no-expression template and returns inside that branch for every value it matched. A
`StringLiteral` therefore never reaches the later `isStringLiteral` check, which cannot
run and is a second copy of the canonical-octal rule to maintain.

* test(javascript): cover the spread and boundary paths of the mode guard

Review feedback from @greptile-apps: the new mode tests skipped both paths the
guard deliberately leaves undecided. Two spread cases are added:

- `openSync(...args, "r", "0600")`, where a spread in the flag slot shifts the
  runtime position of the mode;
- `openSync(p, "r", ...modes)`, where a spread occupies the mode slot itself.

Both must keep the acquisition, because neither establishes the mode value. The
boundary cases were already present: `2147483647` and `2147483648` are keep
cases (`2147483648` aborts the process rather than throwing) and `4294967296` is
the reject case, alongside the octal `37777777777` keep and `40000000000` reject
pair.

127 cases pass in this file.
2026-11-06 15:14:03 +08:00
..
…
…