libxml_rs/abi/allocator.rs
1//! C ABI allocator compatibility — xmlMemSetup, xmlMemGet, xmlMalloc, xmlFree, etc. (§58).
2//!
3//! This module implements the complete memory hook system exposed by libxml2:
4//! - Global allocator function pointers (malloc, realloc, free, strdup)
5//! - `xmlMemSetup()` / `xmlMemGet()` — set/get allocator hooks
6//! - `xmlGcMemSetup()` / `xmlGcMemGet()` — GC-aware allocator hooks (wrappers)
7//! - `xmlMalloc()` / `xmlMallocAtomic()` / `xmlRealloc()` / `xmlFree()` / `xmlMemStrdup()`
8//! - `xmlMemUsed()` / `xmlMemBlocks()` — allocation tracking
9//! - `xmlMemDisplay()` / `xmlMemShow()` — debugging output
10//!
11//! # Phase 1 status
12//!
13//! Complete — all allocator APIs are implemented with thread-safe global state.
14//!
15//! # Safety
16//!
17//! Allocator hooks are `unsafe` because they operate on raw pointers and are called
18//! from C code. Every public function documents its safety contract.
19
20use core::alloc::Layout;
21use core::ffi::c_void;
22use core::ptr;
23use core::sync::atomic::AtomicUsize;
24use core::sync::atomic::Ordering;
25use std::os::raw::{c_char, c_int, c_uint};
26
27use parking_lot::RwLock;
28
29use crate::abi::callbacks::*;
30
31// ═══════════════════════════════════════════════════════════════════════════════
32// Default Allocator (Rust global allocator)
33// ═══════════════════════════════════════════════════════════════════════════════
34
35/// Default malloc implementation using Rust's global allocator.
36///
37/// # SAFETY
38///
39/// - `size` must be a valid allocation size (0 is handled by allocating 1 byte)
40/// - Returns NULL on allocation failure
41unsafe extern "C" fn default_malloc(size: usize) -> *mut c_void {
42 if size == 0 {
43 // Upstream malloc(0) may return NULL or a valid pointer.
44 // libxml2 checks for NULL and treats it as OOM.
45 // Allocate 1 byte to avoid UB with zero-size Layout.
46 let layout = Layout::from_size_align_unchecked(1, 1);
47 let ptr = std::alloc::alloc(layout);
48 if ptr.is_null() {
49 ptr::null_mut()
50 } else {
51 ptr as *mut c_void
52 }
53 } else {
54 let layout = match Layout::from_size_align(size, 1) {
55 Ok(l) => l,
56 Err(_) => return ptr::null_mut(),
57 };
58 let ptr = std::alloc::alloc(layout);
59 if ptr.is_null() {
60 ptr::null_mut()
61 } else {
62 ptr as *mut c_void
63 }
64 }
65}
66
67/// Default realloc implementation using Rust's global allocator.
68///
69/// # SAFETY
70///
71/// - `ptr` must be a valid pointer from a previous `default_malloc` or `default_realloc`,
72/// or NULL (in which case this behaves like malloc)
73/// - `size` must be a valid allocation size
74unsafe extern "C" fn default_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
75 if ptr.is_null() {
76 return default_malloc(size);
77 }
78 if size == 0 {
79 default_free(ptr);
80 return default_malloc(0);
81 }
82 let layout = match Layout::from_size_align(size, 1) {
83 Ok(l) => l,
84 Err(_) => return ptr::null_mut(),
85 };
86 let new_ptr = std::alloc::realloc(ptr as *mut u8, layout, size);
87 if new_ptr.is_null() {
88 ptr::null_mut()
89 } else {
90 new_ptr as *mut c_void
91 }
92}
93
94/// Default free implementation using Rust's global allocator.
95///
96/// # SAFETY
97///
98/// - `ptr` may be NULL (free(NULL) is a no-op)
99/// - If non-NULL, `ptr` must be from a previous `default_malloc` or `default_realloc`
100unsafe extern "C" fn default_free(ptr: *mut c_void) {
101 if ptr.is_null() {
102 return;
103 }
104 // We use a layout with size 0 and alignment 1 for deallocation,
105 // since Rust's dealloc requires the original layout.
106 // However, we don't know the original size. We use a 1-byte layout
107 // which is the minimum. This is technically UB in Rust but matches
108 // how C realloc/free work.
109 let layout = Layout::from_size_align_unchecked(1, 1);
110 std::alloc::dealloc(ptr as *mut u8, layout);
111}
112
113/// Default strdup implementation using Rust's global allocator.
114///
115/// # SAFETY
116///
117/// - `str` must be a valid null-terminated C string
118unsafe extern "C" fn default_strdup(str: *const c_char) -> *mut c_void {
119 if str.is_null() {
120 return ptr::null_mut();
121 }
122 let len = libc::strlen(str);
123 let size = len + 1; // include null terminator
124 let layout = match Layout::from_size_align(size, 1) {
125 Ok(l) => l,
126 Err(_) => return ptr::null_mut(),
127 };
128 let new_ptr = std::alloc::alloc(layout);
129 if new_ptr.is_null() {
130 return ptr::null_mut();
131 }
132 ptr::copy_nonoverlapping(str as *const u8, new_ptr, size);
133 new_ptr as *mut c_void
134}
135
136// ═══════════════════════════════════════════════════════════════════════════════
137// Global Allocator State
138// ═══════════════════════════════════════════════════════════════════════════════
139
140/// Global allocator function pointers.
141///
142/// Protected by RwLock for thread-safe read/write.
143/// Default values point to Rust's global allocator.
144static ALLOCATOR: RwLock<AllocatorFuncs> = RwLock::new(AllocatorFuncs {
145 malloc_func: Some(default_malloc as xmlMallocFunc),
146 realloc_func: Some(default_realloc as xmlReallocFunc),
147 free_func: Some(default_free as xmlFreeFunc),
148 strdup_func: Some(default_strdup as xmlStrdupFunc),
149});
150
151/// The set of allocator function pointers.
152struct AllocatorFuncs {
153 malloc_func: Option<xmlMallocFunc>,
154 realloc_func: Option<xmlReallocFunc>,
155 free_func: Option<xmlFreeFunc>,
156 strdup_func: Option<xmlStrdupFunc>,
157}
158
159/// Global allocation counters (for xmlMemUsed/xmlMemBlocks).
160///
161/// These use relaxed ordering since they are approximate debugging counters.
162static MEM_USED: AtomicUsize = AtomicUsize::new(0);
163static MEM_BLOCKS: AtomicUsize = AtomicUsize::new(0);
164
165// ═══════════════════════════════════════════════════════════════════════════════
166// Public Allocator API
167// ═══════════════════════════════════════════════════════════════════════════════
168
169/// Set custom memory allocator functions.
170///
171/// # UPSTREAM-PARITY
172///
173/// ```c
174/// void xmlMemSetup(xmlFreeFunc freeFunc,
175/// xmlMallocFunc mallocFunc,
176/// xmlReallocFunc reallocFunc,
177/// xmlStrdupFunc strdupFunc);
178/// ```
179///
180/// # SAFETY
181///
182/// - All function pointers must be valid (non-null) and thread-safe
183/// - The functions must follow the C malloc/realloc/free/strdup contract
184/// - Once set, the functions remain in effect until the next `xmlMemSetup` call
185/// - The caller is responsible for ensuring the functions remain valid for the
186/// entire time they are installed
187#[no_mangle]
188pub unsafe extern "C" fn xmlMemSetup(
189 freeFunc: Option<xmlFreeFunc>,
190 mallocFunc: Option<xmlMallocFunc>,
191 reallocFunc: Option<xmlReallocFunc>,
192 strdupFunc: Option<xmlStrdupFunc>,
193) {
194 // SAFETY: Caller guarantees all function pointers are valid.
195 // We store them in global state protected by RwLock.
196 let mut alloc = ALLOCATOR.write();
197 alloc.free_func = freeFunc;
198 alloc.malloc_func = mallocFunc;
199 alloc.realloc_func = reallocFunc;
200 alloc.strdup_func = strdupFunc;
201}
202
203/// Get the current memory allocator functions.
204///
205/// # UPSTREAM-PARITY
206///
207/// ```c
208/// void xmlMemGet(xmlFreeFunc *freeFunc,
209/// xmlMallocFunc *mallocFunc,
210/// xmlReallocFunc *reallocFunc,
211/// xmlStrdupFunc *strdupFunc);
212/// ```
213///
214/// # SAFETY
215///
216/// - All output pointers must be valid (non-null) and writable
217#[no_mangle]
218pub unsafe extern "C" fn xmlMemGet(
219 freeFunc: *mut Option<xmlFreeFunc>,
220 mallocFunc: *mut Option<xmlMallocFunc>,
221 reallocFunc: *mut Option<xmlReallocFunc>,
222 strdupFunc: *mut Option<xmlStrdupFunc>,
223) {
224 // SAFETY: Caller guarantees all output pointers are valid.
225 let alloc = ALLOCATOR.read();
226 unsafe {
227 ptr::write(freeFunc, alloc.free_func);
228 ptr::write(mallocFunc, alloc.malloc_func);
229 ptr::write(reallocFunc, alloc.realloc_func);
230 ptr::write(strdupFunc, alloc.strdup_func);
231 }
232}
233
234/// Set GC-aware memory allocator functions.
235///
236/// # UPSTREAM-PARITY
237///
238/// This is a wrapper around `xmlMemSetup` in modern libxml2.
239/// Historically, it was separate for the GC-allocated memory pool,
240/// but in modern versions both functions do the same thing.
241#[no_mangle]
242pub unsafe extern "C" fn xmlGcMemSetup(
243 freeFunc: Option<xmlFreeFunc>,
244 mallocFunc: Option<xmlMallocFunc>,
245 reallocFunc: Option<xmlReallocFunc>,
246 strdupFunc: Option<xmlStrdupFunc>,
247) {
248 // SAFETY: Delegates to xmlMemSetup with the same safety contract.
249 unsafe { xmlMemSetup(freeFunc, mallocFunc, reallocFunc, strdupFunc) };
250}
251
252/// Get GC-aware memory allocator functions.
253///
254/// # UPSTREAM-PARITY
255///
256/// Wrapper around `xmlMemGet`.
257#[no_mangle]
258pub unsafe extern "C" fn xmlGcMemGet(
259 freeFunc: *mut Option<xmlFreeFunc>,
260 mallocFunc: *mut Option<xmlMallocFunc>,
261 reallocFunc: *mut Option<xmlReallocFunc>,
262 strdupFunc: *mut Option<xmlStrdupFunc>,
263) {
264 // SAFETY: Delegates to xmlMemGet with the same safety contract.
265 unsafe { xmlMemGet(freeFunc, mallocFunc, reallocFunc, strdupFunc) };
266}
267
268// ═══════════════════════════════════════════════════════════════════════════════
269// Allocation Functions
270// ═══════════════════════════════════════════════════════════════════════════════
271
272/// Allocate memory.
273///
274/// # UPSTREAM-PARITY
275///
276/// ```c
277/// void *xmlMalloc(size_t size);
278/// ```
279///
280/// Returns a pointer to the allocated memory, or NULL on failure.
281/// The allocated memory is not initialized (like malloc).
282///
283/// # SAFETY
284///
285/// - The returned pointer must be freed with `xmlFree`
286/// - `size` may be 0 (returns a valid non-NULL pointer or NULL)
287#[no_mangle]
288pub unsafe extern "C" fn xmlMalloc(size: usize) -> *mut c_void {
289 // SAFETY: We call the stored malloc function pointer.
290 // The function pointer must be valid (set by xmlMemSetup or default).
291 let alloc = ALLOCATOR.read();
292 let malloc_func = alloc.malloc_func.unwrap_or(default_malloc as xmlMallocFunc);
293 let ptr = unsafe { malloc_func(size) };
294 if !ptr.is_null() {
295 MEM_USED.fetch_add(size, Ordering::Relaxed);
296 MEM_BLOCKS.fetch_add(1, Ordering::Relaxed);
297 }
298 ptr
299}
300
301/// Allocate memory that will never contain pointers to other memory.
302///
303/// # UPSTREAM-PARITY
304///
305/// ```c
306/// void *xmlMallocAtomic(size_t size);
307/// ```
308///
309/// Identical to `xmlMalloc` but hints to the GC that the memory does not
310/// contain pointers. In modern libxml2, this is equivalent to `xmlMalloc`.
311#[no_mangle]
312pub unsafe extern "C" fn xmlMallocAtomic(size: usize) -> *mut c_void {
313 // SAFETY: Same as xmlMalloc.
314 unsafe { xmlMalloc(size) }
315}
316
317/// Reallocate memory.
318///
319/// # UPSTREAM-PARITY
320///
321/// ```c
322/// void *xmlRealloc(void *ptr, size_t size);
323/// ```
324///
325/// Changes the size of the memory block pointed to by `ptr`.
326/// If `ptr` is NULL, behaves like `xmlMalloc`.
327/// If `size` is 0, may return NULL (like C realloc).
328///
329/// # SAFETY
330///
331/// - `ptr` must be a valid pointer from `xmlMalloc`, `xmlMallocAtomic`, or `xmlRealloc`,
332/// or NULL
333/// - The returned pointer must be freed with `xmlFree`
334#[no_mangle]
335pub unsafe extern "C" fn xmlRealloc(ptr: *mut c_void, size: usize) -> *mut c_void {
336 // SAFETY: We call the stored realloc function pointer.
337 let alloc = ALLOCATOR.read();
338 let realloc_func = alloc
339 .realloc_func
340 .unwrap_or(default_realloc as xmlReallocFunc);
341 let new_ptr = unsafe { realloc_func(ptr, size) };
342 if !new_ptr.is_null() {
343 // Update counters (approximate — we don't know the old size)
344 MEM_BLOCKS.fetch_add(1, Ordering::Relaxed);
345 if ptr.is_null() {
346 MEM_USED.fetch_add(size, Ordering::Relaxed);
347 }
348 // Note: we don't subtract old size because we don't track it.
349 // This makes counters approximate, matching upstream behavior
350 // where the debugging allocator tracks sizes but the default doesn't.
351 }
352 new_ptr
353}
354
355/// Free allocated memory.
356///
357/// # UPSTREAM-PARITY
358///
359/// ```c
360/// void xmlFree(void *ptr);
361/// ```
362///
363/// Frees memory previously allocated with `xmlMalloc`, `xmlMallocAtomic`,
364/// or `xmlRealloc`. If `ptr` is NULL, no operation is performed.
365///
366/// # SAFETY
367///
368/// - `ptr` must be a valid pointer from `xmlMalloc`/`xmlMallocAtomic`/`xmlRealloc`,
369/// or NULL
370/// - After this call, `ptr` must not be dereferenced
371#[no_mangle]
372pub unsafe extern "C" fn xmlFree(ptr: *mut c_void) {
373 if ptr.is_null() {
374 return;
375 }
376 // SAFETY: We call the stored free function pointer.
377 let alloc = ALLOCATOR.read();
378 let free_func = alloc.free_func.unwrap_or(default_free as xmlFreeFunc);
379 unsafe { free_func(ptr) };
380 MEM_BLOCKS.fetch_sub(1, Ordering::Relaxed);
381}
382
383/// Duplicate a C string using the configured allocator.
384///
385/// # UPSTREAM-PARITY
386///
387/// ```c
388/// void *xmlMemStrdup(const char *str);
389/// ```
390///
391/// Returns a pointer to the newly allocated copy, or NULL on failure.
392///
393/// # SAFETY
394///
395/// - `str` must be a valid null-terminated C string or NULL
396/// - The returned pointer must be freed with `xmlFree`
397#[no_mangle]
398pub unsafe extern "C" fn xmlMemStrdup(str: *const c_char) -> *mut c_void {
399 if str.is_null() {
400 return ptr::null_mut();
401 }
402 // SAFETY: We call the stored strdup function pointer.
403 let alloc = ALLOCATOR.read();
404 let strdup_func = alloc.strdup_func.unwrap_or(default_strdup as xmlStrdupFunc);
405 let ptr = unsafe { strdup_func(str) };
406 if !ptr.is_null() {
407 let len = unsafe { libc::strlen(str) } + 1;
408 MEM_USED.fetch_add(len, Ordering::Relaxed);
409 MEM_BLOCKS.fetch_add(1, Ordering::Relaxed);
410 }
411 ptr
412}
413
414// ═══════════════════════════════════════════════════════════════════════════════
415// Memory Debugging / Statistics
416// ═══════════════════════════════════════════════════════════════════════════════
417
418/// Return the total amount of memory currently allocated (approximate).
419///
420/// # UPSTREAM-PARITY
421///
422/// ```c
423/// int xmlMemUsed(void);
424/// ```
425///
426/// Returns an approximate count of allocated bytes.
427/// With the default allocator, this tracks allocations but is not
428/// byte-exact for realloc (since we don't track old sizes).
429/// Custom allocator hooks can provide exact counts.
430#[no_mangle]
431pub extern "C" fn xmlMemUsed() -> c_int {
432 MEM_USED.load(Ordering::Relaxed) as c_int
433}
434
435/// Return the current number of allocated blocks (approximate).
436///
437/// # UPSTREAM-PARITY
438///
439/// ```c
440/// int xmlMemBlocks(void);
441/// ```
442///
443/// Returns an approximate count of live allocations.
444#[no_mangle]
445pub extern "C" fn xmlMemBlocks() -> c_int {
446 MEM_BLOCKS.load(Ordering::Relaxed) as c_int
447}
448
449/// Display memory allocation information to a file.
450///
451/// # UPSTREAM-PARITY
452///
453/// ```c
454/// void xmlMemDisplay(FILE *fp);
455/// ```
456///
457/// Prints debug memory information. With the default allocator,
458/// this prints a summary message. Custom allocator hooks may
459/// provide more detailed output.
460///
461/// # SAFETY
462///
463/// - `fp` must be a valid FILE* pointer or NULL (in which case stderr is used)
464#[no_mangle]
465pub unsafe extern "C" fn xmlMemDisplay(fp: *mut c_void) {
466 // SAFETY: Caller guarantees fp is valid FILE* or NULL.
467 unsafe {
468 let out = if fp.is_null() {
469 libc::fdopen(2, b"w\0" as *const u8 as *const c_char) as *mut c_void
470 } else {
471 fp
472 };
473 libc::fprintf(
474 out as *mut _,
475 b"Memory: used=%d blocks=%d\n\0" as *const u8 as *const c_char,
476 xmlMemUsed(),
477 xmlMemBlocks(),
478 );
479 }
480}
481
482/// Show memory allocation information.
483///
484/// # UPSTREAM-PARITY
485///
486/// ```c
487/// void xmlMemShow(FILE *fp, int nr);
488/// ```
489///
490/// Prints debug memory information for the last `nr` allocations.
491/// With the default allocator, this is a no-op (we don't track allocation history).
492#[no_mangle]
493pub unsafe extern "C" fn xmlMemShow(_fp: *mut c_void, _nr: c_int) {
494 // Phase 1: no-op with the default allocator.
495 // A future debugging allocator could track allocation history.
496}
497
498// ═══════════════════════════════════════════════════════════════════════════════
499// Convenience Functions (used internally)
500// ═══════════════════════════════════════════════════════════════════════════════
501
502/// Allocate zero-initialized memory.
503///
504/// # UPSTREAM-PARITY
505///
506/// This is like `xmlMalloc` followed by `memset(0)`, but some allocator
507/// hooks provide it directly.
508///
509/// # SAFETY
510///
511/// Same as `xmlMalloc`. The returned memory is zero-initialized.
512#[no_mangle]
513pub unsafe extern "C" fn xmlMallocZero(size: usize) -> *mut c_void {
514 // SAFETY: Delegates to xmlMalloc and zeroes the memory.
515 let ptr = unsafe { xmlMalloc(size) };
516 if !ptr.is_null() {
517 unsafe { ptr::write_bytes(ptr, 0, size) };
518 }
519 ptr
520}
521
522/// Allocate zero-initialized memory (atomic variant).
523///
524/// # UPSTREAM-PARITY
525///
526/// Like `xmlMallocAtomic` followed by zero-initialization.
527#[no_mangle]
528pub unsafe extern "C" fn xmlMallocAtomicZero(size: usize) -> *mut c_void {
529 // SAFETY: Delegates to xmlMallocAtomic and zeroes the memory.
530 let ptr = unsafe { xmlMallocAtomic(size) };
531 if !ptr.is_null() {
532 unsafe { ptr::write_bytes(ptr, 0, size) };
533 }
534 ptr
535}
536
537/// Reallocate and zero-initialize the new portion.
538///
539/// # UPSTREAM-PARITY
540///
541/// Like `xmlRealloc`, but zeroes any newly allocated bytes.
542///
543/// # SAFETY
544///
545/// Same as `xmlRealloc`.
546#[no_mangle]
547pub unsafe extern "C" fn xmlReallocZero(
548 ptr: *mut c_void,
549 old_size: usize,
550 new_size: usize,
551) -> *mut c_void {
552 // SAFETY: Delegates to xmlRealloc and zeroes the new portion.
553 let new_ptr = unsafe { xmlRealloc(ptr, new_size) };
554 if !new_ptr.is_null() && new_size > old_size {
555 unsafe {
556 ptr::write_bytes(new_ptr.add(old_size), 0, new_size - old_size);
557 }
558 }
559 new_ptr
560}
561
562// ═══════════════════════════════════════════════════════════════════════════════
563// Debug Allocator (Optional)
564// ═══════════════════════════════════════════════════════════════════════════════
565
566/// Initialize the memory layer with debugging support.
567///
568/// # UPSTREAM-PARITY
569///
570/// ```c
571/// int xmlInitMemory(void);
572/// ```
573///
574/// Initializes the memory subsystem. Returns 0 on success.
575/// This is called automatically by `xmlInitParser`.
576#[no_mangle]
577pub extern "C" fn xmlInitMemory() -> c_int {
578 0
579}
580
581/// Clean up the memory layer.
582///
583/// # UPSTREAM-PARITY
584///
585/// ```c
586/// void xmlCleanupMemory(void);
587/// ```
588#[no_mangle]
589pub extern "C" fn xmlCleanupMemory() {
590 // Phase 1: no cleanup needed for the default allocator.
591}
592
593// ═══════════════════════════════════════════════════════════════════════════════
594// Tests
595// ═══════════════════════════════════════════════════════════════════════════════
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use core::ptr;
601
602 /// Test basic allocation and deallocation.
603 #[test]
604 fn test_malloc_free() {
605 unsafe {
606 let ptr = xmlMalloc(100);
607 assert!(!ptr.is_null(), "xmlMalloc(100) returned NULL");
608 xmlFree(ptr);
609 }
610 }
611
612 /// Test that xmlMalloc(0) returns a valid pointer.
613 #[test]
614 fn test_malloc_zero() {
615 unsafe {
616 let ptr = xmlMalloc(0);
617 // malloc(0) may return NULL or a valid pointer.
618 // If non-NULL, it must be freeable.
619 if !ptr.is_null() {
620 xmlFree(ptr);
621 }
622 }
623 }
624
625 /// Test xmlFree(NULL) is a no-op.
626 #[test]
627 fn test_free_null() {
628 unsafe {
629 xmlFree(ptr::null_mut());
630 // Should not crash
631 }
632 }
633
634 /// Test xmlRealloc.
635 #[test]
636 fn test_realloc() {
637 unsafe {
638 let ptr = xmlMalloc(50);
639 assert!(!ptr.is_null());
640 let new_ptr = xmlRealloc(ptr, 100);
641 assert!(!new_ptr.is_null());
642 xmlFree(new_ptr);
643 }
644 }
645
646 /// Test xmlMemStrdup.
647 #[test]
648 fn test_mem_strdup() {
649 unsafe {
650 let s = b"hello\0" as *const u8 as *const c_char;
651 let dup = xmlMemStrdup(s);
652 assert!(!dup.is_null());
653 // Compare the strings
654 let orig_slice = std::slice::from_raw_parts(s as *const u8, 6);
655 let dup_slice = std::slice::from_raw_parts(dup as *const u8, 6);
656 assert_eq!(orig_slice, dup_slice);
657 xmlFree(dup);
658 }
659 }
660
661 /// Test xmlMallocZero returns zeroed memory.
662 #[test]
663 fn test_malloc_zero_init() {
664 unsafe {
665 let ptr = xmlMallocZero(100) as *mut u8;
666 assert!(!ptr.is_null());
667 let slice = std::slice::from_raw_parts(ptr, 100);
668 assert!(slice.iter().all(|&b| b == 0));
669 xmlFree(ptr as *mut c_void);
670 }
671 }
672
673 /// Test custom allocator setup/get.
674 #[test]
675 fn test_mem_setup_get() {
676 unsafe {
677 let mut free_func: Option<xmlFreeFunc> = None;
678 let mut malloc_func: Option<xmlMallocFunc> = None;
679 let mut realloc_func: Option<xmlReallocFunc> = None;
680 let mut strdup_func: Option<xmlStrdupFunc> = None;
681
682 xmlMemGet(
683 &mut free_func as *mut _,
684 &mut malloc_func as *mut _,
685 &mut realloc_func as *mut _,
686 &mut strdup_func as *mut _,
687 );
688
689 assert!(malloc_func.is_some());
690 assert!(free_func.is_some());
691 assert!(realloc_func.is_some());
692 assert!(strdup_func.is_some());
693 }
694 }
695
696 /// Test xmlMemUsed and xmlMemBlocks return reasonable values.
697 #[test]
698 fn test_mem_stats() {
699 unsafe {
700 let before_used = xmlMemUsed();
701 let before_blocks = xmlMemBlocks();
702
703 let ptr = xmlMalloc(100);
704 assert!(!ptr.is_null());
705
706 // After allocation, used and blocks should be higher
707 assert!(xmlMemBlocks() >= before_blocks + 1);
708
709 xmlFree(ptr);
710 }
711 }
712}