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