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//!
29//! # Upstream contract
30//!
31//! Mirrors upstream globals.c (SRC-LIBXML2-2.15.0-GLOBALS-C, oracle tree
32//! `oracle/historical/src/libxml2-2.15.0/globals.c`): the library-wide global
33//! variables (xmlDoValidityCheckingDefaultValue, xmlLoadExtDtdDefaultValue,
34//! xmlKeepBlanksDefaultValue, xmlGenericError, xmlStructuredError,
35//! xmlDefaultSAXHandler, xmlLastError, xmlParserVersion, ...) exposed as
36//! public ABI data symbols (R-000135).
37//!
38//! # Conceptual behavior
39//!
40//! Manages parser defaults, generic/structured error callbacks, catalog
41//! defaults, memory hooks, thread-local error state and init/cleanup
42//! reference counting. The exported data symbols are wired to the parser-
43//! default accessors so there is a single source of truth.
44//!
45//! # Ownership & safety invariants
46//!
47//! SAFETY: exported C-visible globals keep upstream documented racy semantics
48//! for direct readers, while the internal Rust accessors serialize (handler,
49//! ctx) slot pairs under ERROR_HANDLER_LOCK so readers never observe a new
50//! handler with an old context (R-000171). The xmlLastError mirror is deep-
51//! copied under LAST_ERROR_MIRROR_LOCK (R-000170: concurrent sync/reset
52//! double-freed the mirror strings). Error state is thread-local.
53//!
54//! # Historical quirks & epochs
55//!
56//! Thread support predates the thread-local-globals era: globals.c threading
57//! integrated 2001-10-12/13 (commits b847864f, d0463560; LORE-0005). Modern
58//! 2.10+ initialization is lazy; deprecated init/cleanup entry points are
59//! genuine no-ops (R-000138). R-000161 fixed the exported default values
60//! (xmlLineNumbersDefaultValue 1, xmlTreeIndentString two spaces,
61//! xmlParserVersion 21503-GITv2.15.3).
62//!
63//! # Deliberate oddities
64//!
65//! Deliberate oddities: xmlGenericError and xsltGenericError default to the
66//! variadic stderr printers (asm va_list shims; R-000161) rather than NULL;
67//! the deprecated init entry points are exported no-ops reproducing upstream
68//! empty bodies (R-000138).
69//!
70//! # Proving courts
71//!
72//! ABI-DATA, ALLOCATOR, GLOBAL-STATE and THREADING court families;
73//! DATA-GLOBALS-001 differential probe (byte-identical), GLOBALS-THREADING
74//! probe, DSO-LOADER, and `cargo test --lib` (100/100 parallel runs
75//! clean after R-000170/R-000171).
76//!
77//! # Tempting simplifications that would break parity
78//!
79//! The tempting simplification is hiding the C globals behind accessors only
80//! — downstream code that reads/writes xmlDoValidityCheckingDefaultValue
81//! directly would fail to link (R-000135). Do not fix the racy C-visible
82//! symbols: the internal locks must not change the documented upstream
83//! semantics for direct consumers.
84
85use core::cell::RefCell;
86use core::ffi::c_void;
87use core::ptr;
88use core::sync::atomic::{AtomicBool, AtomicI32, Ordering};
89use std::os::raw::c_int;
90
91use crate::abi::allocator;
92use crate::abi::callbacks::{xmlGenericErrorFunc, xmlStructuredErrorFunc};
93use crate::abi::structs::_xmlError;
94use crate::abi::versioning;
95
96/// Serializes the exported error-handler slot pairs
97/// (`xmlGenericError`/`xmlGenericErrorContext` and
98/// `xmlStructuredError`/`xmlStructuredErrorContext`). Upstream's globals are
99/// bare racy `static mut` slots; the candidate keeps the C-visible symbols
100/// but makes internal set/get atomic as a (handler, ctx) pair so readers
101/// never observe a new handler with an old context (or vice versa). C
102/// consumers that touch the symbols directly keep upstream's documented
103/// racy semantics.
104static ERROR_HANDLER_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
105
106/// Serializes the error-handler tests that mutate the shared handler slots
107/// (11.1-X regression court wiring): `test_error_callbacks_*` (globals) and
108/// `test_structured_error_callback` (errors) must not run concurrently, or
109/// `test_error_callbacks_default_handlers` observes another test's
110/// temporarily-installed structured handler.
111#[cfg(test)]
112pub(crate) static ERROR_HANDLER_TEST_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
113
114// ═══════════════════════════════════════════════════════════════════════════════
115// Initialization Reference Counting
116// ═══════════════════════════════════════════════════════════════════════════════
117
118/// Reference count for xmlInitParser / xmlCleanupParser.
119static INIT_REF_COUNT: AtomicI32 = AtomicI32::new(0);
120
121/// Whether threading has been initialized.
122static THREADS_INITIALIZED: AtomicBool = AtomicBool::new(false);
123
124// ═══════════════════════════════════════════════════════════════════════════════
125// Parser Defaults
126// ═══════════════════════════════════════════════════════════════════════════════
127//
128// These are the global parser default variables exposed by libxml2's ABI.
129// Applications can read and write them directly to change default behavior.
130//
131// Upstream declarations (from parser.h / parserInternals.h):
132//
133// ```c
134// extern int xmlDoValidityCheckingDefaultValue;
135// extern int xmlDoWarningsDefaultValue;
136// extern int xmlIndentTreeOutput;
137// extern int xmlKeepBlanksDefaultValue;
138// extern int xmlLoadExtDtdDefaultValue;
139// extern int xmlPedanticParserDefaultValue;
140// ═══════════════════════════════════════════════════════════════════════════════
141// Parser Defaults
142// ═══════════════════════════════════════════════════════════════════════════════
143//
144// The defaults live in the EXPORTED C globals (src/abi/data_globals.rs) so
145// that downstream C code reading/writing them directly (the upstream
146// contract) observes and controls the same state the parser uses. The
147// accessors below are the safe-Rust view of those statics.
148
149// ═══════════════════════════════════════════════════════════════════════════════
150// Error Callback Globals
151// ═══════════════════════════════════════════════════════════════════════════════
152// Also exported as C globals (xmlGenericError/xmlGenericErrorContext,
153// xmlStructuredError/xmlStructuredErrorContext).
154
155// ═══════════════════════════════════════════════════════════════════════════════
156// Catalog Defaults
157// ═══════════════════════════════════════════════════════════════════════════════
158
159/// Catalog default allow value.
160/// 0 = strict, 1 = allow, 2 = allow document, -1 = none.
161/// Internal only (upstream keeps catalog state inside xmlCatalogSetDefaults;
162/// there is no public C global for it).
163static CATALOG_DEFAULTS: AtomicI32 = AtomicI32::new(0);
164
165// ═══════════════════════════════════════════════════════════════════════════════
166// Thread-Local Error State
167// ═══════════════════════════════════════════════════════════════════════════════
168
169thread_local! {
170    /// Last error for this thread.
171    static LAST_ERROR: RefCell<Option<_xmlError>> = const { RefCell::new(None) };
172}
173
174// ═══════════════════════════════════════════════════════════════════════════════
175// Public Accessors — Parser Defaults
176// ═══════════════════════════════════════════════════════════════════════════════
177
178/// Get the default validity checking value.
179pub fn get_validity_checking_default() -> c_int {
180    // SAFETY: reading an exported int global; matches upstream semantics.
181    unsafe { crate::abi::data_globals::xmlDoValidityCheckingDefaultValue }
182}
183
184/// Set the default validity checking value.
185pub fn set_validity_checking_default(val: c_int) {
186    // SAFETY: writing an exported int global; matches upstream semantics.
187    unsafe {
188        crate::abi::data_globals::xmlDoValidityCheckingDefaultValue = val;
189    }
190}
191
192/// Get the default warnings value.
193pub fn get_do_warnings_default() -> c_int {
194    // SAFETY: reading an exported int global (upstream xmlGetWarningsDefaultValue).
195    unsafe { crate::abi::data_globals::xmlGetWarningsDefaultValue }
196}
197
198/// Set the default warnings value.
199pub fn set_do_warnings_default(val: c_int) {
200    // SAFETY: writing an exported int global.
201    unsafe {
202        crate::abi::data_globals::xmlGetWarningsDefaultValue = val;
203    }
204}
205
206/// Get the indent tree output default.
207pub fn get_indent_tree_output() -> c_int {
208    // SAFETY: reading an exported int global (upstream xmlIndentTreeOutput).
209    unsafe { crate::abi::data_globals::xmlIndentTreeOutput }
210}
211
212/// Set the indent tree output default.
213pub fn set_indent_tree_output(val: c_int) {
214    // SAFETY: writing an exported int global.
215    unsafe {
216        crate::abi::data_globals::xmlIndentTreeOutput = val;
217    }
218}
219
220/// Get the keep blanks default value.
221pub fn get_keep_blanks_default() -> c_int {
222    // SAFETY: reading an exported int global (upstream xmlKeepBlanksDefaultValue).
223    unsafe { crate::abi::data_globals::xmlKeepBlanksDefaultValue }
224}
225
226/// Set the keep blanks default value.
227pub fn set_keep_blanks_default(val: c_int) {
228    // SAFETY: writing an exported int global.
229    unsafe {
230        crate::abi::data_globals::xmlKeepBlanksDefaultValue = val;
231    }
232}
233
234/// Get the load external DTD default value.
235pub fn get_load_ext_dtd_default() -> c_int {
236    // SAFETY: reading an exported int global (upstream xmlLoadExtDtdDefaultValue).
237    unsafe { crate::abi::data_globals::xmlLoadExtDtdDefaultValue }
238}
239
240/// Set the load external DTD default value.
241pub fn set_load_ext_dtd_default(val: c_int) {
242    // SAFETY: writing an exported int global.
243    unsafe {
244        crate::abi::data_globals::xmlLoadExtDtdDefaultValue = val;
245    }
246}
247
248/// Get the pedantic parser default.
249pub fn get_pedantic_parser_default() -> c_int {
250    // SAFETY: reading an exported int global (upstream xmlPedanticParserDefaultValue).
251    unsafe { crate::abi::data_globals::xmlPedanticParserDefaultValue }
252}
253
254/// Set the pedantic parser default.
255pub fn set_pedantic_parser_default(val: c_int) {
256    // SAFETY: writing an exported int global.
257    unsafe {
258        crate::abi::data_globals::xmlPedanticParserDefaultValue = val;
259    }
260}
261
262/// Get the substitute entities default.
263pub fn get_substitute_entities_default() -> c_int {
264    // SAFETY: reading an exported int global (upstream xmlSubstituteEntitiesDefaultValue).
265    unsafe { crate::abi::data_globals::xmlSubstituteEntitiesDefaultValue }
266}
267
268/// Set the substitute entities default.
269pub fn set_substitute_entities_default(val: c_int) {
270    // SAFETY: writing an exported int global.
271    unsafe {
272        crate::abi::data_globals::xmlSubstituteEntitiesDefaultValue = val;
273    }
274}
275
276/// Get the save no empty tags default.
277pub fn get_save_no_empty_tags() -> c_int {
278    // SAFETY: reading an exported int global (upstream xmlSaveNoEmptyTags).
279    unsafe { crate::abi::data_globals::xmlSaveNoEmptyTags }
280}
281
282/// Set the save no empty tags default.
283pub fn set_save_no_empty_tags(val: c_int) {
284    // SAFETY: writing an exported int global.
285    unsafe {
286        crate::abi::data_globals::xmlSaveNoEmptyTags = val;
287    }
288}
289
290/// Get the get warnings default.
291pub fn get_get_warnings_default() -> c_int {
292    // SAFETY: reading an exported int global (upstream xmlGetWarningsDefaultValue).
293    unsafe { crate::abi::data_globals::xmlGetWarningsDefaultValue }
294}
295
296/// Set the get warnings default.
297pub fn set_get_warnings_default(val: c_int) {
298    // SAFETY: writing an exported int global.
299    unsafe {
300        crate::abi::data_globals::xmlGetWarningsDefaultValue = val;
301    }
302}
303
304// ═══════════════════════════════════════════════════════════════════════════════
305// Public Accessors — Error Callbacks
306// ═══════════════════════════════════════════════════════════════════════════════
307
308/// Set the generic error handler.
309///
310/// # SAFETY
311///
312/// - `handler` must be a valid function pointer or NULL (to reset to default).
313/// - If non-NULL, the handler may be called at any time with `ctx`.
314pub unsafe fn set_generic_error_func(ctx: *mut c_void, handler: Option<xmlGenericErrorFunc>) {
315    // SAFETY: writing the exported C globals xmlGenericErrorContext /
316    // xmlGenericError; matches upstream xmlSetGenericErrorFunc (NULL resets
317    // to the built-in default stderr printer, error.c). The (ctx, func)
318    // pair is written atomically under ERROR_HANDLER_LOCK.
319    let resolved = match handler {
320        Some(h) => Some(h),
321        None => crate::abi::data_globals::default_generic_error_func(),
322    };
323    let _guard = ERROR_HANDLER_LOCK.lock();
324    unsafe {
325        crate::abi::data_globals::xmlGenericErrorContext = ctx;
326        crate::abi::data_globals::xmlGenericError = resolved;
327    }
328}
329
330/// Get the generic error handler context.
331pub fn get_generic_error_ctx() -> *mut c_void {
332    let _guard = ERROR_HANDLER_LOCK.lock();
333    // SAFETY: reading the exported C global xmlGenericErrorContext.
334    unsafe { crate::abi::data_globals::xmlGenericErrorContext }
335}
336
337/// Get the generic error handler function pointer.
338pub fn get_generic_error_func() -> Option<xmlGenericErrorFunc> {
339    let _guard = ERROR_HANDLER_LOCK.lock();
340    // SAFETY: reading the exported C global xmlGenericError.
341    unsafe { crate::abi::data_globals::xmlGenericError }
342}
343
344/// Read the generic error (func, ctx) pair atomically.
345///
346/// The closure runs after the lock is released, so a handler installed
347/// by the callback cannot deadlock.
348pub fn with_generic_error<R>(f: impl FnOnce(Option<xmlGenericErrorFunc>, *mut c_void) -> R) -> R {
349    let (h, c) = {
350        let _guard = ERROR_HANDLER_LOCK.lock();
351        (
352            unsafe { crate::abi::data_globals::xmlGenericError },
353            unsafe { crate::abi::data_globals::xmlGenericErrorContext },
354        )
355    };
356    f(h, c)
357}
358
359/// Set the structured error handler.
360///
361/// # SAFETY
362///
363/// - `handler` must be a valid function pointer or NULL.
364pub unsafe fn set_structured_error_func(ctx: *mut c_void, handler: Option<xmlStructuredErrorFunc>) {
365    // SAFETY: writing the exported C globals xmlStructuredErrorContext /
366    // xmlStructuredError; matches upstream xmlSetStructuredErrorFunc. The
367    // (ctx, func) pair is written atomically under ERROR_HANDLER_LOCK.
368    let _guard = ERROR_HANDLER_LOCK.lock();
369    unsafe {
370        crate::abi::data_globals::xmlStructuredErrorContext = ctx;
371        crate::abi::data_globals::xmlStructuredError = handler;
372    }
373}
374
375/// Get the structured error handler context.
376pub fn get_structured_error_ctx() -> *mut c_void {
377    let _guard = ERROR_HANDLER_LOCK.lock();
378    // SAFETY: reading the exported C global xmlStructuredErrorContext.
379    unsafe { crate::abi::data_globals::xmlStructuredErrorContext }
380}
381
382/// Get the structured error handler function pointer.
383pub fn get_structured_error_func() -> Option<xmlStructuredErrorFunc> {
384    let _guard = ERROR_HANDLER_LOCK.lock();
385    // SAFETY: reading the exported C global xmlStructuredError.
386    unsafe { crate::abi::data_globals::xmlStructuredError }
387}
388
389/// Read the structured error (func, ctx) pair atomically.
390///
391/// The closure runs after the lock is released, so a handler installed by
392/// the callback (or an error raised from inside the handler) cannot
393/// deadlock on ERROR_HANDLER_LOCK.
394pub fn with_structured_error<R>(
395    f: impl FnOnce(Option<xmlStructuredErrorFunc>, *mut c_void) -> R,
396) -> R {
397    let (h, c) = {
398        let _guard = ERROR_HANDLER_LOCK.lock();
399        (
400            unsafe { crate::abi::data_globals::xmlStructuredError },
401            unsafe { crate::abi::data_globals::xmlStructuredErrorContext },
402        )
403    };
404    f(h, c)
405}
406
407// ═══════════════════════════════════════════════════════════════════════════════
408// Public Accessors — Thread-Local Error State
409// ═══════════════════════════════════════════════════════════════════════════════
410
411/// Get the last error for this thread.
412///
413/// Returns a mutable pointer to the last error, or NULL if no error occurred.
414/// The returned pointer is valid until the next libxml2 call in this thread.
415pub fn get_last_error() -> *mut _xmlError {
416    LAST_ERROR.with(|last| {
417        let mut last = last.borrow_mut();
418        last.as_mut()
419            .map_or(ptr::null_mut(), |e| e as *mut _xmlError)
420    })
421}
422
423/// Get a reference to the last error (for structured error callback).
424pub fn with_last_error<F, R>(f: F) -> R
425where
426    F: FnOnce(Option<&_xmlError>) -> R,
427{
428    LAST_ERROR.with(|last| f(last.borrow().as_ref()))
429}
430
431/// Set the last error for this thread.
432pub fn set_last_error(err: _xmlError) {
433    // UPSTREAM-PARITY: mirror the error into the exported C global
434    // `xmlLastError` (data-ABI, residual R-000135). Upstream keeps a single
435    // global; the candidate keeps a thread-local truth and a deep-copied
436    // mirror so C consumers see the most recent error with upstream
437    // lifetime semantics (mirror strings are owned by the mirror and freed
438    // on reset, matching xmlResetError).
439    //
440    // SAFETY: sync_xml_last_error only reads `err` and writes the global
441    // mirror with freshly owned copies; the thread-local slot takes
442    // ownership of `err` itself.
443    unsafe { crate::abi::data_globals::sync_xml_last_error(&err) };
444    LAST_ERROR.with(|last| {
445        let mut last = last.borrow_mut();
446        // Free the previous slot's owned strings (upstream xmlResetError).
447        if let Some(prev) = last.as_ref() {
448            free_error_strings(prev);
449        }
450        *last = Some(err);
451    });
452}
453
454/// Free the owned string fields of a stored error (upstream xmlResetError:
455/// message/file/str1/str2/str3 are xmlMalloc'd copies).
456fn free_error_strings(err: &_xmlError) {
457    use crate::abi::allocator::xmlFreeImpl;
458    unsafe {
459        if !err.message.is_null() {
460            xmlFreeImpl(err.message as *mut core::ffi::c_void);
461        }
462        if !err.file.is_null() {
463            xmlFreeImpl(err.file as *mut core::ffi::c_void);
464        }
465        if !err.str1.is_null() {
466            xmlFreeImpl(err.str1 as *mut core::ffi::c_void);
467        }
468        if !err.str2.is_null() {
469            xmlFreeImpl(err.str2 as *mut core::ffi::c_void);
470        }
471        if !err.str3.is_null() {
472            xmlFreeImpl(err.str3 as *mut core::ffi::c_void);
473        }
474    }
475}
476
477/// Reset the last error for this thread.
478pub fn reset_last_error() {
479    LAST_ERROR.with(|last| {
480        let mut last = last.borrow_mut();
481        if let Some(prev) = last.as_ref() {
482            free_error_strings(prev);
483        }
484        *last = None;
485    });
486    // SAFETY: frees the mirror's owned strings and zeroes the global.
487    unsafe { crate::abi::data_globals::reset_xml_last_error() };
488}
489
490// ═══════════════════════════════════════════════════════════════════════════════
491// Public Accessors — Catalog Defaults
492// ═══════════════════════════════════════════════════════════════════════════════
493
494/// Get the catalog default allow value.
495pub fn get_catalog_defaults() -> c_int {
496    CATALOG_DEFAULTS.load(Ordering::Relaxed)
497}
498
499/// Set the catalog default allow value.
500pub fn set_catalog_defaults(val: c_int) {
501    CATALOG_DEFAULTS.store(val, Ordering::Relaxed);
502}
503
504// ═══════════════════════════════════════════════════════════════════════════════
505// Initialization / Cleanup
506// ═══════════════════════════════════════════════════════════════════════════════
507
508/// Initialize the parser library.
509///
510/// Must be called before any other libxml2 functions.
511/// Safe to call multiple times (reference-counted in modern libxml2).
512///
513/// # UPSTREAM-PARITY
514///
515/// In modern libxml2 (2.12+), `xmlInitParser` is reference-counted.
516/// The first call initializes all subsystems; subsequent calls
517/// increment a counter. `xmlCleanupParser` decrements the counter
518/// and only performs cleanup when it reaches zero.
519///
520/// # SAFETY
521///
522/// Not fully thread-safe during the first call; callers should
523/// call `xmlInitParser` before creating threads.
524pub unsafe fn init_parser() {
525    let prev = INIT_REF_COUNT.fetch_add(1, Ordering::AcqRel);
526    if prev == 0 {
527        // First initialization — initialize all subsystems.
528        // 1. Initialize memory subsystem.
529        allocator::xmlInitMemory();
530
531        // 2. Mark the library as initialized.
532        versioning::set_initialized();
533
534        // 3. Initialize encoding handlers.
535        crate::xml::encoding::init_encodings();
536
537        // 4. Initialize thread support.
538        init_threads();
539    }
540}
541
542/// Clean up the parser library.
543///
544/// Should be called when the library is no longer needed.
545/// Only performs actual cleanup when the reference count reaches zero.
546///
547/// # SAFETY
548///
549/// Must not be called while other libxml2 functions are executing
550/// in any thread.
551pub unsafe fn cleanup_parser() {
552    let prev = INIT_REF_COUNT.fetch_sub(1, Ordering::AcqRel);
553    if prev <= 1 {
554        // Last cleanup — clean up all subsystems.
555        // 1. Clean up catalog.
556        crate::xml::catalog::cleanup();
557
558        // 2. Clean up encoding handlers.
559        crate::xml::encoding::cleanup_encodings();
560
561        // 3. Clean up memory.
562        allocator::xmlCleanupMemory();
563
564        // 3. Reset initialization state.
565        // Note: we do NOT reset the initialized flag in case
566        // some code checks it after cleanup. This matches
567        // upstream behavior where xmlCleanupParser is best-effort.
568    }
569}
570
571/// Initialize threading support.
572///
573/// # UPSTREAM-PARITY
574///
575/// In modern libxml2, threading is initialized automatically
576/// by `xmlInitParser`. This function exists for backward
577/// compatibility.
578///
579/// Returns 0 on success.
580pub fn init_threads() -> c_int {
581    if !THREADS_INITIALIZED.swap(true, Ordering::Release) {
582        // First initialization — no-op in Rust since we use
583        // standard thread-safe primitives.
584        // In libxml2 this would set up pthread mutexes.
585    }
586    0
587}
588
589/// Clean up threading support.
590pub fn cleanup_threads() {
591    THREADS_INITIALIZED.store(false, Ordering::Release);
592}
593
594/// Check whether threads have been initialized.
595pub fn threads_initialized() -> bool {
596    THREADS_INITIALIZED.load(Ordering::Acquire)
597}
598
599/// Get the current initialization reference count.
600pub fn init_ref_count() -> c_int {
601    INIT_REF_COUNT.load(Ordering::Relaxed)
602}
603
604// ═══════════════════════════════════════════════════════════════════════════════
605// Tests
606// ═══════════════════════════════════════════════════════════════════════════════
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611    use crate::abi::types::xmlErrorLevel::XML_ERR_NONE;
612    use crate::abi::types::*;
613
614    #[test]
615    fn test_parser_defaults_initial_values() {
616        assert_eq!(get_validity_checking_default(), 0);
617        assert_eq!(get_do_warnings_default(), 1);
618        // UPSTREAM-PARITY (globals.c 2.15): xmlIndentTreeOutputThrDef = 1.
619        assert_eq!(get_indent_tree_output(), 1);
620        assert_eq!(get_keep_blanks_default(), 1);
621        assert_eq!(get_load_ext_dtd_default(), 0);
622        assert_eq!(get_pedantic_parser_default(), 0);
623        assert_eq!(get_substitute_entities_default(), 0);
624        assert_eq!(get_save_no_empty_tags(), 0);
625        assert_eq!(get_get_warnings_default(), 1);
626    }
627
628    #[test]
629    fn test_parser_defaults_set_and_get() {
630        set_validity_checking_default(1);
631        assert_eq!(get_validity_checking_default(), 1);
632        set_validity_checking_default(0);
633        assert_eq!(get_validity_checking_default(), 0);
634
635        set_keep_blanks_default(0);
636        assert_eq!(get_keep_blanks_default(), 0);
637        set_keep_blanks_default(1);
638        assert_eq!(get_keep_blanks_default(), 1);
639
640        set_substitute_entities_default(1);
641        assert_eq!(get_substitute_entities_default(), 1);
642        set_substitute_entities_default(0);
643        assert_eq!(get_substitute_entities_default(), 0);
644    }
645
646    #[test]
647    fn test_init_cleanup_ref_count() {
648        // Reset for test
649        unsafe {
650            init_parser();
651            assert_eq!(init_ref_count(), 1);
652
653            init_parser();
654            assert_eq!(init_ref_count(), 2);
655
656            cleanup_parser();
657            assert!(init_ref_count() == 1 || init_ref_count() == 0);
658
659            // Final cleanup
660            cleanup_parser();
661        }
662    }
663
664    #[test]
665    fn test_error_callbacks_default_handlers() {
666        // UPSTREAM-PARITY (error.c): xmlGenericError defaults to the built-in
667        // stderr printer (never NULL); xmlStructuredError defaults to NULL.
668        // Serialized against the other handler-mutating tests (11.1-X): the
669        // slots are shared global state.
670        let _guard = ERROR_HANDLER_TEST_LOCK.lock();
671        #[cfg(target_arch = "x86_64")]
672        assert!(get_generic_error_func().is_some());
673        assert!(get_structured_error_func().is_none());
674    }
675
676    #[test]
677    fn test_error_callbacks_set_and_get() {
678        let _guard = ERROR_HANDLER_TEST_LOCK.lock();
679        unsafe {
680            unsafe extern "C" fn dummy_handler(_ctx: *mut c_void, _msg: *const core::ffi::c_char) {}
681            let dummy_func: xmlGenericErrorFunc = dummy_handler;
682            let dummy_ctx: *mut c_void = &mut 0 as *mut i32 as *mut c_void;
683
684            set_generic_error_func(dummy_ctx, Some(dummy_func));
685            assert!(get_generic_error_func().is_some());
686            assert_eq!(get_generic_error_ctx(), dummy_ctx);
687
688            // UPSTREAM-PARITY (xmlSetGenericErrorFunc): NULL resets to the
689            // built-in default printer, it does not unset the handler.
690            set_generic_error_func(ptr::null_mut(), None);
691            #[cfg(target_arch = "x86_64")]
692            assert!(get_generic_error_func().is_some());
693            assert_eq!(get_generic_error_ctx(), ptr::null_mut());
694        }
695    }
696
697    #[test]
698    fn test_last_error_thread_local() {
699        assert!(get_last_error().is_null());
700
701        let err = _xmlError {
702            domain: XML_FROM_PARSER,
703            code: XML_ERR_OK as c_int,
704            message: ptr::null_mut(),
705            level: XML_ERR_NONE as c_int,
706            file: ptr::null_mut(),
707            line: 0,
708            str1: ptr::null_mut(),
709            str2: ptr::null_mut(),
710            str3: ptr::null_mut(),
711            int1: 0,
712            int2: 0,
713            ctxt: ptr::null_mut(),
714            node: ptr::null_mut(),
715        };
716        set_last_error(err);
717        assert!(!get_last_error().is_null());
718        unsafe {
719            assert_eq!((*get_last_error()).domain, XML_FROM_PARSER);
720        }
721
722        reset_last_error();
723        assert!(get_last_error().is_null());
724    }
725
726    #[test]
727    fn test_catalog_defaults() {
728        // Save original value (may have been set by init_parser in other tests)
729        let orig = get_catalog_defaults();
730        set_catalog_defaults(1);
731        assert_eq!(get_catalog_defaults(), 1);
732        set_catalog_defaults(0);
733        assert_eq!(get_catalog_defaults(), 0);
734        // Restore
735        set_catalog_defaults(orig);
736    }
737}