| use std::alloc::Layout; |
| use std::borrow::Cow; |
| use std::{alloc, slice}; |
| |
| use rustc_middle::mir::interpret::AllocBytes; |
| use rustc_target::abi::{Align, Size}; |
| |
| /// Allocation bytes that explicitly handle the layout of the data they're storing. |
| /// This is necessary to interface with native code that accesses the program store in Miri. |
| #[derive(Debug)] |
| pub struct MiriAllocBytes { |
| /// Stored layout information about the allocation. |
| layout: alloc::Layout, |
| /// Pointer to the allocation contents. |
| /// Invariant: |
| /// * If `self.layout.size() == 0`, then `self.ptr` was allocated with the equivalent layout with size 1. |
| /// * Otherwise, `self.ptr` points to memory allocated with `self.layout`. |
| ptr: *mut u8, |
| } |
| |
| impl Clone for MiriAllocBytes { |
| fn clone(&self) -> Self { |
| let bytes: Cow<'_, [u8]> = Cow::Borrowed(self); |
| let align = Align::from_bytes(self.layout.align().try_into().unwrap()).unwrap(); |
| MiriAllocBytes::from_bytes(bytes, align) |
| } |
| } |
| |
| impl Drop for MiriAllocBytes { |
| fn drop(&mut self) { |
| // We have to reconstruct the actual layout used for allocation. |
| // (`Deref` relies on `size` so we can't just always set it to at least 1.) |
| let alloc_layout = if self.layout.size() == 0 { |
| Layout::from_size_align(1, self.layout.align()).unwrap() |
| } else { |
| self.layout |
| }; |
| // SAFETY: Invariant, `self.ptr` points to memory allocated with `self.layout`. |
| unsafe { alloc::dealloc(self.ptr, alloc_layout) } |
| } |
| } |
| |
| impl std::ops::Deref for MiriAllocBytes { |
| type Target = [u8]; |
| |
| fn deref(&self) -> &Self::Target { |
| // SAFETY: `ptr` is non-null, properly aligned, and valid for reading out `self.layout.size()`-many bytes. |
| // Note that due to the invariant this is true even if `self.layout.size() == 0`. |
| unsafe { slice::from_raw_parts(self.ptr, self.layout.size()) } |
| } |
| } |
| |
| impl std::ops::DerefMut for MiriAllocBytes { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| // SAFETY: `ptr` is non-null, properly aligned, and valid for reading out `self.layout.size()`-many bytes. |
| // Note that due to the invariant this is true even if `self.layout.size() == 0`. |
| unsafe { slice::from_raw_parts_mut(self.ptr, self.layout.size()) } |
| } |
| } |
| |
| impl MiriAllocBytes { |
| /// This method factors out how a `MiriAllocBytes` object is allocated, given a specific allocation function. |
| /// If `size == 0` we allocate using a different `alloc_layout` with `size = 1`, to ensure each allocation has a unique address. |
| /// Returns `Err(alloc_layout)` if the allocation function returns a `ptr` where `ptr.is_null()`. |
| fn alloc_with( |
| size: u64, |
| align: u64, |
| alloc_fn: impl FnOnce(Layout) -> *mut u8, |
| ) -> Result<MiriAllocBytes, ()> { |
| let size = usize::try_from(size).map_err(|_| ())?; |
| let align = usize::try_from(align).map_err(|_| ())?; |
| let layout = Layout::from_size_align(size, align).map_err(|_| ())?; |
| // When size is 0 we allocate 1 byte anyway, to ensure each allocation has a unique address. |
| let alloc_layout = |
| if size == 0 { Layout::from_size_align(1, align).unwrap() } else { layout }; |
| let ptr = alloc_fn(alloc_layout); |
| if ptr.is_null() { |
| Err(()) |
| } else { |
| // SAFETY: All `MiriAllocBytes` invariants are fulfilled. |
| Ok(Self { ptr, layout }) |
| } |
| } |
| } |
| |
| impl AllocBytes for MiriAllocBytes { |
| fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, align: Align) -> Self { |
| let slice = slice.into(); |
| let size = slice.len(); |
| let align = align.bytes(); |
| // SAFETY: `alloc_fn` will only be used with `size != 0`. |
| let alloc_fn = |layout| unsafe { alloc::alloc(layout) }; |
| let alloc_bytes = MiriAllocBytes::alloc_with(size.try_into().unwrap(), align, alloc_fn) |
| .unwrap_or_else(|()| { |
| panic!("Miri ran out of memory: cannot create allocation of {size} bytes") |
| }); |
| // SAFETY: `alloc_bytes.ptr` and `slice.as_ptr()` are non-null, properly aligned |
| // and valid for the `size`-many bytes to be copied. |
| unsafe { alloc_bytes.ptr.copy_from(slice.as_ptr(), size) }; |
| alloc_bytes |
| } |
| |
| fn zeroed(size: Size, align: Align) -> Option<Self> { |
| let size = size.bytes(); |
| let align = align.bytes(); |
| // SAFETY: `alloc_fn` will only be used with `size != 0`. |
| let alloc_fn = |layout| unsafe { alloc::alloc_zeroed(layout) }; |
| MiriAllocBytes::alloc_with(size, align, alloc_fn).ok() |
| } |
| |
| fn as_mut_ptr(&mut self) -> *mut u8 { |
| self.ptr |
| } |
| |
| fn as_ptr(&self) -> *const u8 { |
| self.ptr |
| } |
| } |