Skip to content

fix(json): JSON.parse reviver on a lazy-tape array no longer SIGSEGVs (#1424)#1447

Merged
proggeramlug merged 1 commit into
mainfrom
fix/json-lazy-reviver-1424
May 23, 2026
Merged

fix(json): JSON.parse reviver on a lazy-tape array no longer SIGSEGVs (#1424)#1447
proggeramlug merged 1 commit into
mainfrom
fix/json-lazy-reviver-1424

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

JSON.parse(blob, reviver) on a large top-level array crashed with SIGSEGV (exit 139, zero output) under the lazy-tape path (PERRY_JSON_TAPE=1, and the default for this shape), and the reviver was never invoked on the elements (#1424).

apply_reviver walks the parsed value as an ArrayHeader, reading length/capacity/element f64s directly off the pointer. A LazyArrayHeader has a different layout, so those reads returned garbage → out-of-bounds element access → crash. Fix: materialize a lazy array via force_materialize_lazy at the top of apply_reviver (mirroring the stringify path's redirect_lazy_to_materialized, but forcing materialization since nothing has indexed the array yet), so the in-place element walk + write-backs operate on a real ArrayHeader and the caller receives the materialized array.

Test

Flips test_json_lazy_reviver to PASS in both default and PERRY_JSON_TAPE=1 modes:

len 300
first 1
last 300
calls 301

JSON parity sweep clean in the default (CI) config: 18 pass, 0 fail.

Note: a separate, pre-existing tape-only bug remains — console.log() of a non-revived lazy string array prints [object Object] (test_json_sso_strings under PERRY_JSON_TAPE=1); unrelated to the reviver path. Filing separately.

Closes #1424.

…#1424)

apply_reviver walked the parsed value as an ArrayHeader, reading
length/capacity/element f64s directly off the pointer. Under
PERRY_JSON_TAPE the top-level array is a LazyArrayHeader with a different
layout, so the walk read garbage and crashed (exit 139) before any
console.log fired, and the reviver never ran on the elements. Materialize
a lazy array (force_materialize_lazy) at the top of apply_reviver so the
in-place element walk + write-backs operate on a real ArrayHeader, and the
caller receives the materialized array. Flips test_json_lazy_reviver to
PASS in both default and PERRY_JSON_TAPE=1 modes (301 reviver calls,
matching Node).
@proggeramlug proggeramlug merged commit bbf4659 into main May 23, 2026
9 checks passed
@proggeramlug proggeramlug deleted the fix/json-lazy-reviver-1424 branch May 23, 2026 01:15
proggeramlug added a commit that referenced this pull request May 23, 2026
…es (#1458)

Rolls up 22 PRs that merged to main post-v0.5.1025 without per-PR
version bumps.

- node:timers epic (#1213, 6 PRs #1449-#1455): node:timers/promises
  setTimeout/setImmediate, numeric-id clear*, namespace import, ref()
  typeof, Symbol.dispose, global setImmediate/clearImmediate.
- node:perf_hooks fan-out (10 PRs across #1320 #1327 #1337-#1340
  #1388-#1390 #1403): typeof methods, performance singleton identity,
  nodeTiming, toJSON, clearResourceTimings, structured-clone detail,
  observer arg + buffered + throw cases.
- node:json lazy-tape closes #1424 (#1447) — JSON.parse reviver on
  lazy-tape arrays no longer SIGSEGVs (test un-skip-listed).
- #1448 console.log/util.inspect on lazy-tape arrays materialises
  before formatting.
- #1429 GC unsafe-zone guards for fastify/hyper handlers.
- #1341 codegen: Any-typed .includes()/.indexOf() dynamic dispatch.
- #1370 perry/ui web driver debug noise dropped.

#1423 (AsyncLocalStorage .enterWith/.exit) remains skip-listed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON.parse(blob, reviver) with lazy-tape top-level array: reviver not called on elements

1 participant