Skip to content

js_generator_attach_prototype derefs retired from-space memory (SIGBUS under GC zeal) #7577

Description

@proggeramlug

Constructing a generator in a hot loop takes a bus error on retired
from-space memory
under the #7154 rooting instruments. The faulting frame is
js_generator_attach_prototype, i.e. generator construction — not stepping,
not the result object.

Found while working #7564. Not caused by it#7564 touches no generator
code (git diff --stat covers only array/iter_object.rs, buffer/iter.rs,
collection_iter_object.rs, iterator_helpers.rs, string/iter_object.rs,
string/mod.rs, iter_result.rs, gc/mod.rs). It surfaced only after
#7564, because before that the process died earlier at
iterator_helpers::make_iter_result and never reached this site.

Repro

function churn(i: number): string {
    return "pad-" + i + "-" + (i * 7919) + "-" + (i % 13);
}
function* small(n: number): Generator<number, void, undefined> {
    for (let k = 0; k < n; k++) yield k + n;
}
let acc = 0;
for (let i = 0; i < 4000; i++) {
    const g = small(4);          // ← construction is what faults
    let gs = g.next();
    while (!gs.done) { acc += gs.value as number; gs = g.next(); }
    churn(i);
}
console.log(acc);

Compile with PERRY_GC_MOVING_LOOP_POLLS=1 (loop polls are off by default
since #7161, so a default build never collects inside the loop), then:

PERRY_GC_ZEAL=1 PERRY_GC_PROTECT_FROMSPACE=1 \
PERRY_GC_PROTECT_FROMSPACE_DEPTH=800 PERRY_GC_DIAG=1 ./repro

Fault

[gc-fromspace-protect] mode=ProtectPages retired_set=#0 blocks=17 sets_held=1/800
                       bytes_protected=17825792
[gc-fromspace-protect] FAULT: signal 10 at 0x2cc0000fea0
  This address is RETIRED FROM-SPACE. The evacuating minor moved or
  freed the object here and the holder kept the pre-collection address.
  block=0x2cbfff10000 +1048224 retired_bytes=1048512 retired_by_minor=#1
  last-known object: user_ptr=0x2cc0000fea8 obj_type=2 size=72
2  js_generator_attach_prototype + 248
3  js_generator_attach_prototype + 248
4  perry_fn_..._small + 668
5  main + 7840

Exit 138 (128 + SIGBUS). The instrument is confirmed live —
mode=ProtectPages retired_set=#N printed and the run performed real copying
minors.

obj_type=2 is GC_TYPE_OBJECT. The doubled frame 2/3 suggests the fault is
inside a call js_generator_attach_prototype makes, with the return address
landing back in the same function.

Why this class matters

Per CLAUDE.md, a stale-from-space read is invisible at collection time — there
is nothing for the collector to find — and surfaces cycles later as
TypeError: value is not a function or a silently wrong answer. Without the
instruments this program produces a wrong result and exits 0. That is the
expensive failure mode, and it is why the fault is worth fixing even though
the default build does not reproduce it.

Where to look

js_generator_attach_prototype — whatever receiver or prototype pointer it
holds across an allocating call (a prototype lookup, a shape install, an
object allocation) needs to come back out of a RuntimeHandleScope handle via
RuntimeHandle::across_{mut,const,nanbox} rather than being bound before the
call. See docs/src/internals/gc-rooting-invariant.md.

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