libxml_rs/abi/ownership.rs
1//! C ABI ownership contracts — who frees what, when, and under what conditions (§18).
2//!
3//! This module documents the ownership rules governing the libxml2/libxslt C API.
4//! It does not export any `#[no_mangle]` functions — it exists to:
5//!
6//! 1. Document ownership contracts for every public API function
7//! 2. Define Rust types that track ownership state across the ABI membrane
8//! 3. Provide safe wrappers around raw pointer ownership transfers
9//! 4. Define the `Owned`, `Borrowed`, and `Transferred` markers
10//!
11//! # Phase 1 status
12//!
13//! Complete — ownership contracts are documented and the ownership tracking
14//! infrastructure is defined. Active enforcement will be implemented in Phase 2
15//! (Tree and ownership).
16//!
17//! # Ownership categories
18//!
19//! Every pointer parameter and return value in the libxml2 C API falls into
20//! one of these categories:
21//!
22//! | Category | Description |
23//! |---|---|
24//! | `Owned` | Caller owns the pointer and must free it |
25//! | `Borrowed` | Caller borrows the pointer; must not free it |
26//! | `Transferred` | Ownership transfers from caller to callee (or vice versa) |
27//! | `ConsumedOnSuccess` | Consumed only if function succeeds; caller must free on failure |
28//! | `Nullable` | Pointer may be NULL |
29//! | `Static` | Pointer to a static/global object; must not be freed |
30//! | `Opaque` | Internal pointer; caller must not dereference or free |
31//!
32//! # UPSTREAM-PARITY
33//!
34//! These ownership rules are derived from:
35//! - Upstream source code analysis
36//! - API documentation
37//! - Historical bug reports about double-free / use-after-free
38//! - Empirical testing with the oracle
39//!
40//! See `atlas/LORE.md` for detailed ownership archaeology.
41
42#![allow(dead_code)]
43
44use core::ffi::c_void;
45use core::marker::PhantomData;
46use core::ops::Deref;
47use core::ptr::NonNull;
48
49// ═══════════════════════════════════════════════════════════════════════════════
50// Ownership Marker Types
51// ═══════════════════════════════════════════════════════════════════════════════
52
53/// Marker type for owned pointers.
54///
55/// An `Owned<T>` wraps a `NonNull<T>` and indicates that the holder
56/// is responsible for freeing the underlying object at the end of its lifetime.
57///
58/// # SAFETY
59///
60/// The `Drop` implementation will free the wrapped pointer using the appropriate
61/// `xmlFree*` function. The caller must ensure:
62/// - The pointer was allocated by libxml2's allocator
63/// - No other code holds a mutable reference to the pointed-to data
64/// - The pointer is not freed twice
65pub struct Owned<T: ?Sized> {
66 ptr: NonNull<T>,
67 _marker: PhantomData<T>,
68}
69
70/// Marker type for borrowed pointers.
71///
72/// A `Borrowed<T>` wraps a `*const T` or `*mut T` and indicates that
73/// the holder is NOT responsible for freeing the underlying object.
74///
75/// # SAFETY
76///
77/// The borrower must ensure:
78/// - The pointer remains valid for the duration of the borrow
79/// - No mutable access occurs through a shared borrow
80pub struct Borrowed<T: ?Sized> {
81 ptr: *const T,
82 _marker: PhantomData<T>,
83}
84
85/// Marker type for pointers that transfer ownership.
86///
87/// A `Transferred<T>` wraps a `*mut T` that is being transferred
88/// between caller and callee. The recipient assumes ownership.
89pub struct Transferred<T: ?Sized> {
90 ptr: *mut T,
91 _marker: PhantomData<T>,
92}
93
94// ═══════════════════════════════════════════════════════════════════════════════
95// Ownership Tracking for Tree Nodes
96// ═══════════════════════════════════════════════════════════════════════════════
97
98/// The ownership state of a tree node.
99///
100/// This is used internally to track whether a node pointer is:
101/// - Part of a document tree (owned by the document)
102/// - A standalone node (caller-owned, must be freed or adopted)
103/// - A borrowed reference (must not be freed)
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum NodeOwnership {
106 /// The node is owned by its parent document or parent node.
107 /// It will be freed when the document is freed.
108 /// Callers must NOT free it directly.
109 TreeOwned,
110 /// The node is standalone (not attached to any document).
111 /// The caller owns it and must free it or attach it to a tree.
112 CallerOwned,
113 /// The node is borrowed from a tree. The caller must NOT free it.
114 Borrowed,
115 /// The node has been unlinked from its tree.
116 /// The caller now owns it and must free it or re-attach it.
117 Unlinked,
118}
119
120/// The ownership state of a document.
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum DocOwnership {
123 /// The document is owned by the caller.
124 /// It must be freed with `xmlFreeDoc`.
125 Owned,
126 /// The document is owned by a parser context.
127 /// It will be freed when the context is freed.
128 ParserOwned,
129 /// The document is borrowed. Caller must not free it.
130 Borrowed,
131}
132
133// ═══════════════════════════════════════════════════════════════════════════════
134// Ownership Contracts for Key API Functions
135// ═══════════════════════════════════════════════════════════════════════════════
136
137// ─── Document functions ───
138
139/// `xmlNewDoc(const xmlChar *version)`
140///
141/// # Ownership
142///
143/// - `version`: Borrowed (nullable). If non-NULL, the string is copied internally.
144/// The caller retains ownership of the original string.
145/// - **Returns**: Owned. Caller must free with `xmlFreeDoc`.
146/// On failure, returns NULL.
147pub const DOC_NEWDOC_OWNERSHIP: &str = "version: Borrowed (copied). Return: Owned (xmlFreeDoc).";
148
149/// `xmlFreeDoc(xmlDocPtr doc)`
150///
151/// # Ownership
152///
153/// - `doc`: Consumed. The document and all its contents are freed.
154/// After this call, `doc` must not be dereferenced.
155pub const DOC_FREEDOC_OWNERSHIP: &str = "doc: Consumed. Must not be used after call.";
156
157/// `xmlDocSetRootElement(xmlDocPtr doc, xmlNodePtr root)`
158///
159/// # Ownership
160///
161/// - `doc`: Borrowed (mutated). Caller retains ownership.
162/// - `root`: Transferred. The document takes ownership of the root element.
163/// If the document already had a root element, the old root is returned
164/// as owned by the caller.
165/// - **Returns**: Owned (nullable). The old root element, or NULL if there was none.
166/// Caller must free the old root with `xmlFreeNode` if non-NULL.
167pub const DOC_SETROOT_OWNERSHIP: &str =
168 "doc: Borrowed(mut). root: Transferred to doc. Return: Owned old root (xmlFreeNode).";
169
170/// `xmlDocGetRootElement(const xmlDoc *doc)`
171///
172/// # Ownership
173///
174/// - `doc`: Borrowed.
175/// - **Returns**: Borrowed. Must not be freed. The pointer is valid
176/// until the document is freed.
177pub const DOC_GETROOT_OWNERSHIP: &str = "doc: Borrowed. Return: Borrowed (valid until doc freed).";
178
179// ─── Node functions ───
180
181/// `xmlNewNode(xmlNsPtr ns, const xmlChar *name)`
182///
183/// # Ownership
184///
185/// - `ns`: Borrowed (nullable). If non-NULL, the namespace is not copied;
186/// the node holds a pointer to it. The namespace must remain valid
187/// as long as the node exists.
188/// - `name`: Borrowed. The name is copied into the dictionary.
189/// - **Returns**: Owned. Caller must free with `xmlFreeNode` or adopt into a tree.
190pub const NODE_NEWNODE_OWNERSHIP: &str =
191 "ns: Borrowed (must outlive node). name: Borrowed (copied). Return: Owned (xmlFreeNode).";
192
193/// `xmlFreeNode(xmlNodePtr node)`
194///
195/// # Ownership
196///
197/// - `node`: Consumed (nullable). If non-NULL, the node and its subtree are freed.
198/// The node must NOT be part of a document tree (must be unlinked first).
199pub const NODE_FREENODE_OWNERSHIP: &str = "node: Consumed (must be unlinked). NULL-safe.";
200
201/// `xmlUnlinkNode(xmlNodePtr node)`
202///
203/// # Ownership
204///
205/// - `node`: Borrowed (mutated). After unlinking, the node becomes caller-owned
206/// and must be freed or re-attached. The node is removed from its parent
207/// and sibling chain but its memory is not freed.
208pub const NODE_UNLINK_OWNERSHIP: &str =
209 "node: Borrowed(mut). After: caller owns unlinked node (must free or reattach).";
210
211/// `xmlAddChild(xmlNodePtr parent, xmlNodePtr cur)`
212///
213/// # Ownership
214///
215/// - `parent`: Borrowed (mutated).
216/// - `cur`: Transferred. Parent takes ownership of the child node.
217/// - **Returns**: Borrowed. Pointer to the added child (or NULL on error).
218/// Caller must NOT free the returned pointer.
219pub const NODE_ADDCHILD_OWNERSHIP: &str =
220 "parent: Borrowed(mut). cur: Transferred to parent. Return: Borrowed (do not free).";
221
222/// `xmlAddSibling(xmlNodePtr cur, xmlNodePtr sibling)`
223///
224/// # Ownership
225///
226/// - `cur`: Borrowed (mutated).
227/// - `sibling`: Transferred. The sibling list takes ownership.
228/// - **Returns**: Borrowed. Pointer to the added sibling.
229pub const NODE_ADDSIBLING_OWNERSHIP: &str =
230 "cur: Borrowed(mut). sibling: Transferred. Return: Borrowed.";
231
232/// `xmlCopyNode(const xmlNodePtr node, int extended)`
233///
234/// # Ownership
235///
236/// - `node`: Borrowed.
237/// - **Returns**: Owned. A deep or shallow copy. Caller must free with `xmlFreeNode`.
238pub const NODE_COPYNODE_OWNERSHIP: &str = "node: Borrowed. Return: Owned (xmlFreeNode).";
239
240/// `xmlCopyDoc(const xmlDocPtr doc, int recursive)`
241///
242/// # Ownership
243///
244/// - `doc`: Borrowed.
245/// - **Returns**: Owned. Caller must free with `xmlFreeDoc`.
246pub const DOC_COPYDOC_OWNERSHIP: &str = "doc: Borrowed. Return: Owned (xmlFreeDoc).";
247
248// ─── Attribute functions ───
249
250/// `xmlSetProp(xmlNodePtr node, const xmlChar *name, const xmlChar *value)`
251///
252/// # Ownership
253///
254/// - `node`: Borrowed (mutated).
255/// - `name`: Borrowed (copied).
256/// - `value`: Borrowed (copied, nullable).
257/// - **Returns**: Borrowed. Pointer to the attribute (or NULL on error).
258pub const ATTR_SETPROP_OWNERSHIP: &str =
259 "node: Borrowed(mut). name/value: Borrowed(copied). Return: Borrowed.";
260
261/// `xmlGetProp(const xmlNode *node, const xmlChar *name)`
262///
263/// # Ownership
264///
265/// - `node`: Borrowed.
266/// - `name`: Borrowed.
267/// - **Returns**: Owned (nullable). The property value string.
268/// Caller must free with `xmlFree`.
269pub const ATTR_GETPROP_OWNERSHIP: &str = "node/name: Borrowed. Return: Owned string (xmlFree).";
270
271/// `xmlSetNsProp(xmlNodePtr node, xmlNsPtr ns, const xmlChar *name, const xmlChar *value)`
272///
273/// # Ownership
274///
275/// - `node`: Borrowed (mutated).
276/// - `ns`: Borrowed (nullable).
277/// - `name`, `value`: Borrowed (copied).
278/// - **Returns**: Borrowed.
279pub const ATTR_SETNSPROP_OWNERSHIP: &str =
280 "node: Borrowed(mut). ns: Borrowed(nullable). name/value: Borrowed. Return: Borrowed.";
281
282// ─── Namespace functions ───
283
284/// `xmlNewNs(xmlNodePtr node, const xmlChar *href, const xmlChar *prefix)`
285///
286/// # Ownership
287///
288/// - `node`: Borrowed (mutated, nullable).
289/// - `href`: Borrowed (copied, nullable).
290/// - `prefix`: Borrowed (copied, nullable).
291/// - **Returns**: Owned (nullable). The new namespace definition.
292/// The namespace is owned by the node it is attached to.
293/// Caller must NOT free it directly.
294pub const NS_NEWNS_OWNERSHIP: &str =
295 "node: Borrowed(mut). href/prefix: Borrowed(copied). Return: Borrowed (owned by node).";
296
297/// `xmlSetNs(xmlNodePtr node, xmlNsPtr ns)`
298///
299/// # Ownership
300///
301/// - `node`: Borrowed (mutated).
302/// - `ns`: Borrowed. The namespace must be valid for the node's document.
303pub const NS_SETNS_OWNERSHIP: &str = "node/ns: Borrowed.";
304
305// ─── Entity functions ───
306
307/// `xmlNewEntity(xmlDocPtr doc, const xmlChar *name, int type,
308/// const xmlChar *ExternalID, const xmlChar *SystemID,
309/// const xmlChar *content)`
310///
311/// # Ownership
312///
313/// - `doc`: Borrowed (mutated). Entity is added to the document's entities table.
314/// - `name`, `ExternalID`, `SystemID`, `content`: Borrowed (copied).
315/// - **Returns**: Borrowed. The entity is owned by the document.
316pub const ENTITY_NEWENTITY_OWNERSHIP: &str =
317 "doc: Borrowed(mut). name/IDs/content: Borrowed(copied). Return: Borrowed (owned by doc).";
318
319// ─── DTD functions ───
320
321/// `xmlNewDtd(xmlDocPtr doc, const xmlChar *name,
322/// const xmlChar *ExternalID, const xmlChar *SystemID)`
323///
324/// # Ownership
325///
326/// - `doc`: Borrowed (mutated, nullable).
327/// - `name`, `ExternalID`, `SystemID`: Borrowed (copied).
328/// - **Returns**: Owned (nullable). The DTD is owned by the document.
329pub const DTD_NEWDTD_OWNERSHIP: &str =
330 "doc: Borrowed(mut). name/IDs: Borrowed(copied). Return: Borrowed (owned by doc).";
331
332// ─── Parser context functions ───
333
334/// `xmlCreateFileParserCtxt(const char *filename)`
335///
336/// # Ownership
337///
338/// - `filename`: Borrowed.
339/// - **Returns**: Owned. Caller must free with `xmlFreeParserCtxt`.
340pub const PARSE_CREATEFILE_OWNERSHIP: &str =
341 "filename: Borrowed. Return: Owned (xmlFreeParserCtxt).";
342
343/// `xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)`
344///
345/// # Ownership
346///
347/// - `ctxt`: Consumed.
348pub const PARSE_FREECTXT_OWNERSHIP: &str = "ctxt: Consumed.";
349
350// ─── XPath functions ───
351
352/// `xmlXPathNewContext(xmlDocPtr doc)`
353///
354/// # Ownership
355///
356/// - `doc`: Borrowed. The document must outlive the XPath context.
357/// - **Returns**: Owned. Caller must free with `xmlXPathFreeContext`.
358pub const XPATH_NEWCTX_OWNERSHIP: &str =
359 "doc: Borrowed (must outlive context). Return: Owned (xmlXPathFreeContext).";
360
361/// `xmlXPathFreeContext(xmlXPathContextPtr ctxt)`
362///
363/// # Ownership
364///
365/// - `ctxt`: Consumed.
366pub const XPATH_FREECTX_OWNERSHIP: &str = "ctxt: Consumed.";
367
368/// `xmlXPathEvalExpression(const xmlChar *str, xmlXPathContextPtr ctxt)`
369///
370/// # Ownership
371///
372/// - `str`: Borrowed.
373/// - `ctxt`: Borrowed (mutated).
374/// - **Returns**: Owned. Caller must free with `xmlXPathFreeObject`.
375pub const XPATH_EVAL_OWNERSHIP: &str =
376 "str: Borrowed. ctxt: Borrowed(mut). Return: Owned (xmlXPathFreeObject).";
377
378/// `xmlXPathFreeObject(xmlXPathObjectPtr obj)`
379///
380/// # Ownership
381///
382/// - `obj`: Consumed (nullable).
383pub const XPATH_FREEOBJ_OWNERSHIP: &str = "obj: Consumed. NULL-safe.";
384
385// ─── XSLT functions ───
386
387/// `xsltParseStylesheetFile(const xmlChar *filename)`
388///
389/// # Ownership
390///
391/// - `filename`: Borrowed.
392/// - **Returns**: Owned. Caller must free with `xsltFreeStylesheet`.
393pub const XSLT_PARSE_OWNERSHIP: &str = "filename: Borrowed. Return: Owned (xsltFreeStylesheet).";
394
395/// `xsltFreeStylesheet(xsltStylesheetPtr style)`
396///
397/// # Ownership
398///
399/// - `style`: Consumed (nullable).
400pub const XSLT_FREESTYLE_OWNERSHIP: &str = "style: Consumed. NULL-safe.";
401
402/// `xsltApplyStylesheet(xsltStylesheetPtr style, xmlDocPtr doc, const char **params)`
403///
404/// # Ownership
405///
406/// - `style`: Borrowed.
407/// - `doc`: Borrowed. The source document is not modified.
408/// - `params`: Borrowed (nullable). NULL-terminated array of name=value strings.
409/// - **Returns**: Owned. The result document. Caller must free with `xmlFreeDoc`.
410pub const XSLT_APPLY_OWNERSHIP: &str =
411 "style/doc: Borrowed. params: Borrowed(nullable). Return: Owned (xmlFreeDoc).";
412
413// ═══════════════════════════════════════════════════════════════════════════════
414// Ownership Enforcement Helpers (for use in Phase 2+)
415// ═══════════════════════════════════════════════════════════════════════════════
416
417/// A wrapper around a raw pointer that enforces single ownership.
418///
419/// When `UniquePtr<T>` is dropped, it frees the underlying object
420/// using the provided `DropFn`.
421///
422/// # Safety
423///
424/// This is an internal helper for the ABI membrane. It should not be
425/// exposed to external callers.
426pub struct UniquePtr<T> {
427 ptr: Option<NonNull<T>>,
428 drop_fn: unsafe fn(*mut T),
429}
430
431impl<T> UniquePtr<T> {
432 /// Create a new `UniquePtr` from a raw pointer.
433 ///
434 /// # Safety
435 ///
436 /// - `ptr` must be a valid, uniquely-owned pointer or NULL.
437 /// - `drop_fn` must correctly free the pointed-to object.
438 /// - No other code may hold a reference to this pointer.
439 pub unsafe fn new(ptr: *mut T, drop_fn: unsafe fn(*mut T)) -> Self {
440 Self {
441 ptr: NonNull::new(ptr),
442 drop_fn,
443 }
444 }
445
446 /// Get a raw pointer (for FFI calls).
447 /// The caller must not free the pointer.
448 pub fn as_ptr(&self) -> *const T {
449 self.ptr
450 .map_or(core::ptr::null(), |p| p.as_ptr() as *const T)
451 }
452
453 /// Get a mutable raw pointer (for FFI calls that mutate).
454 /// The caller must not free the pointer.
455 pub fn as_mut_ptr(&mut self) -> *mut T {
456 self.ptr.map_or(core::ptr::null_mut(), |p| p.as_ptr())
457 }
458
459 /// Release ownership and return the raw pointer.
460 /// The caller is now responsible for freeing it.
461 pub fn into_raw(mut self) -> *mut T {
462 let ptr = self
463 .ptr
464 .take()
465 .map_or(core::ptr::null_mut(), |p| p.as_ptr());
466 core::mem::forget(self);
467 ptr
468 }
469}
470
471impl<T> Drop for UniquePtr<T> {
472 fn drop(&mut self) {
473 if let Some(ptr) = self.ptr.take() {
474 unsafe { (self.drop_fn)(ptr.as_ptr()) };
475 }
476 }
477}
478
479// ═══════════════════════════════════════════════════════════════════════════════
480// Ownership Assertions
481// ═══════════════════════════════════════════════════════════════════════════════
482
483/// Assert that a pointer is non-null and valid.
484///
485/// Used in debug builds to catch NULL pointer dereferences early.
486#[inline]
487pub fn assert_non_null<T>(ptr: *const T, what: &str) {
488 debug_assert!(!ptr.is_null(), "{} must not be NULL", what);
489}
490
491/// Assert that a mutable pointer is non-null and valid.
492#[inline]
493pub fn assert_non_null_mut<T>(ptr: *mut T, what: &str) {
494 debug_assert!(!ptr.is_null(), "{} must not be NULL", what);
495}