Skip to content

gc: zod dep-corpus under PERRY_GC_SCHEDULE_RATE=1 with the quarantine OFF dies with a rooting-shaped TypeError (seed 1, safepoint 3319) #7803

Description

@proggeramlug

Found by #7793's witness during its merge audit, in the sabotage arm that turns the from-space quarantine OFF — i.e., this is a latent defect on main (v0.5.1458 runtime), not a defect of that PR, and the shipped witness configuration (quarantine ON) runs the same workload clean.

Reproduce

cargo build --release -p perry -p perry-runtime-static -p perry-stdlib-static
npm ci --ignore-scripts --no-audit --no-fund
target/release/perry test-files/gc-dep-corpus/main.ts -o /tmp/w
PERRY_GC_SCHEDULE_SEED=1 PERRY_GC_SCHEDULE_RATE=1 \
PERRY_GC_PROTECT_FROMSPACE=0 PERRY_GC_DIAG=1 /tmp/w

Observed (M1 Pro, macOS, release build):

TypeError: Cannot read properties of undefined (reading 'toString')
    at <anonymous>
[gc-schedule] done: seed=1 safepoints=3319 scheduled_collections=3319 polls_paced=23075 copying_minors=3319 moved_objects=385030 loop_polls=26394

exit 1, at safepoint 3,319 of the ~8,000 the full run performs. The workload is single-threaded, so the seeded schedule should replay exactly ((seed, counter) — one observation so far; the box was at load 20+ and a second confirmation run was cut off, so start by confirming determinism).

Why this smells like a #7154-class rooting bug and not an instrument bug

  • The same binary, same seed, same rate with PERRY_GC_PROTECT_FROMSPACE=1 (depth 800) passes: 8,022 copying minors, 852,411 objects moved, output byte-identical to the unscheduled run — verified twice.
  • The failure shape is the classic surfacing form: TypeError on an undefined that should be an object, cycles after the collection that lost it.
  • The quarantine changes recycling — retired from-space page-sets are held (and PROT_NONE'd) instead of being recycled into Eden. Two readings, both worth chasing:
    1. the allocation/recycling dynamics under quarantine shift which safepoints land in the vulnerable window, so the bug's window simply never lines up in the protected arm (the protected arm also paced differently: 63,936 polls vs 26,394 at the failure point); or
    2. less likely, a stale read that the quarantine should be catching is landing in memory the quarantine does not cover (same-cycle, pre-retirement).
  • If (1), PERRY_GC_SCHEDULE_SEED sweeps over the PROTECTED arm (scripts/gc_schedule_fuzz.sh) should eventually find a seed whose protected run faults precisely — that would hand us the exact stale address and retiring minor.

Context

The zod dependency corpus is exactly the population #7280 showed the curated fixtures don't reach (js_object_assign_one, js_new_function_construct). #7793's shipped witness gates the protected arm in CI; this issue tracks the unprotected finding it flushed out on its first run.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions