Skip to content

fix(mir): extend chain-sibling drop compensator to match-hop and tuple-chain escapes#2386

Merged
slepp merged 2 commits into
mainfrom
fix/drop-chain-compensator-tuple-match
Jul 3, 2026
Merged

fix(mir): extend chain-sibling drop compensator to match-hop and tuple-chain escapes#2386
slepp merged 2 commits into
mainfrom
fix/drop-chain-compensator-tuple-match

Conversation

@slepp

@slepp slepp commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

I extended the multi-hop chain-sibling drop compensator to two more escape shapes: a match-bound intermediate alias hop (previously double-freed the intermediate, #2384) and a multi-hop tuple return chain (previously leaked sibling fields, #2383). Both reach exactly-once drop behaviour for the covered shapes. The compensator's escape triggers stay a strict subset of the provers' exclusion triggers, so it never double-frees — a discharge only ever lands where the composite drop is already suppressed.

Verification

Out of scope

Both pre-existing, bounded, safe, and tracked as follow-ups — neither is touched or made worse by this change:

  • A residual fail-closed leak (~2 strings/call) on the match-bound-hop shape where the compensator can't fully attribute the hop back to its owner (only let-bound projections are recorded in the alias projection chain today).
  • A tuple-of-user-record composite coverage gap: the tuple composite prover only recognizes string tuple elements as dischargeable, so a tuple whose elements are user records still leaks unconditionally in the caller. Confirmed identical before and after this change.

Fixes #2383, fixes #2384.

slepp added 2 commits July 3, 2026 16:38
A return alias chain whose intermediate hop is destructured via `match`
(`let mid = o.mid; let leaf = match mid { Mid { leaf, x: _ } => leaf };
leaf`) double-freed the returned subtree: the destructure lowers to a
field load off the scrutinee copy, so the match-bound binder was
invisible to the composite-drop provers' Move-only alias forward-walk.
The owner stayed admitted and its RecordInPlace drop re-freed the
strings the returned value still owns (`free_cstring: C-string header
sentinel missing` abort / SIGSEGV under Guard Malloc). Same failure
through the tuple prover for the tuple twin.

Add a gated interior descent (`descend_match_bound_hop_aliases`): a
RecordFieldLoad/TupleFieldLoad off a tracked alias carrier attributes
the dest to the same owner root iff the dest is a byte-copy inline
aggregate (retained-string and handle-transfer dests stay real owners).
The descended binders feed the record and tuple provers' escape arms
only — never the release-owner Defect-1 blanket, which would newly
exclude owners in clean non-escaping match shapes — so exclusion widens
exactly when the binder leaves the function. Fail-closed: the owner's
non-escaped siblings leak; nothing double-frees.

Regression oracles pin both twins under the poisoned allocator.

Fixes #2384
…oppable

A multi-hop tuple-return alias chain (`let mid = o.0; let leaf = mid.0;
leaf`) leaked four strings per call through two gaps the record path
had already closed:

- The multi-hop chain-sibling compensator only walked record roots, so
  when the tuple composite prover excluded the owner for a >=2-hop
  escape, nothing discharged the owned siblings along the chain — the
  intermediate `mid.1` and the outer `o.1` leaked every frame.
  Generalize the walk to record AND tuple candidate roots, addressing
  each level with the selector matching the node's own type
  (`FieldAddr::Tuple` on tuple nodes, `FieldAddr::Record` otherwise).

- The tuple prover counted the spliced `hew_string_drop` for a
  `string` `TupleFieldLoad` read-temp as an element release path, so
  a caller that merely read the returned pair (`l.0.len()`) tripped
  the Defect-1 blanket and lost its `TupleInPlace` — both elements
  leaked. Mirror the record prover's read-temp exemption: the spliced
  drop balances a codegen-cloned fresh `+1`, not the tuple's original,
  so it never seeds `release_owner_bases`. Non-string extractions
  still seed.

The compensator's escape triggers stay a strict subset of the provers'
exclusion triggers (a discharge only ever lands where the composite
drop is already excluded, so the two can never disagree into a
double-free); any unmodelled shape still bails the pass and leaks as
before. A leak-slope oracle pins the flat slope on the returned-tuple
chain.

Verified 0 leaks/call under the poisoned-allocator triple at 3 and 50
frames, and no double-free under Guard Malloc across the record, tuple,
store, and match-hop escape shapes.

Fixes #2383
@slepp slepp enabled auto-merge (squash) July 3, 2026 22:44
@slepp slepp merged commit b62479e into main Jul 3, 2026
12 checks passed
@slepp slepp deleted the fix/drop-chain-compensator-tuple-match branch July 3, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Double-free when an intermediate alias-chain hop is bound via match then returned Multi-hop tuple-return alias chain leaks sibling fields

1 participant