You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(borrow): propagate ref-binding through reborrow indirection (Refs #177)
Pre-fix, `let r2 = r1` and `r2 = r1` (where `r1` is itself a ref-binder
rather than a fresh `&p`) silently skipped recording `r2`'s ref-graph
entry — both `record_ref_binding` and `StmtAssign` resolved the RHS via
`ref_target`, which only matches direct `&place`/`&mut place`. Without
the binding, the indirection chain `r2 = r1 = &local` slipped past
`returned_borrow`'s lookup in `state.ref_bindings`, so `return r2`
escaped without erroring. This is the "Reborrow through indirection"
item in the deferred-items comment at lib/borrow.ml:1483.
Fix is three coupled pieces:
1. `record_ref_binding` delegates to `returned_borrow` (which already
handled both `&p` and binder lookup for the return-escape path);
`returned_borrow` is moved above `record_ref_binding` to make the
dependency satisfiable in OCaml's top-down order.
2. `StmtAssign` pre_release + post-rebind use `returned_borrow` for
the same reason. A self-assignment guard (`r = r`) prevents the
no-op case from unbinding `r` and leaving it unprotected.
3. `expire_dead_ref_bindings` gates `end_borrow` on a multi-binder
liveness check: a borrow whose `b_id` is still referenced by any
live entry in `still_live` MUST NOT be ended when the first
binder dies. Without this gate, sharing the borrow object across
binders creates a soundness hole — the borrow is ended at the
first binder's last use, leaving the second binder unprotected
and the underlying place silently writable.
Four new e2e tests pin the fix:
- `borrow_reborrow_indir_ok.affine` — `let r2 = r1; *r1; *r2` (Ok).
- `borrow_reborrow_indir_escape.affine` — `return r2` where
`r2 = r1 = &local` now correctly errors `BorrowOutlivesOwner`.
- `borrow_reborrow_indir_nll_alias.affine` — `r1` dies, `r2` alive;
`x = 5` must error `MoveWhileBorrowed` (multi-binder gate).
- `borrow_reborrow_indir_assign.affine` — `r2 = r1` rebinds and
releases `r2`'s old loan; the later `y = 10` succeeds.
327 prior tests + 4 new = 331/331 green. Slice A/B/C anti-regressions
preserved. Comment at lib/borrow.ml:1483 left in place — the
indirection item description still applies as historical context for
the merged fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments