Skip to content

gc: from-space fault in the Object.defineProperty / descriptor-getter family (the window #6949's scope note defers) #7963

Description

@proggeramlug

Found while validating #7949 (PR #7962). This is a latent defect on main, not a defect of that PR: it reproduces with none of #7949's code on the path.

Symptom

Under the from-space quarantine, a program that does some allocating work and then runs Object.defineProperty with a getter on the descriptor bag faults on a retired from-space address:

[gc-fromspace-protect] FAULT: signal 10 at 0x504f8160e5c
  This address is RETIRED FROM-SPACE. The evacuating minor moved or
  freed the object here and the holder kept the pre-collection address.
  block=0x504f8160000 +3676 retired_bytes=4128 retired_by_minor=#213
  last-known object: user_ptr=0x504f8160e58 obj_type=3 size=216
  The faulting instruction IS the stale use.

obj_type=3 is GC_TYPE_STRING — a key string, which is what the shape below predicts.

Reproduce

Witness configuration:

PERRY_GC_SCHEDULE_SEED=1 PERRY_GC_SCHEDULE_RATE=1 \
PERRY_GC_PROTECT_FROMSPACE=1 PERRY_GC_PROTECT_FROMSPACE_DEPTH=800

Program: any allocating first arm, then a descriptor bag whose getters allocate, installed one property at a time. Two independent reproducers were used during the #7949 validation:

  1. test_gap_gc_container_value_rooting.ts's objectGroupBy() arm followed by a hand-written loop

    for (const key of Object.keys(bag)) {
      Object.defineProperty(target, key, (bag as any)[key]);
    }

    — faults on the fix(runtime): root JS values retained in Rust containers across allocations (#7949) #7962 branch, i.e. with Object.defineProperties (the function runtime: root JSValues retained in Rust containers across allocations #7949 fixes) not on the path at all. This is what proves the defect is elsewhere.

  2. A manual grouping loop (grouped[k].push(src[i]) with an allocating churn per element) followed by the same defineProperty work — faults on main and on the fix(runtime): root JS values retained in Rust containers across allocations (#7949) #7962 branch identically, in the first arm.

Where to look

#6949's scope note already names this window and defers it:

js_object_define_property also holds obj / descriptor_value and the six raw JSValues inside DescView across its own later js_string_from_bytes calls, and obj_value_has_own_key holds keys / key_str across a js_array_get walk that can materialize a lazy array. That "cold arms never root anything" shape is bigger than any of these three families.

obj_type=3 / size 216 is consistent with a key_str or a descriptor-field name string held across one of those interning calls.

Why it matters beyond the fixture

zod calls Object.defineProperties in two places (node_modules/zod/src/v4/core/util.ts:316, node_modules/zod/src/v4/classic/errors.ts:28) and builds descriptor bags with getters, so this is on the path of the dependency-scale corpus behind #7803.

Note on the corpus

test-files/gc-dep-corpus/main.ts does not link on main today (Undefined symbols: _perry_fn_node_modules_zod_src_v4_core_index_ts__NEVER, …__brand, …__gt, … — the export * from "./core.js" re-export set in the currently pinned zod@4.3.5). Identical failure on a pristine origin/main build, so it is not related to #7962 — but it does mean #7803's own reproducer cannot be run as written right now. Worth its own issue or a corpus regeneration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions