Skip to content

fix(rt): remove mutability guard on local variable assignment#948

Merged
automergerpr-permission-manager[bot] merged 3 commits intomasterfrom
jh/fix-immutable-local-reassign
Mar 2, 2026
Merged

fix(rt): remove mutability guard on local variable assignment#948
automergerpr-permission-manager[bot] merged 3 commits intomasterfrom
jh/fix-immutable-local-reassign

Conversation

@Stevengre
Copy link
Contributor

@Stevengre Stevengre commented Feb 26, 2026

Summary

Remove the mutabilityOf(...) ==K mutabilityMut guard from #setLocalValue in rt/data.md. MIR's LocalDecl::mutability is a source-level annotation, not an assignment constraint — the Rust compiler validates legality before emitting MIR and may reuse immutable locals across loop iterations.

  • Remove mutability guard from the initialized-local #setLocalValue rule
  • Preserve original mutability on write (mutabilityOf(...)) instead of forcing mutabilityMut
  • Add regression test immutable-local-reassign.rs (loop variable with mutability: Not)

Follow-up: #949 (remove mutability tracking entirely)

Context

A for i in 0..2 loop variable is bound via pattern matching (Some(i)) on each iteration, so rustc marks it as mutability: Not. However, rustc reuses the same MIR local across iterations, producing repeated assignments to an immutable local. This is valid MIR — confirmed via rustc -Z unpretty=mir and the LocalDecl documentation.

Without this fix, proof execution gets stuck at step 693 on #setLocalValue(place(local(8), .ProjectionElems), Integer(1, 64, false)) — the loop counter assignment that no rule can handle.

Proof evidence

Without fix (RED):

APRProof: immutable-local-reassign.main
    status: ProofStatus.FAILED
    stuck: 1, failing: 1

Leaf <k>:
  #setLocalValue(place(local(8), .ProjectionElems), Integer(1, 64, false))
  function: repro

With fix (GREEN):

test_prove_rs[immutable-local-reassign] PASSED (53.31s)

Test plan

  • immutable-local-reassign.rs passes with fix, fails (stuck) without fix
  • Full integration test suite (make test-integration)

MIR loop-carried control flow can assign to locals with `mutability: "Not"`
(e.g. loop counters). The `#setLocalValue` rule previously required
`mutabilityOf(...) ==K mutabilityMut`, causing proof execution to get stuck.

Remove the mutability guard so initialized typed locals can be written
regardless of their declared mutability.
Proof now reaches the target node instead of getting stuck, reflecting
the immutable local reassignment fix.
@Stevengre Stevengre force-pushed the jh/fix-immutable-local-reassign branch from 06cb4be to 1f09bbf Compare February 28, 2026 00:45
@Stevengre Stevengre self-assigned this Mar 2, 2026
Copy link
Collaborator

@dkcumming dkcumming left a comment

Choose a reason for hiding this comment

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

The mutability of a Place is not considered when assigning at the MIR level. This is a topic that is currently in some debate amongst the rustc community.

@mariaKt made a post in the stable-mir channel of the Rust zulip where we were informed that mutability is ignored. They directed us to this issue where they discuss if writing to an immutable Local should be considered UB - currently it is not.

Miri evaluates an assignment of a MIR Place by rustc_const_eval interpreter method eval_statement which does not consider mutability at all in the evaluation.

Kani also ignores the mutability of a Local when declaring them for the GOTO C backend as it does not at all read the mutability field of the LocalDecl. Similarly there is no check when for the StatementKind::Assign case for codegen_statement.

@automergerpr-permission-manager automergerpr-permission-manager bot merged commit 7355946 into master Mar 2, 2026
7 checks passed
@automergerpr-permission-manager automergerpr-permission-manager bot deleted the jh/fix-immutable-local-reassign branch March 2, 2026 04:22
dkcumming added a commit to runtimeverification/solana-token that referenced this pull request Mar 4, 2026
- More `BinOp::Offset` tests
https://github.com/runtimeverification/mir-semantics/pull/#935
- fix: type of offset for applyBinOp(binOpOffset, ...)
https://github.com/runtimeverification/mir-semantics/pull/#936
- Add type-correcting projections on pointer cast and related operations
https://github.com/runtimeverification/mir-semantics/pull/#937
- Fix metadata on `PtrToPtr` cast
https://github.com/runtimeverification/mir-semantics/pull/#941
- Corrections to Multisig cheatcodes
https://github.com/runtimeverification/mir-semantics/pull/#942
- Handled additional Range constructor in `toSigners` side condition.
https://github.com/runtimeverification/mir-semantics/pull/#944
- Update dependency: deps/stable-mir-json_release
runtimeverification/mir-semantics#938
- Improved `show` printing for leaves
runtimeverification/mir-semantics#946
- Fix/cachix pin no response
runtimeverification/mir-semantics#950
- Hotfix/cachix pin checks
runtimeverification/mir-semantics#951
- fix(rt): remove mutability guard on local variable assignment
runtimeverification/mir-semantics#948
- Makefile `stable-mir-json` command to build release also
runtimeverification/mir-semantics#963
- Add `rust-toolchain.toml`
runtimeverification/mir-semantics#959
- Add cut-point rules for specific functions / intrinsics (via
definition)
runtimeverification/mir-semantics#960
- Update dependency: deps/stable-mir-json_release
runtimeverification/mir-semantics#947
- Updated Solana cheatcodes with `Span` in `Call` `Terminator`
(c17566dc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants