miri/shims/
alloc.rs

1use rustc_abi::{Align, Size};
2use rustc_ast::expand::allocator::AllocatorKind;
3
4use crate::*;
5
6impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
7pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
8    /// Returns the alignment that `malloc` would guarantee for requests of the given size.
9    fn malloc_align(&self, size: u64) -> Align {
10        let this = self.eval_context_ref();
11        // The C standard says: "The pointer returned if the allocation succeeds is suitably aligned
12        // so that it may be assigned to a pointer to any type of object with a fundamental
13        // alignment requirement and size less than or equal to the size requested."
14        // So first we need to figure out what the limits are for "fundamental alignment".
15        // This is given by `alignof(max_align_t)`. The following list is taken from
16        // `library/std/src/sys/pal/common/alloc.rs` (where this is called `MIN_ALIGN`) and should
17        // be kept in sync.
18        let max_fundamental_align = match this.tcx.sess.target.arch.as_ref() {
19            "x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8,
20            "x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
21                16,
22            arch => bug!("unsupported target architecture for malloc: `{}`", arch),
23        };
24        // The C standard only requires sufficient alignment for any *type* with size less than or
25        // equal to the size requested. Types one can define in standard C seem to never have an alignment
26        // bigger than their size. So if the size is 2, then only alignment 2 is guaranteed, even if
27        // `max_fundamental_align` is bigger.
28        // This matches what some real-world implementations do, see e.g.
29        // - https://github.com/jemalloc/jemalloc/issues/1533
30        // - https://github.com/llvm/llvm-project/issues/53540
31        // - https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2293.htm
32        if size >= max_fundamental_align {
33            return Align::from_bytes(max_fundamental_align).unwrap();
34        }
35        // C doesn't have zero-sized types, so presumably nothing is guaranteed here.
36        if size == 0 {
37            return Align::ONE;
38        }
39        // We have `size < min_align`. Round `size` *down* to the next power of two and use that.
40        fn prev_power_of_two(x: u64) -> u64 {
41            let next_pow2 = x.next_power_of_two();
42            if next_pow2 == x {
43                // x *is* a power of two, just use that.
44                x
45            } else {
46                // x is between two powers, so next = 2*prev.
47                next_pow2 / 2
48            }
49        }
50        Align::from_bytes(prev_power_of_two(size)).unwrap()
51    }
52
53    /// Emulates calling the internal __rust_* allocator functions
54    fn emulate_allocator(
55        &mut self,
56        default: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx>,
57    ) -> InterpResult<'tcx, EmulateItemResult> {
58        let this = self.eval_context_mut();
59
60        let Some(allocator_kind) = this.tcx.allocator_kind(()) else {
61            // in real code, this symbol does not exist without an allocator
62            return interp_ok(EmulateItemResult::NotSupported);
63        };
64
65        match allocator_kind {
66            AllocatorKind::Global => {
67                // When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion
68                // of this attribute. As such we have to call an exported Rust function,
69                // and not execute any Miri shim. Somewhat unintuitively doing so is done
70                // by returning `NotSupported`, which triggers the `lookup_exported_symbol`
71                // fallback case in `emulate_foreign_item`.
72                interp_ok(EmulateItemResult::NotSupported)
73            }
74            AllocatorKind::Default => {
75                default(this)?;
76                interp_ok(EmulateItemResult::NeedsReturn)
77            }
78        }
79    }
80
81    fn malloc(&mut self, size: u64, init: AllocInit) -> InterpResult<'tcx, Pointer> {
82        let this = self.eval_context_mut();
83        let align = this.malloc_align(size);
84        let ptr =
85            this.allocate_ptr(Size::from_bytes(size), align, MiriMemoryKind::C.into(), init)?;
86        interp_ok(ptr.into())
87    }
88
89    fn posix_memalign(
90        &mut self,
91        memptr: &OpTy<'tcx>,
92        align: &OpTy<'tcx>,
93        size: &OpTy<'tcx>,
94    ) -> InterpResult<'tcx, Scalar> {
95        let this = self.eval_context_mut();
96        let memptr = this.deref_pointer_as(memptr, this.machine.layouts.mut_raw_ptr)?;
97        let align = this.read_target_usize(align)?;
98        let size = this.read_target_usize(size)?;
99
100        // Align must be power of 2, and also at least ptr-sized (POSIX rules).
101        // But failure to adhere to this is not UB, it's an error condition.
102        if !align.is_power_of_two() || align < this.pointer_size().bytes() {
103            interp_ok(this.eval_libc("EINVAL"))
104        } else {
105            let ptr = this.allocate_ptr(
106                Size::from_bytes(size),
107                Align::from_bytes(align).unwrap(),
108                MiriMemoryKind::C.into(),
109                AllocInit::Uninit,
110            )?;
111            this.write_pointer(ptr, &memptr)?;
112            interp_ok(Scalar::from_i32(0))
113        }
114    }
115
116    fn free(&mut self, ptr: Pointer) -> InterpResult<'tcx> {
117        let this = self.eval_context_mut();
118        if !this.ptr_is_null(ptr)? {
119            this.deallocate_ptr(ptr, None, MiriMemoryKind::C.into())?;
120        }
121        interp_ok(())
122    }
123
124    fn realloc(&mut self, old_ptr: Pointer, new_size: u64) -> InterpResult<'tcx, Pointer> {
125        let this = self.eval_context_mut();
126        let new_align = this.malloc_align(new_size);
127        if this.ptr_is_null(old_ptr)? {
128            // Here we must behave like `malloc`.
129            self.malloc(new_size, AllocInit::Uninit)
130        } else {
131            if new_size == 0 {
132                // C, in their infinite wisdom, made this UB.
133                // <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf>
134                throw_ub_format!("`realloc` with a size of zero");
135            } else {
136                let new_ptr = this.reallocate_ptr(
137                    old_ptr,
138                    None,
139                    Size::from_bytes(new_size),
140                    new_align,
141                    MiriMemoryKind::C.into(),
142                    AllocInit::Uninit,
143                )?;
144                interp_ok(new_ptr.into())
145            }
146        }
147    }
148
149    fn aligned_alloc(
150        &mut self,
151        align: &OpTy<'tcx>,
152        size: &OpTy<'tcx>,
153    ) -> InterpResult<'tcx, Pointer> {
154        let this = self.eval_context_mut();
155        let align = this.read_target_usize(align)?;
156        let size = this.read_target_usize(size)?;
157
158        // Alignment must be a power of 2, and "supported by the implementation".
159        // We decide that "supported by the implementation" means that the
160        // size must be a multiple of the alignment. (This restriction seems common
161        // enough that it is stated on <https://en.cppreference.com/w/c/memory/aligned_alloc>
162        // as a general rule, but the actual standard has no such rule.)
163        // If any of these are violated, we have to return NULL.
164        // All fundamental alignments must be supported.
165        //
166        // macOS and Illumos are buggy in that they require the alignment
167        // to be at least the size of a pointer, so they do not support all fundamental
168        // alignments. We do not emulate those platform bugs.
169        //
170        // Linux also sets errno to EINVAL, but that's non-standard behavior that we do not
171        // emulate.
172        // FreeBSD says some of these cases are UB but that's violating the C standard.
173        // http://en.cppreference.com/w/cpp/memory/c/aligned_alloc
174        // Linux: https://linux.die.net/man/3/aligned_alloc
175        // FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=aligned_alloc&apropos=0&sektion=3&manpath=FreeBSD+9-current&format=html
176        match size.checked_rem(align) {
177            Some(0) if align.is_power_of_two() => {
178                let align = align.max(this.malloc_align(size).bytes());
179                let ptr = this.allocate_ptr(
180                    Size::from_bytes(size),
181                    Align::from_bytes(align).unwrap(),
182                    MiriMemoryKind::C.into(),
183                    AllocInit::Uninit,
184                )?;
185                interp_ok(ptr.into())
186            }
187            _ => interp_ok(Pointer::null()),
188        }
189    }
190}