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