Follow-up to #546/#547. The capped vm_borrow_scan is sound today — the only scan-reliant builtins (coalesce/append/dict_set) return index 0/1 of the arg vector, and the deep-element returners (get_at) incref locally. But the invariant "no builtin returns a borrowed direct child past index 7" is enforced only by a comment in the registry.
If a future builtin violates it, the failure is not loud: the missed compensating incref means the VM pushes a result it doesn't own a ref to, and the eventual over-release is a use-after-free that fires later and lifetime-dependently — a heisenbug, not a test failure.
Ask: in debug/sanitizer builds only (e.g. #ifndef NDEBUG or an EIGS_DEBUG gate), have vm_borrow_scan continue scanning past VM_BORROW_SCAN_CAP and abort() with a message naming the builtin if a pointer match is found beyond the cap. Zero release-build cost; converts a future silent memory corruption into an immediate red ASan run the moment the offending builtin's test executes.
🤖 Generated with Claude Code
Follow-up to #546/#547. The capped
vm_borrow_scanis sound today — the only scan-reliant builtins (coalesce/append/dict_set) return index 0/1 of the arg vector, and the deep-element returners (get_at) incref locally. But the invariant "no builtin returns a borrowed direct child past index 7" is enforced only by a comment in the registry.If a future builtin violates it, the failure is not loud: the missed compensating incref means the VM pushes a result it doesn't own a ref to, and the eventual over-release is a use-after-free that fires later and lifetime-dependently — a heisenbug, not a test failure.
Ask: in debug/sanitizer builds only (e.g.
#ifndef NDEBUGor anEIGS_DEBUGgate), havevm_borrow_scancontinue scanning pastVM_BORROW_SCAN_CAPandabort()with a message naming the builtin if a pointer match is found beyond the cap. Zero release-build cost; converts a future silent memory corruption into an immediate red ASan run the moment the offending builtin's test executes.🤖 Generated with Claude Code