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
- Verify the owner is actually rooted/marked live before sweeping; if it's intentionally dead, update the test to expect no forwarding entry.
- Check that sweep_and_forward records a forwarding entry for every object that survives marking.
- Run the GC tests with debug assertions and a smaller heap to catch which phase loses the object.
- 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
- Keep references to objects that must survive a sweep so the marker sees them as live roots.
- After GC changes, run the dispatch-forwarding GC tests before merging.
- Verify sweep_and_forward records a forwarding entry for every surviving object.
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
- Package.classes only contains class pointers
- Package.enums only contains enum pointers
- Function `{name}` is not invokable as an entry point (kind:
- VM internal error: {0}
- dynamic-impl table lock poisoned
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/aef157878beb4e7c.
Report an issue: GitHub.