Skip to content

Commit 5dfccc2

Browse files
committed
Auto merge of #3484 - RalfJung:realloc, r=RalfJung
make realloc with a size of zero fail Fixes #2774
2 parents 4373a8e + c2562d0 commit 5dfccc2

File tree

9 files changed

+205
-158
lines changed

9 files changed

+205
-158
lines changed

src/shims/alloc.rs

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::iter;
2+
3+
use rustc_ast::expand::allocator::AllocatorKind;
4+
use rustc_target::abi::{Align, Size};
5+
6+
use crate::*;
7+
use shims::foreign_items::EmulateForeignItemResult;
8+
9+
/// Check some basic requirements for this allocation request:
10+
/// non-zero size, power-of-two alignment.
11+
pub(super) fn check_alloc_request<'tcx>(size: u64, align: u64) -> InterpResult<'tcx> {
12+
if size == 0 {
13+
throw_ub_format!("creating allocation with size 0");
14+
}
15+
if !align.is_power_of_two() {
16+
throw_ub_format!("creating allocation with non-power-of-two alignment {}", align);
17+
}
18+
Ok(())
19+
}
20+
21+
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
22+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
23+
/// Returns the minimum alignment for the target architecture for allocations of the given size.
24+
fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
25+
let this = self.eval_context_ref();
26+
// List taken from `library/std/src/sys/pal/common/alloc.rs`.
27+
// This list should be kept in sync with the one from libstd.
28+
let min_align = match this.tcx.sess.target.arch.as_ref() {
29+
"x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8,
30+
"x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
31+
16,
32+
arch => bug!("unsupported target architecture for malloc: `{}`", arch),
33+
};
34+
// Windows always aligns, even small allocations.
35+
// Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
36+
// But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big.
37+
if kind == MiriMemoryKind::WinHeap || size >= min_align {
38+
return Align::from_bytes(min_align).unwrap();
39+
}
40+
// We have `size < min_align`. Round `size` *down* to the next power of two and use that.
41+
fn prev_power_of_two(x: u64) -> u64 {
42+
let next_pow2 = x.next_power_of_two();
43+
if next_pow2 == x {
44+
// x *is* a power of two, just use that.
45+
x
46+
} else {
47+
// x is between two powers, so next = 2*prev.
48+
next_pow2 / 2
49+
}
50+
}
51+
Align::from_bytes(prev_power_of_two(size)).unwrap()
52+
}
53+
54+
/// Emulates calling the internal __rust_* allocator functions
55+
fn emulate_allocator(
56+
&mut self,
57+
default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>,
58+
) -> InterpResult<'tcx, EmulateForeignItemResult> {
59+
let this = self.eval_context_mut();
60+
61+
let Some(allocator_kind) = this.tcx.allocator_kind(()) else {
62+
// in real code, this symbol does not exist without an allocator
63+
return Ok(EmulateForeignItemResult::NotSupported);
64+
};
65+
66+
match allocator_kind {
67+
AllocatorKind::Global => {
68+
// When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion
69+
// of this attribute. As such we have to call an exported Rust function,
70+
// and not execute any Miri shim. Somewhat unintuitively doing so is done
71+
// by returning `NotSupported`, which triggers the `lookup_exported_symbol`
72+
// fallback case in `emulate_foreign_item`.
73+
return Ok(EmulateForeignItemResult::NotSupported);
74+
}
75+
AllocatorKind::Default => {
76+
default(this)?;
77+
Ok(EmulateForeignItemResult::NeedsJumping)
78+
}
79+
}
80+
}
81+
82+
fn malloc(
83+
&mut self,
84+
size: u64,
85+
zero_init: bool,
86+
kind: MiriMemoryKind,
87+
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
88+
let this = self.eval_context_mut();
89+
if size == 0 {
90+
Ok(Pointer::null())
91+
} else {
92+
let align = this.min_align(size, kind);
93+
let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
94+
if zero_init {
95+
// We just allocated this, the access is definitely in-bounds and fits into our address space.
96+
this.write_bytes_ptr(
97+
ptr.into(),
98+
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
99+
)
100+
.unwrap();
101+
}
102+
Ok(ptr.into())
103+
}
104+
}
105+
106+
fn free(
107+
&mut self,
108+
ptr: Pointer<Option<Provenance>>,
109+
kind: MiriMemoryKind,
110+
) -> InterpResult<'tcx> {
111+
let this = self.eval_context_mut();
112+
if !this.ptr_is_null(ptr)? {
113+
this.deallocate_ptr(ptr, None, kind.into())?;
114+
}
115+
Ok(())
116+
}
117+
118+
fn realloc(
119+
&mut self,
120+
old_ptr: Pointer<Option<Provenance>>,
121+
new_size: u64,
122+
kind: MiriMemoryKind,
123+
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
124+
let this = self.eval_context_mut();
125+
let new_align = this.min_align(new_size, kind);
126+
if this.ptr_is_null(old_ptr)? {
127+
// Here we must behave like `malloc`.
128+
if new_size == 0 {
129+
Ok(Pointer::null())
130+
} else {
131+
let new_ptr =
132+
this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
133+
Ok(new_ptr.into())
134+
}
135+
} else {
136+
if new_size == 0 {
137+
// C, in their infinite wisdom, made this UB.
138+
// <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2464.pdf>
139+
throw_ub_format!("`realloc` with a size of zero");
140+
} else {
141+
let new_ptr = this.reallocate_ptr(
142+
old_ptr,
143+
None,
144+
Size::from_bytes(new_size),
145+
new_align,
146+
kind.into(),
147+
)?;
148+
Ok(new_ptr.into())
149+
}
150+
}
151+
}
152+
}

src/shims/foreign_items.rs

+5-144
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{collections::hash_map::Entry, io::Write, iter, path::Path};
22

33
use rustc_apfloat::Float;
4-
use rustc_ast::expand::allocator::{alloc_error_handler_name, AllocatorKind};
4+
use rustc_ast::expand::allocator::alloc_error_handler_name;
55
use rustc_hir::{def::DefKind, def_id::CrateNum};
66
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
77
use rustc_middle::mir;
@@ -12,6 +12,7 @@ use rustc_target::{
1212
spec::abi::Abi,
1313
};
1414

15+
use super::alloc::{check_alloc_request, EvalContextExt as _};
1516
use super::backtrace::EvalContextExt as _;
1617
use crate::*;
1718
use helpers::{ToHost, ToSoft};
@@ -232,138 +233,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
232233
Some(instance) => Ok(Some((this.load_mir(instance.def, None)?, instance))),
233234
}
234235
}
235-
236-
fn malloc(
237-
&mut self,
238-
size: u64,
239-
zero_init: bool,
240-
kind: MiriMemoryKind,
241-
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
242-
let this = self.eval_context_mut();
243-
if size == 0 {
244-
Ok(Pointer::null())
245-
} else {
246-
let align = this.min_align(size, kind);
247-
let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
248-
if zero_init {
249-
// We just allocated this, the access is definitely in-bounds and fits into our address space.
250-
this.write_bytes_ptr(
251-
ptr.into(),
252-
iter::repeat(0u8).take(usize::try_from(size).unwrap()),
253-
)
254-
.unwrap();
255-
}
256-
Ok(ptr.into())
257-
}
258-
}
259-
260-
fn free(
261-
&mut self,
262-
ptr: Pointer<Option<Provenance>>,
263-
kind: MiriMemoryKind,
264-
) -> InterpResult<'tcx> {
265-
let this = self.eval_context_mut();
266-
if !this.ptr_is_null(ptr)? {
267-
this.deallocate_ptr(ptr, None, kind.into())?;
268-
}
269-
Ok(())
270-
}
271-
272-
fn realloc(
273-
&mut self,
274-
old_ptr: Pointer<Option<Provenance>>,
275-
new_size: u64,
276-
kind: MiriMemoryKind,
277-
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
278-
let this = self.eval_context_mut();
279-
let new_align = this.min_align(new_size, kind);
280-
if this.ptr_is_null(old_ptr)? {
281-
if new_size == 0 {
282-
Ok(Pointer::null())
283-
} else {
284-
let new_ptr =
285-
this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
286-
Ok(new_ptr.into())
287-
}
288-
} else {
289-
if new_size == 0 {
290-
this.deallocate_ptr(old_ptr, None, kind.into())?;
291-
Ok(Pointer::null())
292-
} else {
293-
let new_ptr = this.reallocate_ptr(
294-
old_ptr,
295-
None,
296-
Size::from_bytes(new_size),
297-
new_align,
298-
kind.into(),
299-
)?;
300-
Ok(new_ptr.into())
301-
}
302-
}
303-
}
304236
}
305237

306238
impl<'mir, 'tcx: 'mir> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
307239
trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
308-
/// Returns the minimum alignment for the target architecture for allocations of the given size.
309-
fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
310-
let this = self.eval_context_ref();
311-
// List taken from `library/std/src/sys/pal/common/alloc.rs`.
312-
// This list should be kept in sync with the one from libstd.
313-
let min_align = match this.tcx.sess.target.arch.as_ref() {
314-
"x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "wasm32" => 8,
315-
"x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" =>
316-
16,
317-
arch => bug!("unsupported target architecture for malloc: `{}`", arch),
318-
};
319-
// Windows always aligns, even small allocations.
320-
// Source: <https://support.microsoft.com/en-us/help/286470/how-to-use-pageheap-exe-in-windows-xp-windows-2000-and-windows-server>
321-
// But jemalloc does not, so for the C heap we only align if the allocation is sufficiently big.
322-
if kind == MiriMemoryKind::WinHeap || size >= min_align {
323-
return Align::from_bytes(min_align).unwrap();
324-
}
325-
// We have `size < min_align`. Round `size` *down* to the next power of two and use that.
326-
fn prev_power_of_two(x: u64) -> u64 {
327-
let next_pow2 = x.next_power_of_two();
328-
if next_pow2 == x {
329-
// x *is* a power of two, just use that.
330-
x
331-
} else {
332-
// x is between two powers, so next = 2*prev.
333-
next_pow2 / 2
334-
}
335-
}
336-
Align::from_bytes(prev_power_of_two(size)).unwrap()
337-
}
338-
339-
/// Emulates calling the internal __rust_* allocator functions
340-
fn emulate_allocator(
341-
&mut self,
342-
default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>,
343-
) -> InterpResult<'tcx, EmulateForeignItemResult> {
344-
let this = self.eval_context_mut();
345-
346-
let Some(allocator_kind) = this.tcx.allocator_kind(()) else {
347-
// in real code, this symbol does not exist without an allocator
348-
return Ok(EmulateForeignItemResult::NotSupported);
349-
};
350-
351-
match allocator_kind {
352-
AllocatorKind::Global => {
353-
// When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion
354-
// of this attribute. As such we have to call an exported Rust function,
355-
// and not execute any Miri shim. Somewhat unintuitively doing so is done
356-
// by returning `NotSupported`, which triggers the `lookup_exported_symbol`
357-
// fallback case in `emulate_foreign_item`.
358-
return Ok(EmulateForeignItemResult::NotSupported);
359-
}
360-
AllocatorKind::Default => {
361-
default(this)?;
362-
Ok(EmulateForeignItemResult::NeedsJumping)
363-
}
364-
}
365-
}
366-
367240
fn emulate_foreign_item_inner(
368241
&mut self,
369242
link_name: Symbol,
@@ -610,7 +483,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
610483
let size = this.read_target_usize(size)?;
611484
let align = this.read_target_usize(align)?;
612485

613-
Self::check_alloc_request(size, align)?;
486+
check_alloc_request(size, align)?;
614487

615488
let memory_kind = match link_name.as_str() {
616489
"__rust_alloc" => MiriMemoryKind::Rust,
@@ -644,7 +517,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
644517
let size = this.read_target_usize(size)?;
645518
let align = this.read_target_usize(align)?;
646519

647-
Self::check_alloc_request(size, align)?;
520+
check_alloc_request(size, align)?;
648521

649522
let ptr = this.allocate_ptr(
650523
Size::from_bytes(size),
@@ -708,7 +581,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
708581
let new_size = this.read_target_usize(new_size)?;
709582
// No need to check old_size; we anyway check that they match the allocation.
710583

711-
Self::check_alloc_request(new_size, align)?;
584+
check_alloc_request(new_size, align)?;
712585

713586
let align = Align::from_bytes(align).unwrap();
714587
let new_ptr = this.reallocate_ptr(
@@ -1100,16 +973,4 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
1100973
// i.e., if we actually emulated the function with one of the shims.
1101974
Ok(EmulateForeignItemResult::NeedsJumping)
1102975
}
1103-
1104-
/// Check some basic requirements for this allocation request:
1105-
/// non-zero size, power-of-two alignment.
1106-
fn check_alloc_request(size: u64, align: u64) -> InterpResult<'tcx> {
1107-
if size == 0 {
1108-
throw_ub_format!("creating allocation with size 0");
1109-
}
1110-
if !align.is_power_of_two() {
1111-
throw_ub_format!("creating allocation with non-power-of-two alignment {}", align);
1112-
}
1113-
Ok(())
1114-
}
1115976
}

src/shims/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![warn(clippy::arithmetic_side_effects)]
22

3+
mod alloc;
34
mod backtrace;
45
#[cfg(target_os = "linux")]
56
pub mod ffi_support;

src/shims/unix/foreign_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc_span::Symbol;
66
use rustc_target::abi::{Align, Size};
77
use rustc_target::spec::abi::Abi;
88

9+
use crate::shims::alloc::EvalContextExt as _;
910
use crate::shims::unix::*;
1011
use crate::*;
1112
use shims::foreign_items::EmulateForeignItemResult;

0 commit comments

Comments
 (0)