use core::alloc::Layout;
use core::ffi::c_void;
use core::ptr;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
use std::os::raw::{c_char, c_int, c_uint};
use parking_lot::RwLock;
use crate::abi::callbacks::*;
unsafe extern "C" fn default_malloc(size: usize) -> *mut c_void {
if size == 0 {
let layout = Layout::from_size_align_unchecked(1, 1);
let ptr = std::alloc::alloc(layout);
if ptr.is_null() {
ptr::null_mut()
} else {
ptr as *mut c_void
}
} else {
let layout = match Layout::from_size_align(size, 1) {
Ok(l) => l,
Err(_) => return ptr::null_mut(),
};
let ptr = std::alloc::alloc(layout);
if ptr.is_null() {
ptr::null_mut()
} else {
ptr as *mut c_void
}
}
}
unsafe extern "C" fn default_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
if ptr.is_null() {
return default_malloc(size);
}
if size == 0 {
default_free(ptr);
return default_malloc(0);
}
let layout = match Layout::from_size_align(size, 1) {
Ok(l) => l,
Err(_) => return ptr::null_mut(),
};
let new_ptr = std::alloc::realloc(ptr as *mut u8, layout, size);
if new_ptr.is_null() {
ptr::null_mut()
} else {
new_ptr as *mut c_void
}
}
unsafe extern "C" fn default_free(ptr: *mut c_void) {
if ptr.is_null() {
return;
}
let layout = Layout::from_size_align_unchecked(1, 1);
std::alloc::dealloc(ptr as *mut u8, layout);
}
unsafe extern "C" fn default_strdup(str: *const c_char) -> *mut c_void {
if str.is_null() {
return ptr::null_mut();
}
let len = libc::strlen(str);
let size = len + 1; let layout = match Layout::from_size_align(size, 1) {
Ok(l) => l,
Err(_) => return ptr::null_mut(),
};
let new_ptr = std::alloc::alloc(layout);
if new_ptr.is_null() {
return ptr::null_mut();
}
ptr::copy_nonoverlapping(str as *const u8, new_ptr, size);
new_ptr as *mut c_void
}
static ALLOCATOR: RwLock<AllocatorFuncs> = RwLock::new(AllocatorFuncs {
malloc_func: Some(default_malloc as xmlMallocFunc),
realloc_func: Some(default_realloc as xmlReallocFunc),
free_func: Some(default_free as xmlFreeFunc),
strdup_func: Some(default_strdup as xmlStrdupFunc),
});
struct AllocatorFuncs {
malloc_func: Option<xmlMallocFunc>,
realloc_func: Option<xmlReallocFunc>,
free_func: Option<xmlFreeFunc>,
strdup_func: Option<xmlStrdupFunc>,
}
static MEM_USED: AtomicUsize = AtomicUsize::new(0);
static MEM_BLOCKS: AtomicUsize = AtomicUsize::new(0);
#[no_mangle]
pub unsafe extern "C" fn xmlMemSetup(
freeFunc: Option<xmlFreeFunc>,
mallocFunc: Option<xmlMallocFunc>,
reallocFunc: Option<xmlReallocFunc>,
strdupFunc: Option<xmlStrdupFunc>,
) {
let mut alloc = ALLOCATOR.write();
alloc.free_func = freeFunc;
alloc.malloc_func = mallocFunc;
alloc.realloc_func = reallocFunc;
alloc.strdup_func = strdupFunc;
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemGet(
freeFunc: *mut Option<xmlFreeFunc>,
mallocFunc: *mut Option<xmlMallocFunc>,
reallocFunc: *mut Option<xmlReallocFunc>,
strdupFunc: *mut Option<xmlStrdupFunc>,
) {
let alloc = ALLOCATOR.read();
unsafe {
ptr::write(freeFunc, alloc.free_func);
ptr::write(mallocFunc, alloc.malloc_func);
ptr::write(reallocFunc, alloc.realloc_func);
ptr::write(strdupFunc, alloc.strdup_func);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlGcMemSetup(
freeFunc: Option<xmlFreeFunc>,
mallocFunc: Option<xmlMallocFunc>,
reallocFunc: Option<xmlReallocFunc>,
strdupFunc: Option<xmlStrdupFunc>,
) {
unsafe { xmlMemSetup(freeFunc, mallocFunc, reallocFunc, strdupFunc) };
}
#[no_mangle]
pub unsafe extern "C" fn xmlGcMemGet(
freeFunc: *mut Option<xmlFreeFunc>,
mallocFunc: *mut Option<xmlMallocFunc>,
reallocFunc: *mut Option<xmlReallocFunc>,
strdupFunc: *mut Option<xmlStrdupFunc>,
) {
unsafe { xmlMemGet(freeFunc, mallocFunc, reallocFunc, strdupFunc) };
}
#[no_mangle]
pub unsafe extern "C" fn xmlMalloc(size: usize) -> *mut c_void {
let alloc = ALLOCATOR.read();
let malloc_func = alloc.malloc_func.unwrap_or(default_malloc as xmlMallocFunc);
let ptr = unsafe { malloc_func(size) };
if !ptr.is_null() {
MEM_USED.fetch_add(size, Ordering::Relaxed);
MEM_BLOCKS.fetch_add(1, Ordering::Relaxed);
}
ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlMallocAtomic(size: usize) -> *mut c_void {
unsafe { xmlMalloc(size) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlRealloc(ptr: *mut c_void, size: usize) -> *mut c_void {
let alloc = ALLOCATOR.read();
let realloc_func = alloc
.realloc_func
.unwrap_or(default_realloc as xmlReallocFunc);
let new_ptr = unsafe { realloc_func(ptr, size) };
if !new_ptr.is_null() {
MEM_BLOCKS.fetch_add(1, Ordering::Relaxed);
if ptr.is_null() {
MEM_USED.fetch_add(size, Ordering::Relaxed);
}
}
new_ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlFree(ptr: *mut c_void) {
if ptr.is_null() {
return;
}
let alloc = ALLOCATOR.read();
let free_func = alloc.free_func.unwrap_or(default_free as xmlFreeFunc);
unsafe { free_func(ptr) };
MEM_BLOCKS.fetch_sub(1, Ordering::Relaxed);
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemStrdup(str: *const c_char) -> *mut c_void {
if str.is_null() {
return ptr::null_mut();
}
let alloc = ALLOCATOR.read();
let strdup_func = alloc.strdup_func.unwrap_or(default_strdup as xmlStrdupFunc);
let ptr = unsafe { strdup_func(str) };
if !ptr.is_null() {
let len = unsafe { libc::strlen(str) } + 1;
MEM_USED.fetch_add(len, Ordering::Relaxed);
MEM_BLOCKS.fetch_add(1, Ordering::Relaxed);
}
ptr
}
#[no_mangle]
pub extern "C" fn xmlMemUsed() -> c_int {
MEM_USED.load(Ordering::Relaxed) as c_int
}
#[no_mangle]
pub extern "C" fn xmlMemBlocks() -> c_int {
MEM_BLOCKS.load(Ordering::Relaxed) as c_int
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemDisplay(fp: *mut c_void) {
unsafe {
let out = if fp.is_null() {
libc::fdopen(2, b"w\0" as *const u8 as *const c_char) as *mut c_void
} else {
fp
};
libc::fprintf(
out as *mut _,
b"Memory: used=%d blocks=%d\n\0" as *const u8 as *const c_char,
xmlMemUsed(),
xmlMemBlocks(),
);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemShow(_fp: *mut c_void, _nr: c_int) {
}
#[no_mangle]
pub unsafe extern "C" fn xmlMallocZero(size: usize) -> *mut c_void {
let ptr = unsafe { xmlMalloc(size) };
if !ptr.is_null() {
unsafe { ptr::write_bytes(ptr, 0, size) };
}
ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlMallocAtomicZero(size: usize) -> *mut c_void {
let ptr = unsafe { xmlMallocAtomic(size) };
if !ptr.is_null() {
unsafe { ptr::write_bytes(ptr, 0, size) };
}
ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlReallocZero(
ptr: *mut c_void,
old_size: usize,
new_size: usize,
) -> *mut c_void {
let new_ptr = unsafe { xmlRealloc(ptr, new_size) };
if !new_ptr.is_null() && new_size > old_size {
unsafe {
ptr::write_bytes(new_ptr.add(old_size), 0, new_size - old_size);
}
}
new_ptr
}
#[no_mangle]
pub extern "C" fn xmlInitMemory() -> c_int {
0
}
#[no_mangle]
pub extern "C" fn xmlCleanupMemory() {
}
#[cfg(test)]
mod tests {
use super::*;
use core::ptr;
#[test]
fn test_malloc_free() {
unsafe {
let ptr = xmlMalloc(100);
assert!(!ptr.is_null(), "xmlMalloc(100) returned NULL");
xmlFree(ptr);
}
}
#[test]
fn test_malloc_zero() {
unsafe {
let ptr = xmlMalloc(0);
if !ptr.is_null() {
xmlFree(ptr);
}
}
}
#[test]
fn test_free_null() {
unsafe {
xmlFree(ptr::null_mut());
}
}
#[test]
fn test_realloc() {
unsafe {
let ptr = xmlMalloc(50);
assert!(!ptr.is_null());
let new_ptr = xmlRealloc(ptr, 100);
assert!(!new_ptr.is_null());
xmlFree(new_ptr);
}
}
#[test]
fn test_mem_strdup() {
unsafe {
let s = b"hello\0" as *const u8 as *const c_char;
let dup = xmlMemStrdup(s);
assert!(!dup.is_null());
let orig_slice = std::slice::from_raw_parts(s as *const u8, 6);
let dup_slice = std::slice::from_raw_parts(dup as *const u8, 6);
assert_eq!(orig_slice, dup_slice);
xmlFree(dup);
}
}
#[test]
fn test_malloc_zero_init() {
unsafe {
let ptr = xmlMallocZero(100) as *mut u8;
assert!(!ptr.is_null());
let slice = std::slice::from_raw_parts(ptr, 100);
assert!(slice.iter().all(|&b| b == 0));
xmlFree(ptr as *mut c_void);
}
}
#[test]
fn test_mem_setup_get() {
unsafe {
let mut free_func: Option<xmlFreeFunc> = None;
let mut malloc_func: Option<xmlMallocFunc> = None;
let mut realloc_func: Option<xmlReallocFunc> = None;
let mut strdup_func: Option<xmlStrdupFunc> = None;
xmlMemGet(
&mut free_func as *mut _,
&mut malloc_func as *mut _,
&mut realloc_func as *mut _,
&mut strdup_func as *mut _,
);
assert!(malloc_func.is_some());
assert!(free_func.is_some());
assert!(realloc_func.is_some());
assert!(strdup_func.is_some());
}
}
#[test]
fn test_mem_stats() {
unsafe {
let before_used = xmlMemUsed();
let before_blocks = xmlMemBlocks();
let ptr = xmlMalloc(100);
assert!(!ptr.is_null());
assert!(xmlMemBlocks() >= before_blocks + 1);
xmlFree(ptr);
}
}
}