You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
Ralph Küpper
committed
fix(path): #7621 — path.* arms read an SSO string's inline bytes as a header
`path.resolve("/root", computedShortString)` threw ERR_INVALID_ARG_TYPE where
node returns the path. Every `path.*` codegen arm unboxed its operand with
`unbox_to_i64` (`bitcast double -> i64; and POINTER_MASK`) and handed the low 48
bits to a runtime entry that dereferences them as `*const StringHeader`. That is
the header for a HEAP string and the CHARACTERS for a small-string-optimized one
(SHORT_STRING_TAG, <= SHORT_STRING_MAX_LEN = 5 inline bytes) — so a literal
worked (interned onto the heap) and a computed short string did not. Bisected by
length: 5 bytes throws, 6 works.
Twelve arms were affected, not the five the issue named: resolve, resolve's fold
step, join, win32.join, normalize, extname, dirname, basename, basename(p, ext),
isAbsolute, parse, matchesGlob, plus the win32 sub-namespace equivalents. `parse`
and `matchesGlob` were SILENTLY wrong rather than throwing.
Single-operand arms now call `js_path_arg_header`, which materializes only the
SSO case and reproduces the old mask bit for bit for heap strings AND every
non-string — so each entry point keeps its own established non-string behaviour
(throw, or `unwrap_or_default`). It is deliberately not
`js_get_string_pointer_unified`, which coerces numbers to strings and would turn
`path.isAbsolute(5)` from a throw into `false`.
Two-operand arms take both operands NaN-boxed and unbox inside one runtime call
(`js_path_*_value`). Codegen cannot close that window itself: materializing the
first operand allocates, and `rooting::with_operands_rooted` hands the lowering
registers rather than slots, so the second operand's register is stale with no
re-read to reach for. The runtime entry roots the first operand across the
second's materialization via `RuntimeHandle::across_const`.
Validated locally: the new gap test is byte-identical to node 26.5.1 and exits 0
(it threw at line 2 before the fix); byte-identical again under PERRY_GC_ZEAL=1 +
PERRY_GC_PROTECT_FROMSPACE=1 with 402k copying minors observed; test_parity_path,
test_gap_node_path, test_gap_6371_path_lexical_semantics, test_gap_node_fs and
test_cli_simulation unchanged.
0 commit comments