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