Skip to main content

miri/shims/
alloc.rs

1use rustc_abi::{Align, AlignFromBytesError, Size};
2use rustc_ast::expand::allocator::SpecialAllocatorMethod;
3use rustc_middle::ty::Ty;
4use rustc_span::Symbol;
5use rustc_target::callconv::FnAbi;
6use rustc_target::spec::{Arch, Os};
7
8use crate::*;
9
10impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
11pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
12    /// Returns the alignment that `malloc` would guarantee for requests of the given size.
13    fn malloc_align(&self, size: u64) -> Align {
14        let this = self.eval_context_ref();
15        // The C standard says: "The pointer returned if the allocation succeeds is suitably aligned
16        // so that it may be assigned to a pointer to any type of object with a fundamental
17        // alignment requirement and size less than or equal to the size requested."
18        // So first we need to figure out what the limits are for "fundamental alignment".
19        // This is given by `alignof(max_align_t)`. The following list is taken from
20        // `library/std/src/sys/alloc/mod.rs` (where this is called `MIN_ALIGN`) and should
21        // be kept in sync.
22        let os = &this.tcx.sess.target.os;
23        let max_fundamental_align = match &this.tcx.sess.target.arch {
24            Arch::RiscV32 if matches!(os, Os::EspIdf | Os::Zkvm) => 4,
25            Arch::Xtensa if matches!(os, Os::EspIdf) => 4,
26            Arch::X86
27            | Arch::Arm
28            | Arch::M68k
29            | Arch::CSky
30            | Arch::LoongArch32
31            | Arch::Mips
32            | Arch::Mips32r6
33            | Arch::PowerPC
34            | Arch::PowerPC64
35            | Arch::Sparc
36            | Arch::Wasm32
37            | Arch::Hexagon
38            | Arch::RiscV32
39            | Arch::Xtensa => 8,
40            Arch::X86_64
41            | Arch::AArch64
42            | Arch::Arm64EC
43            | Arch::LoongArch64
44            | Arch::Mips64
45            | Arch::Mips64r6
46            | Arch::S390x
47            | Arch::Sparc64
48            | Arch::RiscV64
49            | Arch::Wasm64 => 16,
50            arch @ (Arch::AmdGpu
51            | Arch::Avr
52            | Arch::Bpf
53            | Arch::Msp430
54            | Arch::Nvptx64
55            | Arch::SpirV
56            | Arch::Other(_)) => bug!("unsupported target architecture for malloc: `{arch}`"),
57        };
58        // The C standard only requires sufficient alignment for any *type* with size less than or
59        // equal to the size requested. Types one can define in standard C seem to never have an alignment
60        // bigger than their size. So if the size is 2, then only alignment 2 is guaranteed, even if
61        // `max_fundamental_align` is bigger.
62        // This matches what some real-world implementations do, see e.g.
63        // - https://github.com/jemalloc/jemalloc/issues/1533
64        // - https://github.com/llvm/llvm-project/issues/53540
65        // - https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2293.htm
66        if size >= max_fundamental_align {
67            return Align::from_bytes(max_fundamental_align).unwrap();
68        }
69        // C doesn't have zero-sized types, so presumably nothing is guaranteed here.
70        if size == 0 {
71            return Align::ONE;
72        }
73        // We have `size < min_align`. Round `size` *down* to the next power of two and use that.
74        fn prev_power_of_two(x: u64) -> u64 {
75            let next_pow2 = x.next_power_of_two();
76            if next_pow2 == x {
77                // x *is* a power of two, just use that.
78                x
79            } else {
80                // x is between two powers, so next = 2*prev.
81                next_pow2 / 2
82            }
83        }
84        Align::from_bytes(prev_power_of_two(size)).unwrap()
85    }
86
87    /// Check some basic requirements for this allocation request:
88    /// non-zero size, power-of-two alignment.
89    fn check_rust_alloc_request(&self, size: u64, align: u64) -> InterpResult<'tcx> {
90        let this = self.eval_context_ref();
91        if size == 0 {
92            throw_ub_format!("creating allocation with size 0");
93        }
94        if size > this.max_size_of_val().bytes() {
95            throw_ub_format!("creating an allocation larger than half the address space");
96        }
97        if let Err(e) = Align::from_bytes(align) {
98            match e {
99                AlignFromBytesError::TooLarge(_) => {
100                    throw_unsup_format!(
101                        "creating allocation with alignment {align} exceeding rustc's maximum \
102                         supported value"
103                    );
104                }
105                AlignFromBytesError::NotPowerOfTwo(_) => {
106                    throw_ub_format!("creating allocation with non-power-of-two alignment {align}");
107                }
108            }
109        }
110
111        interp_ok(())
112    }
113
114    fn rust_special_allocator_method(
115        &mut self,
116        method: SpecialAllocatorMethod,
117        link_name: Symbol,
118        abi: &FnAbi<'tcx, Ty<'tcx>>,
119        args: &[OpTy<'tcx>],
120        dest: &PlaceTy<'tcx>,
121    ) -> InterpResult<'tcx> {
122        let this = self.eval_context_mut();
123
124        match method {
125            SpecialAllocatorMethod::Alloc | SpecialAllocatorMethod::AllocZeroed => {
126                let [size, align] = this.check_shim_sig(
127                    shim_sig!(extern "Rust" fn(usize, core::mem::Alignment) -> *_),
128                    (link_name, abi, args),
129                )?;
130                let size = this.read_target_usize(size)?;
131                let align = this.read_target_usize(align)?;
132
133                this.check_rust_alloc_request(size, align)?;
134
135                let ptr = this.allocate_ptr(
136                    Size::from_bytes(size),
137                    Align::from_bytes(align).unwrap(),
138                    MiriMemoryKind::Rust.into(),
139                    if matches!(method, SpecialAllocatorMethod::AllocZeroed) {
140                        AllocInit::Zero
141                    } else {
142                        AllocInit::Uninit
143                    },
144                )?;
145
146                this.write_pointer(ptr, dest)
147            }
148            SpecialAllocatorMethod::Dealloc => {
149                let [ptr, old_size, align] = this.check_shim_sig(
150                    shim_sig!(extern "Rust" fn(*_, usize, core::mem::Alignment) -> ()),
151                    (link_name, abi, args),
152                )?;
153                let ptr = this.read_pointer(ptr)?;
154                let old_size = this.read_target_usize(old_size)?;
155                let align = this.read_target_usize(align)?;
156
157                // No need to check old_size/align; we anyway check that they match the allocation.
158                this.deallocate_ptr(
159                    ptr,
160                    Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
161                    MiriMemoryKind::Rust.into(),
162                )
163            }
164            SpecialAllocatorMethod::Realloc => {
165                let [ptr, old_size, align, new_size] = this.check_shim_sig(
166                    shim_sig!(extern "Rust" fn(*_, usize, core::mem::Alignment, usize) -> *_),
167                    (link_name, abi, args),
168                )?;
169                let ptr = this.read_pointer(ptr)?;
170                let old_size = this.read_target_usize(old_size)?;
171                let align = this.read_target_usize(align)?;
172                let new_size = this.read_target_usize(new_size)?;
173                // No need to check old_size; we anyway check that they match the allocation.
174
175                this.check_rust_alloc_request(new_size, align)?;
176
177                let align = Align::from_bytes(align).unwrap();
178                let new_ptr = this.reallocate_ptr(
179                    ptr,
180                    Some((Size::from_bytes(old_size), align)),
181                    Size::from_bytes(new_size),
182                    align,
183                    MiriMemoryKind::Rust.into(),
184                    AllocInit::Uninit,
185                )?;
186                this.write_pointer(new_ptr, dest)
187            }
188        }
189    }
190
191    fn malloc(&mut self, size: u64, init: AllocInit) -> InterpResult<'tcx, Pointer> {
192        let this = self.eval_context_mut();
193        let align = this.malloc_align(size);
194        let ptr =
195            this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?;
196        interp_ok(ptr.into())
197    }
198
199    fn posix_memalign(
200        &mut self,
201        memptr: &OpTy<'tcx>,
202        align: &OpTy<'tcx>,
203        size: &OpTy<'tcx>,
204    ) -> InterpResult<'tcx, Scalar> {
205        let this = self.eval_context_mut();
206        let memptr = this.deref_pointer_as(memptr, this.machine.layouts.mut_raw_ptr)?;
207        let align = this.read_target_usize(align)?;
208        let size = this.read_target_usize(size)?;
209
210        // Align must be power of 2, and also at least ptr-sized (POSIX rules).
211        // But failure to adhere to this is not UB, it's an error condition.
212        if !align.is_power_of_two() || align < this.pointer_size().bytes() {
213            interp_ok(this.eval_libc("EINVAL"))
214        } else {
215            let ptr = this.allocate_ptr(
216                Size::from_bytes(size),
217                Align::from_bytes(align).unwrap(),
218                MiriMemoryKind::C.into(),
219                AllocInit::Uninit,
220            )?;
221            this.write_pointer(ptr, &memptr)?;
222            interp_ok(Scalar::from_i32(0))
223        }
224    }
225
226    fn free(&mut self, ptr: Pointer) -> InterpResult<'tcx> {
227        let this = self.eval_context_mut();
228        if !this.ptr_is_null(ptr)? {
229            this.deallocate_ptr(ptr, None, MiriMemoryKind::C.into())?;
230        }
231        interp_ok(())
232    }
233
234    fn realloc(&mut self, old_ptr: Pointer, new_size: u64) -> InterpResult<'tcx, Pointer> {
235        let this = self.eval_context_mut();
236        let new_align = this.malloc_align(new_size);
237        if this.ptr_is_null(old_ptr)? {
238            // Here we must behave like `malloc`.
239            self.malloc(new_size, AllocInit::Uninit)
240        } else {
241            if new_size == 0 {
242                // C, in their infinite wisdom, made this UB.
243                // <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf>
244                throw_ub_format!("`realloc` with a size of zero");
245            } else {
246                let new_ptr = this.reallocate_ptr(
247                    old_ptr,
248                    None,
249                    Size::from_bytes(new_size),
250                    new_align,
251                    MiriMemoryKind::C.into(),
252                    AllocInit::Uninit,
253                )?;
254                interp_ok(new_ptr.into())
255            }
256        }
257    }
258
259    fn aligned_alloc(
260        &mut self,
261        align: &OpTy<'tcx>,
262        size: &OpTy<'tcx>,
263    ) -> InterpResult<'tcx, Pointer> {
264        let this = self.eval_context_mut();
265        let align = this.read_target_usize(align)?;
266        let size = this.read_target_usize(size)?;
267
268        // Alignment must be a power of 2, and "supported by the implementation".
269        // We decide that "supported by the implementation" means that the
270        // size must be a multiple of the alignment. (This restriction seems common
271        // enough that it is stated on <https://en.cppreference.com/w/c/memory/aligned_alloc>
272        // as a general rule, but the actual standard has no such rule.)
273        // If any of these are violated, we have to return NULL.
274        // All fundamental alignments must be supported.
275        //
276        // macOS and Illumos are buggy in that they require the alignment
277        // to be at least the size of a pointer, so they do not support all fundamental
278        // alignments. We do not emulate those platform bugs.
279        //
280        // Linux also sets errno to EINVAL, but that's non-standard behavior that we do not
281        // emulate.
282        // FreeBSD says some of these cases are UB but that's violating the C standard.
283        // http://en.cppreference.com/w/cpp/memory/c/aligned_alloc
284        // Linux: https://linux.die.net/man/3/aligned_alloc
285        // FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=aligned_alloc&apropos=0&sektion=3&manpath=FreeBSD+9-current&format=html
286        match size.checked_rem(align) {
287            Some(0) if align.is_power_of_two() => {
288                let align = align.max(this.malloc_align(size).bytes());
289                let ptr = this.allocate_ptr(
290                    Size::from_bytes(size),
291                    Align::from_bytes(align).unwrap(),
292                    MiriMemoryKind::C.into(),
293                    AllocInit::Uninit,
294                )?;
295                interp_ok(ptr.into())
296            }
297            _ => interp_ok(Pointer::null()),
298        }
299    }
300}