use core::alloc::Layout;
use core::ffi::c_void;
use core::ptr;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_long};
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);
#[derive(Clone, Copy)]
struct BlockMeta {
size: usize,
file: usize,
line: c_int,
}
static BLOCKS: once_cell::sync::Lazy<
parking_lot::Mutex<std::collections::HashMap<usize, BlockMeta>>,
> = once_cell::sync::Lazy::new(|| parking_lot::Mutex::new(std::collections::HashMap::new()));
unsafe fn block_record(ptr: *mut c_void, size: usize, file: *const c_char, line: c_int) {
if ptr.is_null() {
return;
}
BLOCKS.lock().insert(
ptr as usize,
BlockMeta {
size,
file: file as usize,
line,
},
);
}
unsafe fn block_forget(ptr: *mut c_void) -> usize {
if ptr.is_null() {
return 0;
}
BLOCKS
.lock()
.remove(&(ptr as usize))
.map(|m| m.size)
.unwrap_or(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) };
}
pub unsafe extern "C" fn xmlMallocImpl(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);
unsafe { block_record(ptr, size, ptr::null(), 0) };
}
ptr
}
pub unsafe extern "C" fn xmlMallocAtomicImpl(size: usize) -> *mut c_void {
unsafe { xmlMallocImpl(size) }
}
pub unsafe extern "C" fn xmlReallocImpl(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() {
let old_size = unsafe { block_forget(ptr) };
MEM_USED.fetch_add(size.saturating_sub(old_size), Ordering::Relaxed);
if ptr.is_null() {
MEM_BLOCKS.fetch_add(1, Ordering::Relaxed);
}
unsafe { block_record(new_ptr, size, ptr::null(), 0) };
} else if !ptr.is_null() {
unsafe { block_record(ptr, block_forget(ptr), ptr::null(), 0) };
}
new_ptr
}
pub unsafe extern "C" fn xmlFreeImpl(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);
let old_size = unsafe { block_forget(ptr) };
unsafe { free_func(ptr) };
MEM_BLOCKS.fetch_sub(1, Ordering::Relaxed);
if old_size > 0 {
MEM_USED.fetch_sub(old_size, Ordering::Relaxed);
}
}
pub unsafe extern "C" fn xmlMemStrdupImpl(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);
unsafe { block_record(ptr, len, ptr::null(), 0) };
}
ptr
}
#[no_mangle]
pub static mut xmlMalloc: xmlMallocFunc = xmlMallocImpl;
#[no_mangle]
pub static mut xmlMallocAtomic: xmlMallocFunc = xmlMallocAtomicImpl;
#[no_mangle]
pub static mut xmlRealloc: xmlReallocFunc = xmlReallocImpl;
#[no_mangle]
pub static mut xmlFree: xmlFreeFunc = xmlFreeImpl;
#[no_mangle]
pub static mut xmlMemStrdup: xmlStrdupFunc = xmlMemStrdupImpl;
#[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) {
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
};
if out.is_null() {
return;
}
let mut msg = String::from("Recent blocks\n");
let map = BLOCKS.lock();
let mut entries: Vec<(usize, &BlockMeta)> = map.iter().map(|(k, v)| (*k, v)).collect();
entries.sort_by_key(|(k, _)| *k);
let mut shown = 0;
for (addr, meta) in entries {
if nr > 0 && shown >= nr {
break;
}
msg.push_str(&format!(
" {:018p} : {:>7} bytes\n",
addr as *const c_void, meta.size
));
shown += 1;
}
let bytes = msg.as_bytes();
libc::fwrite(
bytes.as_ptr() as *const c_void,
1,
bytes.len(),
out as *mut libc::FILE,
);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlMallocZero(size: usize) -> *mut c_void {
let ptr = unsafe { xmlMallocImpl(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 { xmlMallocAtomicImpl(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 { xmlReallocImpl(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() {
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemMalloc(size: usize) -> *mut c_void {
unsafe { xmlMallocImpl(size) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemFree(ptr: *mut c_void) {
unsafe { xmlFreeImpl(ptr) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemRealloc(ptr: *mut c_void, size: usize) -> *mut c_void {
unsafe { xmlReallocImpl(ptr, size) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemoryStrdup(str: *const c_char) -> *mut c_void {
unsafe { xmlMemStrdupImpl(str) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlMallocLoc(
size: usize,
file: *const c_char,
line: c_int,
) -> *mut c_void {
let ptr = unsafe { xmlMallocImpl(size) };
if !ptr.is_null() {
unsafe { block_record(ptr, size, file, line) };
}
ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlMallocAtomicLoc(
size: usize,
file: *const c_char,
line: c_int,
) -> *mut c_void {
let ptr = unsafe { xmlMallocZero(size) };
if !ptr.is_null() {
unsafe { block_record(ptr, size, file, line) };
}
ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlReallocLoc(
ptr: *mut c_void,
size: usize,
file: *const c_char,
line: c_int,
) -> *mut c_void {
let new_ptr = unsafe { xmlReallocImpl(ptr, size) };
if !new_ptr.is_null() {
unsafe { block_record(new_ptr, size, file, line) };
} else if !ptr.is_null() {
let meta = BLOCKS.lock().get(&(ptr as usize)).copied();
if let Some(m) = meta {
unsafe { block_record(ptr, m.size, m.file as *const c_char, m.line) };
}
}
new_ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemStrdupLoc(
str: *const c_char,
file: *const c_char,
line: c_int,
) -> *mut c_void {
let ptr = unsafe { xmlMemStrdupImpl(str) };
if !ptr.is_null() {
let len = unsafe { libc::strlen(str) } + 1;
unsafe { block_record(ptr, len, file, line) };
}
ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemSize(ptr: *mut c_void) -> usize {
if ptr.is_null() {
return 0;
}
BLOCKS
.lock()
.get(&(ptr as usize))
.map(|m| m.size)
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemDisplayLast(fp: *mut c_void, nb_bytes: c_long) {
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
};
if out.is_null() {
return;
}
let mut total: usize = 0;
let mut msg = String::new();
msg.push_str("MEMORY ALLOCATED : 0, MAX : 0, BLOCKS : ");
let blocks = MEM_BLOCKS.load(Ordering::Relaxed);
msg.push_str(&blocks.to_string());
msg.push('\n');
let map = BLOCKS.lock();
let mut entries: Vec<(usize, &BlockMeta)> = map.iter().map(|(k, v)| (*k, v)).collect();
entries.sort_by_key(|(k, _)| *k);
for (addr, meta) in entries {
if nb_bytes > 0 && (total as c_long) >= nb_bytes {
break;
}
total += meta.size;
msg.push_str(&format!(
" {:018p} : {:>7} bytes",
addr as *const c_void, meta.size
));
if meta.file != 0 {
let file = CStr::from_ptr(meta.file as *const c_char).to_string_lossy();
msg.push_str(&format!(" @ {}:{}", file, meta.line));
}
msg.push('\n');
}
drop(map);
let used = MEM_USED.load(Ordering::Relaxed);
msg.push_str(&format!(
"TOTAL MEMORY ALLOCATED : {} bytes, TOTAL BLOCKS : {}\n",
used, blocks
));
let bytes = msg.as_bytes();
libc::fwrite(
bytes.as_ptr() as *const c_void,
1,
bytes.len(),
out as *mut libc::FILE,
);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlMemoryDump() -> c_int {
unsafe {
xmlMemDisplayLast(ptr::null_mut(), -1);
}
0
}
#[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 ptr = xmlMalloc(100);
assert!(!ptr.is_null());
assert_eq!(xmlMemSize(ptr), 100);
xmlFree(ptr);
assert_eq!(xmlMemSize(ptr), 0);
}
}
}