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_long};
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// Legacy-named allocator API (upstream xmlmemory.h)
595// ═══════════════════════════════════════════════════════════════════════════════
596// Upstream xmlmemory.h historically exported `xmlMemMalloc`, `xmlMemFree`,
597// `xmlMemRealloc`, `xmlMemoryStrdup`, the `*Loc` location-tracking variants,
598// `xmlMemSize`, `xmlMemDisplayLast` and `xmlMemoryDump` alongside the modern
599// names. Downstream code (older consumers, some language bindings) links
600// against these names, so the candidate exports them with identical
601// semantics. The `*Loc` variants take a source file/line that the default
602// allocator does not track (upstream uses it for leak reports only); the
603// location arguments are accepted and ignored — a documented safe divergence
604// (residual R-000131).
605
606/// Allocate memory (legacy name; same contract as `xmlMalloc`).
607///
608/// ```c
609/// void *xmlMemMalloc(size_t size);
610/// ```
611#[no_mangle]
612pub unsafe extern "C" fn xmlMemMalloc(size: usize) -> *mut c_void {
613 // SAFETY: identical contract to xmlMalloc.
614 unsafe { xmlMalloc(size) }
615}
616
617/// Free memory (legacy name; same contract as `xmlFree`).
618///
619/// ```c
620/// void xmlMemFree(void *ptr);
621/// ```
622#[no_mangle]
623pub unsafe extern "C" fn xmlMemFree(ptr: *mut c_void) {
624 // SAFETY: identical contract to xmlFree.
625 unsafe { xmlFree(ptr) }
626}
627
628/// Reallocate memory (legacy name; same contract as `xmlRealloc`).
629///
630/// ```c
631/// void *xmlMemRealloc(void *ptr, size_t size);
632/// ```
633#[no_mangle]
634pub unsafe extern "C" fn xmlMemRealloc(ptr: *mut c_void, size: usize) -> *mut c_void {
635 // SAFETY: identical contract to xmlRealloc.
636 unsafe { xmlRealloc(ptr, size) }
637}
638
639/// Duplicate a string (legacy name; same contract as `xmlMemStrdup`).
640///
641/// ```c
642/// void *xmlMemoryStrdup(const char *str);
643/// ```
644#[no_mangle]
645pub unsafe extern "C" fn xmlMemoryStrdup(str: *const c_char) -> *mut c_void {
646 // SAFETY: identical contract to xmlMemStrdup.
647 unsafe { xmlMemStrdup(str) }
648}
649
650/// Allocate memory, recording the allocation site (upstream xmlmemory.h).
651///
652/// ```c
653/// void *xmlMallocLoc(size_t size, const char *file, int line);
654/// ```
655///
656/// The default candidate allocator does not track allocation sites (see
657/// residual R-000131); the location arguments are accepted for ABI
658/// compatibility and ignored.
659#[no_mangle]
660pub unsafe extern "C" fn xmlMallocLoc(
661 size: usize,
662 _file: *const c_char,
663 _line: c_int,
664) -> *mut c_void {
665 // SAFETY: identical contract to xmlMalloc.
666 unsafe { xmlMalloc(size) }
667}
668
669/// Allocate zeroed memory, recording the allocation site (upstream xmlmemory.h).
670///
671/// ```c
672/// void *xmlMallocAtomicLoc(size_t size, const char *file, int line);
673/// ```
674#[no_mangle]
675pub unsafe extern "C" fn xmlMallocAtomicLoc(
676 size: usize,
677 _file: *const c_char,
678 _line: c_int,
679) -> *mut c_void {
680 // SAFETY: identical contract to xmlMallocZero.
681 unsafe { xmlMallocZero(size) }
682}
683
684/// Reallocate memory, recording the allocation site (upstream xmlmemory.h).
685///
686/// ```c
687/// void *xmlReallocLoc(void *ptr, size_t size, const char *file, int line);
688/// ```
689#[no_mangle]
690pub unsafe extern "C" fn xmlReallocLoc(
691 ptr: *mut c_void,
692 size: usize,
693 _file: *const c_char,
694 _line: c_int,
695) -> *mut c_void {
696 // SAFETY: identical contract to xmlRealloc.
697 unsafe { xmlRealloc(ptr, size) }
698}
699
700/// Duplicate a string, recording the allocation site (upstream xmlmemory.h).
701///
702/// ```c
703/// void *xmlMemStrdupLoc(const char *str, const char *file, int line);
704/// ```
705#[no_mangle]
706pub unsafe extern "C" fn xmlMemStrdupLoc(
707 str: *const c_char,
708 _file: *const c_char,
709 _line: c_int,
710) -> *mut c_void {
711 // SAFETY: identical contract to xmlMemStrdup.
712 unsafe { xmlMemStrdup(str) }
713}
714
715/// Return the size of an allocated block (upstream xmlmemory.h).
716///
717/// ```c
718/// size_t xmlMemSize(void *ptr);
719/// ```
720///
721/// The default candidate allocator does not maintain a per-block size table
722/// (that is the upstream debug-allocator block list); it therefore returns 0
723/// for all blocks — a documented safe divergence (residual R-000131) until
724/// the allocator instrumentation court (11.1-J) adds block metadata.
725#[no_mangle]
726pub unsafe extern "C" fn xmlMemSize(_ptr: *mut c_void) -> usize {
727 0
728}
729
730/// Display a limited amount of memory debug information (upstream xmlmemory.h).
731///
732/// ```c
733/// void xmlMemDisplayLast(FILE *fp, long nbBytes);
734/// ```
735///
736/// The default candidate allocator prints the global counters (it has no
737/// per-block list); the output format is intentionally simpler than
738/// upstream's block dump — a documented safe divergence (residual R-000131).
739#[no_mangle]
740pub unsafe extern "C" fn xmlMemDisplayLast(fp: *mut c_void, _nb_bytes: c_long) {
741 // SAFETY: fp must be a valid FILE* or NULL (stderr used).
742 unsafe {
743 let used = MEM_USED.load(Ordering::Relaxed);
744 let blocks = MEM_BLOCKS.load(Ordering::Relaxed);
745 let out = if fp.is_null() {
746 libc::fdopen(2, b"w\0" as *const u8 as *const c_char) as *mut c_void
747 } else {
748 fp
749 };
750 if !out.is_null() {
751 let msg = format!(
752 "libxml-rs allocator: {} blocks, {} bytes in use\n",
753 blocks, used
754 );
755 let bytes = msg.as_bytes();
756 libc::fwrite(
757 bytes.as_ptr() as *const c_void,
758 1,
759 bytes.len(),
760 out as *mut libc::FILE,
761 );
762 }
763 }
764}
765
766/// Dump memory allocation statistics (upstream xmlmemory.h).
767///
768/// ```c
769/// int xmlMemoryDump(void);
770/// ```
771///
772/// Prints the global counters to stderr and returns 0 (no leak detector is
773/// active in the default allocator).
774#[no_mangle]
775pub unsafe extern "C" fn xmlMemoryDump() -> c_int {
776 unsafe {
777 xmlMemDisplayLast(ptr::null_mut(), -1);
778 }
779 0
780}
781
782// ═══════════════════════════════════════════════════════════════════════════════
783// Tests
784// ═══════════════════════════════════════════════════════════════════════════════
785
786#[cfg(test)]
787mod tests {
788 use super::*;
789 use core::ptr;
790
791 /// Test basic allocation and deallocation.
792 #[test]
793 fn test_malloc_free() {
794 unsafe {
795 let ptr = xmlMalloc(100);
796 assert!(!ptr.is_null(), "xmlMalloc(100) returned NULL");
797 xmlFree(ptr);
798 }
799 }
800
801 /// Test that xmlMalloc(0) returns a valid pointer.
802 #[test]
803 fn test_malloc_zero() {
804 unsafe {
805 let ptr = xmlMalloc(0);
806 // malloc(0) may return NULL or a valid pointer.
807 // If non-NULL, it must be freeable.
808 if !ptr.is_null() {
809 xmlFree(ptr);
810 }
811 }
812 }
813
814 /// Test xmlFree(NULL) is a no-op.
815 #[test]
816 fn test_free_null() {
817 unsafe {
818 xmlFree(ptr::null_mut());
819 // Should not crash
820 }
821 }
822
823 /// Test xmlRealloc.
824 #[test]
825 fn test_realloc() {
826 unsafe {
827 let ptr = xmlMalloc(50);
828 assert!(!ptr.is_null());
829 let new_ptr = xmlRealloc(ptr, 100);
830 assert!(!new_ptr.is_null());
831 xmlFree(new_ptr);
832 }
833 }
834
835 /// Test xmlMemStrdup.
836 #[test]
837 fn test_mem_strdup() {
838 unsafe {
839 let s = b"hello\0" as *const u8 as *const c_char;
840 let dup = xmlMemStrdup(s);
841 assert!(!dup.is_null());
842 // Compare the strings
843 let orig_slice = std::slice::from_raw_parts(s as *const u8, 6);
844 let dup_slice = std::slice::from_raw_parts(dup as *const u8, 6);
845 assert_eq!(orig_slice, dup_slice);
846 xmlFree(dup);
847 }
848 }
849
850 /// Test xmlMallocZero returns zeroed memory.
851 #[test]
852 fn test_malloc_zero_init() {
853 unsafe {
854 let ptr = xmlMallocZero(100) as *mut u8;
855 assert!(!ptr.is_null());
856 let slice = std::slice::from_raw_parts(ptr, 100);
857 assert!(slice.iter().all(|&b| b == 0));
858 xmlFree(ptr as *mut c_void);
859 }
860 }
861
862 /// Test custom allocator setup/get.
863 #[test]
864 fn test_mem_setup_get() {
865 unsafe {
866 let mut free_func: Option<xmlFreeFunc> = None;
867 let mut malloc_func: Option<xmlMallocFunc> = None;
868 let mut realloc_func: Option<xmlReallocFunc> = None;
869 let mut strdup_func: Option<xmlStrdupFunc> = None;
870
871 xmlMemGet(
872 &mut free_func as *mut _,
873 &mut malloc_func as *mut _,
874 &mut realloc_func as *mut _,
875 &mut strdup_func as *mut _,
876 );
877
878 assert!(malloc_func.is_some());
879 assert!(free_func.is_some());
880 assert!(realloc_func.is_some());
881 assert!(strdup_func.is_some());
882 }
883 }
884
885 /// Test xmlMemUsed and xmlMemBlocks return reasonable values.
886 #[test]
887 fn test_mem_stats() {
888 unsafe {
889 let before_used = xmlMemUsed();
890 let before_blocks = xmlMemBlocks();
891
892 let ptr = xmlMalloc(100);
893 assert!(!ptr.is_null());
894
895 // After allocation, used and blocks should be higher
896 assert!(xmlMemBlocks() >= before_blocks + 1);
897
898 xmlFree(ptr);
899 }
900 }
901}