miri/shims/
alloc.rs

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