From 42d7bf1edf0ece82edcc55c108dd60559f4bd9c1 Mon Sep 17 00:00:00 2001 From: Dominik Schwaiger Date: Fri, 12 Jun 2026 08:55:09 +0000 Subject: [PATCH 1/3] add #[rustc_no_writable] to slice::get_unchecked_mut * add #[rustc_no_writable] to slice::get_unchecked_mut * add #[rustc_no_writable] to slice::get_mut * add unchecked_mut miri test --- library/core/src/slice/mod.rs | 2 ++ .../implicit_writes/unchecked_mut.rs | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index a838ba009b484..41c49b11e0b03 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -596,6 +596,7 @@ impl [T] { #[inline] #[must_use] #[rustc_const_unstable(feature = "const_index", issue = "143775")] + #[rustc_no_writable] pub const fn get_mut(&mut self, index: I) -> Option<&mut I::Output> where I: [const] SliceIndex, @@ -681,6 +682,7 @@ impl [T] { #[must_use] #[track_caller] #[rustc_const_unstable(feature = "const_index", issue = "143775")] + #[rustc_no_writable] pub const unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut I::Output where I: [const] SliceIndex, diff --git a/src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs b/src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs new file mode 100644 index 0000000000000..e2785183ff599 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs @@ -0,0 +1,28 @@ +// This test reproduces the pattern used by `BorrowedCursor::as_mut`, which appears in `Socket::recv_with_flags` and `std::fs::read`. +// Many crates depend on similar patterns. Before https://github.com/rust-lang/rust/pull/157202 this failed under Tree +// Borrows with Implicit Writes. With the attribute `#[rustc_no_writable]` added to +// `slice::get_unchecked_mut`, both this test and the affected crates work. +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes + +struct BorrowedBuf<'a> { + buf: &'a mut [u8], +} + +impl<'a> BorrowedBuf<'a> { + fn capacity(&self) -> usize { + self.buf.len() + } + + unsafe fn as_mut(&mut self) -> &mut [u8] { + unsafe { self.buf.get_unchecked_mut(..) } + } +} + +fn main() { + let mut arr = [0u8; 4]; + let mut buf = BorrowedBuf { buf: &mut arr }; + + let ptr = unsafe { buf.as_mut() }.as_mut_ptr(); + let _ = buf.capacity(); + unsafe { ptr.write(42); } +} From 5e3c06770fa5f1a85078733676a5bce53dfe39b8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 12 Jun 2026 16:13:29 +0200 Subject: [PATCH 2/3] move test --- .../unchecked_mut.rs => slice_get_mut_no_implicit_write.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/tools/miri/tests/pass/tree_borrows/{implicit_writes/unchecked_mut.rs => slice_get_mut_no_implicit_write.rs} (100%) diff --git a/src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs b/src/tools/miri/tests/pass/tree_borrows/slice_get_mut_no_implicit_write.rs similarity index 100% rename from src/tools/miri/tests/pass/tree_borrows/implicit_writes/unchecked_mut.rs rename to src/tools/miri/tests/pass/tree_borrows/slice_get_mut_no_implicit_write.rs From 96fcf8eb9093c47b6e2db9a84cd29ef8973861ab Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 12 Jun 2026 16:15:15 +0200 Subject: [PATCH 3/3] make more slice mutable ref getters rustc_no_writable --- library/core/src/slice/index.rs | 27 +++++++++++ .../slice_get_mut_no_implicit_write.rs | 48 +++++++++++++------ 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index 0efe87a2d5368..d6906702ec861 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -27,6 +27,7 @@ where I: [const] SliceIndex<[T]>, { #[inline(always)] + #[rustc_no_writable] fn index_mut(&mut self, index: I) -> &mut I::Output { index.index_mut(self) } @@ -225,6 +226,7 @@ unsafe impl const SliceIndex<[T]> for usize { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut T> { if self < slice.len() { // SAFETY: `self` is checked to be in bounds. @@ -273,6 +275,7 @@ unsafe impl const SliceIndex<[T]> for usize { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut T { // N.B., use intrinsic indexing &mut (*slice)[self] @@ -296,6 +299,7 @@ unsafe impl const SliceIndex<[T]> for ops::IndexRange { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { if self.end() <= slice.len() { // SAFETY: `self` is checked to be valid and in bounds above. @@ -344,6 +348,7 @@ unsafe impl const SliceIndex<[T]> for ops::IndexRange { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { if self.end() <= slice.len() { // SAFETY: `self` is checked to be valid and in bounds above. @@ -376,6 +381,7 @@ unsafe impl const SliceIndex<[T]> for ops::Range { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { if let Some(new_len) = usize::checked_sub(self.end, self.start) && self.end <= slice.len() @@ -445,6 +451,7 @@ unsafe impl const SliceIndex<[T]> for ops::Range { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { // Using checked_sub is a safe way to get `SubUnchecked` in MIR if let Some(new_len) = usize::checked_sub(self.end, self.start) @@ -469,6 +476,7 @@ unsafe impl const SliceIndex<[T]> for range::Range { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { ops::Range::from(self).get_mut(slice) } @@ -491,6 +499,7 @@ unsafe impl const SliceIndex<[T]> for range::Range { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { ops::Range::from(self).index_mut(slice) } @@ -508,6 +517,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeTo { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { (0..self.end).get_mut(slice) } @@ -530,6 +540,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeTo { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { (0..self.end).index_mut(slice) } @@ -547,6 +558,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFrom { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { (self.start..slice.len()).get_mut(slice) } @@ -576,6 +588,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFrom { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { if self.start > slice.len() { slice_index_fail(self.start, slice.len(), slice.len()) @@ -599,6 +612,7 @@ unsafe impl const SliceIndex<[T]> for range::RangeFrom { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { ops::RangeFrom::from(self).get_mut(slice) } @@ -621,6 +635,7 @@ unsafe impl const SliceIndex<[T]> for range::RangeFrom { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { ops::RangeFrom::from(self).index_mut(slice) } @@ -637,6 +652,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFull { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { Some(slice) } @@ -657,6 +673,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFull { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { slice } @@ -676,6 +693,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeInclusive { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { if *self.end() >= slice.len() { None } else { self.into_slice_range().get_mut(slice) } } @@ -708,6 +726,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeInclusive { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { let Self { mut start, mut end, exhausted } = self; let len = slice.len(); @@ -734,6 +753,7 @@ unsafe impl const SliceIndex<[T]> for range::RangeInclusive { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { ops::RangeInclusive::from(self).get_mut(slice) } @@ -756,6 +776,7 @@ unsafe impl const SliceIndex<[T]> for range::RangeInclusive { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { ops::RangeInclusive::from(self).index_mut(slice) } @@ -773,6 +794,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeToInclusive { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { (0..=self.end).get_mut(slice) } @@ -795,6 +817,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeToInclusive { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { (0..=self.end).index_mut(slice) } @@ -812,6 +835,7 @@ unsafe impl const SliceIndex<[T]> for range::RangeToInclusive { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { (0..=self.last).get_mut(slice) } @@ -834,6 +858,7 @@ unsafe impl const SliceIndex<[T]> for range::RangeToInclusive { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut [T] { (0..=self.last).index_mut(slice) } @@ -1047,6 +1072,7 @@ unsafe impl SliceIndex<[T]> for (ops::Bound, ops::Bound) { } #[inline] + #[rustc_no_writable] fn get_mut(self, slice: &mut [T]) -> Option<&mut Self::Output> { try_into_slice_range(slice.len(), self)?.get_mut(slice) } @@ -1069,6 +1095,7 @@ unsafe impl SliceIndex<[T]> for (ops::Bound, ops::Bound) { } #[inline] + #[rustc_no_writable] fn index_mut(self, slice: &mut [T]) -> &mut Self::Output { into_slice_range(slice.len(), self).index_mut(slice) } diff --git a/src/tools/miri/tests/pass/tree_borrows/slice_get_mut_no_implicit_write.rs b/src/tools/miri/tests/pass/tree_borrows/slice_get_mut_no_implicit_write.rs index e2785183ff599..789f0a8b88ce6 100644 --- a/src/tools/miri/tests/pass/tree_borrows/slice_get_mut_no_implicit_write.rs +++ b/src/tools/miri/tests/pass/tree_borrows/slice_get_mut_no_implicit_write.rs @@ -1,28 +1,48 @@ +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes + // This test reproduces the pattern used by `BorrowedCursor::as_mut`, which appears in `Socket::recv_with_flags` and `std::fs::read`. // Many crates depend on similar patterns. Before https://github.com/rust-lang/rust/pull/157202 this failed under Tree // Borrows with Implicit Writes. With the attribute `#[rustc_no_writable]` added to // `slice::get_unchecked_mut`, both this test and the affected crates work. -//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes - -struct BorrowedBuf<'a> { - buf: &'a mut [u8], -} - -impl<'a> BorrowedBuf<'a> { - fn capacity(&self) -> usize { - self.buf.len() +fn borrowed_buf() { + struct BorrowedBuf<'a> { + buf: &'a mut [u8], } - unsafe fn as_mut(&mut self) -> &mut [u8] { - unsafe { self.buf.get_unchecked_mut(..) } + impl<'a> BorrowedBuf<'a> { + fn capacity(&self) -> usize { + self.buf.len() + } + + unsafe fn as_mut(&mut self) -> &mut [u8] { + unsafe { self.buf.get_unchecked_mut(..) } + } } -} -fn main() { let mut arr = [0u8; 4]; let mut buf = BorrowedBuf { buf: &mut arr }; let ptr = unsafe { buf.as_mut() }.as_mut_ptr(); let _ = buf.capacity(); - unsafe { ptr.write(42); } + unsafe { + ptr.write(42); + } +} + +// A variant of the above that uses `index_mut` notation. +fn index_mut() { + fn dostuff(x: &mut [u8]) { + unsafe { + let ptr = (&mut x[..]).as_mut_ptr(); + let _len = x.len(); + ptr.write(99); + } + } + + dostuff(&mut [1, 2, 3, 4]); +} + +fn main() { + borrowed_buf(); + index_mut(); }