use core::ffi::c_void;
use core::ptr::null_mut;
pub unsafe fn recallocarray(
ptr: *mut c_void,
oldnmemb: usize,
newnmemb: usize,
size: usize,
) -> *mut c_void {
const MUL_NO_OVERFLOW: usize = 1usize << (size_of::<usize>() * 4);
unsafe extern "C" {
fn getpagesize() -> i32;
}
unsafe {
if ptr.is_null() {
return libc::calloc(newnmemb, size);
}
if (newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW)
&& newnmemb > 0
&& usize::MAX / newnmemb < size
{
crate::errno!() = libc::ENOMEM;
return null_mut();
}
let newsize = newnmemb * size;
if (oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW)
&& oldnmemb > 0
&& usize::MAX / oldnmemb < size
{
crate::errno!() = libc::EINVAL;
return null_mut();
}
let oldsize = oldnmemb * size;
if newsize <= oldsize {
let d = oldsize - newsize;
if d < oldsize / 2 && d < getpagesize() as usize {
libc::memset((ptr as *mut u8).add(newsize).cast(), 0, d);
return ptr;
}
}
let newptr = libc::malloc(newsize);
if newptr.is_null() {
return null_mut();
}
if newsize > oldsize {
libc::memcpy(newptr, ptr, oldsize);
libc::memset(
(newptr as *mut u8).add(oldsize).cast(),
0,
newsize - oldsize,
);
} else {
libc::memcpy(newptr, ptr, newsize);
}
libc::memset(ptr, 0, oldsize);
libc::free(ptr);
newptr
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recallocarray_null_ptr_is_calloc() {
unsafe {
let p = recallocarray(null_mut(), 0, 5, 4) as *mut u8;
assert!(!p.is_null());
for i in 0..20 {
assert_eq!(*p.add(i), 0, "calloc'd byte {i} not zero");
}
libc::free(p.cast());
}
}
#[test]
fn test_recallocarray_grow_zeroes_new_and_preserves_old() {
unsafe {
let p = libc::malloc(4) as *mut u8;
assert!(!p.is_null());
for i in 0..4 {
*p.add(i) = 0xAA;
}
let q = recallocarray(p.cast(), 4, 8, 1) as *mut u8;
assert!(!q.is_null());
for i in 0..4 {
assert_eq!(*q.add(i), 0xAA, "old byte {i} not preserved");
}
for i in 4..8 {
assert_eq!(*q.add(i), 0, "grown byte {i} not zeroed");
}
libc::free(q.cast());
}
}
#[test]
fn test_recallocarray_small_shrink_zeroes_tail_in_place() {
unsafe {
let p = libc::malloc(100) as *mut u8;
assert!(!p.is_null());
libc::memset(p.cast(), 0xFF, 100);
let q = recallocarray(p.cast(), 100, 90, 1) as *mut u8;
assert!(!q.is_null());
assert_eq!(q, p);
for i in 0..90 {
assert_eq!(*q.add(i), 0xFF, "retained byte {i} changed");
}
for i in 90..100 {
assert_eq!(*q.add(i), 0, "shrunk tail byte {i} not zeroed");
}
libc::free(q.cast());
}
}
#[test]
fn test_recallocarray_large_shrink_copies_prefix() {
unsafe {
let p = libc::malloc(100) as *mut u8;
assert!(!p.is_null());
libc::memset(p.cast(), 0x5A, 100);
let q = recallocarray(p.cast(), 100, 10, 1) as *mut u8;
assert!(!q.is_null());
for i in 0..10 {
assert_eq!(*q.add(i), 0x5A, "copied prefix byte {i} wrong");
}
libc::free(q.cast());
}
}
#[test]
fn test_recallocarray_overflow_returns_null() {
unsafe {
let p = libc::malloc(8);
assert!(!p.is_null());
crate::errno!() = 0;
let r = recallocarray(p, 1, usize::MAX, 2);
assert!(r.is_null(), "overflow should return NULL");
assert_eq!(crate::errno!(), libc::ENOMEM);
libc::free(p);
}
}
#[test]
fn test_recallocarray_oldnmemb_overflow_einval() {
unsafe {
let p = libc::malloc(8);
assert!(!p.is_null());
crate::errno!() = 0;
let r = recallocarray(p, usize::MAX, 4, 2);
assert!(r.is_null(), "old overflow should return NULL");
assert_eq!(crate::errno!(), libc::EINVAL);
libc::free(p);
}
}
#[test]
fn test_recallocarray_same_size_returns_same_ptr() {
unsafe {
let p = libc::malloc(16) as *mut u8;
assert!(!p.is_null());
libc::memset(p.cast(), 0x5A, 16);
let q = recallocarray(p.cast(), 16, 16, 1) as *mut u8;
assert_eq!(q, p, "no-op resize should keep the same allocation");
for i in 0..16 {
assert_eq!(*q.add(i), 0x5A, "byte {i} changed");
}
libc::free(q.cast());
}
}
#[test]
fn test_recallocarray_grow_with_size_field() {
unsafe {
let p = libc::malloc(8) as *mut u8;
assert!(!p.is_null());
for i in 0..8 {
*p.add(i) = 0xAA;
}
let q = recallocarray(p.cast(), 2, 4, 4) as *mut u8;
assert!(!q.is_null());
for i in 0..8 {
assert_eq!(*q.add(i), 0xAA, "old byte {i} not preserved");
}
for i in 8..16 {
assert_eq!(*q.add(i), 0, "grown byte {i} not zeroed");
}
libc::free(q.cast());
}
}
#[test]
fn test_recallocarray_large_shrink_with_size_field() {
unsafe {
let p = libc::malloc(100) as *mut u8;
assert!(!p.is_null());
libc::memset(p.cast(), 0x33, 100);
let q = recallocarray(p.cast(), 50, 5, 2) as *mut u8;
assert!(!q.is_null());
for i in 0..10 {
assert_eq!(*q.add(i), 0x33, "copied prefix byte {i} wrong");
}
libc::free(q.cast());
}
}
#[test]
fn test_recallocarray_null_ptr_size_field_zeroed() {
unsafe {
let p = recallocarray(null_mut(), 0, 3, 8) as *mut u8;
assert!(!p.is_null());
for i in 0..24 {
assert_eq!(*p.add(i), 0, "calloc'd byte {i} not zero");
}
libc::free(p.cast());
}
}
}