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}