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}