Commit Graph
4 Commits
Author SHA1 Message Date
Chris Tate e3bda96fd1 Merge main 2026-10-24 20:31:45 -05:00
Chris Tate 055c9ffa25 Evaluate the arguments of deferred require.resolve calls before they throw
Preflight admits a createRequire loader's require.resolve call with a
computed request inside a try block, but lowering refused the call, so
in JavaScript source the whole statement became a runtime fence. The
fence threw before the request expression ran, and before anything else
in the statement: `try { where = load.resolve(candidate(name)); }`
skipped candidate()'s side effects, so the catch clause saw a different
state than in Node.

Such calls (a computed request, another argument shape, or
runtime-computed options, on require.resolve and require.resolve.paths)
now evaluate their arguments in order and then throw the fence, as a
computed require call does. The addon-probe fixture checks that a
computed candidate path runs before each failed lookup.
2026-10-24 17:07:44 -05:00
Chris Tate 1e2f9b2dfc Resolve computed createRequire specifiers at run time
In JavaScript dependencies, a createRequire require call with a computed
specifier, and require.resolve called with a computed request or read as
a value, now compile. The new runtime unit scr_require.c resolves the
specifier with Node 24's CommonJS algorithm (builtins, relative and
absolute paths with Node's extension search, package "main", "exports"
and self-reference, node_modules and NODE_PATH lookup) when the call runs:

- require.resolve returns the id or real path Node returns, and throws
  Node's MODULE_NOT_FOUND error when nothing resolves.
- require throws the same MODULE_NOT_FOUND error when nothing resolves,
  so packages that probe for optional addons, such as pi-tui, take their
  absent branch as in Node. When the specifier resolves to a native addon,
  a builtin or another file, Node would load that module: the program
  prints an SC2020 error naming it and exits with status 1, without
  running the catch clause around the call.

Coverage lists these calls as deferred to runtime. TypeScript sources keep
the compile-time refusal.

Refs #1777.
2026-10-23 23:52:50 -05:00
Chris Tate d10ddff548 Compile createRequire probes for optional addons as deferred refusals
Packages such as pi-tui look for their prebuilt Node-API addons at run
time: a createRequire require call with a computed path, and the
loader's resolve passed as a value, each inside a try block whose catch
takes a fallback. Preflight refused these packages.

Preflight now classifies each use of a createRequire loader. Inside the
try block of a try statement in the same function, a call with a
computed specifier or another argument shape, and resolve or
resolve.paths read as a value, no longer refuse the package. Outside a
try block they keep it refused, with a reason that names the computed
specifier. The loader itself as a value, writes and optional calls stay
refused.

In JavaScript source, such a call evaluates its arguments and then
throws a runtime fence, and the resolve value is a function that throws
one when called, so `(options.resolve ?? require.resolve)(id)` still
calls the option. Both are listed in coverage as deferred to runtime.
TypeScript source keeps the compile-time refusals.

Refs #1777
2026-10-23 11:36:23 -05:00