BoundaryML/baml · error

live owner must be forwarded

Error message

live owner must be forwarded

What it means

This is a test assertion in `dynamic_dispatch_entries_are_forwarded_then_swept_with_owners` that fails when a GC forwarding map does not contain an entry for an owner that the test marked live. After sweeping, every surviving object must appear in the forwarding table with its new address; a missing entry means the sweep either collected a live object or failed to record its relocation, which would corrupt dynamic dispatch for that class.

Source

Thrown at baml_language/crates/bex_vm/src/package_load.rs:624

        let (mut tlab, owners) = register_witnesses(&heap, &tables, interface, 128);

        // Root every other owner AND its rule, as a live class's own reachability
        // would (an instance reaches the class; the class's provenance/type value
        // reaches its witnesses).
        let mut live: Vec<_> = owners.iter().step_by(2).copied().collect();
        live.extend(tables.rules_of(interface).into_iter().step_by(2));
        #[expect(unsafe_code, reason = "standalone stop-the-world GC test")]
        let (_stats, _remapped, forwarding) = unsafe { heap.collect_garbage(&live) };
        hook.forward_roots(&forwarding);
        assert_eq!(
            tables.rule_count(),
            64,
            "dead owners must sweep their rules"
        );
        let forwarded_owner = forwarding
            .get(&owners[0])
            .copied()
            .expect("live owner must be forwarded");
        assert!(
            !tables.rules_for_class(forwarded_owner).is_empty(),
            "live owner pointers must follow relocation"
        );
        for rule in tables.rules_of(interface) {
            #[expect(unsafe_code, reason = "reading a just-forwarded pointer")]
            let obj = unsafe { rule.get() };
            assert!(
                matches!(obj, Object::ImplRule(_)),
                "rule pointers must follow relocation"
            );
        }

        tlab.invalidate();
        #[expect(unsafe_code, reason = "standalone stop-the-world GC test")]
        let (_stats, _remapped, forwarding) = unsafe { heap.collect_garbage(&[]) };
        hook.forward_roots(&forwarding);
        assert_eq!(

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Verify the owner is actually rooted/marked live before sweeping; if it's intentionally dead, update the test to expect no forwarding entry.
  2. Check that sweep_and_forward records a forwarding entry for every object that survives marking.
  3. Run the GC tests with debug assertions and a smaller heap to catch which phase loses the object.
  4. Inspect recent changes to marking/roots and revert or fix the regression.

Example fix

// before (test assumes liveness not established)
sweep_and_forward();
let forwarded_owner = forwarding.get(&owners[0]).copied().expect("live owner must be forwarded");
// after (keep the owner alive across the sweep)
let keep_alive: Vec<HeapPtr> = owners.clone(); // reachable during sweep
sweep_and_forward();
let forwarded_owner = forwarding.get(&keep_alive[0]).copied().expect("live owner must be forwarded");
Defensive patterns

Strategy: validation

Validate before calling

// test-side check before asserting
assert!(owners.iter().all(|o| is_marked_live(o)), "all owners must be rooted before sweep_and_forward");

Type guard

// narrow the forwarding result before use
let forwarded_owner: Option<HeapPtr> = forwarding.get(&owners[0]).copied();
if let Some(owner) = forwarded_owner { /* proceed */ }

Prevention

When it happens

Trigger: Running the VM GC tests where `owners[0]` is expected to survive the sweep but `forwarding.get(&owners[0])` returns None — e.g. the object wasn't rooted/marked before sweep_and_forward, or the sweep skipped recording a forwarding entry for a live object.

Common situations: Changes to the GC marking or forwarding logic; tests that forget to keep a reference alive across the sweep; regressions where sweep_and_forward drops entries whose owners are still reachable.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/aef157878beb4e7c. Report an issue: GitHub.