Skip to main content

libxml_rs/xml/globals/
mod.rs

1//! Global state management (§57, §85 Phase 1).
2//!
3//! Manages all library-wide global state:
4//!
5//! - Parser defaults (validity checking, entity substitution, blanks, etc.)
6//! - Generic and structured error callbacks
7//! - Catalog defaults
8//! - Memory hooks (connects to allocator.rs)
9//! - Thread-local state for errors
10//! - Initialization/cleanup reference counting
11//!
12//! # UPSTREAM-PARITY
13//!
14//! libxml2 has many global variables that control default parser behavior.
15//! These are exposed as public ABI symbols that applications can read/write
16//! directly. We use atomic operations for thread safety while maintaining
17//! the same observable semantics.
18//!
19//! # Thread safety
20//!
21//! All global state uses atomic operations or parking_lot locks.
22//! Error state is thread-local via `thread_local!`.
23//!
24//! # Phase 1 status
25//!
26//! Complete — all global state management is implemented.
27//! Future phases may add historical version-specific behavior.
28
29use core::cell::RefCell;
30use core::ffi::c_void;
31use core::ptr;
32use core::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, Ordering};
33use std::os::raw::c_int;
34
35use crate::abi::allocator;
36use crate::abi::callbacks::{xmlGenericErrorFunc, xmlStructuredErrorFunc};
37use crate::abi::structs::_xmlError;
38use crate::abi::types::xmlErrorLevel::XML_ERR_NONE;
39use crate::abi::types::*;
40use crate::abi::versioning;
41
42// ═══════════════════════════════════════════════════════════════════════════════
43// Initialization Reference Counting
44// ═══════════════════════════════════════════════════════════════════════════════
45
46/// Reference count for xmlInitParser / xmlCleanupParser.
47static INIT_REF_COUNT: AtomicI32 = AtomicI32::new(0);
48
49/// Whether threading has been initialized.
50static THREADS_INITIALIZED: AtomicBool = AtomicBool::new(false);
51
52// ═══════════════════════════════════════════════════════════════════════════════
53// Parser Defaults
54// ═══════════════════════════════════════════════════════════════════════════════
55//
56// These are the global parser default variables exposed by libxml2's ABI.
57// Applications can read and write them directly to change default behavior.
58//
59// Upstream declarations (from parser.h / parserInternals.h):
60//
61// ```c
62// extern int xmlDoValidityCheckingDefaultValue;
63// extern int xmlDoWarningsDefaultValue;
64// extern int xmlIndentTreeOutput;
65// extern int xmlKeepBlanksDefaultValue;
66// extern int xmlLoadExtDtdDefaultValue;
67// extern int xmlPedanticParserDefaultValue;
68// ═══════════════════════════════════════════════════════════════════════════════
69// Parser Defaults
70// ═══════════════════════════════════════════════════════════════════════════════
71//
72// The defaults live in the EXPORTED C globals (src/abi/data_globals.rs) so
73// that downstream C code reading/writing them directly (the upstream
74// contract) observes and controls the same state the parser uses. The
75// accessors below are the safe-Rust view of those statics.
76
77// ═══════════════════════════════════════════════════════════════════════════════
78// Error Callback Globals
79// ═══════════════════════════════════════════════════════════════════════════════
80// Also exported as C globals (xmlGenericError/xmlGenericErrorContext,
81// xmlStructuredError/xmlStructuredErrorContext).
82
83// ═══════════════════════════════════════════════════════════════════════════════
84// Catalog Defaults
85// ═══════════════════════════════════════════════════════════════════════════════
86
87/// Catalog default allow value.
88/// 0 = strict, 1 = allow, 2 = allow document, -1 = none.
89/// Internal only (upstream keeps catalog state inside xmlCatalogSetDefaults;
90/// there is no public C global for it).
91static CATALOG_DEFAULTS: AtomicI32 = AtomicI32::new(0);
92
93// ═══════════════════════════════════════════════════════════════════════════════
94// Thread-Local Error State
95// ═══════════════════════════════════════════════════════════════════════════════
96
97thread_local! {
98    /// Last error for this thread.
99    static LAST_ERROR: RefCell<Option<_xmlError>> = const { RefCell::new(None) };
100}
101
102// ═══════════════════════════════════════════════════════════════════════════════
103// Public Accessors — Parser Defaults
104// ═══════════════════════════════════════════════════════════════════════════════
105
106/// Get the default validity checking value.
107pub fn get_validity_checking_default() -> c_int {
108    // SAFETY: reading an exported int global; matches upstream semantics.
109    unsafe { crate::abi::data_globals::xmlDoValidityCheckingDefaultValue }
110}
111
112/// Set the default validity checking value.
113pub fn set_validity_checking_default(val: c_int) {
114    // SAFETY: writing an exported int global; matches upstream semantics.
115    unsafe {
116        crate::abi::data_globals::xmlDoValidityCheckingDefaultValue = val;
117    }
118}
119
120/// Get the default warnings value.
121pub fn get_do_warnings_default() -> c_int {
122    // SAFETY: reading an exported int global (upstream xmlGetWarningsDefaultValue).
123    unsafe { crate::abi::data_globals::xmlGetWarningsDefaultValue }
124}
125
126/// Set the default warnings value.
127pub fn set_do_warnings_default(val: c_int) {
128    // SAFETY: writing an exported int global.
129    unsafe {
130        crate::abi::data_globals::xmlGetWarningsDefaultValue = val;
131    }
132}
133
134/// Get the indent tree output default.
135pub fn get_indent_tree_output() -> c_int {
136    // SAFETY: reading an exported int global (upstream xmlIndentTreeOutput).
137    unsafe { crate::abi::data_globals::xmlIndentTreeOutput }
138}
139
140/// Set the indent tree output default.
141pub fn set_indent_tree_output(val: c_int) {
142    // SAFETY: writing an exported int global.
143    unsafe {
144        crate::abi::data_globals::xmlIndentTreeOutput = val;
145    }
146}
147
148/// Get the keep blanks default value.
149pub fn get_keep_blanks_default() -> c_int {
150    // SAFETY: reading an exported int global (upstream xmlKeepBlanksDefaultValue).
151    unsafe { crate::abi::data_globals::xmlKeepBlanksDefaultValue }
152}
153
154/// Set the keep blanks default value.
155pub fn set_keep_blanks_default(val: c_int) {
156    // SAFETY: writing an exported int global.
157    unsafe {
158        crate::abi::data_globals::xmlKeepBlanksDefaultValue = val;
159    }
160}
161
162/// Get the load external DTD default value.
163pub fn get_load_ext_dtd_default() -> c_int {
164    // SAFETY: reading an exported int global (upstream xmlLoadExtDtdDefaultValue).
165    unsafe { crate::abi::data_globals::xmlLoadExtDtdDefaultValue }
166}
167
168/// Set the load external DTD default value.
169pub fn set_load_ext_dtd_default(val: c_int) {
170    // SAFETY: writing an exported int global.
171    unsafe {
172        crate::abi::data_globals::xmlLoadExtDtdDefaultValue = val;
173    }
174}
175
176/// Get the pedantic parser default.
177pub fn get_pedantic_parser_default() -> c_int {
178    // SAFETY: reading an exported int global (upstream xmlPedanticParserDefaultValue).
179    unsafe { crate::abi::data_globals::xmlPedanticParserDefaultValue }
180}
181
182/// Set the pedantic parser default.
183pub fn set_pedantic_parser_default(val: c_int) {
184    // SAFETY: writing an exported int global.
185    unsafe {
186        crate::abi::data_globals::xmlPedanticParserDefaultValue = val;
187    }
188}
189
190/// Get the substitute entities default.
191pub fn get_substitute_entities_default() -> c_int {
192    // SAFETY: reading an exported int global (upstream xmlSubstituteEntitiesDefaultValue).
193    unsafe { crate::abi::data_globals::xmlSubstituteEntitiesDefaultValue }
194}
195
196/// Set the substitute entities default.
197pub fn set_substitute_entities_default(val: c_int) {
198    // SAFETY: writing an exported int global.
199    unsafe {
200        crate::abi::data_globals::xmlSubstituteEntitiesDefaultValue = val;
201    }
202}
203
204/// Get the save no empty tags default.
205pub fn get_save_no_empty_tags() -> c_int {
206    // SAFETY: reading an exported int global (upstream xmlSaveNoEmptyTags).
207    unsafe { crate::abi::data_globals::xmlSaveNoEmptyTags }
208}
209
210/// Set the save no empty tags default.
211pub fn set_save_no_empty_tags(val: c_int) {
212    // SAFETY: writing an exported int global.
213    unsafe {
214        crate::abi::data_globals::xmlSaveNoEmptyTags = val;
215    }
216}
217
218/// Get the get warnings default.
219pub fn get_get_warnings_default() -> c_int {
220    // SAFETY: reading an exported int global (upstream xmlGetWarningsDefaultValue).
221    unsafe { crate::abi::data_globals::xmlGetWarningsDefaultValue }
222}
223
224/// Set the get warnings default.
225pub fn set_get_warnings_default(val: c_int) {
226    // SAFETY: writing an exported int global.
227    unsafe {
228        crate::abi::data_globals::xmlGetWarningsDefaultValue = val;
229    }
230}
231
232// ═══════════════════════════════════════════════════════════════════════════════
233// Public Accessors — Error Callbacks
234// ═══════════════════════════════════════════════════════════════════════════════
235
236/// Set the generic error handler.
237///
238/// # SAFETY
239///
240/// - `handler` must be a valid function pointer or NULL (to reset to default).
241/// - If non-NULL, the handler may be called at any time with `ctx`.
242pub unsafe fn set_generic_error_func(ctx: *mut c_void, handler: Option<xmlGenericErrorFunc>) {
243    // SAFETY: writing the exported C globals xmlGenericErrorContext /
244    // xmlGenericError; matches upstream xmlSetGenericErrorFunc (NULL resets
245    // to the built-in default stderr printer, error.c).
246    unsafe {
247        crate::abi::data_globals::xmlGenericErrorContext = ctx;
248        crate::abi::data_globals::xmlGenericError = match handler {
249            Some(h) => Some(h),
250            None => crate::abi::data_globals::default_generic_error_func(),
251        };
252    }
253}
254
255/// Get the generic error handler context.
256pub fn get_generic_error_ctx() -> *mut c_void {
257    // SAFETY: reading the exported C global xmlGenericErrorContext.
258    unsafe { crate::abi::data_globals::xmlGenericErrorContext }
259}
260
261/// Get the generic error handler function pointer.
262pub fn get_generic_error_func() -> Option<xmlGenericErrorFunc> {
263    // SAFETY: reading the exported C global xmlGenericError.
264    unsafe { crate::abi::data_globals::xmlGenericError }
265}
266
267/// Set the structured error handler.
268///
269/// # SAFETY
270///
271/// - `handler` must be a valid function pointer or NULL.
272pub unsafe fn set_structured_error_func(ctx: *mut c_void, handler: Option<xmlStructuredErrorFunc>) {
273    // SAFETY: writing the exported C globals xmlStructuredErrorContext /
274    // xmlStructuredError; matches upstream xmlSetStructuredErrorFunc.
275    unsafe {
276        crate::abi::data_globals::xmlStructuredErrorContext = ctx;
277        crate::abi::data_globals::xmlStructuredError = handler;
278    }
279}
280
281/// Get the structured error handler context.
282pub fn get_structured_error_ctx() -> *mut c_void {
283    // SAFETY: reading the exported C global xmlStructuredErrorContext.
284    unsafe { crate::abi::data_globals::xmlStructuredErrorContext }
285}
286
287/// Get the structured error handler function pointer.
288pub fn get_structured_error_func() -> Option<xmlStructuredErrorFunc> {
289    // SAFETY: reading the exported C global xmlStructuredError.
290    unsafe { crate::abi::data_globals::xmlStructuredError }
291}
292
293// ═══════════════════════════════════════════════════════════════════════════════
294// Public Accessors — Thread-Local Error State
295// ═══════════════════════════════════════════════════════════════════════════════
296
297/// Get the last error for this thread.
298///
299/// Returns a mutable pointer to the last error, or NULL if no error occurred.
300/// The returned pointer is valid until the next libxml2 call in this thread.
301pub fn get_last_error() -> *mut _xmlError {
302    LAST_ERROR.with(|last| {
303        let mut last = last.borrow_mut();
304        last.as_mut()
305            .map_or(ptr::null_mut(), |e| e as *mut _xmlError)
306    })
307}
308
309/// Get a reference to the last error (for structured error callback).
310pub fn with_last_error<F, R>(f: F) -> R
311where
312    F: FnOnce(Option<&_xmlError>) -> R,
313{
314    LAST_ERROR.with(|last| f(last.borrow().as_ref()))
315}
316
317/// Set the last error for this thread.
318pub fn set_last_error(err: _xmlError) {
319    // UPSTREAM-PARITY: mirror the error into the exported C global
320    // `xmlLastError` (data-ABI, residual R-000135). Upstream keeps a single
321    // global; the candidate keeps a thread-local truth and a deep-copied
322    // mirror so C consumers see the most recent error with upstream
323    // lifetime semantics (mirror strings are owned by the mirror and freed
324    // on reset, matching xmlResetError).
325    //
326    // SAFETY: sync_xml_last_error only reads `err` and writes the global
327    // mirror with freshly owned copies; the thread-local slot takes
328    // ownership of `err` itself.
329    unsafe { crate::abi::data_globals::sync_xml_last_error(&err) };
330    LAST_ERROR.with(|last| {
331        *last.borrow_mut() = Some(err);
332    });
333}
334
335/// Reset the last error for this thread.
336pub fn reset_last_error() {
337    LAST_ERROR.with(|last| {
338        *last.borrow_mut() = None;
339    });
340    // SAFETY: frees the mirror's owned strings and zeroes the global.
341    unsafe { crate::abi::data_globals::reset_xml_last_error() };
342}
343
344// ═══════════════════════════════════════════════════════════════════════════════
345// Public Accessors — Catalog Defaults
346// ═══════════════════════════════════════════════════════════════════════════════
347
348/// Get the catalog default allow value.
349pub fn get_catalog_defaults() -> c_int {
350    CATALOG_DEFAULTS.load(Ordering::Relaxed)
351}
352
353/// Set the catalog default allow value.
354pub fn set_catalog_defaults(val: c_int) {
355    CATALOG_DEFAULTS.store(val, Ordering::Relaxed);
356}
357
358// ═══════════════════════════════════════════════════════════════════════════════
359// Initialization / Cleanup
360// ═══════════════════════════════════════════════════════════════════════════════
361
362/// Initialize the parser library.
363///
364/// Must be called before any other libxml2 functions.
365/// Safe to call multiple times (reference-counted in modern libxml2).
366///
367/// # UPSTREAM-PARITY
368///
369/// In modern libxml2 (2.12+), `xmlInitParser` is reference-counted.
370/// The first call initializes all subsystems; subsequent calls
371/// increment a counter. `xmlCleanupParser` decrements the counter
372/// and only performs cleanup when it reaches zero.
373///
374/// # SAFETY
375///
376/// Not fully thread-safe during the first call; callers should
377/// call `xmlInitParser` before creating threads.
378pub unsafe fn init_parser() {
379    let prev = INIT_REF_COUNT.fetch_add(1, Ordering::AcqRel);
380    if prev == 0 {
381        // First initialization — initialize all subsystems.
382        // 1. Initialize memory subsystem.
383        allocator::xmlInitMemory();
384
385        // 2. Mark the library as initialized.
386        versioning::set_initialized();
387
388        // 3. Initialize encoding handlers.
389        crate::xml::encoding::init_encodings();
390
391        // 4. Initialize thread support.
392        init_threads();
393    }
394}
395
396/// Clean up the parser library.
397///
398/// Should be called when the library is no longer needed.
399/// Only performs actual cleanup when the reference count reaches zero.
400///
401/// # SAFETY
402///
403/// Must not be called while other libxml2 functions are executing
404/// in any thread.
405pub unsafe fn cleanup_parser() {
406    let prev = INIT_REF_COUNT.fetch_sub(1, Ordering::AcqRel);
407    if prev <= 1 {
408        // Last cleanup — clean up all subsystems.
409        // 1. Clean up catalog.
410        crate::xml::catalog::cleanup();
411
412        // 2. Clean up encoding handlers.
413        crate::xml::encoding::cleanup_encodings();
414
415        // 3. Clean up memory.
416        allocator::xmlCleanupMemory();
417
418        // 3. Reset initialization state.
419        // Note: we do NOT reset the initialized flag in case
420        // some code checks it after cleanup. This matches
421        // upstream behavior where xmlCleanupParser is best-effort.
422    }
423}
424
425/// Initialize threading support.
426///
427/// # UPSTREAM-PARITY
428///
429/// In modern libxml2, threading is initialized automatically
430/// by `xmlInitParser`. This function exists for backward
431/// compatibility.
432///
433/// Returns 0 on success.
434pub fn init_threads() -> c_int {
435    if !THREADS_INITIALIZED.swap(true, Ordering::Release) {
436        // First initialization — no-op in Rust since we use
437        // standard thread-safe primitives.
438        // In libxml2 this would set up pthread mutexes.
439    }
440    0
441}
442
443/// Clean up threading support.
444pub fn cleanup_threads() {
445    THREADS_INITIALIZED.store(false, Ordering::Release);
446}
447
448/// Check whether threads have been initialized.
449pub fn threads_initialized() -> bool {
450    THREADS_INITIALIZED.load(Ordering::Acquire)
451}
452
453/// Get the current initialization reference count.
454pub fn init_ref_count() -> c_int {
455    INIT_REF_COUNT.load(Ordering::Relaxed)
456}
457
458// ═══════════════════════════════════════════════════════════════════════════════
459// Tests
460// ═══════════════════════════════════════════════════════════════════════════════
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465
466    #[test]
467    fn test_parser_defaults_initial_values() {
468        assert_eq!(get_validity_checking_default(), 0);
469        assert_eq!(get_do_warnings_default(), 1);
470        // UPSTREAM-PARITY (globals.c 2.15): xmlIndentTreeOutputThrDef = 1.
471        assert_eq!(get_indent_tree_output(), 1);
472        assert_eq!(get_keep_blanks_default(), 1);
473        assert_eq!(get_load_ext_dtd_default(), 0);
474        assert_eq!(get_pedantic_parser_default(), 0);
475        assert_eq!(get_substitute_entities_default(), 0);
476        assert_eq!(get_save_no_empty_tags(), 0);
477        assert_eq!(get_get_warnings_default(), 1);
478    }
479
480    #[test]
481    fn test_parser_defaults_set_and_get() {
482        set_validity_checking_default(1);
483        assert_eq!(get_validity_checking_default(), 1);
484        set_validity_checking_default(0);
485        assert_eq!(get_validity_checking_default(), 0);
486
487        set_keep_blanks_default(0);
488        assert_eq!(get_keep_blanks_default(), 0);
489        set_keep_blanks_default(1);
490        assert_eq!(get_keep_blanks_default(), 1);
491
492        set_substitute_entities_default(1);
493        assert_eq!(get_substitute_entities_default(), 1);
494        set_substitute_entities_default(0);
495        assert_eq!(get_substitute_entities_default(), 0);
496    }
497
498    #[test]
499    fn test_init_cleanup_ref_count() {
500        // Reset for test
501        unsafe {
502            init_parser();
503            assert_eq!(init_ref_count(), 1);
504
505            init_parser();
506            assert_eq!(init_ref_count(), 2);
507
508            cleanup_parser();
509            assert!(init_ref_count() == 1 || init_ref_count() == 0);
510
511            // Final cleanup
512            cleanup_parser();
513        }
514    }
515
516    #[test]
517    fn test_error_callbacks_default_handlers() {
518        // UPSTREAM-PARITY (error.c): xmlGenericError defaults to the built-in
519        // stderr printer (never NULL); xmlStructuredError defaults to NULL.
520        #[cfg(target_arch = "x86_64")]
521        assert!(get_generic_error_func().is_some());
522        assert!(get_structured_error_func().is_none());
523    }
524
525    #[test]
526    fn test_error_callbacks_set_and_get() {
527        unsafe {
528            unsafe extern "C" fn dummy_handler(_ctx: *mut c_void, _msg: *const core::ffi::c_char) {}
529            let dummy_func: xmlGenericErrorFunc = dummy_handler;
530            let dummy_ctx: *mut c_void = &mut 0 as *mut i32 as *mut c_void;
531
532            set_generic_error_func(dummy_ctx, Some(dummy_func));
533            assert!(get_generic_error_func().is_some());
534            assert_eq!(get_generic_error_ctx(), dummy_ctx);
535
536            // UPSTREAM-PARITY (xmlSetGenericErrorFunc): NULL resets to the
537            // built-in default printer, it does not unset the handler.
538            set_generic_error_func(ptr::null_mut(), None);
539            #[cfg(target_arch = "x86_64")]
540            assert!(get_generic_error_func().is_some());
541            assert_eq!(get_generic_error_ctx(), ptr::null_mut());
542        }
543    }
544
545    #[test]
546    fn test_last_error_thread_local() {
547        assert!(get_last_error().is_null());
548
549        let err = _xmlError {
550            domain: XML_FROM_PARSER,
551            code: XML_ERR_OK as c_int,
552            message: ptr::null_mut(),
553            level: XML_ERR_NONE as c_int,
554            file: ptr::null_mut(),
555            line: 0,
556            str1: ptr::null_mut(),
557            str2: ptr::null_mut(),
558            str3: ptr::null_mut(),
559            int1: 0,
560            int2: 0,
561            ctxt: ptr::null_mut(),
562            node: ptr::null_mut(),
563        };
564        set_last_error(err);
565        assert!(!get_last_error().is_null());
566        unsafe {
567            assert_eq!((*get_last_error()).domain, XML_FROM_PARSER);
568        }
569
570        reset_last_error();
571        assert!(get_last_error().is_null());
572    }
573
574    #[test]
575    fn test_catalog_defaults() {
576        // Save original value (may have been set by init_parser in other tests)
577        let orig = get_catalog_defaults();
578        set_catalog_defaults(1);
579        assert_eq!(get_catalog_defaults(), 1);
580        set_catalog_defaults(0);
581        assert_eq!(get_catalog_defaults(), 0);
582        // Restore
583        set_catalog_defaults(orig);
584    }
585}