| //! Test related to <https://github.com/rust-lang/miri/issues/3341>: | |
| //! `Box` with custom allocators are still `noalias`, leading to UB. | |
| //@revisions: stack tree | |
| //@[tree]compile-flags: -Zmiri-tree-borrows | |
| //@normalize-stderr-test: "\[0x[a-fx\d.]+\]" -> "[RANGE]" | |
| #![feature(allocator_api)] | |
| use std::alloc::{AllocError, Allocator, Layout}; | |
| use std::cell::{Cell, UnsafeCell}; | |
| use std::mem; | |
| use std::ptr::{self, NonNull, addr_of}; | |
| use std::thread::{self, ThreadId}; | |
| const BIN_SIZE: usize = 8; | |
| // A bin represents a collection of blocks of a specific layout. | |
| #[repr(align(128))] | |
| struct MyBin { | |
| top: Cell<usize>, | |
| thread_id: ThreadId, | |
| memory: UnsafeCell<[usize; BIN_SIZE]>, | |
| } | |
| impl MyBin { | |
| fn pop(&self) -> Option<NonNull<u8>> { | |
| let top = self.top.get(); | |
| if top == BIN_SIZE { | |
| return None; | |
| } | |
| // Cast the *entire* thing to a raw pointer to not restrict its provenance. | |
| let bin = self as *const MyBin; | |
| let base_ptr = UnsafeCell::raw_get(unsafe { addr_of!((*bin).memory) }).cast::<usize>(); | |
| let ptr = unsafe { NonNull::new_unchecked(base_ptr.add(top)) }; | |
| self.top.set(top + 1); | |
| Some(ptr.cast()) | |
| } | |
| // Pretends to not be a throwaway allocation method like this. A more realistic | |
| // substitute is using intrusive linked lists, which requires access to the | |
| // metadata of this bin as well. | |
| unsafe fn push(&self, ptr: NonNull<u8>) { | |
| // For now just check that this really is in this bin. | |
| let start = self.memory.get().addr(); | |
| let end = start + BIN_SIZE * mem::size_of::<usize>(); | |
| let addr = ptr.addr().get(); | |
| assert!((start..end).contains(&addr)); | |
| // We can't update `top` as this may not be the last bin, but we can pretend to do so | |
| // such that the aliasing model checks things. | |
| // We access this via raw pointers so that the error span is in this file. | |
| let top_ptr = (&raw const self.top) as *mut usize; | |
| let top = top_ptr.read(); | |
| //~[tree]^ERROR: /read access .* is forbidden/ | |
| top_ptr.write(top); | |
| } | |
| } | |
| // A collection of bins. | |
| struct MyAllocator { | |
| thread_id: ThreadId, | |
| // Pretends to be some complex collection of bins, such as an array of linked lists. | |
| bins: Box<[MyBin; 1]>, | |
| } | |
| impl MyAllocator { | |
| fn new() -> Self { | |
| let thread_id = thread::current().id(); | |
| MyAllocator { | |
| thread_id, | |
| bins: Box::new( | |
| [MyBin { top: Cell::new(0), thread_id, memory: UnsafeCell::default() }; 1], | |
| ), | |
| } | |
| } | |
| // Pretends to be expensive finding a suitable bin for the layout. | |
| fn find_bin(&self, layout: Layout) -> Option<&MyBin> { | |
| if layout == Layout::new::<usize>() { Some(&self.bins[0]) } else { None } | |
| } | |
| } | |
| unsafe impl Allocator for MyAllocator { | |
| fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { | |
| // Expensive bin search. | |
| let bin = self.find_bin(layout).ok_or(AllocError)?; | |
| let ptr = bin.pop().ok_or(AllocError)?; | |
| Ok(NonNull::slice_from_raw_parts(ptr, layout.size())) | |
| } | |
| unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) { | |
| // Make sure accesses via `self` don't disturb anything. | |
| let _val = self.bins[0].top.get(); | |
| // Since manually finding the corresponding bin of `ptr` is very expensive, | |
| // doing pointer arithmetics is preferred. | |
| // But this means we access `top` via `ptr` rather than `self`! | |
| // That is fundamentally the source of the aliasing trouble in this example. | |
| let their_bin = ptr.as_ptr().map_addr(|addr| addr & !127).cast::<MyBin>(); | |
| let thread_id = ptr::read(ptr::addr_of!((*their_bin).thread_id)); | |
| //~[stack]^ERROR: tag does not exist in the borrow stack | |
| if self.thread_id == thread_id { | |
| unsafe { (*their_bin).push(ptr) }; | |
| } else { | |
| todo!("Deallocating from another thread"); | |
| } | |
| // Make sure we can also still access this via `self` after the rest is done. | |
| let _val = self.bins[0].top.get(); | |
| } | |
| } | |
| // Make sure to involve `Box` in allocating these, | |
| // as that's where `noalias` may come from. | |
| fn v1<T, A: Allocator>(t: T, a: A) -> Vec<T, A> { | |
| (Box::new_in([t], a) as Box<[T], A>).into_vec() | |
| } | |
| fn v2<T, A: Allocator>(t: T, a: A) -> Vec<T, A> { | |
| let v = v1(t, a); | |
| // There was a bug in `into_boxed_slice` that caused aliasing issues, | |
| // so round-trip through that as well. | |
| v.into_boxed_slice().into_vec() | |
| } | |
| fn main() { | |
| assert!(mem::size_of::<MyBin>() <= 128); // if it grows bigger, the trick to access the "header" no longer works | |
| let my_alloc = MyAllocator::new(); | |
| let a = v1(1usize, &my_alloc); | |
| let b = v2(2usize, &my_alloc); | |
| assert_eq!(a[0] + 1, b[0]); | |
| assert_eq!(addr_of!(a[0]).wrapping_add(1), addr_of!(b[0])); | |
| drop((a, b)); | |
| } |