Skip to content

Fix unsoundness from closure capturing &mut in a struct/tuple field (#41)#92

Open
coord-e wants to merge 1 commit into
mainfrom
claude/investigate-issue-41-lCJWc
Open

Fix unsoundness from closure capturing &mut in a struct/tuple field (#41)#92
coord-e wants to merge 1 commit into
mainfrom
claude/investigate-issue-41-lCJWc

Conversation

@coord-e
Copy link
Copy Markdown
Owner

@coord-e coord-e commented May 21, 2026

Summary

Fixes #41. A closure capturing &mut stored in a tuple/struct field made thrust derive a contradictory environment and unsoundly accept programs that can panic (the issue's assert!(false) was proven unreachable).

Root cause

The drop-point analysis (src/analyze/basic_block/drop_point.rs) treats a local's last use as its drop site, based purely on liveness. When a value is moved into an aggregate (e.g. a closure moved into a tuple via _2 = (move _3, 0)), the moved-out temporary _3 was still dropped at the move site. Dropping a value containing a &mut asserts final == current on the captured reference, which prematurely resolves its prophecy to the construction-time value (0). The subsequent (s.0)() call mutates through the closure (final = 1), contradicting the pinned 0. An inconsistent environment makes any following assertion vacuously "safe" — the unsoundness.

Fix

Exclude owned (non-reference) locals whose ownership is transferred away by a move from the drop set — their drop obligation belongs to the destination. Moved references are deliberately left untouched, because ReborrowVisitor/RustCallVisitor rewrite them into reborrows, so the source local stays live and must still be dropped. (Restricting to non-reference types is essential: suppressing all moved-local drops regresses cases like closure_mut_0, where the original MIR moves a &mut that thrust reborrows.)

https://claude.ai/code/session_01HHar2z2xTNwffns5SF7wRe


Generated by Claude Code

@coord-e coord-e force-pushed the claude/investigate-issue-41-lCJWc branch 2 times, most recently from 365e389 to 115a8e0 Compare May 21, 2026 16:17
@coord-e coord-e marked this pull request as ready for review May 21, 2026 16:18
@coord-e coord-e requested a review from Copilot May 21, 2026 16:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a soundness bug in Thrust’s implicit drop-point analysis where moved (owned) locals could still be treated as dropped at the move site, producing contradictory environments when closures capturing &mut are stored in tuple/struct fields.

Changes:

  • Add tracking of locals whose ownership is transferred via move and exclude them from implicit drop points (for non-reference types).
  • Add UI regression tests covering both the accepted (“pass”) and rejected (“fail”) variants of the closure-in-field mutation scenario.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/analyze/basic_block/drop_point.rs Excludes moved (owned) locals from implicit drop sets to avoid premature drops at move sites.
tests/ui/pass/closure_field_mut.rs Regression test ensuring closure mutation through a tuple field is tracked and accepted when asserted correctly.
tests/ui/fail/closure_field_mut.rs Regression test ensuring incorrect post-call assertions are rejected (avoids vacuous acceptance via inconsistent env).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/analyze/basic_block/drop_point.rs Outdated
Fixes #41. A closure capturing `&mut` stored in a tuple/struct field made
thrust derive a contradictory environment and unsoundly accept programs that
can panic.

The drop-point analysis treats a local's last use as its drop site. For a value
moved into an aggregate (e.g. a closure moved into a tuple), this dropped the
moved-out temporary at the move site, prematurely resolving the captured
reference's prophecy (final == current) to its construction value. The later
call mutating through the closure then contradicted that, making the
environment inconsistent and any following assertion vacuously "safe".

Owned (non-reference) locals whose ownership is transferred away by a move are
now excluded from the drop set: their drop obligation belongs to the
destination. Moved references are left untouched since ReborrowVisitor/
RustCallVisitor turn them into reborrows, so the source remains live.

https://claude.ai/code/session_01HHar2z2xTNwffns5SF7wRe
@coord-e coord-e force-pushed the claude/investigate-issue-41-lCJWc branch from acdbb24 to afadb7a Compare May 21, 2026 16:27
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.

Mutation by a closure in a struct field leads to unsoundness

3 participants