Skip to content

GC: a closure's captured this / new.target slots are unrooted allocas (closure.rs reserves no +1 slot where method.rs does) #7208

Description

@proggeramlug

Summary

codegen/closure.rs allocates the closure's captured this (:704) and new.target (:687) into plain alloca_entry slots and never binds either. They hold a heap receiver for the whole closure body, across every this_stack.last() reader (~14 sites), so an evacuating minor neither marks nor rewrites them and every load below a collection point names from-space.

Found by the #7202 bare-alloca enumeration (PR #7207), which fixed the sibling this slot on the inline-constructor path.

It is a structural asymmetry, not one missing line

codegen/method.rs:316   lf.enable_shadow_frame(m.len() as u32 + 1);   // the +1 IS the `this` slot
codegen/method.rs:1344  lf.enable_shadow_frame(m.len() as u32 + 1);
codegen/closure.rs:589  lf.enable_shadow_frame(m.len() as u32);       // no slot reserved

method.rs:334/:1372 then bind that reserved slot (js_shadow_slot_bind(this_shadow_slot_idx, this_slot)). Closures reserve nothing, so there is no index to bind.

The in-tree comment at codegen/closure.rs:815-817 — "The this / new.target capture reads are exempt … they run in the entry-block prologue, ahead of any statement that could collect" — justifies the timing of the read, not the lifetime of the slot, which spans the whole body.

Fix shape

expr::scalar_slot_root::root_entry_alloca (generalized in #7207 from #6968's machinery) is the mechanism: seed the alloca to undefined in entry_allocas, reserve_shadow_slot(), bind once in entry setup. It grows the frame on demand, so enable_shadow_frame's pre-lowering count does not have to predict it.

A reproducer needs a closure whose body allocates between two this uses, with the closure's receiver reachable only from the capture cell. It is worth writing one before fixing: #7207's inline-ctor equivalent turned out to be masked at runtime (the class-field inline guard fails on the forwarded header and the runtime fallback resolves forwarding), so a static fix without a witness should say so.

Refs #7202, #7154, #7161, #7207.

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