d72fe0c2e61e44026f506e6bc9a8f1ad9a68ce8d
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d72fe0c2e6 |
fix(codegen): drop branch-consume-split owned params (#63 leg 3)
The last leg of the #63 drop-soundness cluster: an (own ...) heap param consumed on one match-arm / if-branch but live on a sibling was never dropped on the live path, leaking one slab per call. This was the residual live=2 leak in series_sma that kept the series headline from being leak-clean. Root: the per-fn aggregate consume_count (the worst-case max over all branches from uniqueness::merge_states) was codegen's only consume signal, and every owned-param drop site gated on it. A param consumed on SOME branch makes the aggregate >= 1, so every site skipped it — including the branch where it stays live. match and if share this root (if is first-class MTerm::If, not desugared to match). Mechanism (spec 0068): - CAPTURE: uniqueness retains the per-branch consume snapshots it already computes per branch and discarded at the max merge — new BranchConsume + infer_module_with_cross_branches (the aggregate-only infer_module_with_cross now delegates and drops the channel). - CARRY: additive MArm.consume / MTerm::If.{then,else}_consume, attached by lower_to_mir via a traversal-order cursor (post-order pop, kind- and exhaustion-asserts make a desync a loud panic; neutral for const bodies which uniqueness does not walk). The AST carries no node id, so the correspondence is the structural pre-order both walks share — pinned by branch_consume_maps_attach_to_matching_arms. - GATE: emit_leakclass_branch_param_drops fires a fall-through drop only for the leak class (branch_consume==0 AND aggregate>=1), in match arms + both if branches; the pre-tail-call dec switches its gate source from the aggregate to per-arm. The fn-return dec and arm-close pattern-binder dec are UNCHANGED. Safety: - Double-free: the drop sites partition by aggregate (==0 -> existing fn-return/pre-tail-call; >=1 -> new per-branch). Disjoint, so no param is dropped twice — no fn-return disable, no tail-position analysis. - Use-after-free: the checker rejects use-after-consume, so an aggregate>=1 param is provably dead past the construct (path-terminal drop, tail position irrelevant). - Type eligibility (found by the full-suite gate during implement; spec 0068 refined): the new agg>=1 site is the FIRST drop site that can reach a STATIC closure-pair param (a top-level fn ref like inc in Either.either, consumed on one arm, live on the other) — a .rodata constant with no rc-header. field_drop_call routes Type::Fn / static-Str / Type::Var to the bare ailang_rc_dec, which underflowed on it. The helper now drops only params with a real per-type heap-ADT drop fn (field_drop_call != "ailang_rc_dec"). Sound (no underflow) and leak-correct (a static closure/Str allocates nothing). Witnessed green by std_either_demo / std_list_demo / poly_rec_capture_demo. Verification: full workspace suite green (116 binaries); both new leak pins (if + match) and series_sma_no_leak_pin green at live=0; legs 1/2 pins still green; INTERCEPTS<->(intrinsic) bijection intact; no Pattern::Lit reject path added. The leg-3 helper's type precondition was applied inline by the orchestrator (a narrowing guard clause in an already-reviewed Task-4 helper, full context loaded) after the implement-orchestrator correctly surfaced the spec gap rather than papering over the regression. This clears the series_sma leak tail; the series milestone (#61) close stays a separate deliberate step (its end-to-end milestone fieldtest). closes #63 |
||
|
|
f7226cfba5 |
spec: branch-consume-split drop soundness (#63 leg 3)
Design spec for the last open leg of bug #63: an (own ...) heap param consumed on one branch of a match/if but live on a sibling branch is never dropped on the live branch, leaking one slab per call. This is the residual live=2 leak in series_sma that keeps the series milestone #61 from closing. The leak is general (match AND if, not Series-specific): the per-fn aggregate consume_count — the worst-case max over all branches from uniqueness::merge_states — is codegen's only consume signal, and every owned-param drop site gates on it. A param consumed on some branch makes the aggregate >= 1, so every drop site skips it, including the branch where it stays live. Approach A (chosen, scope = the whole branch-consume-split class): retain the per-branch consume snapshots the uniqueness walker already computes and currently discards at the max merge; carry them additively on MArm.consume and MTerm::If.{then,else}_consume (attached by lower_to_mir in structural traversal-order lock-step, since Term/Match/If/Arm carry no node id); codegen reads them at the branch drop sites. The check pass stays the sole consume authority (the mir.3a direction). Mechanism (refined during planning recon, simpler + provably safe than the first draft's fn-return-disable): the drop sites PARTITION by aggregate consume, so the per-branch map only ever ADDS drops for the leak class and never collides with an existing site: - fn-return dec (lib.rs) and arm-close pattern-binder dec: UNCHANGED. They keep firing for aggregate == 0 (live on every path). - pre-tail-call dec (match_lower.rs): gate source switches from the per-fn aggregate to the per-arm map. Identical decision for legs 1/2 (aggregate 0 => per-arm 0 on every arm); additionally drops a param live on this tail-call arm but consumed on a sibling. - NEW fall-through drop (match arms + if branches), before the br to the join: fires ONLY for the leak class — branch_consume == 0 AND aggregate >= 1. Double-free safety is disjointness by aggregate: the new drop needs aggregate >= 1, fn-return needs aggregate == 0 — mutually exclusive, so no fn-return disable and no tail-position analysis are needed. Use-after-free safety: the checker REJECTS using an owned value after it was consumed on any branch (use-after-consume), pinned green by use_after_consume_on_own_param_is_reported and harden_ownership_heap_double_consume_still_errors — so an aggregate>=1 param is provably never live past the construct, making the per-branch drop path-terminal regardless of tail position. grounding-check PASS (all load-bearing assumptions ratified by green tests / structural fact, incl. the disjointness and use-after-consume claims). Two RED repros carried: the already-committed ignored match pin and a new if-branch pin. refs #63 |