libxml_rs/xml/memory/mod.rs
1//! Memory management (§58, §85 Phase 1).
2//!
3//! Wraps the allocator hooks from `crate::abi::allocator` for use by the XML
4//! implementation modules. This module provides the internal Rust interface
5//! to the allocator system.
6//!
7//! # UPSTREAM-PARITY
8//!
9//! libxml2 exposes several memory management APIs:
10//!
11//! - `xmlMemSetup` / `xmlMemGet` — set/get custom allocator hooks
12//! - `xmlGcMemSetup` / `xmlGcMemGet` — GC-aware variants (now identical)
13//! - `xmlMalloc` / `xmlMallocAtomic` / `xmlRealloc` / `xmlFree` / `xmlMemStrdup`
14//! - `xmlMallocZero` / `xmlMallocAtomicZero` / `xmlReallocZero`
15//! - `xmlMemUsed` / `xmlMemBlocks` — debugging statistics
16//! - `xmlMemDisplay` / `xmlMemShow` — debugging output
17//! - `xmlInitMemory` / `xmlCleanupMemory` — lifecycle
18//!
19//! All of these are implemented in `crate::abi::allocator`. This module
20//! re-exports them for internal use.
21//!
22//! # Phase 1 status
23//!
24//! Complete — all memory functions delegate to the ABI allocator layer.
25//!
26//! # Upstream contract
27//!
28//! Mirrors upstream xmlmemory.c (SRC-LIBXML2-2.15.0-XMLMEMORY-C): xmlMemSetup
29//! / xmlMemGet / xmlGcMemSetup / xmlMemUsed / xmlMemBlocks / xmlMemDisplay /
30//! xmlMemShow and the xmlMalloc* family. The actual implementation lives in
31//! `crate::abi::allocator`; this module is the internal Rust interface.
32//!
33//! # Conceptual behavior
34//!
35//! Wraps the allocator hooks for use by the XML implementation modules. The
36//! allocator defaults to libc malloc and is swappable via xmlMemSetup; the
37//! block registry (R-000131) tracks blocks for xmlMemSize, xmlMemUsed and the
38//! debug dumps.
39//!
40//! # Ownership & safety invariants
41//!
42//! Ownership rule (atlas/OWNERSHIP_ATLAS.md): a pointer returned by an xml*
43//! allocator must be freed by xmlFree; a pointer from libc::calloc inside the
44//! engine is freed internally and never escapes. SAFETY: xmlFree on a
45//! foreign/unknown pointer is a no-op removal from the registry (documented
46//! safe divergence — upstream would corrupt).
47//!
48//! # Historical quirks & epochs
49//!
50//! The debug allocator with block tracking has been part of libxml2 since the
51//! early 2.x era; xmlMemDisplayLast / xmlMemShow report per-block data.
52//! xmlMemSetup keeps counter-only accounting when custom allocators are
53//! installed (R-000131 divergence, matching upstream debug-allocator-only
54//! block table).
55//!
56//! # Deliberate oddities
57//!
58//! Deliberate oddities: the exported allocator entry points are DATA
59//! function-pointer globals (xmlMallocImpl etc.) matching the oracle ABI
60//! (R-000162: upstream exports them as data variables so the xmlMalloc =
61//! custom override can link).
62//!
63//! # Proving courts
64//!
65//! ALLOCATOR court family, the DATA-GLOBALS-001 probe (allocator globals
66//! byte-identical), ASan full-suite runs (0 invalid reads/writes, 0 double-
67//! free) and `cargo test --lib` (1135+ tests).
68//!
69//! # Tempting simplifications that would break parity
70//!
71//! A tempting simplification is routing all allocation through the Rust global
72//! allocator — it would break xmlMemSetup overrides, xmlMemUsed accounting
73//! and the exported xmlMalloc data-symbol ABI (R-000162). Do not replace the
74//! registry no-op free with a real free of foreign pointers: that would
75//! corrupt the allocator (documented divergence, OWNERSHIP_ATLAS section 8).
76
77pub use crate::abi::allocator::{
78 xmlFreeImpl, xmlInitMemory, xmlMallocAtomicImpl, xmlMallocAtomicZero, xmlMallocImpl,
79 xmlMallocZero, xmlMemBlocks, xmlMemDisplay, xmlMemGet, xmlMemSetup, xmlMemShow,
80 xmlMemStrdupImpl, xmlMemUsed, xmlReallocImpl, xmlReallocZero,
81};
82
83/// Initialize the memory subsystem.
84///
85/// Called during `xmlInitParser`. Returns 0 on success.
86pub const fn init_memory() -> i32 {
87 xmlInitMemory()
88}
89
90/// Clean up the memory subsystem.
91pub const fn cleanup_memory() {
92 // Phase 1: no cleanup needed for the default allocator.
93}
94
95// ═══════════════════════════════════════════════════════════════════════════════
96// Tests
97// ═══════════════════════════════════════════════════════════════════════════════
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_memory_module_delegates_to_allocator() {
105 unsafe {
106 let ptr = xmlMallocImpl(100);
107 assert!(!ptr.is_null());
108 xmlFreeImpl(ptr);
109 }
110 }
111
112 #[test]
113 fn test_memory_zero_alloc() {
114 unsafe {
115 let ptr = xmlMallocZero(64);
116 assert!(!ptr.is_null());
117 // Verify zero-initialized
118 let bytes = core::slice::from_raw_parts(ptr as *const u8, 64);
119 assert!(bytes.iter().all(|&b| b == 0));
120 xmlFreeImpl(ptr);
121 }
122 }
123}