Skip to content

Commit

Permalink
Auto merge of #3585 - devnexen:aligned_alloc, r=RalfJung
Browse files Browse the repository at this point in the history
support aligned_alloc for unixes.

Fixes #3577
  • Loading branch information
bors committed May 19, 2024
2 parents 4f8b3ef + 78ed7ac commit b850b4d
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 44 deletions.
41 changes: 41 additions & 0 deletions src/shims/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,45 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}
}

fn aligned_alloc(
&mut self,
align: &OpTy<'tcx, Provenance>,
size: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let this = self.eval_context_mut();
let align = this.read_target_usize(align)?;
let size = this.read_target_usize(size)?;

// Alignment must be a power of 2, and "supported by the implementation".
// We decide that "supported by the implementation" means that the
// size must be a multiple of the alignment. (This restriction seems common
// enough that it is stated on <https://en.cppreference.com/w/c/memory/aligned_alloc>
// as a general rule, but the actual standard has no such rule.)
// If any of these are violated, we have to return NULL.
// All fundamental alignments must be supported.
//
// macOS and Illumos are buggy in that they require the alignment
// to be at least the size of a pointer, so they do not support all fundamental
// alignments. We do not emulate those platform bugs.
//
// Linux also sets errno to EINVAL, but that's non-standard behavior that we do not
// emulate.
// FreeBSD says some of these cases are UB but that's violating the C standard.
// http://en.cppreference.com/w/cpp/memory/c/aligned_alloc
// Linux: https://linux.die.net/man/3/aligned_alloc
// FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=aligned_alloc&apropos=0&sektion=3&manpath=FreeBSD+9-current&format=html
match size.checked_rem(align) {
Some(0) if align.is_power_of_two() => {
let align = align.max(this.malloc_align(size).bytes());
let ptr = this.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::C.into(),
)?;
Ok(ptr.into())
}
_ => Ok(Pointer::null()),
}
}
}
8 changes: 8 additions & 0 deletions src/shims/unix/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
}
}
"aligned_alloc" => {
// This is a C11 function, we assume all Unixes have it.
// (MSVC explicitly does not support this.)
let [align, size] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let res = this.aligned_alloc(align, size)?;
this.write_pointer(res, dest)?;
}

// Dynamic symbol loading
"dlsym" => {
Expand Down
6 changes: 6 additions & 0 deletions src/shims/wasi/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let result = this.posix_memalign(memptr, align, size)?;
this.write_scalar(result, dest)?;
}
"aligned_alloc" => {
let [align, size] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let res = this.aligned_alloc(align, size)?;
this.write_pointer(res, dest)?;
}

_ => return Ok(EmulateItemResult::NotSupported),
}
Expand Down
15 changes: 15 additions & 0 deletions tests/fail-dep/libc/aligned_alloc_size_zero_leak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//@ignore-target-windows: Windows does not support the standard C11 aligned_alloc.

fn main() {
// libc doesn't have this function (https://github.com/rust-lang/libc/issues/3689),
// so we declare it ourselves.
extern "C" {
fn aligned_alloc(alignment: libc::size_t, size: libc::size_t) -> *mut libc::c_void;
}

// Make sure even zero-sized allocations need to be freed.

unsafe {
aligned_alloc(2, 0); //~ERROR: memory leaked
}
}
15 changes: 15 additions & 0 deletions tests/fail-dep/libc/aligned_alloc_size_zero_leak.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: memory leaked: ALLOC (C heap, size: 0, align: 2), allocated here:
--> $DIR/aligned_alloc_size_zero_leak.rs:LL:CC
|
LL | aligned_alloc(2, 0);
| ^^^^^^^^^^^^^^^^^^^
|
= note: BACKTRACE:
= note: inside `main` at $DIR/aligned_alloc_size_zero_leak.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check

error: aborting due to 1 previous error

130 changes: 86 additions & 44 deletions tests/pass-dep/libc/libc-mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,53 +148,55 @@ fn test_calloc() {

#[cfg(not(target_os = "windows"))]
fn test_memalign() {
// A normal allocation.
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 8;
let size = 64;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
ptr.cast::<u8>().write_bytes(1, size);
libc::free(ptr);
}
for _ in 0..16 {
// A normal allocation.
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 8;
let size = 64;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
ptr.cast::<u8>().write_bytes(1, size);
libc::free(ptr);
}

// Align > size.
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 64;
let size = 8;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
ptr.cast::<u8>().write_bytes(1, size);
libc::free(ptr);
}
// Align > size.
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 64;
let size = 8;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
ptr.cast::<u8>().write_bytes(1, size);
libc::free(ptr);
}

// Size not multiple of align
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 16;
let size = 31;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
ptr.cast::<u8>().write_bytes(1, size);
libc::free(ptr);
}
// Size not multiple of align
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 16;
let size = 31;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
ptr.cast::<u8>().write_bytes(1, size);
libc::free(ptr);
}

// Size == 0
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 64;
let size = 0;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
// Non-null pointer is returned if size == 0.
// (This is not a guarantee, it just reflects our current behavior.)
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
libc::free(ptr);
// Size == 0
unsafe {
let mut ptr: *mut libc::c_void = ptr::null_mut();
let align = 64;
let size = 0;
assert_eq!(libc::posix_memalign(&mut ptr, align, size), 0);
// Non-null pointer is returned if size == 0.
// (This is not a guarantee, it just reflects our current behavior.)
assert!(!ptr.is_null());
assert!(ptr.is_aligned_to(align));
libc::free(ptr);
}
}

// Non-power of 2 align
Expand Down Expand Up @@ -241,6 +243,44 @@ fn test_reallocarray() {
}
}

#[cfg(not(target_os = "windows"))]
fn test_aligned_alloc() {
// libc doesn't have this function (https://github.com/rust-lang/libc/issues/3689),
// so we declare it ourselves.
extern "C" {
fn aligned_alloc(alignment: libc::size_t, size: libc::size_t) -> *mut libc::c_void;
}
// size not a multiple of the alignment
unsafe {
let p = aligned_alloc(16, 3);
assert_eq!(p, ptr::null_mut());
}

// alignment not power of 2
unsafe {
let p = aligned_alloc(63, 8);
assert_eq!(p, ptr::null_mut());
}

// repeated tests on correct alignment/size
for _ in 0..16 {
// alignment 1, size 4 should succeed and actually must align to 4 (because C says so...)
unsafe {
let p = aligned_alloc(1, 4);
assert!(!p.is_null());
assert!(p.is_aligned_to(4));
libc::free(p);
}

unsafe {
let p = aligned_alloc(64, 64);
assert!(!p.is_null());
assert!(p.is_aligned_to(64));
libc::free(p);
}
}
}

fn main() {
test_malloc();
test_calloc();
Expand All @@ -254,6 +294,8 @@ fn main() {
target_os = "wasi",
)))]
test_reallocarray();
#[cfg(not(target_os = "windows"))]
test_aligned_alloc();

test_memcpy();
test_strcpy();
Expand Down

0 comments on commit b850b4d

Please sign in to comment.