about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-05-19 09:50:25 +0000
committerbors <bors@rust-lang.org>2024-05-19 09:50:25 +0000
commit3726afad7edc24a4ae0c3a16f6f2a18921d5c405 (patch)
tree9acb38606099fdb26df797211bdf059b1f63f41a
parent8d7c8ac50acc59d80c6d10508b3e2b91c86d33f9 (diff)
parent430298c3adc16e09d9fbd8f295b404fdb682d9eb (diff)
downloadrust-3726afad7edc24a4ae0c3a16f6f2a18921d5c405.tar.gz
rust-3726afad7edc24a4ae0c3a16f6f2a18921d5c405.zip
Auto merge of #3585 - devnexen:aligned_alloc, r=RalfJung
support aligned_alloc for unixes.

Fixes https://github.com/rust-lang/miri/issues/3577
-rw-r--r--src/tools/miri/src/shims/alloc.rs41
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs8
-rw-r--r--src/tools/miri/src/shims/wasi/foreign_items.rs6
-rw-r--r--src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.rs15
-rw-r--r--src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.stderr15
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-mem.rs130
6 files changed, 171 insertions, 44 deletions
diff --git a/src/tools/miri/src/shims/alloc.rs b/src/tools/miri/src/shims/alloc.rs
index d0f36bd4757..bd84de81e69 100644
--- a/src/tools/miri/src/shims/alloc.rs
+++ b/src/tools/miri/src/shims/alloc.rs
@@ -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()),
+        }
+    }
 }
diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs
index 78d297d4b04..74fb2fb4b2b 100644
--- a/src/tools/miri/src/shims/unix/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/foreign_items.rs
@@ -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" => {
diff --git a/src/tools/miri/src/shims/wasi/foreign_items.rs b/src/tools/miri/src/shims/wasi/foreign_items.rs
index 12bf0490932..d911f43eb62 100644
--- a/src/tools/miri/src/shims/wasi/foreign_items.rs
+++ b/src/tools/miri/src/shims/wasi/foreign_items.rs
@@ -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),
         }
diff --git a/src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.rs b/src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.rs
new file mode 100644
index 00000000000..9a33cdccd27
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.rs
@@ -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
+    }
+}
diff --git a/src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.stderr b/src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.stderr
new file mode 100644
index 00000000000..b0756d57212
--- /dev/null
+++ b/src/tools/miri/tests/fail-dep/libc/aligned_alloc_size_zero_leak.stderr
@@ -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
+
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs
index 5bd205dd085..aa383a99bc2 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-mem.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-mem.rs
@@ -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
@@ -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();
@@ -254,6 +294,8 @@ fn main() {
         target_os = "wasi",
     )))]
     test_reallocarray();
+    #[cfg(not(target_os = "windows"))]
+    test_aligned_alloc();
 
     test_memcpy();
     test_strcpy();