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
{{ message }}
Repository navigation
qs parse: stale from-space array pointer in js_dyn_index_set_strict (latent on main; wrong checksum when allocation timing shifts) #11550
benchmarks/packages/qs/parse_nested.ts (qs 6.16.0) holds a stale from-space array pointer across an evacuating minor. The stale use is js_dyn_index_set_strict → js_array_set_f64_extend_strict_impl, called from qs's lib/parse.js. On main it is latent: a plain run happens to print the right checksum. Under a seeded schedule with from-space protection it faults on 5 of 6 seeds. With any runtime change that shifts allocation timing, it becomes a wrong answer on a plain run: a different wrong checksum each run, with no crash.
Reproduce (Linux x86-64, main at c1d93bb, release build)
cd benchmarks/packages && npm ci
PERRY_NO_AUTO_OPTIMIZE=1 PERRY_KEEP_SYMBOLS=1 perry compile qs/parse_nested.ts -o qs_parse_nested
PERRY_GC_PROTECT_FROMSPACE=1 PERRY_GC_PROTECT_FROMSPACE_DEPTH=64 PERRY_GC_SCHEDULE_SEED=7 ./qs_parse_nested 3000 1000
[gc-fromspace-protect] FAULT: signal 11 at 0x3e9972d2dbc
This address is RETIRED FROM-SPACE. The evacuating minor moved or
freed the object here and the holder kept the pre-collection address.
block=0x3e9972c0000 +77244 retired_bytes=84960 retired_by_minor=#24
last-known object: user_ptr=0x3e9972d2db0 obj_type=3 size=32
#0 perry_runtime::array::indexing::js_array_set_f64_extend_strict_impl
#1 js_dyn_index_set_strict
#2 perry_closure_node_modules_qs_lib_parse_js
#3 perry_closure_node_modules_qs_lib_parse_js
#4 perry_closure_node_modules_qs_lib_parse_js
#5 js_native_call_value
...
#10 perry_fn_qs_parse_nested_ts.op
Seeds 1, 7, 42, 99 and 123 fault; seed 555 completes (1,031 copying minors, 362,457 moved objects).
Wrong answer without the instrument
With a runtime whose allocation timing differs from main's, a plain ./qs_parse_nested 3000 1000 prints a wrong checksum in 9 of 12 runs, and a different one each time. Node 26.5.1 prints checksum fa2bbd09. The branch that showed this is wip/protochain-keyconv. It also fails with its only cache disabled (PERRY_INHERITED_IC=0), and the stale use above is on main. Base main was correct 12 of 12 on the plain run. Any qs instruction-count A/B is untrustworthy until this is fixed, because a timing change can flip the output.
Found while measuring the prototype-chain lane (#10495). The obj_type 3 target and the extend-store frame point at the dynamic-index store path (#10513 / #10694 neighbourhood), not the property path.
I checked this against #11599, the #11590 fix, which roots the packed-range loop's cached module-global copy. It does not fix this. qs@6.16.0, parse_nested 3000 1000, PERRY_GC_PROTECT_FROMSPACE=1, DEPTH=64: seeds 1, 7, 42, 99, 123 and 555 all still fault with #11599 applied, as they do on main (ed3ad3c29).
The shape is different, which may help whoever picks this up:
The stale object is obj_type=3, a string. It is not the array.
It faults in js_array_set_f64_extend_strict_impl, reached through js_array_set_index_or_string_with_strictness ← js_dyn_index_set_strict ← perry_closure_node_modules_qs_lib_parse_js__18.
The likeliest site is js_string_addref_if_heap_string(value) at array/indexing.rs:1344, reading a value operand that went stale in the compiled closure before the call.
Activity
proggeramlug commentedon Sep 27, 2026
I checked this against #11599, the #11590 fix, which roots the packed-range loop's cached module-global copy. It does not fix this. qs@6.16.0,
parse_nested 3000 1000,PERRY_GC_PROTECT_FROMSPACE=1,DEPTH=64: seeds 1, 7, 42, 99, 123 and 555 all still fault with #11599 applied, as they do on main (ed3ad3c29).The shape is different, which may help whoever picks this up:
obj_type=3, a string. It is not the array.js_array_set_f64_extend_strict_impl, reached throughjs_array_set_index_or_string_with_strictness←js_dyn_index_set_strict←perry_closure_node_modules_qs_lib_parse_js__18.js_string_addref_if_heap_string(value)atarray/indexing.rs:1344, reading a value operand that went stale in the compiled closure before the call.fix(runtime): object spill store read the uninitialized tail as the o…
fix(runtime): object spill store read the uninitialized tail as the o…