miri/alloc/
alloc_bytes.rs

1use std::alloc::Layout;
2use std::borrow::Cow;
3use std::{alloc, slice};
4#[cfg(target_os = "linux")]
5use std::{cell::RefCell, rc::Rc};
6
7use rustc_abi::{Align, Size};
8use rustc_middle::mir::interpret::AllocBytes;
9
10#[cfg(target_os = "linux")]
11use crate::alloc::isolated_alloc::IsolatedAlloc;
12use crate::helpers::ToU64 as _;
13
14#[derive(Clone, Debug)]
15pub enum MiriAllocParams {
16    Global,
17    #[cfg(target_os = "linux")]
18    Isolated(Rc<RefCell<IsolatedAlloc>>),
19}
20
21/// Allocation bytes that explicitly handle the layout of the data they're storing.
22/// This is necessary to interface with native code that accesses the program store in Miri.
23#[derive(Debug)]
24pub struct MiriAllocBytes {
25    /// Stored layout information about the allocation.
26    layout: alloc::Layout,
27    /// Pointer to the allocation contents.
28    /// Invariant:
29    /// * If `self.layout.size() == 0`, then `self.ptr` was allocated with the equivalent layout with size 1.
30    /// * Otherwise, `self.ptr` points to memory allocated with `self.layout`.
31    ptr: *mut u8,
32    /// Whether this instance of `MiriAllocBytes` had its allocation created by calling `alloc::alloc()`
33    /// (`Global`) or the discrete allocator (`Isolated`)
34    params: MiriAllocParams,
35}
36
37impl Clone for MiriAllocBytes {
38    fn clone(&self) -> Self {
39        let bytes: Cow<'_, [u8]> = Cow::Borrowed(self);
40        let align = Align::from_bytes(self.layout.align().to_u64()).unwrap();
41        MiriAllocBytes::from_bytes(bytes, align, self.params.clone())
42    }
43}
44
45impl Drop for MiriAllocBytes {
46    fn drop(&mut self) {
47        // We have to reconstruct the actual layout used for allocation.
48        // (`Deref` relies on `size` so we can't just always set it to at least 1.)
49        let alloc_layout = if self.layout.size() == 0 {
50            Layout::from_size_align(1, self.layout.align()).unwrap()
51        } else {
52            self.layout
53        };
54
55        // SAFETY: Invariant, `self.ptr` points to memory allocated with `self.layout`.
56        unsafe {
57            match self.params.clone() {
58                MiriAllocParams::Global => alloc::dealloc(self.ptr, alloc_layout),
59                #[cfg(target_os = "linux")]
60                MiriAllocParams::Isolated(alloc) =>
61                    alloc.borrow_mut().dealloc(self.ptr, alloc_layout),
62            }
63        }
64    }
65}
66
67impl std::ops::Deref for MiriAllocBytes {
68    type Target = [u8];
69
70    fn deref(&self) -> &Self::Target {
71        // SAFETY: `ptr` is non-null, properly aligned, and valid for reading out `self.layout.size()`-many bytes.
72        // Note that due to the invariant this is true even if `self.layout.size() == 0`.
73        unsafe { slice::from_raw_parts(self.ptr, self.layout.size()) }
74    }
75}
76
77impl std::ops::DerefMut for MiriAllocBytes {
78    fn deref_mut(&mut self) -> &mut Self::Target {
79        // SAFETY: `ptr` is non-null, properly aligned, and valid for reading out `self.layout.size()`-many bytes.
80        // Note that due to the invariant this is true even if `self.layout.size() == 0`.
81        unsafe { slice::from_raw_parts_mut(self.ptr, self.layout.size()) }
82    }
83}
84
85impl MiriAllocBytes {
86    /// This method factors out how a `MiriAllocBytes` object is allocated, given a specific allocation function.
87    /// If `size == 0` we allocate using a different `alloc_layout` with `size = 1`, to ensure each allocation has a unique address.
88    /// Returns `Err(alloc_layout)` if the allocation function returns a `ptr` where `ptr.is_null()`.
89    fn alloc_with(
90        size: u64,
91        align: u64,
92        params: MiriAllocParams,
93        alloc_fn: impl FnOnce(Layout, &MiriAllocParams) -> *mut u8,
94    ) -> Result<MiriAllocBytes, ()> {
95        let size = usize::try_from(size).map_err(|_| ())?;
96        let align = usize::try_from(align).map_err(|_| ())?;
97        let layout = Layout::from_size_align(size, align).map_err(|_| ())?;
98        // When size is 0 we allocate 1 byte anyway, to ensure each allocation has a unique address.
99        let alloc_layout =
100            if size == 0 { Layout::from_size_align(1, align).unwrap() } else { layout };
101        let ptr = alloc_fn(alloc_layout, &params);
102        if ptr.is_null() {
103            Err(())
104        } else {
105            // SAFETY: All `MiriAllocBytes` invariants are fulfilled.
106            Ok(Self { ptr, layout, params })
107        }
108    }
109}
110
111impl AllocBytes for MiriAllocBytes {
112    type AllocParams = MiriAllocParams;
113
114    fn from_bytes<'a>(
115        slice: impl Into<Cow<'a, [u8]>>,
116        align: Align,
117        params: MiriAllocParams,
118    ) -> Self {
119        let slice = slice.into();
120        let size = slice.len();
121        let align = align.bytes();
122        // SAFETY: `alloc_fn` will only be used with `size != 0`.
123        let alloc_fn = |layout, params: &MiriAllocParams| unsafe {
124            match params {
125                MiriAllocParams::Global => alloc::alloc(layout),
126                #[cfg(target_os = "linux")]
127                MiriAllocParams::Isolated(alloc) => alloc.borrow_mut().alloc(layout),
128            }
129        };
130        let alloc_bytes = MiriAllocBytes::alloc_with(size.to_u64(), align, params, alloc_fn)
131            .unwrap_or_else(|()| {
132                panic!("Miri ran out of memory: cannot create allocation of {size} bytes")
133            });
134        // SAFETY: `alloc_bytes.ptr` and `slice.as_ptr()` are non-null, properly aligned
135        // and valid for the `size`-many bytes to be copied.
136        unsafe { alloc_bytes.ptr.copy_from(slice.as_ptr(), size) };
137        alloc_bytes
138    }
139
140    fn zeroed(size: Size, align: Align, params: MiriAllocParams) -> Option<Self> {
141        let size = size.bytes();
142        let align = align.bytes();
143        // SAFETY: `alloc_fn` will only be used with `size != 0`.
144        let alloc_fn = |layout, params: &MiriAllocParams| unsafe {
145            match params {
146                MiriAllocParams::Global => alloc::alloc_zeroed(layout),
147                #[cfg(target_os = "linux")]
148                MiriAllocParams::Isolated(alloc) => alloc.borrow_mut().alloc_zeroed(layout),
149            }
150        };
151        MiriAllocBytes::alloc_with(size, align, params, alloc_fn).ok()
152    }
153
154    fn as_mut_ptr(&mut self) -> *mut u8 {
155        self.ptr
156    }
157
158    fn as_ptr(&self) -> *const u8 {
159        self.ptr
160    }
161}