miri/alloc_bytes.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
use std::alloc::Layout;
use std::borrow::Cow;
use std::{alloc, slice};
use rustc_abi::{Align, Size};
use rustc_middle::mir::interpret::AllocBytes;
/// 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
}
}