Skip to main content

libxml_rs/abi/
data_globals.rs

1//! Exported C data globals — the classic libxml2/libxslt public variables
2//! (11.1-G data-ABI closure, residual R-000135).
3//!
4//! Downstream C code reads and writes these symbols directly
5//! (e.g. `xmlDoValidityCheckingDefaultValue = 1;`), so they must exist with
6//! upstream names, types and defaults. The parser-default accessors in
7//! `src/xml/globals/mod.rs` read and write the SAME statics (single source
8//! of truth): a C write is immediately observable by the candidate parser.
9//!
10//! Defaults match upstream `globals.c` (libxml2 2.15.3) and `xslt.c`
11//! (libxslt 1.1.45).
12//!
13//! # SAFETY
14//!
15//! `static mut` globals are unsafe to touch from Rust; every access goes
16//! through the accessor functions in `crate::xml::globals` (or directly
17//! here with an explicit safety note). C accesses are inherently racy in
18//! upstream too — upstream documents these globals as deprecated and
19//! not thread-safe.
20//!
21//! # Upstream contract
22//!
23//! The parity target is upstream `globals.c` (libxml2 2.15.3), `SAX2.c` and
24//! `chvalid.c`, plus the libxslt `xslt.c` globals. Every symbol here is
25//! exported `#[no_mangle]` as DATA with the upstream name, type and default so
26//! downstream C code that reads or writes the classic global variables links
27//! and observes the same values (R-000135 data-globals closure).
28//!
29//! # Conceptual behavior
30//!
31//! This module implements the exported C data-global surface: parser defaults
32//! (`xmlDoValidityCheckingDefaultValue`, `xmlKeepBlanksDefaultValue`, ...),
33//! the error handler/context slots (`xmlGenericError`, `xmlStructuredError`,
34//! `xmlLastError`), the default SAX handler structs and locator, the version
35//! data symbols and the allocator function pointers. The parser-default
36//! accessors in `src/xml/globals/mod.rs` read and write the SAME statics
37//! (single source of truth), so a C write is immediately observable by the
38//! candidate parser.
39//!
40//! # Ownership & safety invariants
41//!
42//! `static mut` globals are inherently racy — upstream documents them as
43//! deprecated and not thread-safe, and the candidate preserves that C-visible
44//! contract. Internal writers are serialized where corruption is possible: the
45//! `xmlLastError` mirror deep-copy/free is guarded by `LAST_ERROR_MIRROR_LOCK`
46//! (R-000170) and the handler-slot pairs are read and written atomically
47//! (R-000171), while C readers keep upstreams documented racy semantics.
48//!
49//! # Historical quirks & epochs
50//!
51//! R-000135 (11.1-I): 11 exported data symbols were missing entirely and are
52//! now byte-identical with the oracle DSO. R-000161 (11.1-K): `xmlGenericError`
53//! / `xsltGenericError` default to the variadic stderr printers
54//! (`xmlGenericErrorDefaultFunc` / `xsltGenericErrorDefaultFunc`) via the asm
55//! va_list shims, and five default values were wrong before that fix
56//! (e.g. `xmlLineNumbersDefaultValue` 0 to 1, `xmlTreeIndentString` NULL to a
57//! two-space string). R-000167 (11.1-S): `xsltLibxsltVersion` and friends are
58//! exported as data (R) not functions (T). R-000162 (11.1-L): the allocator
59//! entry points are DATA function pointers. R-000170/R-000171 (11.1-X): the
60//! parallel-suite corruption races.
61//!
62//! # Deliberate oddities
63//!
64//! The candidates internal writers are locked while the exported symbols stay
65//! racy — a deliberate safe divergence that keeps the C-visible contract
66//! identical to upstream while making the Rust internals safe.
67//! `xsltDocDefaultLoader` defaults to NULL instead of upstreams variadic
68//! default function (stable Rust cannot define variadic extern fns) —
69//! documented safe divergence from R-000135.
70//!
71//! # Proving courts
72//!
73//! The DATA-GLOBALS-001 differential court (tools/abi/data_globals_probe.py
74//! and courts/suites/data-abi/data-globals-probe.c) compiles one probe
75//! against the oracle DSO and the candidate and requires byte-identical
76//! output; the ABI-DATA, ALLOCATOR, GLOBAL-STATE and THREADING families
77//! plus DSO-LOADER (symbol-type parity) and HEADER-COMPILE also cover this
78//! module.
79//!
80//! # Tempting simplifications that would break parity
81//!
82//! A tempting simplification is to keep parser defaults only in Rust atomics
83//! and skip the exported C globals — that was the pre-R-000135 state and it
84//! breaks every downstream consumer that reads or writes
85//! `xmlDoValidityCheckingDefaultValue` directly (the link would fail).
86//! Another tempting shortcut is dropping the mirror locks: R-000170 observed
87//! glibc `double free or corruption` aborts in about 12% of parallel runs, so
88//! the locks must not be removed even though upstream is racy.
89
90use core::ffi::c_void;
91use core::ptr;
92use std::os::raw::{c_char, c_int, c_uint};
93
94use crate::abi::callbacks::{xmlGenericErrorFunc, xmlStructuredErrorFunc};
95use crate::abi::exports_xslt_compile::xsltDocLoaderFunc;
96use crate::abi::types::xmlChar;
97
98/// Serializes writes to the exported `xmlLastError` mirror.
99///
100/// Upstream's `xmlLastError` is a bare racy global (deprecated, documented
101/// not thread-safe), and the candidate preserves the C-visible semantics:
102/// downstream C consumers read the symbol directly without a lock. The
103/// candidate's internal deep-copy/free writers are serialized here so that
104/// concurrent error raises on different threads can never free the same
105/// mirror string twice or write while another thread is freeing — observed
106/// as heap corruption (`double free or corruption (!prev)`) in the parallel
107/// lib test suite (xml::errors tests racing with any other raising thread).
108static LAST_ERROR_MIRROR_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
109
110// ═══════════════════════════════════════════════════════════════════════════════
111// Parser defaults (upstream globals.c)
112// ═══════════════════════════════════════════════════════════════════════════════
113
114// ═══════════════════════════════════════════════════════════════════════════════
115// Parser-default / error-handler / node-hook / IO-hook globals
116// ═══════════════════════════════════════════════════════════════════════════════
117//
118// Phase 13 (HOSTILE-THREADS): the 18 globals below were moved to
119// THREAD-LOCAL storage (`crate::xml::globals::tls`), matching upstream 2.15
120// LIBXML_THREAD_ENABLED (`xmlGetThreadLocalStorage`, globals.c). The oracle
121// DSO exports only the `__xmlXxx()` accessor FUNCTIONS for them (nm -D: no
122// data symbols), so the candidate removed the plain data exports and the
123// accessors now return pointers into the per-thread cells; the candidate
124// headers use the upstream macro/accessor contract. See tls.rs.
125//
126// Still exported as plain global data (executed oracle keeps these global):
127// xmlParserVersion, xmlParserMaxDepth, xmlDefaultBufferSize,
128// xmlBufferAllocScheme, xmlParserDebugEntities, xmlLastError (R-000135
129// mirror), the allocator hooks, the SAX handler structs and the character
130// tables.
131
132/// `int xmlParserDebugEntities` (default 0) — candidate-only data export
133/// (CUSTODIAN_EXTENSION); NOT TLS-era in the executed oracle.
134#[no_mangle]
135pub static mut xmlParserDebugEntities: c_int = 0;
136
137/// Upstream static `xmlRegisterCallbacks` (tree.c): the gate that arms the
138/// node register/deregister hooks once a callback has been registered.
139///
140/// R-000177 (three-DSO facade boundary): the gate lives in EVERY DSO copy
141/// (whole-archive facades re-link the staticlib), but only the CORE's copy
142/// is armed by consumer registrations — `xmlRegisterNodeDefault` resolves
143/// to the core DSO's export. The hook functions below therefore consult the
144/// process-visible cell directly (the bridged getter) instead of trusting
145/// the local gate; the static is retained as the upstream arming mirror.
146pub static XML_REGISTER_CALLBACKS: core::sync::atomic::AtomicBool =
147    core::sync::atomic::AtomicBool::new(false);
148
149/// Upstream `xmlRegisterNodeCallback(node)` — invoke the registered node
150/// hook (tree.c `xmlRegisterNode`).
151///
152/// The hook is read through `get_register_node_default`, whose cross-DSO
153/// reader prefers the process-visible (core DSO's) `__xmlRegisterNodeDefaultValue`
154/// cell — so a registration made through the core's exported
155/// `xmlRegisterNodeDefault` is observed by whole-archive facade copies
156/// (upstream: one core instance, one gate). Single-DSO links resolve the
157/// accessor to their own export (the very same cell), so per-thread
158/// semantics and the unregistered fast path are unchanged.
159///
160/// # SAFETY
161///
162/// - `node` must be a valid, fully-initialised node or NULL.
163pub fn register_node_hook(node: *mut crate::abi::structs::_xmlNode) {
164    let hook = crate::xml::globals::get_register_node_default();
165    if let Some(h) = hook {
166        if !node.is_null() {
167            // SAFETY: the hook is a valid C callback registered by the user.
168            unsafe { h(node) };
169        }
170    }
171}
172
173/// Upstream `xmlDeregisterNodeCallback(node)` — invoke the registered node
174/// deregister hook (tree.c `xmlDeregisterNode`); see [`register_node_hook`]
175/// for the cross-DSO read rationale.
176///
177/// # SAFETY
178///
179/// - `node` must be a valid node about to be freed, or NULL.
180pub fn deregister_node_hook(node: *mut crate::abi::structs::_xmlNode) {
181    let hook = crate::xml::globals::get_deregister_node_default();
182    if let Some(h) = hook {
183        if !node.is_null() {
184            // SAFETY: the hook is a valid C callback registered by the user.
185            unsafe { h(node) };
186        }
187    }
188}
189
190/// `const char *xmlParserVersion` — matched to the system oracle build
191/// (libxml2 2.15.3 GIT build: LIBXML_VERSION_STRING "21503" plus the
192/// upstream version extra "-GITv2.15.3").
193///
194/// SAFETY: the pointed-to string is a static, immutable, null-terminated
195/// literal; `static mut` is used because C raw pointers are not `Sync`.
196/// Reads/writes of the pointer itself are racy only if C code mutates it
197/// (upstream treats it as a constant).
198#[no_mangle]
199pub static mut xmlParserVersion: *const c_char = {
200    static V: [u8; 17] = *b"21503-GITv2.15.3\0";
201    V.as_ptr() as *const c_char
202};
203
204/// `int xmlParserMaxDepth` (default 256)
205#[no_mangle]
206pub static mut xmlParserMaxDepth: c_int = 256;
207
208// ═══════════════════════════════════════════════════════════════════════════════
209// Buffer globals (upstream tree.h / xmlIO.c)
210// ═══════════════════════════════════════════════════════════════════════════════
211
212/// `int xmlDefaultBufferSize` (default 4096)
213#[no_mangle]
214pub static mut xmlDefaultBufferSize: c_int = 4096;
215
216/// `xmlBufferAllocationScheme xmlBufferAllocScheme` (default XML_BUFFER_ALLOC_EXACT = 1;
217/// upstream globals.c `xmlBufferAllocSchemeThrDef = XML_BUFFER_ALLOC_EXACT`)
218#[no_mangle]
219pub static mut xmlBufferAllocScheme: c_int = 1;
220
221// ═══════════════════════════════════════════════════════════════════════════════
222// Error callback globals (upstream xmlerror.h) — THREAD-LOCAL since Phase 13
223// ═══════════════════════════════════════════════════════════════════════════════
224//
225// `xmlGenericError` / `xmlGenericErrorContext` / `xmlStructuredError` /
226// `xmlStructuredErrorContext` live in per-thread cells
227// (`crate::xml::globals::tls`), matching upstream 2.15
228// LIBXML_THREAD_ENABLED: `xmlSetGenericErrorFunc`/`xmlSetStructuredErrorFunc`
229// affect only the calling thread, and the oracle DSO exports only the
230// `__xmlGenericError` family of accessor FUNCTIONS (no data symbols).
231//
232// The default `xmlGenericError` value is `xmlGenericErrorDefaultFunc` (the
233// R-000161 x86-64 variadic stderr shim), exactly like upstream error.c; the
234// shim below reproduces it, so a freshly-initialized library routes errors
235// to stderr exactly like upstream.
236
237// ═══════════════════════════════════════════════════════════════════════════════
238// Variadic default error handlers (upstream error.c / xsltutils.c)
239// ═══════════════════════════════════════════════════════════════════════════════
240//
241// Upstream `xmlGenericErrorDefaultFunc(void *ctx, const char *msg, ...)`
242// prints the formatted message to `xmlGenericErrorContext` (stderr when
243// NULL). Stable Rust cannot define a variadic extern fn body, so the ABI
244// entry is an x86_64 SysV inline-asm shim that materialises the caller's
245// register/stack arguments into a `va_list` and forwards to a non-variadic
246// receiver — the same pattern as `xsltTransformError`
247// (exports_xslt_util.rs) and the writer's `vfmt_shim!`. Neither default
248// function is a dynamic export upstream (both are internal); the exported
249// data globals merely point at them.
250
251/// System V AMD64 `__va_list_tag` (24 bytes) — same layout as the writer's
252/// shims and `exports_xslt_util.rs`.
253#[cfg(target_arch = "x86_64")]
254#[repr(C)]
255#[derive(Clone, Copy, Debug)]
256pub struct VaListTag {
257    gp_offset: c_uint,
258    fp_offset: c_uint,
259    overflow_arg_area: *mut c_void,
260    reg_save_area: *mut c_void,
261}
262
263#[cfg(target_arch = "x86_64")]
264unsafe extern "C" {
265    fn vfprintf(stream: *mut c_void, format: *const c_char, ap: *mut VaListTag) -> c_int;
266}
267
268/// The `stderr` FILE* — glibc exports the `stderr` data object, an
269/// 8-byte pointer variable whose value is `&_IO_2_1_stderr_`. Upstream
270/// `xmlGenericErrorDefaultFunc` defaults the error context to `stderr`;
271/// using the real stdio object (unbuffered, fd-2 relative) keeps writes
272/// byte-exact and honors fd-2 redirection, unlike a private `fdopen(2)`
273/// FILE* which is fully buffered and lands at exit on whatever fd 2 then
274/// points to.
275#[cfg(target_arch = "x86_64")]
276unsafe fn stderr_file() -> *mut c_void {
277    extern "C" {
278        static stderr: *mut c_void;
279    }
280    unsafe { stderr }
281}
282
283/// Variadic receiver for the `xmlGenericErrorDefaultFunc` shim (upstream
284/// error.c semantics: default the context to stderr, then `vfprintf`).
285///
286/// # SAFETY
287///
288/// - `_ctx`, `ap` must be valid pointers (or NULL
289///   where the upstream C contract allows), obtained from the
290///   matching constructor/owner and not yet freed; the callee may
291///   take or keep ownership exactly as the C API specifies.
292///
293/// - `msg` must point to valid NUL-terminated
294///   strings (or NULL where the C contract allows) for the lifetime
295///   of the call.
296///
297/// The caller must not race this call with concurrent mutation of the
298/// same objects from other threads (per-object state is not internally
299/// synchronized). Violating any of the above is undefined behavior.
300///
301/// Exercised by the C-API differential courts
302/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
303/// courts; those pass byte-for-byte against the upstream oracle.
304#[cfg(target_arch = "x86_64")]
305#[no_mangle]
306pub unsafe extern "C" fn xmlGenericErrorDefaultFuncV(
307    _ctx: *mut c_void,
308    msg: *const c_char,
309    ap: *mut VaListTag,
310) -> c_int {
311    unsafe {
312        if crate::xml::globals::get_generic_error_ctx().is_null() {
313            crate::xml::globals::set_generic_error_ctx(stderr_file());
314        }
315        let stream = crate::xml::globals::get_generic_error_ctx();
316        if msg.is_null() || stream.is_null() {
317            return 0;
318        }
319        vfprintf(stream, msg, ap)
320    }
321}
322
323/// `xmlGenericErrorDefaultFunc(void *ctx, const char *msg, ...)` — the
324/// upstream default generic error handler. Not exported dynamically (matches
325/// upstream, where the symbol is internal).
326///
327/// 2 fixed args (ctx=rdi, msg=rsi) → `gp_offset` 16; the va_list pointer is
328/// passed as the 3rd arg (rdx) of the receiver.
329///
330/// # SAFETY
331///
332/// The function touches crate-global state only; it is safe
333/// as long as the caller respects the library's global
334/// initialization/cleanup ordering (xmlInitParser before use,
335/// xmlCleanupParser only after all users are done).
336///
337/// Violating the global lifecycle ordering, or calling this after
338/// teardown or from a signal handler, is undefined behavior.
339#[cfg(target_arch = "x86_64")]
340pub unsafe extern "C" fn xmlGenericErrorDefaultFunc() -> c_int {
341    unsafe {
342        core::arch::asm!(
343            "sub rsp, 240",
344            "mov [rsp+0], rdi",
345            "mov [rsp+8], rsi",
346            "mov [rsp+16], rdx",
347            "mov [rsp+24], rcx",
348            "mov [rsp+32], r8",
349            "mov [rsp+40], r9",
350            "movaps [rsp+48], xmm0",
351            "movaps [rsp+64], xmm1",
352            "movaps [rsp+80], xmm2",
353            "movaps [rsp+96], xmm3",
354            "movaps [rsp+112], xmm4",
355            "movaps [rsp+128], xmm5",
356            "movaps [rsp+144], xmm6",
357            "movaps [rsp+160], xmm7",
358            "mov dword ptr [rsp+176], 16",
359            "mov dword ptr [rsp+180], 48",
360            "lea rax, [rsp+256]",
361            "mov [rsp+184], rax",
362            "lea rax, [rsp]",
363            "mov [rsp+192], rax",
364            "lea rdx, [rsp+176]",
365            "call xmlGenericErrorDefaultFuncV",
366            "add rsp, 240",
367            "add rsp, 8",
368            "ret",
369            options(noreturn),
370        );
371    }
372}
373
374/// Default value of the exported `xmlGenericError` data global.
375#[cfg(target_arch = "x86_64")]
376#[doc(hidden)]
377pub(crate) const XML_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
378    // SAFETY: the shim and the function-pointer type have identical ABI
379    // (a code pointer); the declared arity is a Rust-side fiction required
380    // to store a variadic entry in the non-variadic pointer type.
381    core::mem::transmute::<
382        unsafe extern "C" fn() -> c_int,
383        unsafe extern "C" fn(*mut c_void, *const c_char),
384    >(xmlGenericErrorDefaultFunc)
385};
386
387/// Variadic receiver for the `xsltGenericErrorDefaultFunc` shim (upstream
388/// xsltutils.c semantics: default the context to stderr, then `vfprintf`).
389///
390/// # SAFETY
391///
392/// - `_ctx`, `ap` must be valid pointers (or NULL
393///   where the upstream C contract allows), obtained from the
394///   matching constructor/owner and not yet freed; the callee may
395///   take or keep ownership exactly as the C API specifies.
396///
397/// - `msg` must point to valid NUL-terminated
398///   strings (or NULL where the C contract allows) for the lifetime
399///   of the call.
400///
401/// The caller must not race this call with concurrent mutation of the
402/// same objects from other threads (per-object state is not internally
403/// synchronized). Violating any of the above is undefined behavior.
404///
405/// Exercised by the C-API differential courts
406/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
407/// courts; those pass byte-for-byte against the upstream oracle.
408#[cfg(target_arch = "x86_64")]
409#[no_mangle]
410pub unsafe extern "C" fn xsltGenericErrorDefaultFuncV(
411    _ctx: *mut c_void,
412    msg: *const c_char,
413    ap: *mut VaListTag,
414) -> c_int {
415    unsafe {
416        if crate::abi::data_globals::xsltGenericErrorContext.is_null() {
417            crate::abi::data_globals::xsltGenericErrorContext = stderr_file();
418        }
419        let stream = crate::abi::data_globals::xsltGenericErrorContext;
420        if msg.is_null() || stream.is_null() {
421            return 0;
422        }
423        vfprintf(stream, msg, ap)
424    }
425}
426
427/// `xsltGenericErrorDefaultFunc(void *ctx, const char *msg, ...)` — the
428/// upstream default XSLT error handler (xsltutils.c).
429///
430/// # SAFETY
431///
432/// The function touches crate-global state only; it is safe
433/// as long as the caller respects the library's global
434/// initialization/cleanup ordering (xmlInitParser before use,
435/// xmlCleanupParser only after all users are done).
436///
437/// Violating the global lifecycle ordering, or calling this after
438/// teardown or from a signal handler, is undefined behavior.
439#[cfg(target_arch = "x86_64")]
440pub unsafe extern "C" fn xsltGenericErrorDefaultFunc() -> c_int {
441    unsafe {
442        core::arch::asm!(
443            "sub rsp, 240",
444            "mov [rsp+0], rdi",
445            "mov [rsp+8], rsi",
446            "mov [rsp+16], rdx",
447            "mov [rsp+24], rcx",
448            "mov [rsp+32], r8",
449            "mov [rsp+40], r9",
450            "movaps [rsp+48], xmm0",
451            "movaps [rsp+64], xmm1",
452            "movaps [rsp+80], xmm2",
453            "movaps [rsp+96], xmm3",
454            "movaps [rsp+112], xmm4",
455            "movaps [rsp+128], xmm5",
456            "movaps [rsp+144], xmm6",
457            "movaps [rsp+160], xmm7",
458            "mov dword ptr [rsp+176], 16",
459            "mov dword ptr [rsp+180], 48",
460            "lea rax, [rsp+256]",
461            "mov [rsp+184], rax",
462            "lea rax, [rsp]",
463            "mov [rsp+192], rax",
464            "lea rdx, [rsp+176]",
465            "call xsltGenericErrorDefaultFuncV",
466            "add rsp, 240",
467            "add rsp, 8",
468            "ret",
469            options(noreturn),
470        );
471    }
472}
473
474/// Default value of the exported `xsltGenericError` data global.
475#[cfg(target_arch = "x86_64")]
476const XSLT_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
477    // SAFETY: as above — ABI-identical code pointer.
478    core::mem::transmute::<
479        unsafe extern "C" fn() -> c_int,
480        unsafe extern "C" fn(*mut c_void, *const c_char),
481    >(xsltGenericErrorDefaultFunc)
482};
483
484/// The built-in default generic error handler (upstream
485/// `xmlGenericErrorDefaultFunc`), for use when a caller resets the handler
486/// with NULL. Only available on x86_64 (the variadic shim is SysV-specific);
487/// on other targets there is no default (resets leave the handler unset).
488pub fn default_generic_error_func() -> Option<xmlGenericErrorFunc> {
489    #[cfg(target_arch = "x86_64")]
490    {
491        Some(XML_GENERIC_ERROR_DEFAULT)
492    }
493    #[cfg(not(target_arch = "x86_64"))]
494    {
495        None
496    }
497}
498
499/// The built-in default XSLT generic error handler (upstream
500/// `xsltGenericErrorDefaultFunc`), mirroring the initial value of the
501/// exported `xsltGenericError` static.
502pub fn xslt_default_generic_error_func() -> Option<xmlGenericErrorFunc> {
503    #[cfg(target_arch = "x86_64")]
504    {
505        Some(XSLT_GENERIC_ERROR_DEFAULT)
506    }
507    #[cfg(not(target_arch = "x86_64"))]
508    {
509        None
510    }
511}
512
513// ═══════════════════════════════════════════════════════════════════════════════
514// Static strings (upstream xmlstring.h / tree.c)
515// ═══════════════════════════════════════════════════════════════════════════════
516
517/// `const xmlChar xmlStringText[]` — "text"
518#[no_mangle]
519pub static xmlStringText: [xmlChar; 5] = [b't', b'e', b'x', b't', 0];
520
521/// `const xmlChar xmlStringTextNoenc[]` — "textnoenc"
522#[no_mangle]
523pub static xmlStringTextNoenc: [xmlChar; 9] = *b"textnoenc";
524
525/// `const xmlChar xmlStringComment[]` — "comment"
526#[no_mangle]
527pub static xmlStringComment: [xmlChar; 8] = [b'c', b'o', b'm', b'm', b'e', b'n', b't', 0];
528
529// ═══════════════════════════════════════════════════════════════════════════════
530// XPath numeric constants (upstream xpath.c)
531// ═══════════════════════════════════════════════════════════════════════════════
532
533/// `double xmlXPathNAN` — NaN
534#[no_mangle]
535pub static xmlXPathNAN: f64 = f64::NAN;
536
537/// `double xmlXPathPINF` — +infinity
538#[no_mangle]
539pub static xmlXPathPINF: f64 = f64::INFINITY;
540
541/// `double xmlXPathNINF` — -infinity
542#[no_mangle]
543pub static xmlXPathNINF: f64 = f64::NEG_INFINITY;
544
545// ═══════════════════════════════════════════════════════════════════════════════
546// libxslt globals (upstream xslt.c / xsltutils.c / documents.c / xslt.h)
547// ═══════════════════════════════════════════════════════════════════════════════
548// xsltMaxDepth / xsltMaxVars are exported from src/xslt/transform/mod.rs
549// (they are read by the transform engine).
550
551/// `const int xsltLibxmlVersion` = 21501 — the libxml2 version the system
552/// libxslt 1.1.45 was compiled against (upstream xslt.c `LIBXML_VERSION`;
553/// the system libxslt was built against libxml2 2.15.1). Byte-parity with
554/// the oracle DSO, read directly by `xsltproc -V`.
555#[no_mangle]
556pub static xsltLibxmlVersion: c_int = 21501;
557
558/// `const int xsltLibxsltVersion` = 10145 — the libxslt version
559/// (upstream xslt.c `XSLTPUBVAR const int xsltLibxsltVersion = LIBXSLT_VERSION`;
560/// oracle DSO symbol type R). Was previously exported as a function (T)
561/// — R-000167.
562#[no_mangle]
563pub static xsltLibxsltVersion: c_int = 10145;
564
565/// `const char *xsltEngineVersion` = "10145-GITv1.1.45" — the libxslt engine
566/// version string (upstream xslt.c `XSLTPUBVAR const char *xsltEngineVersion`;
567/// oracle DSO symbol type D). Was previously exported as a function (T)
568/// — R-000167. `static mut` follows the xmlParserVersion pattern (raw
569/// pointers are not Sync).
570#[no_mangle]
571pub static mut xsltEngineVersion: *const c_char = {
572    static S: [u8; 17] = *b"10145-GITv1.1.45\0";
573    S.as_ptr() as *const c_char
574};
575
576/// `const char *exsltLibraryVersion` = "825-GITv1.1.45" — the libexslt
577/// library version string (upstream exslt.c `EXSLTPUBVAR const char *`;
578/// oracle DSO symbol type D). Read by `xsltproc -V`.
579#[no_mangle]
580pub static mut exsltLibraryVersion: *const c_char = {
581    static S: [u8; 15] = *b"825-GITv1.1.45\0";
582    S.as_ptr() as *const c_char
583};
584
585/// `const int exsltLibexsltVersion` = 825 — the libexslt version
586/// (upstream exslt.h `EXSLTPUBVAR const int`; oracle DSO symbol type R).
587#[no_mangle]
588pub static exsltLibexsltVersion: c_int = 825;
589
590/// `const int exsltLibxmlVersion` = 21501 — the libxml2 version the system
591/// libexslt 0.8.25 was compiled against (oracle DSO symbol type R).
592#[no_mangle]
593pub static exsltLibxmlVersion: c_int = 21501;
594
595/// `const int exsltLibxsltVersion` = 10145 — the libxslt version the system
596/// libexslt 0.8.25 was compiled against (oracle DSO symbol type R).
597#[no_mangle]
598pub static exsltLibxsltVersion: c_int = 10145;
599
600/// `int xslDebugStatus` — the libxslt debugger status (upstream xsltutils.c
601/// `XSLTPUBVAR int xslDebugStatus;`, default XSLT_DEBUG_NONE = 0; oracle DSO
602/// symbol type B). Written by `xsltSetDebuggerStatus`; read by the transform
603/// engine's profiling gates (R-000165 DATA_MISSING closure).
604#[no_mangle]
605pub static mut xslDebugStatus: c_int = 0;
606
607/// `xmlGenericErrorFunc xsltGenericError` — the libxslt error callback.
608/// Upstream defaults to `xsltGenericErrorDefaultFunc` (xsltutils.c, a variadic
609/// stderr printer); the candidate's shim below reproduces it (R-000135
610/// divergence now closed).
611#[cfg(target_arch = "x86_64")]
612#[no_mangle]
613pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = Some(XSLT_GENERIC_ERROR_DEFAULT);
614
615#[cfg(not(target_arch = "x86_64"))]
616#[no_mangle]
617pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = None;
618
619/// `void *xsltGenericErrorContext` (default NULL)
620#[no_mangle]
621pub static mut xsltGenericErrorContext: *mut c_void = core::ptr::null_mut();
622
623/// `void *xsltGenericDebugContext` (default NULL)
624#[no_mangle]
625pub static mut xsltGenericDebugContext: *mut c_void = core::ptr::null_mut();
626
627/// `xmlGenericErrorFunc xsltGenericDebug` — the libxslt debug callback.
628/// Upstream defaults to `xsltGenericDebugDefaultFunc` (xsltutils.c:632), a
629/// handler that writes the message to `xsltGenericDebugContext` (a FILE*)
630/// when that context is non-NULL. The candidate reproduces the default via
631/// `xsltGenericDebugDefaultFunc` below. Was previously exported as a
632/// FUNCTION (T) — an R-000167-class ABI type divergence closed in 11.1-Z.1
633/// (oracle DSO symbol type D, `nm -D /usr/lib/libxslt.so.1`).
634#[no_mangle]
635pub static mut xsltGenericDebug: Option<xmlGenericErrorFunc> = Some(XSLT_GENERIC_DEBUG_DEFAULT);
636
637/// Default debug handler reproducing upstream `xsltGenericDebugDefaultFunc`
638/// (xsltutils.c:621): no-op unless `xsltGenericDebugContext` is non-NULL,
639/// then the (pre-formatted) message is written to that context FILE*.
640/// Upstream's handler is variadic; the candidate's callers format the
641/// message before dispatch (same documented divergence as xsltGenericError).
642///
643/// # Safety
644///
645/// - `msg` must be NULL or a valid NUL-terminated string valid for the
646///   call; when `xsltGenericDebugContext` is non-NULL it must be a valid
647///   `FILE*` writable via `fwrite` for the message length.
648unsafe extern "C" fn xsltGenericDebugDefaultFunc(_ctx: *mut c_void, msg: *const c_char) {
649    if msg.is_null() {
650        return;
651    }
652    let dctx = unsafe { xsltGenericDebugContext };
653    if dctx.is_null() {
654        return;
655    }
656    let len = unsafe { libc::strlen(msg) };
657    unsafe {
658        libc::fwrite(msg as *const libc::c_void, 1, len, dctx as *mut libc::FILE);
659    }
660}
661
662/// Default value of the exported `xsltGenericDebug` data global.
663const XSLT_GENERIC_DEBUG_DEFAULT: xmlGenericErrorFunc = xsltGenericDebugDefaultFunc;
664
665/// `const xmlChar xsltExtMarker[]` — empty string used to mark extension
666/// nodes (upstream transform.c).
667#[no_mangle]
668pub static xsltExtMarker: [xmlChar; 1] = [0];
669
670/// Upstream `xsltDocDefaultLoaderFunc` (documents.c): parse the URI as a
671/// document with the given parser options — a DIRECT parse that never
672/// re-enters the registered loader. Consumers capture the exported data
673/// global at load time (lxml's `_xslt_doc_loader` falls back to
674/// `XSLT_DOC_DEFAULT_LOADER` when its resolver cannot handle a URI) and
675/// must get a real function, not NULL.
676///
677/// # SAFETY
678///
679/// - `uri` must be NULL or a valid NUL-terminated string.
680unsafe extern "C" fn xslt_doc_default_loader_func(
681    uri: *const xmlChar,
682    _dict: *mut c_void,
683    options: c_int,
684    _ctxt: *mut c_void,
685    _kind: c_int,
686) -> *mut crate::abi::structs::_xmlDoc {
687    if uri.is_null() {
688        return ptr::null_mut();
689    }
690    crate::abi::exports_xml2::xmlReadFile(uri as *const c_char, ptr::null(), options)
691}
692
693/// `xsltDocLoaderFunc xsltDocDefaultLoader` — the document loader callback.
694/// Upstream initializes it to `xsltDocDefaultLoaderFunc`; the candidate
695/// mirrors that with the direct-parsing default above (R-000135: the
696/// previous NULL default made lxml's import-time capture of this data
697/// global jump to address zero when its resolver fell back to the default
698/// loader).
699#[no_mangle]
700pub static mut xsltDocDefaultLoader: Option<xsltDocLoaderFunc> = Some(xslt_doc_default_loader_func);
701
702// ═══════════════════════════════════════════════════════════════════════════════
703// I/O filename callback globals (upstream xmlIO.h) — THREAD-LOCAL since
704// Phase 13 (see tls.rs); the `xml*Default` accessor functions below read/write
705// the per-thread slots.
706// ═══════════════════════════════════════════════════════════════════════════════
707
708// ═══════════════════════════════════════════════════════════════════════════════
709// Default SAX v1 handler structs + locator (upstream globals.c 2.15.3)
710// ═══════════════════════════════════════════════════════════════════════════════
711//
712// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` and `htmlDefaultSAXHandler`
713// (parser.h / HTMLparser.h), plus `const xmlSAXLocator xmlDefaultSAXLocator`.
714// The handler instances reproduce the upstream initializer lists exactly
715// (globals.c); every referenced xmlSAX2* entry point is a real candidate
716// export.
717
718/// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` (globals.c 2.15.3).
719///
720/// `#[no_mangle]` data symbol that C consumers (PHP's DOM/XML extensions)
721/// MUTATE (they overwrite the error/warning callbacks on the shared default
722/// handler). Rust places non-`mut` statics in read-only memory; a write to
723/// it would segfault. Make it `static mut` so it lives in writable data.
724#[no_mangle]
725pub static mut xmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
726    crate::abi::structs::_xmlSAXHandlerV1 {
727        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
728        isStandalone: Some(crate::abi::exports_xml2::xmlSAX2IsStandalone),
729        hasInternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasInternalSubset),
730        hasExternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasExternalSubset),
731        resolveEntity: Some(crate::abi::exports_xml2::xmlSAX2ResolveEntity),
732        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
733        entityDecl: Some(crate::abi::exports_xml2::xmlSAX2EntityDecl),
734        notationDecl: Some(crate::abi::exports_xml2::xmlSAX2NotationDecl),
735        attributeDecl: Some(crate::abi::exports_xml2::xmlSAX2AttributeDecl),
736        elementDecl: Some(crate::abi::exports_xml2::xmlSAX2ElementDecl),
737        unparsedEntityDecl: Some(crate::abi::exports_xml2::xmlSAX2UnparsedEntityDecl),
738        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
739        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
740        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
741        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
742        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
743        reference: Some(crate::abi::exports_xml2::xmlSAX2Reference),
744        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
745        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
746        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
747        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
748        warning: Some(crate::xml::errors::XML_PARSER_WARNING_SAX1),
749        error: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
750        fatalError: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
751        getParameterEntity: Some(crate::abi::exports_xml2::xmlSAX2GetParameterEntity),
752        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
753        externalSubset: Some(crate::abi::exports_xml2::xmlSAX2ExternalSubset),
754        initialized: 1,
755    };
756
757/// `const xmlSAXHandlerV1 htmlDefaultSAXHandler` (globals.c 2.15.3).
758///
759/// Mutable (see xmlDefaultSAXHandler) so C consumers (PHP) that overwrite
760/// the shared default handler's callbacks don't fault on read-only memory.
761#[no_mangle]
762pub static mut htmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
763    crate::abi::structs::_xmlSAXHandlerV1 {
764        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
765        isStandalone: None,
766        hasInternalSubset: None,
767        hasExternalSubset: None,
768        resolveEntity: None,
769        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
770        entityDecl: None,
771        notationDecl: None,
772        attributeDecl: None,
773        elementDecl: None,
774        unparsedEntityDecl: None,
775        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
776        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
777        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
778        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
779        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
780        reference: None,
781        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
782        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
783        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
784        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
785        warning: Some(crate::xml::errors::XML_PARSER_WARNING_SAX1),
786        error: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
787        fatalError: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
788        getParameterEntity: None,
789        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
790        externalSubset: None,
791        initialized: 1,
792    };
793
794/// `const xmlSAXLocator xmlDefaultSAXLocator` (globals.c 2.15.3).
795#[no_mangle]
796pub static xmlDefaultSAXLocator: crate::abi::callbacks::_xmlSAXLocator =
797    crate::abi::callbacks::_xmlSAXLocator {
798        getPublicId: Some(crate::abi::exports_xml2::xmlSAX2GetPublicId),
799        getSystemId: Some(crate::abi::exports_xml2::xmlSAX2GetSystemId),
800        getLineNumber: Some(crate::abi::exports_xml2::xmlSAX2GetLineNumber),
801        getColumnNumber: Some(crate::abi::exports_xml2::xmlSAX2GetColumnNumber),
802    };
803
804// ═══════════════════════════════════════════════════════════════════════════════
805// xmlLastError — the exported C global mirror of the thread-local error state
806// ═══════════════════════════════════════════════════════════════════════════════
807//
808// Upstream `XMLPUBVAR xmlError xmlLastError` (xmlerror.h). The candidate's
809// internal error state is thread-local (safe divergence, more correct than
810// upstream's racy global); this mirror is deep-copied on every error raise
811// and freed on reset, so C consumers observe upstream-equivalent lifetime
812// semantics. Residual R-000135.
813
814/// `xmlError xmlLastError` — most recent error (mirror).
815#[no_mangle]
816pub static mut xmlLastError: crate::abi::structs::_xmlError = crate::abi::structs::_xmlError {
817    domain: 0,
818    code: 0,
819    message: core::ptr::null_mut(),
820    level: 0,
821    file: core::ptr::null_mut(),
822    line: 0,
823    str1: core::ptr::null_mut(),
824    str2: core::ptr::null_mut(),
825    str3: core::ptr::null_mut(),
826    int1: 0,
827    int2: 0,
828    ctxt: core::ptr::null_mut(),
829    node: core::ptr::null_mut(),
830};
831
832/// Deep-copy `err` into the exported `xmlLastError` global.
833///
834/// The string fields are copied with `libc::malloc`/`memcpy` so the mirror
835/// owns them (previous mirror strings are freed first — upstream
836/// xmlResetError semantics).
837///
838/// # SAFETY
839///
840/// - `err` must point to a valid `_xmlError` whose string fields are
841///   NUL-terminated or NULL.
842pub unsafe fn sync_xml_last_error(err: *const crate::abi::structs::_xmlError) {
843    if err.is_null() {
844        return;
845    }
846    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
847    unsafe { sync_xml_last_error_locked(err) };
848}
849
850/// Mirror write helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
851///
852/// # Safety
853///
854/// - `err` must be non-NULL and point to a valid `_xmlError` whose string
855///   fields are NULL or NUL-terminated; the caller must hold
856///   `LAST_ERROR_MIRROR_LOCK` so the `xmlLastError` global is not read or
857///   written concurrently; each field is deep-copied with `dup_cstr`.
858unsafe fn sync_xml_last_error_locked(err: *const crate::abi::structs::_xmlError) {
859    unsafe {
860        reset_xml_last_error_locked();
861        let src = &*err;
862        let dst = core::ptr::addr_of_mut!(xmlLastError);
863        (*dst).domain = src.domain;
864        (*dst).code = src.code;
865        (*dst).level = src.level;
866        (*dst).line = src.line;
867        (*dst).int1 = src.int1;
868        (*dst).int2 = src.int2;
869        (*dst).ctxt = src.ctxt;
870        (*dst).node = src.node;
871        (*dst).message = dup_cstr(src.message as *const u8);
872        (*dst).file = dup_cstr(src.file as *const u8);
873        (*dst).str1 = dup_cstr(src.str1 as *const u8);
874        (*dst).str2 = dup_cstr(src.str2 as *const u8);
875        (*dst).str3 = dup_cstr(src.str3 as *const u8);
876    }
877}
878
879/// Reset the exported `xmlLastError` global, freeing owned strings
880/// (upstream xmlResetError).
881///
882/// # SAFETY
883///
884/// Only call while no other thread is reading the global (upstream has the
885/// same race; documented).
886pub unsafe fn reset_xml_last_error() {
887    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
888    unsafe { reset_xml_last_error_locked() };
889}
890
891/// Mirror reset helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
892unsafe fn reset_xml_last_error_locked() {
893    unsafe {
894        let dst = core::ptr::addr_of_mut!(xmlLastError);
895        if !(*dst).message.is_null() {
896            libc::free((*dst).message as *mut libc::c_void);
897        }
898        if !(*dst).file.is_null() {
899            libc::free((*dst).file as *mut libc::c_void);
900        }
901        if !(*dst).str1.is_null() {
902            libc::free((*dst).str1 as *mut libc::c_void);
903        }
904        if !(*dst).str2.is_null() {
905            libc::free((*dst).str2 as *mut libc::c_void);
906        }
907        if !(*dst).str3.is_null() {
908            libc::free((*dst).str3 as *mut libc::c_void);
909        }
910        *dst = crate::abi::structs::_xmlError {
911            domain: 0,
912            code: 0,
913            message: core::ptr::null_mut(),
914            level: 0,
915            file: core::ptr::null_mut(),
916            line: 0,
917            str1: core::ptr::null_mut(),
918            str2: core::ptr::null_mut(),
919            str3: core::ptr::null_mut(),
920            int1: 0,
921            int2: 0,
922            ctxt: core::ptr::null_mut(),
923            node: core::ptr::null_mut(),
924        };
925    }
926}
927
928/// Heap-copy a NUL-terminated string (NULL-safe).
929///
930/// # Safety
931///
932/// - `s` must be NULL or a valid NUL-terminated string readable via
933///   `strlen` for the duration of the copy; the returned pointer is
934///   `libc::malloc`-owned and must be freed with `libc::free`, or is NULL
935///   when `s` is NULL or the allocation fails.
936unsafe fn dup_cstr(s: *const u8) -> *mut c_char {
937    if s.is_null() {
938        return core::ptr::null_mut();
939    }
940    unsafe {
941        let len = libc::strlen(s as *const libc::c_char) as usize;
942        let p = libc::malloc(len + 1) as *mut u8;
943        if p.is_null() {
944            return core::ptr::null_mut();
945        }
946        libc::memcpy(p as *mut libc::c_void, s as *const libc::c_void, len + 1);
947        p as *mut c_char
948    }
949}
950
951// ═══════════════════════════════════════════════════════════════════════════════
952// Default accessor functions (upstream parser.h / tree.h / xmlsave.h)
953// ═══════════════════════════════════════════════════════════════════════════════
954//
955// The deprecated `xmlXxxDefault(v)` accessors STORE the argument
956// unconditionally (0 and 1 alike) and RETURN THE PREVIOUS value — upstream
957// semantics verified against the executed 2.15.3 oracle (parserInternals.c
958// xmlKeepBlanksDefault / xmlSubstituteEntitiesDefault /
959// xmlPedanticParserDefault; tree.c xmlRegisterNodeDefault). Note the plain
960// setter and its xmlThrDef* twin differ from the other deprecated
961// conditional-set globals: `xmlThrDefLineNumbersDefaultValue` is a no-op
962// returning 1.
963
964/// Upstream `xmlKeepBlanksDefault(int v)`.
965///
966/// # SAFETY
967///
968/// The function touches crate-global state only; it is safe
969/// as long as the caller respects the library's global
970/// initialization/cleanup ordering (xmlInitParser before use,
971/// xmlCleanupParser only after all users are done).
972///
973/// Violating the global lifecycle ordering, or calling this after
974/// teardown or from a signal handler, is undefined behavior.
975#[no_mangle]
976pub unsafe extern "C" fn xmlKeepBlanksDefault(v: c_int) -> c_int {
977    // UPSTREAM-PARITY (parserInternals.c xmlKeepBlanksDefault): the value is
978    // STORED UNCONDITIONALLY (0 included) and the previous value is returned.
979    // The candidate's earlier conditional guard made xmlKeepBlanksDefault(0)
980    // a no-op, breaking fresh-context whitespace suppression (R-000177 /
981    // dso-state-coherence probe p1 profile).
982    let old = crate::xml::globals::get_keep_blanks_default();
983    crate::xml::globals::set_keep_blanks_default(v);
984    old
985}
986
987/// Upstream `xmlLineNumbersDefault(int v)`.
988///
989/// # SAFETY
990///
991/// The function touches crate-global state only; it is safe
992/// as long as the caller respects the library's global
993/// initialization/cleanup ordering (xmlInitParser before use,
994/// xmlCleanupParser only after all users are done).
995///
996/// Violating the global lifecycle ordering, or calling this after
997/// teardown or from a signal handler, is undefined behavior.
998#[no_mangle]
999pub unsafe extern "C" fn xmlLineNumbersDefault(_v: c_int) -> c_int {
1000    // UPSTREAM-PARITY (parserInternals.c xmlLineNumbersDefault): "Has no
1001    // effect. Line numbers are always enabled." — always returns 1.
1002    1
1003}
1004
1005/// Upstream `xmlSubstituteEntitiesDefault(int v)`.
1006///
1007/// # SAFETY
1008///
1009/// The function touches crate-global state only; it is safe
1010/// as long as the caller respects the library's global
1011/// initialization/cleanup ordering (xmlInitParser before use,
1012/// xmlCleanupParser only after all users are done).
1013///
1014/// Violating the global lifecycle ordering, or calling this after
1015/// teardown or from a signal handler, is undefined behavior.
1016#[no_mangle]
1017pub unsafe extern "C" fn xmlSubstituteEntitiesDefault(v: c_int) -> c_int {
1018    // UPSTREAM-PARITY (parserInternals.c xmlSubstituteEntitiesDefault):
1019    // stored unconditionally, previous value returned.
1020    let old = crate::xml::globals::get_substitute_entities_default();
1021    crate::xml::globals::set_substitute_entities_default(v);
1022    old
1023}
1024
1025/// Upstream `xmlPedanticParserDefault(int v)`.
1026///
1027/// # SAFETY
1028///
1029/// The function touches crate-global state only; it is safe
1030/// as long as the caller respects the library's global
1031/// initialization/cleanup ordering (xmlInitParser before use,
1032/// xmlCleanupParser only after all users are done).
1033///
1034/// Violating the global lifecycle ordering, or calling this after
1035/// teardown or from a signal handler, is undefined behavior.
1036#[no_mangle]
1037pub unsafe extern "C" fn xmlPedanticParserDefault(v: c_int) -> c_int {
1038    // UPSTREAM-PARITY (parserInternals.c xmlPedanticParserDefault): stored
1039    // unconditionally, previous value returned.
1040    let old = crate::xml::globals::get_pedantic_parser_default();
1041    crate::xml::globals::set_pedantic_parser_default(v);
1042    old
1043}
1044
1045/// Upstream `xmlDoValidityCheckingDefaultValue` accessor is the global
1046/// itself; `xmlGetWarningsDefaultValue` likewise (no accessor functions
1047/// exist for those in upstream 2.15).
1048/// Upstream `xmlRegisterNodeDefault(xmlRegisterNodeFunc func)`.
1049///
1050/// # SAFETY
1051///
1052///
1053/// - `func` must be a valid callback (or None);
1054///   the callback is invoked with the documented context pointer and
1055///   must itself uphold the same pointer invariants.
1056///
1057/// The caller must not race this call with concurrent mutation of the
1058/// same objects from other threads (per-object state is not internally
1059/// synchronized). Violating any of the above is undefined behavior.
1060///
1061/// Exercised by the C-API differential courts
1062/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1063/// courts; those pass byte-for-byte against the upstream oracle.
1064#[no_mangle]
1065pub unsafe extern "C" fn xmlRegisterNodeDefault(
1066    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1067) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1068    // UPSTREAM-PARITY (tree.c xmlRegisterNodeDefault): the callback is
1069    // stored UNCONDITIONALLY (NULL clears it), the gate is armed on every
1070    // call and the PREVIOUS callback is returned.
1071    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1072    let old = crate::xml::globals::get_register_node_default();
1073    crate::xml::globals::set_register_node_default(func);
1074    old
1075}
1076
1077/// Upstream `xmlDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
1078///
1079/// # SAFETY
1080///
1081///
1082/// - `func` must be a valid callback (or None);
1083///   the callback is invoked with the documented context pointer and
1084///   must itself uphold the same pointer invariants.
1085///
1086/// The caller must not race this call with concurrent mutation of the
1087/// same objects from other threads (per-object state is not internally
1088/// synchronized). Violating any of the above is undefined behavior.
1089///
1090/// Exercised by the C-API differential courts
1091/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1092/// courts; those pass byte-for-byte against the upstream oracle.
1093#[no_mangle]
1094pub unsafe extern "C" fn xmlDeregisterNodeDefault(
1095    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1096) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1097    // UPSTREAM-PARITY (tree.c xmlDeregisterNodeDefault): stored
1098    // unconditionally (NULL clears it), gate armed, previous value returned.
1099    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1100    let old = crate::xml::globals::get_deregister_node_default();
1101    crate::xml::globals::set_deregister_node_default(func);
1102    old
1103}
1104
1105/// Upstream `__xmlIndentTreeOutput(void)` (parser.h) — returns a pointer to
1106/// the `xmlIndentTreeOutput` global.
1107///
1108/// # SAFETY
1109///
1110/// The function touches crate-global state only; it is safe
1111/// as long as the caller respects the library's global
1112/// initialization/cleanup ordering (xmlInitParser before use,
1113/// xmlCleanupParser only after all users are done).
1114///
1115/// Violating the global lifecycle ordering, or calling this after
1116/// teardown or from a signal handler, is undefined behavior.
1117#[no_mangle]
1118pub unsafe extern "C" fn __xmlIndentTreeOutput() -> *mut c_int {
1119    crate::xml::globals::indent_tree_output_ptr()
1120}
1121
1122/// Upstream `__xmlSaveNoEmptyTags(void)` (parser.h) — returns a pointer to
1123/// the `xmlSaveNoEmptyTags` global.
1124///
1125/// # SAFETY
1126///
1127/// The function touches crate-global state only; it is safe
1128/// as long as the caller respects the library's global
1129/// initialization/cleanup ordering (xmlInitParser before use,
1130/// xmlCleanupParser only after all users are done).
1131///
1132/// Violating the global lifecycle ordering, or calling this after
1133/// teardown or from a signal handler, is undefined behavior.
1134#[no_mangle]
1135pub unsafe extern "C" fn __xmlSaveNoEmptyTags() -> *mut c_int {
1136    crate::xml::globals::save_no_empty_tags_ptr()
1137}
1138
1139/// Upstream `__xmlTreeIndentString(void)` (parser.h) — returns a pointer to
1140/// the `xmlTreeIndentString` global.
1141///
1142/// # SAFETY
1143///
1144/// The function touches crate-global state only; it is safe
1145/// as long as the caller respects the library's global
1146/// initialization/cleanup ordering (xmlInitParser before use,
1147/// xmlCleanupParser only after all users are done).
1148///
1149/// Violating the global lifecycle ordering, or calling this after
1150/// teardown or from a signal handler, is undefined behavior.
1151#[no_mangle]
1152pub unsafe extern "C" fn __xmlTreeIndentString() -> *mut *const xmlChar {
1153    crate::xml::globals::tree_indent_string_ptr()
1154}
1155
1156// ═══════════════════════════════════════════════════════════════════════════════
1157// xmlThrDef* accessors (upstream threads.c / globals.c)
1158// ═══════════════════════════════════════════════════════════════════════════════
1159//
1160// The deprecated `xmlThrDef*` family (upstream globals.c, LIBXML_THREAD_
1161// ENABLED builds): every member STORES the argument unconditionally (0
1162// included) under the xmlThrDefMutex and returns the PREVIOUS value — with
1163// two exceptions: `xmlThrDefLineNumbersDefaultValue` is a no-op returning 1,
1164// and the node-register twins arm xmlRegisterCallbacks and store the
1165// callback (NULL clears). Verified against the executed 2.15.3 oracle.
1166
1167/// Upstream `xmlThrDefDoValidityCheckingDefaultValue(int v)`.
1168///
1169/// # SAFETY
1170///
1171/// The function touches crate-global state only; it is safe
1172/// as long as the caller respects the library's global
1173/// initialization/cleanup ordering (xmlInitParser before use,
1174/// xmlCleanupParser only after all users are done).
1175///
1176/// Violating the global lifecycle ordering, or calling this after
1177/// teardown or from a signal handler, is undefined behavior.
1178#[no_mangle]
1179pub unsafe extern "C" fn xmlThrDefDoValidityCheckingDefaultValue(v: c_int) -> c_int {
1180    let old = crate::xml::globals::get_validity_checking_default();
1181    crate::xml::globals::set_validity_checking_default(v);
1182    old
1183}
1184
1185/// Upstream `xmlThrDefGetWarningsDefaultValue(int v)`.
1186///
1187/// # SAFETY
1188///
1189/// The function touches crate-global state only; it is safe
1190/// as long as the caller respects the library's global
1191/// initialization/cleanup ordering (xmlInitParser before use,
1192/// xmlCleanupParser only after all users are done).
1193///
1194/// Violating the global lifecycle ordering, or calling this after
1195/// teardown or from a signal handler, is undefined behavior.
1196#[no_mangle]
1197pub unsafe extern "C" fn xmlThrDefGetWarningsDefaultValue(v: c_int) -> c_int {
1198    let old = crate::xml::globals::get_get_warnings_default();
1199    crate::xml::globals::set_get_warnings_default(v);
1200    old
1201}
1202
1203/// Upstream `xmlThrDefLoadExtDtdDefaultValue(int v)`.
1204///
1205/// # SAFETY
1206///
1207/// The function touches crate-global state only; it is safe
1208/// as long as the caller respects the library's global
1209/// initialization/cleanup ordering (xmlInitParser before use,
1210/// xmlCleanupParser only after all users are done).
1211///
1212/// Violating the global lifecycle ordering, or calling this after
1213/// teardown or from a signal handler, is undefined behavior.
1214#[no_mangle]
1215pub unsafe extern "C" fn xmlThrDefLoadExtDtdDefaultValue(v: c_int) -> c_int {
1216    let old = crate::xml::globals::get_load_ext_dtd_default();
1217    crate::xml::globals::set_load_ext_dtd_default(v);
1218    old
1219}
1220
1221/// Upstream `xmlThrDefPedanticParserDefaultValue(int v)`.
1222///
1223/// # SAFETY
1224///
1225/// The function touches crate-global state only; it is safe
1226/// as long as the caller respects the library's global
1227/// initialization/cleanup ordering (xmlInitParser before use,
1228/// xmlCleanupParser only after all users are done).
1229///
1230/// Violating the global lifecycle ordering, or calling this after
1231/// teardown or from a signal handler, is undefined behavior.
1232#[no_mangle]
1233pub unsafe extern "C" fn xmlThrDefPedanticParserDefaultValue(v: c_int) -> c_int {
1234    let old = crate::xml::globals::get_pedantic_parser_default();
1235    crate::xml::globals::set_pedantic_parser_default(v);
1236    old
1237}
1238
1239/// Upstream `xmlThrDefLineNumbersDefaultValue(int v)`.
1240///
1241/// # SAFETY
1242///
1243/// The function touches crate-global state only; it is safe
1244/// as long as the caller respects the library's global
1245/// initialization/cleanup ordering (xmlInitParser before use,
1246/// xmlCleanupParser only after all users are done).
1247///
1248/// Violating the global lifecycle ordering, or calling this after
1249/// teardown or from a signal handler, is undefined behavior.
1250#[no_mangle]
1251pub unsafe extern "C" fn xmlThrDefLineNumbersDefaultValue(_v: c_int) -> c_int {
1252    // UPSTREAM-PARITY (globals.c xmlThrDefLineNumbersDefaultValue): no-op,
1253    // always returns 1 (line numbers are always enabled).
1254    1
1255}
1256
1257/// Upstream `xmlThrDefKeepBlanksDefaultValue(int v)`.
1258///
1259/// # SAFETY
1260///
1261/// The function touches crate-global state only; it is safe
1262/// as long as the caller respects the library's global
1263/// initialization/cleanup ordering (xmlInitParser before use,
1264/// xmlCleanupParser only after all users are done).
1265///
1266/// Violating the global lifecycle ordering, or calling this after
1267/// teardown or from a signal handler, is undefined behavior.
1268#[no_mangle]
1269pub unsafe extern "C" fn xmlThrDefKeepBlanksDefaultValue(v: c_int) -> c_int {
1270    let old = crate::xml::globals::get_keep_blanks_default();
1271    crate::xml::globals::set_keep_blanks_default(v);
1272    old
1273}
1274
1275/// Upstream `xmlThrDefSubstituteEntitiesDefaultValue(int v)`.
1276///
1277/// # SAFETY
1278///
1279/// The function touches crate-global state only; it is safe
1280/// as long as the caller respects the library's global
1281/// initialization/cleanup ordering (xmlInitParser before use,
1282/// xmlCleanupParser only after all users are done).
1283///
1284/// Violating the global lifecycle ordering, or calling this after
1285/// teardown or from a signal handler, is undefined behavior.
1286#[no_mangle]
1287pub unsafe extern "C" fn xmlThrDefSubstituteEntitiesDefaultValue(v: c_int) -> c_int {
1288    let old = crate::xml::globals::get_substitute_entities_default();
1289    crate::xml::globals::set_substitute_entities_default(v);
1290    old
1291}
1292
1293/// Upstream `xmlThrDefParserDebugEntities(int v)`.
1294///
1295/// # SAFETY
1296///
1297/// The function touches crate-global state only; it is safe
1298/// as long as the caller respects the library's global
1299/// initialization/cleanup ordering (xmlInitParser before use,
1300/// xmlCleanupParser only after all users are done).
1301///
1302/// Violating the global lifecycle ordering, or calling this after
1303/// teardown or from a signal handler, is undefined behavior.
1304#[no_mangle]
1305pub unsafe extern "C" fn xmlThrDefParserDebugEntities(v: c_int) -> c_int {
1306    unsafe {
1307        if v != 0 {
1308            xmlParserDebugEntities = v;
1309        }
1310        xmlParserDebugEntities
1311    }
1312}
1313
1314/// Upstream `xmlThrDefIndentTreeOutput(int v)`.
1315///
1316/// # SAFETY
1317///
1318/// The function touches crate-global state only; it is safe
1319/// as long as the caller respects the library's global
1320/// initialization/cleanup ordering (xmlInitParser before use,
1321/// xmlCleanupParser only after all users are done).
1322///
1323/// Violating the global lifecycle ordering, or calling this after
1324/// teardown or from a signal handler, is undefined behavior.
1325#[no_mangle]
1326pub unsafe extern "C" fn xmlThrDefIndentTreeOutput(v: c_int) -> c_int {
1327    let old = crate::xml::globals::get_indent_tree_output();
1328    crate::xml::globals::set_indent_tree_output(v);
1329    old
1330}
1331
1332/// Upstream `xmlThrDefTreeIndentString(const char *v)` — sets the indent
1333/// string when non-NULL and returns the current pointer.
1334///
1335/// # SAFETY
1336///
1337///
1338/// - `v` must point to valid NUL-terminated
1339///   strings (or NULL where the C contract allows) for the lifetime
1340///   of the call.
1341///
1342/// The caller must not race this call with concurrent mutation of the
1343/// same objects from other threads (per-object state is not internally
1344/// synchronized). Violating any of the above is undefined behavior.
1345///
1346/// Exercised by the C-API differential courts
1347/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1348/// courts; those pass byte-for-byte against the upstream oracle.
1349#[no_mangle]
1350pub unsafe extern "C" fn xmlThrDefTreeIndentString(v: *const c_char) -> *const c_char {
1351    // UPSTREAM-PARITY (globals.c xmlThrDefTreeIndentString): stored
1352    // unconditionally (NULL clears), previous pointer returned.
1353    let old = crate::xml::globals::get_tree_indent_string() as *const c_char;
1354    crate::xml::globals::set_tree_indent_string(v as *const crate::abi::types::xmlChar);
1355    old
1356}
1357
1358/// Upstream `xmlThrDefSaveNoEmptyTags(int v)`.
1359///
1360/// # SAFETY
1361///
1362/// The function touches crate-global state only; it is safe
1363/// as long as the caller respects the library's global
1364/// initialization/cleanup ordering (xmlInitParser before use,
1365/// xmlCleanupParser only after all users are done).
1366///
1367/// Violating the global lifecycle ordering, or calling this after
1368/// teardown or from a signal handler, is undefined behavior.
1369#[no_mangle]
1370pub unsafe extern "C" fn xmlThrDefSaveNoEmptyTags(v: c_int) -> c_int {
1371    let old = crate::xml::globals::get_save_no_empty_tags();
1372    crate::xml::globals::set_save_no_empty_tags(v);
1373    old
1374}
1375
1376/// Upstream `xmlThrDefRegisterNodeDefault(xmlRegisterNodeFunc func)`.
1377///
1378/// # SAFETY
1379///
1380///
1381/// - `func` must be a valid callback (or None);
1382///   the callback is invoked with the documented context pointer and
1383///   must itself uphold the same pointer invariants.
1384///
1385/// The caller must not race this call with concurrent mutation of the
1386/// same objects from other threads (per-object state is not internally
1387/// synchronized). Violating any of the above is undefined behavior.
1388///
1389/// Exercised by the C-API differential courts
1390/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1391/// courts; those pass byte-for-byte against the upstream oracle.
1392#[no_mangle]
1393pub unsafe extern "C" fn xmlThrDefRegisterNodeDefault(
1394    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1395) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1396    // UPSTREAM-PARITY (globals.c xmlThrDefRegisterNodeDefault): arms the
1397    // gate, stores the callback unconditionally (NULL clears), returns the
1398    // previous one.
1399    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1400    let old = crate::xml::globals::get_register_node_default();
1401    crate::xml::globals::set_register_node_default(func);
1402    old
1403}
1404
1405/// Upstream `xmlThrDefDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
1406///
1407/// # SAFETY
1408///
1409///
1410/// - `func` must be a valid callback (or None);
1411///   the callback is invoked with the documented context pointer and
1412///   must itself uphold the same pointer invariants.
1413///
1414/// The caller must not race this call with concurrent mutation of the
1415/// same objects from other threads (per-object state is not internally
1416/// synchronized). Violating any of the above is undefined behavior.
1417///
1418/// Exercised by the C-API differential courts
1419/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1420/// courts; those pass byte-for-byte against the upstream oracle.
1421#[no_mangle]
1422pub unsafe extern "C" fn xmlThrDefDeregisterNodeDefault(
1423    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1424) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1425    // UPSTREAM-PARITY (globals.c xmlThrDefDeregisterNodeDefault): arms the
1426    // gate, stores the callback unconditionally (NULL clears), returns the
1427    // previous one.
1428    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1429    let old = crate::xml::globals::get_deregister_node_default();
1430    crate::xml::globals::set_deregister_node_default(func);
1431    old
1432}
1433
1434/// Upstream `xmlThrDefSetGenericErrorFunc(void *ctx, xmlGenericErrorFunc func)`.
1435///
1436/// # SAFETY
1437///
1438/// - `ctx` must be valid pointers (or NULL
1439///   where the upstream C contract allows), obtained from the
1440///   matching constructor/owner and not yet freed; the callee may
1441///   take or keep ownership exactly as the C API specifies.
1442///
1443/// - `func` must be a valid callback (or None);
1444///   the callback is invoked with the documented context pointer and
1445///   must itself uphold the same pointer invariants.
1446///
1447/// The caller must not race this call with concurrent mutation of the
1448/// same objects from other threads (per-object state is not internally
1449/// synchronized). Violating any of the above is undefined behavior.
1450///
1451/// Exercised by the C-API differential courts
1452/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1453/// courts; those pass byte-for-byte against the upstream oracle.
1454#[no_mangle]
1455pub unsafe extern "C" fn xmlThrDefSetGenericErrorFunc(
1456    ctx: *mut c_void,
1457    func: Option<xmlGenericErrorFunc>,
1458) {
1459    crate::xml::globals::set_generic_error_func(ctx, func);
1460}
1461
1462/// Upstream `xmlThrDefSetStructuredErrorFunc(void *ctx, xmlStructuredErrorFunc func)`.
1463///
1464/// # SAFETY
1465///
1466/// - `ctx` must be valid pointers (or NULL
1467///   where the upstream C contract allows), obtained from the
1468///   matching constructor/owner and not yet freed; the callee may
1469///   take or keep ownership exactly as the C API specifies.
1470///
1471/// - `func` must be a valid callback (or None);
1472///   the callback is invoked with the documented context pointer and
1473///   must itself uphold the same pointer invariants.
1474///
1475/// The caller must not race this call with concurrent mutation of the
1476/// same objects from other threads (per-object state is not internally
1477/// synchronized). Violating any of the above is undefined behavior.
1478///
1479/// Exercised by the C-API differential courts
1480/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1481/// courts; those pass byte-for-byte against the upstream oracle.
1482#[no_mangle]
1483pub unsafe extern "C" fn xmlThrDefSetStructuredErrorFunc(
1484    ctx: *mut c_void,
1485    func: Option<xmlStructuredErrorFunc>,
1486) {
1487    crate::xml::globals::set_structured_error_func(ctx, func);
1488}
1489
1490/// Upstream `xmlThrDefDefaultBufferSize(int v)`.
1491///
1492/// # SAFETY
1493///
1494/// The function touches crate-global state only; it is safe
1495/// as long as the caller respects the library's global
1496/// initialization/cleanup ordering (xmlInitParser before use,
1497/// xmlCleanupParser only after all users are done).
1498///
1499/// Violating the global lifecycle ordering, or calling this after
1500/// teardown or from a signal handler, is undefined behavior.
1501#[no_mangle]
1502pub unsafe extern "C" fn xmlThrDefDefaultBufferSize(v: c_int) -> c_int {
1503    unsafe {
1504        if v != 0 {
1505            xmlDefaultBufferSize = v;
1506        }
1507        xmlDefaultBufferSize
1508    }
1509}
1510
1511/// Upstream `xmlThrDefBufferAllocScheme(xmlBufferAllocationScheme v)`.
1512///
1513/// # SAFETY
1514///
1515/// The function touches crate-global state only; it is safe
1516/// as long as the caller respects the library's global
1517/// initialization/cleanup ordering (xmlInitParser before use,
1518/// xmlCleanupParser only after all users are done).
1519///
1520/// Violating the global lifecycle ordering, or calling this after
1521/// teardown or from a signal handler, is undefined behavior.
1522#[no_mangle]
1523pub unsafe extern "C" fn xmlThrDefBufferAllocScheme(v: c_int) -> c_int {
1524    unsafe {
1525        if v != 0 {
1526            xmlBufferAllocScheme = v;
1527        }
1528        xmlBufferAllocScheme
1529    }
1530}
1531
1532/// Upstream `xmlThrDefParserInputBufferCreateFilenameDefault(...)`.
1533///
1534/// # SAFETY
1535///
1536///
1537/// - `func` must be a valid callback (or None);
1538///   the callback is invoked with the documented context pointer and
1539///   must itself uphold the same pointer invariants.
1540///
1541/// The caller must not race this call with concurrent mutation of the
1542/// same objects from other threads (per-object state is not internally
1543/// synchronized). Violating any of the above is undefined behavior.
1544///
1545/// Exercised by the C-API differential courts
1546/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1547/// courts; those pass byte-for-byte against the upstream oracle.
1548#[no_mangle]
1549pub unsafe extern "C" fn xmlThrDefParserInputBufferCreateFilenameDefault(
1550    func: Option<
1551        unsafe extern "C" fn(
1552            *const c_char,
1553            c_int,
1554        ) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1555    >,
1556) -> Option<
1557    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1558> {
1559    if func.is_some() {
1560        crate::xml::globals::set_parser_input_buffer_create_filename_value(func);
1561    }
1562    crate::xml::globals::get_parser_input_buffer_create_filename_value()
1563}
1564
1565/// Upstream `xmlThrDefOutputBufferCreateFilenameDefault(...)`.
1566///
1567/// # SAFETY
1568///
1569///
1570/// - `func` must be a valid callback (or None);
1571///   the callback is invoked with the documented context pointer and
1572///   must itself uphold the same pointer invariants.
1573///
1574/// The caller must not race this call with concurrent mutation of the
1575/// same objects from other threads (per-object state is not internally
1576/// synchronized). Violating any of the above is undefined behavior.
1577///
1578/// Exercised by the C-API differential courts
1579/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1580/// courts; those pass byte-for-byte against the upstream oracle.
1581#[no_mangle]
1582pub unsafe extern "C" fn xmlThrDefOutputBufferCreateFilenameDefault(
1583    func: Option<
1584        unsafe extern "C" fn(
1585            *const c_char,
1586            crate::abi::structs::xmlCharEncodingHandlerPtr,
1587            c_int,
1588        ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1589    >,
1590) -> Option<
1591    unsafe extern "C" fn(
1592        *const c_char,
1593        crate::abi::structs::xmlCharEncodingHandlerPtr,
1594        c_int,
1595    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1596> {
1597    if func.is_some() {
1598        crate::xml::globals::set_output_buffer_create_filename_value(func);
1599    }
1600    crate::xml::globals::get_output_buffer_create_filename_value()
1601}
1602
1603// ═══════════════════════════════════════════════════════════════════════════════
1604// __xmlXxx() pointer accessors (upstream threads.c / globals.c)
1605// ═══════════════════════════════════════════════════════════════════════════════
1606// The deprecated thread-local API exports one `__xmlXxx(void)` accessor per
1607// global; each returns a pointer to the global so callers can read/write it.
1608
1609/// Upstream `__xmlBufferAllocScheme(void)` — returns a pointer to `xmlBufferAllocScheme`.
1610///
1611/// # SAFETY
1612///
1613/// The function touches crate-global state only; it is safe
1614/// as long as the caller respects the library's global
1615/// initialization/cleanup ordering (xmlInitParser before use,
1616/// xmlCleanupParser only after all users are done).
1617///
1618/// Violating the global lifecycle ordering, or calling this after
1619/// teardown or from a signal handler, is undefined behavior.
1620#[no_mangle]
1621pub unsafe extern "C" fn __xmlBufferAllocScheme() -> *mut c_int {
1622    // SAFETY: returning a pointer to an exported static; the caller may
1623    // read/write it exactly as with upstream's deprecated accessor.
1624    core::ptr::addr_of_mut!(xmlBufferAllocScheme)
1625}
1626
1627/// Upstream `__xmlDefaultBufferSize(void)` — returns a pointer to `xmlDefaultBufferSize`.
1628///
1629/// # SAFETY
1630///
1631/// The function touches crate-global state only; it is safe
1632/// as long as the caller respects the library's global
1633/// initialization/cleanup ordering (xmlInitParser before use,
1634/// xmlCleanupParser only after all users are done).
1635///
1636/// Violating the global lifecycle ordering, or calling this after
1637/// teardown or from a signal handler, is undefined behavior.
1638#[no_mangle]
1639pub unsafe extern "C" fn __xmlDefaultBufferSize() -> *mut c_int {
1640    // SAFETY: returning a pointer to an exported static; the caller may
1641    // read/write it exactly as with upstream's deprecated accessor.
1642    core::ptr::addr_of_mut!(xmlDefaultBufferSize)
1643}
1644
1645/// Upstream `__xmlDeregisterNodeDefaultValue(void)` — returns a pointer to `xmlDeregisterNodeDefaultValue`.
1646///
1647/// # SAFETY
1648///
1649/// The function touches crate-global state only; it is safe
1650/// as long as the caller respects the library's global
1651/// initialization/cleanup ordering (xmlInitParser before use,
1652/// xmlCleanupParser only after all users are done).
1653///
1654/// Violating the global lifecycle ordering, or calling this after
1655/// teardown or from a signal handler, is undefined behavior.
1656#[no_mangle]
1657pub unsafe extern "C" fn __xmlDeregisterNodeDefaultValue(
1658) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1659    // SAFETY: returning a pointer to an exported static; the caller may
1660    // read/write it exactly as with upstream's deprecated accessor.
1661    crate::xml::globals::deregister_node_ptr()
1662}
1663
1664/// Upstream `__xmlDoValidityCheckingDefaultValue(void)` — returns a pointer to `xmlDoValidityCheckingDefaultValue`.
1665///
1666/// # SAFETY
1667///
1668/// The function touches crate-global state only; it is safe
1669/// as long as the caller respects the library's global
1670/// initialization/cleanup ordering (xmlInitParser before use,
1671/// xmlCleanupParser only after all users are done).
1672///
1673/// Violating the global lifecycle ordering, or calling this after
1674/// teardown or from a signal handler, is undefined behavior.
1675#[no_mangle]
1676pub unsafe extern "C" fn __xmlDoValidityCheckingDefaultValue() -> *mut c_int {
1677    // SAFETY: returning a pointer to an exported static; the caller may
1678    // read/write it exactly as with upstream's deprecated accessor.
1679    crate::xml::globals::do_validity_ptr()
1680}
1681
1682/// Upstream `__xmlGenericError(void)` — returns a pointer to `xmlGenericError`.
1683///
1684/// # SAFETY
1685///
1686/// The function touches crate-global state only; it is safe
1687/// as long as the caller respects the library's global
1688/// initialization/cleanup ordering (xmlInitParser before use,
1689/// xmlCleanupParser only after all users are done).
1690///
1691/// Violating the global lifecycle ordering, or calling this after
1692/// teardown or from a signal handler, is undefined behavior.
1693#[no_mangle]
1694pub unsafe extern "C" fn __xmlGenericError() -> *mut Option<xmlGenericErrorFunc> {
1695    // SAFETY: returning a pointer to an exported static; the caller may
1696    // read/write it exactly as with upstream's deprecated accessor.
1697    crate::xml::globals::generic_error_ptr()
1698}
1699
1700/// Upstream `__xmlGenericErrorContext(void)` — returns a pointer to `xmlGenericErrorContext`.
1701///
1702/// # SAFETY
1703///
1704/// The function touches crate-global state only; it is safe
1705/// as long as the caller respects the library's global
1706/// initialization/cleanup ordering (xmlInitParser before use,
1707/// xmlCleanupParser only after all users are done).
1708///
1709/// Violating the global lifecycle ordering, or calling this after
1710/// teardown or from a signal handler, is undefined behavior.
1711#[no_mangle]
1712pub unsafe extern "C" fn __xmlGenericErrorContext() -> *mut *mut c_void {
1713    // SAFETY: returning a pointer to an exported static; the caller may
1714    // read/write it exactly as with upstream's deprecated accessor.
1715    crate::xml::globals::generic_error_ctx_ptr()
1716}
1717
1718/// Upstream `__xmlGetWarningsDefaultValue(void)` — returns a pointer to `xmlGetWarningsDefaultValue`.
1719///
1720/// # SAFETY
1721///
1722/// The function touches crate-global state only; it is safe
1723/// as long as the caller respects the library's global
1724/// initialization/cleanup ordering (xmlInitParser before use,
1725/// xmlCleanupParser only after all users are done).
1726///
1727/// Violating the global lifecycle ordering, or calling this after
1728/// teardown or from a signal handler, is undefined behavior.
1729#[no_mangle]
1730pub unsafe extern "C" fn __xmlGetWarningsDefaultValue() -> *mut c_int {
1731    // SAFETY: returning a pointer to an exported static; the caller may
1732    // read/write it exactly as with upstream's deprecated accessor.
1733    crate::xml::globals::get_warnings_ptr()
1734}
1735
1736/// Upstream `__xmlKeepBlanksDefaultValue(void)` — returns a pointer to `xmlKeepBlanksDefaultValue`.
1737///
1738/// # SAFETY
1739///
1740/// The function touches crate-global state only; it is safe
1741/// as long as the caller respects the library's global
1742/// initialization/cleanup ordering (xmlInitParser before use,
1743/// xmlCleanupParser only after all users are done).
1744///
1745/// Violating the global lifecycle ordering, or calling this after
1746/// teardown or from a signal handler, is undefined behavior.
1747#[no_mangle]
1748pub unsafe extern "C" fn __xmlKeepBlanksDefaultValue() -> *mut c_int {
1749    // SAFETY: returning a pointer to an exported static; the caller may
1750    // read/write it exactly as with upstream's deprecated accessor.
1751    crate::xml::globals::keep_blanks_ptr()
1752}
1753
1754/// Upstream `__xmlLineNumbersDefaultValue(void)` — returns a pointer to `xmlLineNumbersDefaultValue`.
1755///
1756/// # SAFETY
1757///
1758/// The function touches crate-global state only; it is safe
1759/// as long as the caller respects the library's global
1760/// initialization/cleanup ordering (xmlInitParser before use,
1761/// xmlCleanupParser only after all users are done).
1762///
1763/// Violating the global lifecycle ordering, or calling this after
1764/// teardown or from a signal handler, is undefined behavior.
1765#[no_mangle]
1766pub unsafe extern "C" fn __xmlLineNumbersDefaultValue() -> *mut c_int {
1767    // SAFETY: returning a pointer to an exported static; the caller may
1768    // read/write it exactly as with upstream's deprecated accessor.
1769    crate::xml::globals::line_numbers_ptr()
1770}
1771
1772/// Upstream `__xmlLoadExtDtdDefaultValue(void)` — returns a pointer to `xmlLoadExtDtdDefaultValue`.
1773///
1774/// # SAFETY
1775///
1776/// The function touches crate-global state only; it is safe
1777/// as long as the caller respects the library's global
1778/// initialization/cleanup ordering (xmlInitParser before use,
1779/// xmlCleanupParser only after all users are done).
1780///
1781/// Violating the global lifecycle ordering, or calling this after
1782/// teardown or from a signal handler, is undefined behavior.
1783#[no_mangle]
1784pub unsafe extern "C" fn __xmlLoadExtDtdDefaultValue() -> *mut c_int {
1785    // SAFETY: returning a pointer to an exported static; the caller may
1786    // read/write it exactly as with upstream's deprecated accessor.
1787    crate::xml::globals::load_ext_dtd_ptr()
1788}
1789
1790/// Upstream `__xmlOutputBufferCreateFilenameValue(void)` — returns a pointer to `xmlOutputBufferCreateFilenameValue`.
1791///
1792/// # SAFETY
1793///
1794/// The function touches crate-global state only; it is safe
1795/// as long as the caller respects the library's global
1796/// initialization/cleanup ordering (xmlInitParser before use,
1797/// xmlCleanupParser only after all users are done).
1798///
1799/// Violating the global lifecycle ordering, or calling this after
1800/// teardown or from a signal handler, is undefined behavior.
1801#[no_mangle]
1802pub unsafe extern "C" fn __xmlOutputBufferCreateFilenameValue() -> *mut Option<
1803    unsafe extern "C" fn(
1804        *const c_char,
1805        crate::abi::structs::xmlCharEncodingHandlerPtr,
1806        c_int,
1807    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1808> {
1809    // SAFETY: returning a pointer to an exported static; the caller may
1810    // read/write it exactly as with upstream's deprecated accessor.
1811    crate::xml::globals::output_create_filename_ptr()
1812}
1813
1814/// Upstream `__xmlParserDebugEntities(void)` — returns a pointer to `xmlParserDebugEntities`.
1815///
1816/// # SAFETY
1817///
1818/// The function touches crate-global state only; it is safe
1819/// as long as the caller respects the library's global
1820/// initialization/cleanup ordering (xmlInitParser before use,
1821/// xmlCleanupParser only after all users are done).
1822///
1823/// Violating the global lifecycle ordering, or calling this after
1824/// teardown or from a signal handler, is undefined behavior.
1825#[no_mangle]
1826pub unsafe extern "C" fn __xmlParserDebugEntities() -> *mut c_int {
1827    // SAFETY: returning a pointer to an exported static; the caller may
1828    // read/write it exactly as with upstream's deprecated accessor.
1829    core::ptr::addr_of_mut!(xmlParserDebugEntities)
1830}
1831
1832/// Upstream `__xmlParserInputBufferCreateFilenameValue(void)` — returns a pointer to `xmlParserInputBufferCreateFilenameValue`.
1833///
1834/// # SAFETY
1835///
1836/// The function touches crate-global state only; it is safe
1837/// as long as the caller respects the library's global
1838/// initialization/cleanup ordering (xmlInitParser before use,
1839/// xmlCleanupParser only after all users are done).
1840///
1841/// Violating the global lifecycle ordering, or calling this after
1842/// teardown or from a signal handler, is undefined behavior.
1843#[no_mangle]
1844pub unsafe extern "C" fn __xmlParserInputBufferCreateFilenameValue() -> *mut Option<
1845    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1846> {
1847    // SAFETY: returning a pointer to an exported static; the caller may
1848    // read/write it exactly as with upstream's deprecated accessor.
1849    crate::xml::globals::parser_input_create_filename_ptr()
1850}
1851
1852/// Upstream `__xmlParserVersion(void)` — returns a pointer to `xmlParserVersion`.
1853///
1854/// # SAFETY
1855///
1856/// The function touches crate-global state only; it is safe
1857/// as long as the caller respects the library's global
1858/// initialization/cleanup ordering (xmlInitParser before use,
1859/// xmlCleanupParser only after all users are done).
1860///
1861/// Violating the global lifecycle ordering, or calling this after
1862/// teardown or from a signal handler, is undefined behavior.
1863#[no_mangle]
1864pub unsafe extern "C" fn __xmlParserVersion() -> *mut *const c_char {
1865    // SAFETY: returning a pointer to an exported static; the caller may
1866    // read/write it exactly as with upstream's deprecated accessor.
1867    core::ptr::addr_of_mut!(xmlParserVersion)
1868}
1869
1870/// Upstream `__xmlPedanticParserDefaultValue(void)` — returns a pointer to `xmlPedanticParserDefaultValue`.
1871///
1872/// # SAFETY
1873///
1874/// The function touches crate-global state only; it is safe
1875/// as long as the caller respects the library's global
1876/// initialization/cleanup ordering (xmlInitParser before use,
1877/// xmlCleanupParser only after all users are done).
1878///
1879/// Violating the global lifecycle ordering, or calling this after
1880/// teardown or from a signal handler, is undefined behavior.
1881#[no_mangle]
1882pub unsafe extern "C" fn __xmlPedanticParserDefaultValue() -> *mut c_int {
1883    // SAFETY: returning a pointer to an exported static; the caller may
1884    // read/write it exactly as with upstream's deprecated accessor.
1885    crate::xml::globals::pedantic_ptr()
1886}
1887
1888/// Upstream `__xmlRegisterNodeDefaultValue(void)` — returns a pointer to `xmlRegisterNodeDefaultValue`.
1889///
1890/// # SAFETY
1891///
1892/// The function touches crate-global state only; it is safe
1893/// as long as the caller respects the library's global
1894/// initialization/cleanup ordering (xmlInitParser before use,
1895/// xmlCleanupParser only after all users are done).
1896///
1897/// Violating the global lifecycle ordering, or calling this after
1898/// teardown or from a signal handler, is undefined behavior.
1899#[no_mangle]
1900pub unsafe extern "C" fn __xmlRegisterNodeDefaultValue(
1901) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1902    // SAFETY: returning a pointer to an exported static; the caller may
1903    // read/write it exactly as with upstream's deprecated accessor.
1904    crate::xml::globals::register_node_ptr()
1905}
1906
1907/// Upstream `__xmlStructuredError(void)` — returns a pointer to `xmlStructuredError`.
1908///
1909/// # SAFETY
1910///
1911/// The function touches crate-global state only; it is safe
1912/// as long as the caller respects the library's global
1913/// initialization/cleanup ordering (xmlInitParser before use,
1914/// xmlCleanupParser only after all users are done).
1915///
1916/// Violating the global lifecycle ordering, or calling this after
1917/// teardown or from a signal handler, is undefined behavior.
1918#[no_mangle]
1919pub unsafe extern "C" fn __xmlStructuredError() -> *mut Option<xmlStructuredErrorFunc> {
1920    // SAFETY: returning a pointer to an exported static; the caller may
1921    // read/write it exactly as with upstream's deprecated accessor.
1922    crate::xml::globals::structured_error_ptr()
1923}
1924
1925/// Upstream `__xmlStructuredErrorContext(void)` — returns a pointer to `xmlStructuredErrorContext`.
1926///
1927/// # SAFETY
1928///
1929/// The function touches crate-global state only; it is safe
1930/// as long as the caller respects the library's global
1931/// initialization/cleanup ordering (xmlInitParser before use,
1932/// xmlCleanupParser only after all users are done).
1933///
1934/// Violating the global lifecycle ordering, or calling this after
1935/// teardown or from a signal handler, is undefined behavior.
1936#[no_mangle]
1937pub unsafe extern "C" fn __xmlStructuredErrorContext() -> *mut *mut c_void {
1938    // SAFETY: returning a pointer to an exported static; the caller may
1939    // read/write it exactly as with upstream's deprecated accessor.
1940    crate::xml::globals::structured_error_ctx_ptr()
1941}
1942
1943/// Upstream `__xmlSubstituteEntitiesDefaultValue(void)` — returns a pointer to `xmlSubstituteEntitiesDefaultValue`.
1944///
1945/// # SAFETY
1946///
1947/// The function touches crate-global state only; it is safe
1948/// as long as the caller respects the library's global
1949/// initialization/cleanup ordering (xmlInitParser before use,
1950/// xmlCleanupParser only after all users are done).
1951///
1952/// Violating the global lifecycle ordering, or calling this after
1953/// teardown or from a signal handler, is undefined behavior.
1954#[no_mangle]
1955pub unsafe extern "C" fn __xmlSubstituteEntitiesDefaultValue() -> *mut c_int {
1956    // SAFETY: returning a pointer to an exported static; the caller may
1957    // read/write it exactly as with upstream's deprecated accessor.
1958    crate::xml::globals::substitute_entities_ptr()
1959}
1960
1961// ═══════════════════════════════════════════════════════════════════════════════
1962// Regression court — xmlLastError mirror concurrency (11.1-X)
1963// ═══════════════════════════════════════════════════════════════════════════════
1964//
1965// R-000135 discovery during 11.1-X: the exported `xmlLastError` mirror was
1966// deep-copied and freed without synchronization, so concurrent error raises
1967// on different threads double-freed the mirror strings. The parallel lib
1968// test suite observed this as `double free or corruption (!prev)` aborts
1969// (xml::errors tests racing with any other raising thread). The writers are
1970// serialized via LAST_ERROR_MIRROR_LOCK; these courts hammer the exact
1971// interleavings and must complete without crashing.
1972
1973#[cfg(test)]
1974mod tests {
1975    use super::*;
1976    use crate::abi::allocator::xmlMallocImpl;
1977    use crate::abi::structs::_xmlError;
1978    use crate::xml::globals;
1979    use core::ptr;
1980
1981    /// Allocate a NUL-terminated C string owned by xmlMallocImpl (the same
1982    /// allocator the thread-local error slot uses).
1983    ///
1984    /// # Safety
1985    ///
1986    /// - `s` must be a valid string; the returned pointer is
1987    ///   allocator-owned, valid for `bytes.len() + 1` bytes, and must be
1988    ///   freed with `xmlFreeImpl` exactly once.
1989    unsafe fn alloc_cstr(s: &str) -> *mut c_char {
1990        let bytes = s.as_bytes();
1991        let p = unsafe { xmlMallocImpl(bytes.len() + 1) as *mut c_char };
1992        assert!(!p.is_null(), "alloc_cstr: xmlMallocImpl failed");
1993        unsafe {
1994            ptr::copy_nonoverlapping(bytes.as_ptr(), p as *mut u8, bytes.len());
1995            *((p as *mut u8).add(bytes.len())) = 0;
1996        }
1997        p
1998    }
1999
2000    /// Build an owned `_xmlError` with distinct string fields.
2001    ///
2002    /// # Safety
2003    ///
2004    /// - Every `message`/`file`/`str1` field is an `alloc_cstr`
2005    ///   allocation that the returned struct owns; the caller must free
2006    ///   them with `xmlFreeImpl` exactly once (e.g. via
2007    ///   `reset_last_error`), or the fields leak.
2008    unsafe fn build_error(tag: &str) -> _xmlError {
2009        _xmlError {
2010            domain: 1,
2011            code: 2,
2012            message: unsafe { alloc_cstr(&format!("msg {tag}")) },
2013            level: 3,
2014            file: unsafe { alloc_cstr(&format!("file {tag}")) },
2015            line: 4,
2016            str1: unsafe { alloc_cstr(&format!("str1 {tag}")) },
2017            str2: ptr::null_mut(),
2018            str3: ptr::null_mut(),
2019            int1: 0,
2020            int2: 0,
2021            ctxt: ptr::null_mut(),
2022            node: ptr::null_mut(),
2023        }
2024    }
2025
2026    /// Concurrent sync/reset hammer: one thread raises errors while another
2027    /// resets. Before the mirror lock this double-freed the shared strings;
2028    /// the test crashes (SIGABRT) under the old code and passes now.
2029    ///
2030    /// # Safety
2031    ///
2032    /// - `build_error` allocates owned string fields with `xmlFreeImpl`;
2033    ///   `set_last_error` deep-copies them into the mirror and the
2034    ///   thread-local slot, and `reset_last_error` frees each copy exactly
2035    ///   once; the mirror is internally serialized by `LAST_ERROR_MIRROR_LOCK`.
2036    #[test]
2037    fn test_last_error_mirror_concurrent_sync_reset() {
2038        let sync = std::thread::spawn(|| {
2039            for i in 0..400 {
2040                unsafe { globals::set_last_error(build_error(&format!("sync {i}"))) };
2041            }
2042        });
2043        let reset = std::thread::spawn(|| {
2044            for _ in 0..400 {
2045                globals::reset_last_error();
2046            }
2047        });
2048        sync.join().unwrap();
2049        reset.join().unwrap();
2050        // Leave the mirror in a clean state for later tests. (No thread-local
2051        // assertion: the harness reuses OS threads across tests, so a prior
2052        // test's error may legitimately live in this thread's slot.)
2053        globals::reset_last_error();
2054    }
2055
2056    /// Many threads raising concurrently (the full parallel-suite shape that
2057    /// originally aborted in `test_encode_entities_reentrant_*` victims).
2058    ///
2059    /// # Safety
2060    ///
2061    /// - Each thread's `build_error` allocations are owned by the
2062    ///   thread-local last-error slot and the locked mirror; every
2063    ///   `set_last_error`/`reset_last_error` pair frees owned strings
2064    ///   exactly once, and the final `reset_last_error` leaves the mirror
2065    ///   clean.
2066    #[test]
2067    fn test_last_error_mirror_many_threads() {
2068        let mut handles = Vec::new();
2069        for t in 0..8 {
2070            handles.push(std::thread::spawn(move || {
2071                for i in 0..150 {
2072                    unsafe { globals::set_last_error(build_error(&format!("t{t} i{i}"))) };
2073                    if i % 7 == 0 {
2074                        globals::reset_last_error();
2075                    }
2076                }
2077            }));
2078        }
2079        for h in handles {
2080            h.join().unwrap();
2081        }
2082        globals::reset_last_error();
2083    }
2084}