Skip to main content

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    /// `xmlMallocImpl`/`xmlFreeImpl` round trip through the ABI allocator.
104    ///
105    /// # Safety
106    ///
107    /// - `ptr` is non-NULL (asserted) and allocator-owned, valid for 100
108    ///   bytes, and freed with `xmlFreeImpl` exactly once.
109    #[test]
110    fn test_memory_module_delegates_to_allocator() {
111        unsafe {
112            let ptr = xmlMallocImpl(100);
113            assert!(!ptr.is_null());
114            xmlFreeImpl(ptr);
115        }
116    }
117
118    /// `xmlMallocZero` returns zero-initialized allocator memory.
119    ///
120    /// # Safety
121    ///
122    /// - `ptr` is non-NULL (asserted) and allocator-owned, valid for 64
123    ///   zeroed bytes while the slice is read, and freed with
124    ///   `xmlFreeImpl` exactly once.
125    #[test]
126    fn test_memory_zero_alloc() {
127        unsafe {
128            let ptr = xmlMallocZero(64);
129            assert!(!ptr.is_null());
130            // Verify zero-initialized
131            let bytes = core::slice::from_raw_parts(ptr as *const u8, 64);
132            assert!(bytes.iter().all(|&b| b == 0));
133            xmlFreeImpl(ptr);
134        }
135    }
136}