From 6fd400b205650d04235be99de13425fdcedbfc57 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 11:16:11 +0000 Subject: [PATCH] Do not reborrow the operand of PtrMetadata `<[T]>::len` is lowered to `PtrMetadata`. From `-C opt-level=1` upward rustc applies it directly to the `&mut [T]` local rather than through a shared reborrow, so `ReborrowVisitor::visit_operand` rewrote the operand into a fresh `&mut` reborrow. `analyze_assignment` then discarded that reborrow and took its own shared borrow of the referent, leaving the reborrow's prophecy unresolved: the synthetic local is absent from the MIR `DropPoints` was computed over, so nothing ever equated it to its current value. Since borrowing a place replaces its value with the prophecy, `*v` was havoc'd for the rest of the function and `if i < v.len()` no longer discharged the bound on the following `v[i]`. `PtrMetadata` only reads the length out of its operand, so skip the reborrow and let `analyze_assignment` borrow the referent itself. Closes #240 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017rubDn1kuLtPnuRoLkkXTj --- src/analyze/basic_block/visitor/reborrow.rs | 10 ++++++++++ tests/ui/fail/slice_len_guard_mut.rs | 12 ++++++++++++ tests/ui/pass/slice_len_guard_mut.rs | 16 ++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 tests/ui/fail/slice_len_guard_mut.rs create mode 100644 tests/ui/pass/slice_len_guard_mut.rs diff --git a/src/analyze/basic_block/visitor/reborrow.rs b/src/analyze/basic_block/visitor/reborrow.rs index 32af11d2..aa51bae2 100644 --- a/src/analyze/basic_block/visitor/reborrow.rs +++ b/src/analyze/basic_block/visitor/reborrow.rs @@ -95,6 +95,16 @@ impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for ReborrowVisitor<'a, 'tcx, self.super_assign(place, rvalue, location); } + fn visit_rvalue(&mut self, rvalue: &mut mir::Rvalue<'tcx>, location: mir::Location) { + // `PtrMetadata` only reads the length out of the reference it is applied to, so its + // operand needs no reborrow. `analyze_assignment` takes the shared borrow it needs. + if let mir::Rvalue::UnaryOp(mir::UnOp::PtrMetadata, _) = rvalue { + return; + } + + self.super_rvalue(rvalue, location); + } + // TODO: is it always true that the operand is not referred again in rvalue fn visit_operand(&mut self, operand: &mut mir::Operand<'tcx>, location: mir::Location) { let Some(p) = operand.place() else { diff --git a/tests/ui/fail/slice_len_guard_mut.rs b/tests/ui/fail/slice_len_guard_mut.rs new file mode 100644 index 00000000..de2c5074 --- /dev/null +++ b/tests/ui/fail/slice_len_guard_mut.rs @@ -0,0 +1,12 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -C opt-level=2 + +#[thrust::callable] +fn check(v: &mut [i32], i: usize) { + if i < v.len() { + v[i] = 7; + assert!(v[i] == 8); + } +} + +fn main() {} diff --git a/tests/ui/pass/slice_len_guard_mut.rs b/tests/ui/pass/slice_len_guard_mut.rs new file mode 100644 index 00000000..23950648 --- /dev/null +++ b/tests/ui/pass/slice_len_guard_mut.rs @@ -0,0 +1,16 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -C opt-level=2 + +// At `-C opt-level=1` and above rustc reads slice metadata straight off the `&mut [i32]` +// local (`_len = PtrMetadata(copy _v)`) instead of through a shared reborrow, so this pins +// down that the length `len()` returns still describes the referent the guarded index +// reads. +#[thrust::callable] +fn check(v: &mut [i32], i: usize) { + if i < v.len() { + v[i] = 7; + assert!(v[i] == 7); + } +} + +fn main() {}