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")]
340#[no_mangle]
341pub unsafe extern "C" fn xmlGenericErrorDefaultFunc() -> c_int {
342    unsafe {
343        core::arch::asm!(
344            "sub rsp, 240",
345            "mov [rsp+0], rdi",
346            "mov [rsp+8], rsi",
347            "mov [rsp+16], rdx",
348            "mov [rsp+24], rcx",
349            "mov [rsp+32], r8",
350            "mov [rsp+40], r9",
351            "movaps [rsp+48], xmm0",
352            "movaps [rsp+64], xmm1",
353            "movaps [rsp+80], xmm2",
354            "movaps [rsp+96], xmm3",
355            "movaps [rsp+112], xmm4",
356            "movaps [rsp+128], xmm5",
357            "movaps [rsp+144], xmm6",
358            "movaps [rsp+160], xmm7",
359            "mov dword ptr [rsp+176], 16",
360            "mov dword ptr [rsp+180], 48",
361            "lea rax, [rsp+256]",
362            "mov [rsp+184], rax",
363            "lea rax, [rsp]",
364            "mov [rsp+192], rax",
365            "lea rdx, [rsp+176]",
366            "call xmlGenericErrorDefaultFuncV",
367            "add rsp, 240",
368            "add rsp, 8",
369            "ret",
370            options(noreturn),
371        );
372    }
373}
374
375/// Default value of the exported `xmlGenericError` data global.
376#[cfg(target_arch = "x86_64")]
377#[doc(hidden)]
378pub(crate) const XML_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
379    // SAFETY: the shim and the function-pointer type have identical ABI
380    // (a code pointer); the declared arity is a Rust-side fiction required
381    // to store a variadic entry in the non-variadic pointer type.
382    core::mem::transmute::<
383        unsafe extern "C" fn() -> c_int,
384        unsafe extern "C" fn(*mut c_void, *const c_char),
385    >(xmlGenericErrorDefaultFunc)
386};
387
388/// Variadic receiver for the `xsltGenericErrorDefaultFunc` shim (upstream
389/// xsltutils.c semantics: default the context to stderr, then `vfprintf`).
390///
391/// # SAFETY
392///
393/// - `_ctx`, `ap` must be valid pointers (or NULL
394///   where the upstream C contract allows), obtained from the
395///   matching constructor/owner and not yet freed; the callee may
396///   take or keep ownership exactly as the C API specifies.
397///
398/// - `msg` must point to valid NUL-terminated
399///   strings (or NULL where the C contract allows) for the lifetime
400///   of the call.
401///
402/// The caller must not race this call with concurrent mutation of the
403/// same objects from other threads (per-object state is not internally
404/// synchronized). Violating any of the above is undefined behavior.
405///
406/// Exercised by the C-API differential courts
407/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
408/// courts; those pass byte-for-byte against the upstream oracle.
409#[cfg(target_arch = "x86_64")]
410#[no_mangle]
411pub unsafe extern "C" fn xsltGenericErrorDefaultFuncV(
412    _ctx: *mut c_void,
413    msg: *const c_char,
414    ap: *mut VaListTag,
415) -> c_int {
416    unsafe {
417        if crate::abi::data_globals::xsltGenericErrorContext.is_null() {
418            crate::abi::data_globals::xsltGenericErrorContext = stderr_file();
419        }
420        let stream = crate::abi::data_globals::xsltGenericErrorContext;
421        if msg.is_null() || stream.is_null() {
422            return 0;
423        }
424        vfprintf(stream, msg, ap)
425    }
426}
427
428/// `xsltGenericErrorDefaultFunc(void *ctx, const char *msg, ...)` — the
429/// upstream default XSLT error handler (xsltutils.c).
430///
431/// # SAFETY
432///
433/// The function touches crate-global state only; it is safe
434/// as long as the caller respects the library's global
435/// initialization/cleanup ordering (xmlInitParser before use,
436/// xmlCleanupParser only after all users are done).
437///
438/// Violating the global lifecycle ordering, or calling this after
439/// teardown or from a signal handler, is undefined behavior.
440#[cfg(target_arch = "x86_64")]
441pub unsafe extern "C" fn xsltGenericErrorDefaultFunc() -> c_int {
442    unsafe {
443        core::arch::asm!(
444            "sub rsp, 240",
445            "mov [rsp+0], rdi",
446            "mov [rsp+8], rsi",
447            "mov [rsp+16], rdx",
448            "mov [rsp+24], rcx",
449            "mov [rsp+32], r8",
450            "mov [rsp+40], r9",
451            "movaps [rsp+48], xmm0",
452            "movaps [rsp+64], xmm1",
453            "movaps [rsp+80], xmm2",
454            "movaps [rsp+96], xmm3",
455            "movaps [rsp+112], xmm4",
456            "movaps [rsp+128], xmm5",
457            "movaps [rsp+144], xmm6",
458            "movaps [rsp+160], xmm7",
459            "mov dword ptr [rsp+176], 16",
460            "mov dword ptr [rsp+180], 48",
461            "lea rax, [rsp+256]",
462            "mov [rsp+184], rax",
463            "lea rax, [rsp]",
464            "mov [rsp+192], rax",
465            "lea rdx, [rsp+176]",
466            "call xsltGenericErrorDefaultFuncV",
467            "add rsp, 240",
468            "add rsp, 8",
469            "ret",
470            options(noreturn),
471        );
472    }
473}
474
475/// Default value of the exported `xsltGenericError` data global.
476#[cfg(target_arch = "x86_64")]
477const XSLT_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
478    // SAFETY: as above — ABI-identical code pointer.
479    core::mem::transmute::<
480        unsafe extern "C" fn() -> c_int,
481        unsafe extern "C" fn(*mut c_void, *const c_char),
482    >(xsltGenericErrorDefaultFunc)
483};
484
485/// The built-in default generic error handler (upstream
486/// `xmlGenericErrorDefaultFunc`), for use when a caller resets the handler
487/// with NULL. Only available on x86_64 (the variadic shim is SysV-specific);
488/// on other targets there is no default (resets leave the handler unset).
489pub fn default_generic_error_func() -> Option<xmlGenericErrorFunc> {
490    #[cfg(target_arch = "x86_64")]
491    {
492        Some(XML_GENERIC_ERROR_DEFAULT)
493    }
494    #[cfg(not(target_arch = "x86_64"))]
495    {
496        None
497    }
498}
499
500/// The built-in default XSLT generic error handler (upstream
501/// `xsltGenericErrorDefaultFunc`), mirroring the initial value of the
502/// exported `xsltGenericError` static.
503pub fn xslt_default_generic_error_func() -> Option<xmlGenericErrorFunc> {
504    #[cfg(target_arch = "x86_64")]
505    {
506        Some(XSLT_GENERIC_ERROR_DEFAULT)
507    }
508    #[cfg(not(target_arch = "x86_64"))]
509    {
510        None
511    }
512}
513
514// ═══════════════════════════════════════════════════════════════════════════════
515// Static strings (upstream xmlstring.h / tree.c)
516// ═══════════════════════════════════════════════════════════════════════════════
517
518/// `const xmlChar xmlStringText[]` — "text"
519#[no_mangle]
520pub static xmlStringText: [xmlChar; 5] = [b't', b'e', b'x', b't', 0];
521
522/// `const xmlChar xmlStringTextNoenc[]` — "textnoenc"
523#[no_mangle]
524pub static xmlStringTextNoenc: [xmlChar; 9] = *b"textnoenc";
525
526/// `const xmlChar xmlStringComment[]` — "comment"
527#[no_mangle]
528pub static xmlStringComment: [xmlChar; 8] = [b'c', b'o', b'm', b'm', b'e', b'n', b't', 0];
529
530// ═══════════════════════════════════════════════════════════════════════════════
531// XPath numeric constants (upstream xpath.c)
532// ═══════════════════════════════════════════════════════════════════════════════
533
534/// `double xmlXPathNAN` — NaN
535#[no_mangle]
536pub static xmlXPathNAN: f64 = f64::NAN;
537
538/// `double xmlXPathPINF` — +infinity
539#[no_mangle]
540pub static xmlXPathPINF: f64 = f64::INFINITY;
541
542/// `double xmlXPathNINF` — -infinity
543#[no_mangle]
544pub static xmlXPathNINF: f64 = f64::NEG_INFINITY;
545
546// ═══════════════════════════════════════════════════════════════════════════════
547// libxslt globals (upstream xslt.c / xsltutils.c / documents.c / xslt.h)
548// ═══════════════════════════════════════════════════════════════════════════════
549// xsltMaxDepth / xsltMaxVars are exported from src/xslt/transform/mod.rs
550// (they are read by the transform engine).
551
552/// `const int xsltLibxmlVersion` = 21501 — the libxml2 version the system
553/// libxslt 1.1.45 was compiled against (upstream xslt.c `LIBXML_VERSION`;
554/// the system libxslt was built against libxml2 2.15.1). Byte-parity with
555/// the oracle DSO, read directly by `xsltproc -V`.
556#[no_mangle]
557pub static xsltLibxmlVersion: c_int = 21501;
558
559/// `const int xsltLibxsltVersion` = 10145 — the libxslt version
560/// (upstream xslt.c `XSLTPUBVAR const int xsltLibxsltVersion = LIBXSLT_VERSION`;
561/// oracle DSO symbol type R). Was previously exported as a function (T)
562/// — R-000167.
563#[no_mangle]
564pub static xsltLibxsltVersion: c_int = 10145;
565
566/// `const char *xsltEngineVersion` = "10145-GITv1.1.45" — the libxslt engine
567/// version string (upstream xslt.c `XSLTPUBVAR const char *xsltEngineVersion`;
568/// oracle DSO symbol type D). Was previously exported as a function (T)
569/// — R-000167. `static mut` follows the xmlParserVersion pattern (raw
570/// pointers are not Sync).
571#[no_mangle]
572pub static mut xsltEngineVersion: *const c_char = {
573    static S: [u8; 17] = *b"10145-GITv1.1.45\0";
574    S.as_ptr() as *const c_char
575};
576
577/// `const char *exsltLibraryVersion` = "825-GITv1.1.45" — the libexslt
578/// library version string (upstream exslt.c `EXSLTPUBVAR const char *`;
579/// oracle DSO symbol type D). Read by `xsltproc -V`.
580#[no_mangle]
581pub static mut exsltLibraryVersion: *const c_char = {
582    static S: [u8; 15] = *b"825-GITv1.1.45\0";
583    S.as_ptr() as *const c_char
584};
585
586/// `const int exsltLibexsltVersion` = 825 — the libexslt version
587/// (upstream exslt.h `EXSLTPUBVAR const int`; oracle DSO symbol type R).
588#[no_mangle]
589pub static exsltLibexsltVersion: c_int = 825;
590
591/// `const int exsltLibxmlVersion` = 21501 — the libxml2 version the system
592/// libexslt 0.8.25 was compiled against (oracle DSO symbol type R).
593#[no_mangle]
594pub static exsltLibxmlVersion: c_int = 21501;
595
596/// `const int exsltLibxsltVersion` = 10145 — the libxslt version the system
597/// libexslt 0.8.25 was compiled against (oracle DSO symbol type R).
598#[no_mangle]
599pub static exsltLibxsltVersion: c_int = 10145;
600
601/// `int xslDebugStatus` — the libxslt debugger status (upstream xsltutils.c
602/// `XSLTPUBVAR int xslDebugStatus;`, default XSLT_DEBUG_NONE = 0; oracle DSO
603/// symbol type B). Written by `xsltSetDebuggerStatus`; read by the transform
604/// engine's profiling gates (R-000165 DATA_MISSING closure).
605#[no_mangle]
606pub static mut xslDebugStatus: c_int = 0;
607
608/// `xmlGenericErrorFunc xsltGenericError` — the libxslt error callback.
609/// Upstream defaults to `xsltGenericErrorDefaultFunc` (xsltutils.c, a variadic
610/// stderr printer); the candidate's shim below reproduces it (R-000135
611/// divergence now closed).
612#[cfg(target_arch = "x86_64")]
613#[no_mangle]
614pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = Some(XSLT_GENERIC_ERROR_DEFAULT);
615
616#[cfg(not(target_arch = "x86_64"))]
617#[no_mangle]
618pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = None;
619
620/// `void *xsltGenericErrorContext` (default NULL)
621#[no_mangle]
622pub static mut xsltGenericErrorContext: *mut c_void = core::ptr::null_mut();
623
624/// `void *xsltGenericDebugContext` (default NULL)
625#[no_mangle]
626pub static mut xsltGenericDebugContext: *mut c_void = core::ptr::null_mut();
627
628/// `xmlGenericErrorFunc xsltGenericDebug` — the libxslt debug callback.
629/// Upstream defaults to `xsltGenericDebugDefaultFunc` (xsltutils.c:632), a
630/// handler that writes the message to `xsltGenericDebugContext` (a FILE*)
631/// when that context is non-NULL. The candidate reproduces the default via
632/// `xsltGenericDebugDefaultFunc` below. Was previously exported as a
633/// FUNCTION (T) — an R-000167-class ABI type divergence closed in 11.1-Z.1
634/// (oracle DSO symbol type D, `nm -D /usr/lib/libxslt.so.1`).
635#[no_mangle]
636pub static mut xsltGenericDebug: Option<xmlGenericErrorFunc> = Some(XSLT_GENERIC_DEBUG_DEFAULT);
637
638/// Default debug handler reproducing upstream `xsltGenericDebugDefaultFunc`
639/// (xsltutils.c:621): no-op unless `xsltGenericDebugContext` is non-NULL,
640/// then the (pre-formatted) message is written to that context FILE*.
641/// Upstream's handler is variadic; the candidate's callers format the
642/// message before dispatch (same documented divergence as xsltGenericError).
643///
644/// # Safety
645///
646/// - `msg` must be NULL or a valid NUL-terminated string valid for the
647///   call; when `xsltGenericDebugContext` is non-NULL it must be a valid
648///   `FILE*` writable via `fwrite` for the message length.
649unsafe extern "C" fn xsltGenericDebugDefaultFunc(_ctx: *mut c_void, msg: *const c_char) {
650    if msg.is_null() {
651        return;
652    }
653    let dctx = unsafe { xsltGenericDebugContext };
654    if dctx.is_null() {
655        return;
656    }
657    let len = unsafe { libc::strlen(msg) };
658    unsafe {
659        libc::fwrite(msg as *const libc::c_void, 1, len, dctx as *mut libc::FILE);
660    }
661}
662
663/// Default value of the exported `xsltGenericDebug` data global.
664const XSLT_GENERIC_DEBUG_DEFAULT: xmlGenericErrorFunc = xsltGenericDebugDefaultFunc;
665
666/// `const xmlChar xsltExtMarker[]` — empty string used to mark extension
667/// nodes (upstream transform.c).
668#[no_mangle]
669pub static xsltExtMarker: [xmlChar; 1] = [0];
670
671/// Upstream `xsltDocDefaultLoaderFunc` (documents.c): parse the URI as a
672/// document with the given parser options — a DIRECT parse that never
673/// re-enters the registered loader. Consumers capture the exported data
674/// global at load time (lxml's `_xslt_doc_loader` falls back to
675/// `XSLT_DOC_DEFAULT_LOADER` when its resolver cannot handle a URI) and
676/// must get a real function, not NULL.
677///
678/// # SAFETY
679///
680/// - `uri` must be NULL or a valid NUL-terminated string.
681unsafe extern "C" fn xslt_doc_default_loader_func(
682    uri: *const xmlChar,
683    _dict: *mut c_void,
684    options: c_int,
685    _ctxt: *mut c_void,
686    _kind: c_int,
687) -> *mut crate::abi::structs::_xmlDoc {
688    if uri.is_null() {
689        return ptr::null_mut();
690    }
691    crate::abi::exports_xml2::xmlReadFile(uri as *const c_char, ptr::null(), options)
692}
693
694/// `xsltDocLoaderFunc xsltDocDefaultLoader` — the document loader callback.
695/// Upstream initializes it to `xsltDocDefaultLoaderFunc`; the candidate
696/// mirrors that with the direct-parsing default above (R-000135: the
697/// previous NULL default made lxml's import-time capture of this data
698/// global jump to address zero when its resolver fell back to the default
699/// loader).
700#[no_mangle]
701pub static mut xsltDocDefaultLoader: Option<xsltDocLoaderFunc> = Some(xslt_doc_default_loader_func);
702
703// ═══════════════════════════════════════════════════════════════════════════════
704// I/O filename callback globals (upstream xmlIO.h) — THREAD-LOCAL since
705// Phase 13 (see tls.rs); the `xml*Default` accessor functions below read/write
706// the per-thread slots.
707// ═══════════════════════════════════════════════════════════════════════════════
708
709// ═══════════════════════════════════════════════════════════════════════════════
710// Default SAX v1 handler structs + locator (upstream globals.c 2.15.3)
711// ═══════════════════════════════════════════════════════════════════════════════
712//
713// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` and `htmlDefaultSAXHandler`
714// (parser.h / HTMLparser.h), plus `const xmlSAXLocator xmlDefaultSAXLocator`.
715// The handler instances reproduce the upstream initializer lists exactly
716// (globals.c); every referenced xmlSAX2* entry point is a real candidate
717// export.
718
719/// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` (globals.c 2.15.3).
720///
721/// `#[no_mangle]` data symbol that C consumers (PHP's DOM/XML extensions)
722/// MUTATE (they overwrite the error/warning callbacks on the shared default
723/// handler). Rust places non-`mut` statics in read-only memory; a write to
724/// it would segfault. Make it `static mut` so it lives in writable data.
725#[no_mangle]
726pub static mut xmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
727    crate::abi::structs::_xmlSAXHandlerV1 {
728        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
729        isStandalone: Some(crate::abi::exports_xml2::xmlSAX2IsStandalone),
730        hasInternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasInternalSubset),
731        hasExternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasExternalSubset),
732        resolveEntity: Some(crate::abi::exports_xml2::xmlSAX2ResolveEntity),
733        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
734        entityDecl: Some(crate::abi::exports_xml2::xmlSAX2EntityDecl),
735        notationDecl: Some(crate::abi::exports_xml2::xmlSAX2NotationDecl),
736        attributeDecl: Some(crate::abi::exports_xml2::xmlSAX2AttributeDecl),
737        elementDecl: Some(crate::abi::exports_xml2::xmlSAX2ElementDecl),
738        unparsedEntityDecl: Some(crate::abi::exports_xml2::xmlSAX2UnparsedEntityDecl),
739        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
740        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
741        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
742        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
743        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
744        reference: Some(crate::abi::exports_xml2::xmlSAX2Reference),
745        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
746        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
747        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
748        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
749        warning: Some(crate::xml::errors::XML_PARSER_WARNING_SAX1),
750        error: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
751        fatalError: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
752        getParameterEntity: Some(crate::abi::exports_xml2::xmlSAX2GetParameterEntity),
753        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
754        externalSubset: Some(crate::abi::exports_xml2::xmlSAX2ExternalSubset),
755        initialized: 1,
756    };
757
758/// `const xmlSAXHandlerV1 htmlDefaultSAXHandler` (globals.c 2.15.3).
759///
760/// Mutable (see xmlDefaultSAXHandler) so C consumers (PHP) that overwrite
761/// the shared default handler's callbacks don't fault on read-only memory.
762#[no_mangle]
763pub static mut htmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
764    crate::abi::structs::_xmlSAXHandlerV1 {
765        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
766        isStandalone: None,
767        hasInternalSubset: None,
768        hasExternalSubset: None,
769        resolveEntity: None,
770        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
771        entityDecl: None,
772        notationDecl: None,
773        attributeDecl: None,
774        elementDecl: None,
775        unparsedEntityDecl: None,
776        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
777        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
778        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
779        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
780        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
781        reference: None,
782        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
783        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
784        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
785        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
786        warning: Some(crate::xml::errors::XML_PARSER_WARNING_SAX1),
787        error: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
788        fatalError: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
789        getParameterEntity: None,
790        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
791        externalSubset: None,
792        initialized: 1,
793    };
794
795/// `const xmlSAXLocator xmlDefaultSAXLocator` (globals.c 2.15.3).
796#[no_mangle]
797pub static xmlDefaultSAXLocator: crate::abi::callbacks::_xmlSAXLocator =
798    crate::abi::callbacks::_xmlSAXLocator {
799        getPublicId: Some(crate::abi::exports_xml2::xmlSAX2GetPublicId),
800        getSystemId: Some(crate::abi::exports_xml2::xmlSAX2GetSystemId),
801        getLineNumber: Some(crate::abi::exports_xml2::xmlSAX2GetLineNumber),
802        getColumnNumber: Some(crate::abi::exports_xml2::xmlSAX2GetColumnNumber),
803    };
804
805// ═══════════════════════════════════════════════════════════════════════════════
806// Legacy 2.9.14 data globals (distro drop-in, R-000180)
807// ═══════════════════════════════════════════════════════════════════════════════
808//
809// The executed oracle (2.15.3) exports only the `__xmlXxx()` accessor FUNCTIONS
810// for the mutable parser/error globals (see the Phase 13 TLS note above). The
811// distro DSO (2.9.14) ALSO exports the deprecated plain *data* symbols for
812// ABI backward compatibility. The versioned `libxml2.so.2` drop-in profile must
813// re-export them; they are legacy main-thread mirrors and the candidate's
814// accessors keep routing through the per-thread cells.
815
816/// `xmlGenericErrorFunc xmlGenericError` (default `xmlGenericErrorDefaultFunc`).
817#[no_mangle]
818pub static mut xmlGenericError: Option<xmlGenericErrorFunc> = Some(XML_GENERIC_ERROR_DEFAULT);
819
820/// `xmlStructuredErrorFunc xmlStructuredError` (default NULL).
821#[no_mangle]
822pub static mut xmlStructuredError: Option<xmlStructuredErrorFunc> = None;
823
824/// `void *xmlGenericErrorContext` (default NULL).
825#[no_mangle]
826pub static mut xmlGenericErrorContext: *mut c_void = core::ptr::null_mut();
827
828/// `void *xmlStructuredErrorContext` (default NULL).
829#[no_mangle]
830pub static mut xmlStructuredErrorContext: *mut c_void = core::ptr::null_mut();
831
832/// `int xmlDoValidityCheckingDefaultValue` (default 0).
833#[no_mangle]
834pub static mut xmlDoValidityCheckingDefaultValue: c_int = 0;
835
836/// `int xmlGetWarningsDefaultValue` (default 1).
837#[no_mangle]
838pub static mut xmlGetWarningsDefaultValue: c_int = 1;
839
840/// `int xmlKeepBlanksDefaultValue` (default 1).
841#[no_mangle]
842pub static mut xmlKeepBlanksDefaultValue: c_int = 1;
843
844/// `int xmlLineNumbersDefaultValue` (default 0).
845#[no_mangle]
846pub static mut xmlLineNumbersDefaultValue: c_int = 0;
847
848/// `int xmlLoadExtDtdDefaultValue` (default 0).
849#[no_mangle]
850pub static mut xmlLoadExtDtdDefaultValue: c_int = 0;
851
852/// `int xmlPedanticParserDefaultValue` (default 0).
853#[no_mangle]
854pub static mut xmlPedanticParserDefaultValue: c_int = 0;
855
856/// `int xmlSubstituteEntitiesDefaultValue` (default 0).
857#[no_mangle]
858pub static mut xmlSubstituteEntitiesDefaultValue: c_int = 0;
859
860/// `int xmlIndentTreeOutput` (default 1).
861#[no_mangle]
862pub static mut xmlIndentTreeOutput: c_int = 1;
863
864/// `int xmlSaveNoEmptyTags` (default 0).
865#[no_mangle]
866pub static mut xmlSaveNoEmptyTags: c_int = 0;
867
868/// `const char *xmlTreeIndentString` (default "  ").
869#[no_mangle]
870pub static mut xmlTreeIndentString: *const c_char = {
871    static S: [u8; 3] = *b"  \0";
872    S.as_ptr() as *const c_char
873};
874
875/// `xmlRegisterNodeFunc xmlRegisterNodeDefaultValue` (default NULL).
876#[no_mangle]
877pub static mut xmlRegisterNodeDefaultValue: Option<
878    unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode),
879> = None;
880
881/// `xmlDeregisterNodeFunc xmlDeregisterNodeDefaultValue` (default NULL).
882#[no_mangle]
883pub static mut xmlDeregisterNodeDefaultValue: Option<
884    unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode),
885> = None;
886
887/// `xmlParserInputBufferCreateFilenameFunc
888/// xmlParserInputBufferCreateFilenameValue` (default NULL).
889#[no_mangle]
890pub static mut xmlParserInputBufferCreateFilenameValue: Option<
891    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
892> = None;
893
894/// `xmlOutputBufferCreateFilenameFunc xmlOutputBufferCreateFilenameValue`
895/// (default NULL).
896#[no_mangle]
897pub static mut xmlOutputBufferCreateFilenameValue: Option<
898    unsafe extern "C" fn(
899        *const c_char,
900        *mut crate::abi::structs::_xmlCharEncodingHandler,
901        c_int,
902    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
903> = None;
904
905/// `int oldXMLWDcompatibility` (default 0, DEPRECATED).
906#[no_mangle]
907pub static mut oldXMLWDcompatibility: c_int = 0;
908
909/// `int __xmlRegisterCallbacks` (default 0) — arms the node register/deregister
910/// hooks once a callback has been installed (globals.c).
911#[no_mangle]
912pub static mut __xmlRegisterCallbacks: c_int = 0;
913
914/// `xmlSAXHandlerV1 docbDefaultSAXHandler` (globals.c 2.9.14) — the SGML
915/// DocBook SAX1 default handler; mirrors the upstream initializer list.
916#[no_mangle]
917pub static mut docbDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
918    crate::abi::structs::_xmlSAXHandlerV1 {
919        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
920        isStandalone: Some(crate::abi::exports_xml2::xmlSAX2IsStandalone),
921        hasInternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasInternalSubset),
922        hasExternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasExternalSubset),
923        resolveEntity: Some(crate::abi::exports_xml2::xmlSAX2ResolveEntity),
924        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
925        entityDecl: Some(crate::abi::exports_xml2::xmlSAX2EntityDecl),
926        notationDecl: None,
927        attributeDecl: None,
928        elementDecl: None,
929        unparsedEntityDecl: None,
930        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
931        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
932        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
933        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
934        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
935        reference: Some(crate::abi::exports_xml2::xmlSAX2Reference),
936        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
937        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
938        processingInstruction: None,
939        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
940        warning: Some(crate::xml::errors::XML_PARSER_WARNING_SAX1),
941        error: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
942        fatalError: Some(crate::xml::errors::XML_PARSER_ERROR_SAX1),
943        getParameterEntity: Some(crate::abi::exports_xml2::xmlSAX2GetParameterEntity),
944        cdataBlock: None,
945        externalSubset: None,
946        initialized: 1,
947    };
948
949// ═══════════════════════════════════════════════════════════════════════════════
950// xmlLastError — the exported C global mirror of the thread-local error state
951// ═══════════════════════════════════════════════════════════════════════════════
952//
953// Upstream `XMLPUBVAR xmlError xmlLastError` (xmlerror.h). The candidate's
954// internal error state is thread-local (safe divergence, more correct than
955// upstream's racy global); this mirror is deep-copied on every error raise
956// and freed on reset, so C consumers observe upstream-equivalent lifetime
957// semantics. Residual R-000135.
958
959/// `xmlError xmlLastError` — most recent error (mirror).
960#[no_mangle]
961pub static mut xmlLastError: crate::abi::structs::_xmlError = crate::abi::structs::_xmlError {
962    domain: 0,
963    code: 0,
964    message: core::ptr::null_mut(),
965    level: 0,
966    file: core::ptr::null_mut(),
967    line: 0,
968    str1: core::ptr::null_mut(),
969    str2: core::ptr::null_mut(),
970    str3: core::ptr::null_mut(),
971    int1: 0,
972    int2: 0,
973    ctxt: core::ptr::null_mut(),
974    node: core::ptr::null_mut(),
975};
976
977/// Deep-copy `err` into the exported `xmlLastError` global.
978///
979/// The string fields are copied with `libc::malloc`/`memcpy` so the mirror
980/// owns them (previous mirror strings are freed first — upstream
981/// xmlResetError semantics).
982///
983/// # SAFETY
984///
985/// - `err` must point to a valid `_xmlError` whose string fields are
986///   NUL-terminated or NULL.
987pub unsafe fn sync_xml_last_error(err: *const crate::abi::structs::_xmlError) {
988    if err.is_null() {
989        return;
990    }
991    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
992    unsafe { sync_xml_last_error_locked(err) };
993}
994
995/// Mirror write helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
996///
997/// # Safety
998///
999/// - `err` must be non-NULL and point to a valid `_xmlError` whose string
1000///   fields are NULL or NUL-terminated; the caller must hold
1001///   `LAST_ERROR_MIRROR_LOCK` so the `xmlLastError` global is not read or
1002///   written concurrently; each field is deep-copied with `dup_cstr`.
1003unsafe fn sync_xml_last_error_locked(err: *const crate::abi::structs::_xmlError) {
1004    unsafe {
1005        reset_xml_last_error_locked();
1006        let src = &*err;
1007        let dst = core::ptr::addr_of_mut!(xmlLastError);
1008        (*dst).domain = src.domain;
1009        (*dst).code = src.code;
1010        (*dst).level = src.level;
1011        (*dst).line = src.line;
1012        (*dst).int1 = src.int1;
1013        (*dst).int2 = src.int2;
1014        (*dst).ctxt = src.ctxt;
1015        (*dst).node = src.node;
1016        (*dst).message = dup_cstr(src.message as *const u8);
1017        (*dst).file = dup_cstr(src.file as *const u8);
1018        (*dst).str1 = dup_cstr(src.str1 as *const u8);
1019        (*dst).str2 = dup_cstr(src.str2 as *const u8);
1020        (*dst).str3 = dup_cstr(src.str3 as *const u8);
1021    }
1022}
1023
1024/// Reset the exported `xmlLastError` global, freeing owned strings
1025/// (upstream xmlResetError).
1026///
1027/// # SAFETY
1028///
1029/// Only call while no other thread is reading the global (upstream has the
1030/// same race; documented).
1031pub unsafe fn reset_xml_last_error() {
1032    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
1033    unsafe { reset_xml_last_error_locked() };
1034}
1035
1036/// Mirror reset helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
1037unsafe fn reset_xml_last_error_locked() {
1038    unsafe {
1039        let dst = core::ptr::addr_of_mut!(xmlLastError);
1040        if !(*dst).message.is_null() {
1041            libc::free((*dst).message as *mut libc::c_void);
1042        }
1043        if !(*dst).file.is_null() {
1044            libc::free((*dst).file as *mut libc::c_void);
1045        }
1046        if !(*dst).str1.is_null() {
1047            libc::free((*dst).str1 as *mut libc::c_void);
1048        }
1049        if !(*dst).str2.is_null() {
1050            libc::free((*dst).str2 as *mut libc::c_void);
1051        }
1052        if !(*dst).str3.is_null() {
1053            libc::free((*dst).str3 as *mut libc::c_void);
1054        }
1055        *dst = crate::abi::structs::_xmlError {
1056            domain: 0,
1057            code: 0,
1058            message: core::ptr::null_mut(),
1059            level: 0,
1060            file: core::ptr::null_mut(),
1061            line: 0,
1062            str1: core::ptr::null_mut(),
1063            str2: core::ptr::null_mut(),
1064            str3: core::ptr::null_mut(),
1065            int1: 0,
1066            int2: 0,
1067            ctxt: core::ptr::null_mut(),
1068            node: core::ptr::null_mut(),
1069        };
1070    }
1071}
1072
1073/// Heap-copy a NUL-terminated string (NULL-safe).
1074///
1075/// # Safety
1076///
1077/// - `s` must be NULL or a valid NUL-terminated string readable via
1078///   `strlen` for the duration of the copy; the returned pointer is
1079///   `libc::malloc`-owned and must be freed with `libc::free`, or is NULL
1080///   when `s` is NULL or the allocation fails.
1081unsafe fn dup_cstr(s: *const u8) -> *mut c_char {
1082    if s.is_null() {
1083        return core::ptr::null_mut();
1084    }
1085    unsafe {
1086        let len = libc::strlen(s as *const libc::c_char) as usize;
1087        let p = libc::malloc(len + 1) as *mut u8;
1088        if p.is_null() {
1089            return core::ptr::null_mut();
1090        }
1091        libc::memcpy(p as *mut libc::c_void, s as *const libc::c_void, len + 1);
1092        p as *mut c_char
1093    }
1094}
1095
1096// ═══════════════════════════════════════════════════════════════════════════════
1097// Default accessor functions (upstream parser.h / tree.h / xmlsave.h)
1098// ═══════════════════════════════════════════════════════════════════════════════
1099//
1100// The deprecated `xmlXxxDefault(v)` accessors STORE the argument
1101// unconditionally (0 and 1 alike) and RETURN THE PREVIOUS value — upstream
1102// semantics verified against the executed 2.15.3 oracle (parserInternals.c
1103// xmlKeepBlanksDefault / xmlSubstituteEntitiesDefault /
1104// xmlPedanticParserDefault; tree.c xmlRegisterNodeDefault). Note the plain
1105// setter and its xmlThrDef* twin differ from the other deprecated
1106// conditional-set globals: `xmlThrDefLineNumbersDefaultValue` is a no-op
1107// returning 1.
1108
1109/// Upstream `xmlKeepBlanksDefault(int v)`.
1110///
1111/// # SAFETY
1112///
1113/// The function touches crate-global state only; it is safe
1114/// as long as the caller respects the library's global
1115/// initialization/cleanup ordering (xmlInitParser before use,
1116/// xmlCleanupParser only after all users are done).
1117///
1118/// Violating the global lifecycle ordering, or calling this after
1119/// teardown or from a signal handler, is undefined behavior.
1120#[no_mangle]
1121pub unsafe extern "C" fn xmlKeepBlanksDefault(v: c_int) -> c_int {
1122    // UPSTREAM-PARITY (parserInternals.c xmlKeepBlanksDefault): the value is
1123    // STORED UNCONDITIONALLY (0 included) and the previous value is returned.
1124    // The candidate's earlier conditional guard made xmlKeepBlanksDefault(0)
1125    // a no-op, breaking fresh-context whitespace suppression (R-000177 /
1126    // dso-state-coherence probe p1 profile).
1127    let old = crate::xml::globals::get_keep_blanks_default();
1128    crate::xml::globals::set_keep_blanks_default(v);
1129    old
1130}
1131
1132/// Upstream `xmlLineNumbersDefault(int v)`.
1133///
1134/// # SAFETY
1135///
1136/// The function touches crate-global state only; it is safe
1137/// as long as the caller respects the library's global
1138/// initialization/cleanup ordering (xmlInitParser before use,
1139/// xmlCleanupParser only after all users are done).
1140///
1141/// Violating the global lifecycle ordering, or calling this after
1142/// teardown or from a signal handler, is undefined behavior.
1143#[no_mangle]
1144pub unsafe extern "C" fn xmlLineNumbersDefault(_v: c_int) -> c_int {
1145    // UPSTREAM-PARITY (parserInternals.c xmlLineNumbersDefault): "Has no
1146    // effect. Line numbers are always enabled." — always returns 1.
1147    1
1148}
1149
1150/// Upstream `xmlSubstituteEntitiesDefault(int v)`.
1151///
1152/// # SAFETY
1153///
1154/// The function touches crate-global state only; it is safe
1155/// as long as the caller respects the library's global
1156/// initialization/cleanup ordering (xmlInitParser before use,
1157/// xmlCleanupParser only after all users are done).
1158///
1159/// Violating the global lifecycle ordering, or calling this after
1160/// teardown or from a signal handler, is undefined behavior.
1161#[no_mangle]
1162pub unsafe extern "C" fn xmlSubstituteEntitiesDefault(v: c_int) -> c_int {
1163    // UPSTREAM-PARITY (parserInternals.c xmlSubstituteEntitiesDefault):
1164    // stored unconditionally, previous value returned.
1165    let old = crate::xml::globals::get_substitute_entities_default();
1166    crate::xml::globals::set_substitute_entities_default(v);
1167    old
1168}
1169
1170/// Upstream `xmlPedanticParserDefault(int v)`.
1171///
1172/// # SAFETY
1173///
1174/// The function touches crate-global state only; it is safe
1175/// as long as the caller respects the library's global
1176/// initialization/cleanup ordering (xmlInitParser before use,
1177/// xmlCleanupParser only after all users are done).
1178///
1179/// Violating the global lifecycle ordering, or calling this after
1180/// teardown or from a signal handler, is undefined behavior.
1181#[no_mangle]
1182pub unsafe extern "C" fn xmlPedanticParserDefault(v: c_int) -> c_int {
1183    // UPSTREAM-PARITY (parserInternals.c xmlPedanticParserDefault): stored
1184    // unconditionally, previous value returned.
1185    let old = crate::xml::globals::get_pedantic_parser_default();
1186    crate::xml::globals::set_pedantic_parser_default(v);
1187    old
1188}
1189
1190/// Upstream `xmlDoValidityCheckingDefaultValue` accessor is the global
1191/// itself; `xmlGetWarningsDefaultValue` likewise (no accessor functions
1192/// exist for those in upstream 2.15).
1193/// Upstream `xmlRegisterNodeDefault(xmlRegisterNodeFunc func)`.
1194///
1195/// # SAFETY
1196///
1197///
1198/// - `func` must be a valid callback (or None);
1199///   the callback is invoked with the documented context pointer and
1200///   must itself uphold the same pointer invariants.
1201///
1202/// The caller must not race this call with concurrent mutation of the
1203/// same objects from other threads (per-object state is not internally
1204/// synchronized). Violating any of the above is undefined behavior.
1205///
1206/// Exercised by the C-API differential courts
1207/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1208/// courts; those pass byte-for-byte against the upstream oracle.
1209#[no_mangle]
1210pub unsafe extern "C" fn xmlRegisterNodeDefault(
1211    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1212) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1213    // UPSTREAM-PARITY (tree.c xmlRegisterNodeDefault): the callback is
1214    // stored UNCONDITIONALLY (NULL clears it), the gate is armed on every
1215    // call and the PREVIOUS callback is returned.
1216    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1217    let old = crate::xml::globals::get_register_node_default();
1218    crate::xml::globals::set_register_node_default(func);
1219    old
1220}
1221
1222/// Upstream `xmlDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
1223///
1224/// # SAFETY
1225///
1226///
1227/// - `func` must be a valid callback (or None);
1228///   the callback is invoked with the documented context pointer and
1229///   must itself uphold the same pointer invariants.
1230///
1231/// The caller must not race this call with concurrent mutation of the
1232/// same objects from other threads (per-object state is not internally
1233/// synchronized). Violating any of the above is undefined behavior.
1234///
1235/// Exercised by the C-API differential courts
1236/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1237/// courts; those pass byte-for-byte against the upstream oracle.
1238#[no_mangle]
1239pub unsafe extern "C" fn xmlDeregisterNodeDefault(
1240    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1241) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1242    // UPSTREAM-PARITY (tree.c xmlDeregisterNodeDefault): stored
1243    // unconditionally (NULL clears it), gate armed, previous value returned.
1244    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1245    let old = crate::xml::globals::get_deregister_node_default();
1246    crate::xml::globals::set_deregister_node_default(func);
1247    old
1248}
1249
1250/// Upstream `__xmlIndentTreeOutput(void)` (parser.h) — returns a pointer to
1251/// the `xmlIndentTreeOutput` global.
1252///
1253/// # SAFETY
1254///
1255/// The function touches crate-global state only; it is safe
1256/// as long as the caller respects the library's global
1257/// initialization/cleanup ordering (xmlInitParser before use,
1258/// xmlCleanupParser only after all users are done).
1259///
1260/// Violating the global lifecycle ordering, or calling this after
1261/// teardown or from a signal handler, is undefined behavior.
1262#[no_mangle]
1263pub unsafe extern "C" fn __xmlIndentTreeOutput() -> *mut c_int {
1264    crate::xml::globals::indent_tree_output_ptr()
1265}
1266
1267/// Upstream `__xmlSaveNoEmptyTags(void)` (parser.h) — returns a pointer to
1268/// the `xmlSaveNoEmptyTags` global.
1269///
1270/// # SAFETY
1271///
1272/// The function touches crate-global state only; it is safe
1273/// as long as the caller respects the library's global
1274/// initialization/cleanup ordering (xmlInitParser before use,
1275/// xmlCleanupParser only after all users are done).
1276///
1277/// Violating the global lifecycle ordering, or calling this after
1278/// teardown or from a signal handler, is undefined behavior.
1279#[no_mangle]
1280pub unsafe extern "C" fn __xmlSaveNoEmptyTags() -> *mut c_int {
1281    crate::xml::globals::save_no_empty_tags_ptr()
1282}
1283
1284/// Upstream `__xmlTreeIndentString(void)` (parser.h) — returns a pointer to
1285/// the `xmlTreeIndentString` global.
1286///
1287/// # SAFETY
1288///
1289/// The function touches crate-global state only; it is safe
1290/// as long as the caller respects the library's global
1291/// initialization/cleanup ordering (xmlInitParser before use,
1292/// xmlCleanupParser only after all users are done).
1293///
1294/// Violating the global lifecycle ordering, or calling this after
1295/// teardown or from a signal handler, is undefined behavior.
1296#[no_mangle]
1297pub unsafe extern "C" fn __xmlTreeIndentString() -> *mut *const xmlChar {
1298    crate::xml::globals::tree_indent_string_ptr()
1299}
1300
1301// ═══════════════════════════════════════════════════════════════════════════════
1302// xmlThrDef* accessors (upstream threads.c / globals.c)
1303// ═══════════════════════════════════════════════════════════════════════════════
1304//
1305// The deprecated `xmlThrDef*` family (upstream globals.c, LIBXML_THREAD_
1306// ENABLED builds): every member STORES the argument unconditionally (0
1307// included) under the xmlThrDefMutex and returns the PREVIOUS value — with
1308// two exceptions: `xmlThrDefLineNumbersDefaultValue` is a no-op returning 1,
1309// and the node-register twins arm xmlRegisterCallbacks and store the
1310// callback (NULL clears). Verified against the executed 2.15.3 oracle.
1311
1312/// Upstream `xmlThrDefDoValidityCheckingDefaultValue(int v)`.
1313///
1314/// # SAFETY
1315///
1316/// The function touches crate-global state only; it is safe
1317/// as long as the caller respects the library's global
1318/// initialization/cleanup ordering (xmlInitParser before use,
1319/// xmlCleanupParser only after all users are done).
1320///
1321/// Violating the global lifecycle ordering, or calling this after
1322/// teardown or from a signal handler, is undefined behavior.
1323#[no_mangle]
1324pub unsafe extern "C" fn xmlThrDefDoValidityCheckingDefaultValue(v: c_int) -> c_int {
1325    let old = crate::xml::globals::get_validity_checking_default();
1326    crate::xml::globals::set_validity_checking_default(v);
1327    old
1328}
1329
1330/// Upstream `xmlThrDefGetWarningsDefaultValue(int v)`.
1331///
1332/// # SAFETY
1333///
1334/// The function touches crate-global state only; it is safe
1335/// as long as the caller respects the library's global
1336/// initialization/cleanup ordering (xmlInitParser before use,
1337/// xmlCleanupParser only after all users are done).
1338///
1339/// Violating the global lifecycle ordering, or calling this after
1340/// teardown or from a signal handler, is undefined behavior.
1341#[no_mangle]
1342pub unsafe extern "C" fn xmlThrDefGetWarningsDefaultValue(v: c_int) -> c_int {
1343    let old = crate::xml::globals::get_get_warnings_default();
1344    crate::xml::globals::set_get_warnings_default(v);
1345    old
1346}
1347
1348/// Upstream `xmlThrDefLoadExtDtdDefaultValue(int v)`.
1349///
1350/// # SAFETY
1351///
1352/// The function touches crate-global state only; it is safe
1353/// as long as the caller respects the library's global
1354/// initialization/cleanup ordering (xmlInitParser before use,
1355/// xmlCleanupParser only after all users are done).
1356///
1357/// Violating the global lifecycle ordering, or calling this after
1358/// teardown or from a signal handler, is undefined behavior.
1359#[no_mangle]
1360pub unsafe extern "C" fn xmlThrDefLoadExtDtdDefaultValue(v: c_int) -> c_int {
1361    let old = crate::xml::globals::get_load_ext_dtd_default();
1362    crate::xml::globals::set_load_ext_dtd_default(v);
1363    old
1364}
1365
1366/// Upstream `xmlThrDefPedanticParserDefaultValue(int v)`.
1367///
1368/// # SAFETY
1369///
1370/// The function touches crate-global state only; it is safe
1371/// as long as the caller respects the library's global
1372/// initialization/cleanup ordering (xmlInitParser before use,
1373/// xmlCleanupParser only after all users are done).
1374///
1375/// Violating the global lifecycle ordering, or calling this after
1376/// teardown or from a signal handler, is undefined behavior.
1377#[no_mangle]
1378pub unsafe extern "C" fn xmlThrDefPedanticParserDefaultValue(v: c_int) -> c_int {
1379    let old = crate::xml::globals::get_pedantic_parser_default();
1380    crate::xml::globals::set_pedantic_parser_default(v);
1381    old
1382}
1383
1384/// Upstream `xmlThrDefLineNumbersDefaultValue(int v)`.
1385///
1386/// # SAFETY
1387///
1388/// The function touches crate-global state only; it is safe
1389/// as long as the caller respects the library's global
1390/// initialization/cleanup ordering (xmlInitParser before use,
1391/// xmlCleanupParser only after all users are done).
1392///
1393/// Violating the global lifecycle ordering, or calling this after
1394/// teardown or from a signal handler, is undefined behavior.
1395#[no_mangle]
1396pub unsafe extern "C" fn xmlThrDefLineNumbersDefaultValue(_v: c_int) -> c_int {
1397    // UPSTREAM-PARITY (globals.c xmlThrDefLineNumbersDefaultValue): no-op,
1398    // always returns 1 (line numbers are always enabled).
1399    1
1400}
1401
1402/// Upstream `xmlThrDefKeepBlanksDefaultValue(int v)`.
1403///
1404/// # SAFETY
1405///
1406/// The function touches crate-global state only; it is safe
1407/// as long as the caller respects the library's global
1408/// initialization/cleanup ordering (xmlInitParser before use,
1409/// xmlCleanupParser only after all users are done).
1410///
1411/// Violating the global lifecycle ordering, or calling this after
1412/// teardown or from a signal handler, is undefined behavior.
1413#[no_mangle]
1414pub unsafe extern "C" fn xmlThrDefKeepBlanksDefaultValue(v: c_int) -> c_int {
1415    let old = crate::xml::globals::get_keep_blanks_default();
1416    crate::xml::globals::set_keep_blanks_default(v);
1417    old
1418}
1419
1420/// Upstream `xmlThrDefSubstituteEntitiesDefaultValue(int v)`.
1421///
1422/// # SAFETY
1423///
1424/// The function touches crate-global state only; it is safe
1425/// as long as the caller respects the library's global
1426/// initialization/cleanup ordering (xmlInitParser before use,
1427/// xmlCleanupParser only after all users are done).
1428///
1429/// Violating the global lifecycle ordering, or calling this after
1430/// teardown or from a signal handler, is undefined behavior.
1431#[no_mangle]
1432pub unsafe extern "C" fn xmlThrDefSubstituteEntitiesDefaultValue(v: c_int) -> c_int {
1433    let old = crate::xml::globals::get_substitute_entities_default();
1434    crate::xml::globals::set_substitute_entities_default(v);
1435    old
1436}
1437
1438/// Upstream `xmlThrDefParserDebugEntities(int v)`.
1439///
1440/// # SAFETY
1441///
1442/// The function touches crate-global state only; it is safe
1443/// as long as the caller respects the library's global
1444/// initialization/cleanup ordering (xmlInitParser before use,
1445/// xmlCleanupParser only after all users are done).
1446///
1447/// Violating the global lifecycle ordering, or calling this after
1448/// teardown or from a signal handler, is undefined behavior.
1449#[no_mangle]
1450pub unsafe extern "C" fn xmlThrDefParserDebugEntities(v: c_int) -> c_int {
1451    unsafe {
1452        if v != 0 {
1453            xmlParserDebugEntities = v;
1454        }
1455        xmlParserDebugEntities
1456    }
1457}
1458
1459/// Upstream `xmlThrDefIndentTreeOutput(int v)`.
1460///
1461/// # SAFETY
1462///
1463/// The function touches crate-global state only; it is safe
1464/// as long as the caller respects the library's global
1465/// initialization/cleanup ordering (xmlInitParser before use,
1466/// xmlCleanupParser only after all users are done).
1467///
1468/// Violating the global lifecycle ordering, or calling this after
1469/// teardown or from a signal handler, is undefined behavior.
1470#[no_mangle]
1471pub unsafe extern "C" fn xmlThrDefIndentTreeOutput(v: c_int) -> c_int {
1472    let old = crate::xml::globals::get_indent_tree_output();
1473    crate::xml::globals::set_indent_tree_output(v);
1474    old
1475}
1476
1477/// Upstream `xmlThrDefTreeIndentString(const char *v)` — sets the indent
1478/// string when non-NULL and returns the current pointer.
1479///
1480/// # SAFETY
1481///
1482///
1483/// - `v` must point to valid NUL-terminated
1484///   strings (or NULL where the C contract allows) for the lifetime
1485///   of the call.
1486///
1487/// The caller must not race this call with concurrent mutation of the
1488/// same objects from other threads (per-object state is not internally
1489/// synchronized). Violating any of the above is undefined behavior.
1490///
1491/// Exercised by the C-API differential courts
1492/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1493/// courts; those pass byte-for-byte against the upstream oracle.
1494#[no_mangle]
1495pub unsafe extern "C" fn xmlThrDefTreeIndentString(v: *const c_char) -> *const c_char {
1496    // UPSTREAM-PARITY (globals.c xmlThrDefTreeIndentString): stored
1497    // unconditionally (NULL clears), previous pointer returned.
1498    let old = crate::xml::globals::get_tree_indent_string() as *const c_char;
1499    crate::xml::globals::set_tree_indent_string(v as *const crate::abi::types::xmlChar);
1500    old
1501}
1502
1503/// Upstream `xmlThrDefSaveNoEmptyTags(int v)`.
1504///
1505/// # SAFETY
1506///
1507/// The function touches crate-global state only; it is safe
1508/// as long as the caller respects the library's global
1509/// initialization/cleanup ordering (xmlInitParser before use,
1510/// xmlCleanupParser only after all users are done).
1511///
1512/// Violating the global lifecycle ordering, or calling this after
1513/// teardown or from a signal handler, is undefined behavior.
1514#[no_mangle]
1515pub unsafe extern "C" fn xmlThrDefSaveNoEmptyTags(v: c_int) -> c_int {
1516    let old = crate::xml::globals::get_save_no_empty_tags();
1517    crate::xml::globals::set_save_no_empty_tags(v);
1518    old
1519}
1520
1521/// Upstream `xmlThrDefRegisterNodeDefault(xmlRegisterNodeFunc func)`.
1522///
1523/// # SAFETY
1524///
1525///
1526/// - `func` must be a valid callback (or None);
1527///   the callback is invoked with the documented context pointer and
1528///   must itself uphold the same pointer invariants.
1529///
1530/// The caller must not race this call with concurrent mutation of the
1531/// same objects from other threads (per-object state is not internally
1532/// synchronized). Violating any of the above is undefined behavior.
1533///
1534/// Exercised by the C-API differential courts
1535/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1536/// courts; those pass byte-for-byte against the upstream oracle.
1537#[no_mangle]
1538pub unsafe extern "C" fn xmlThrDefRegisterNodeDefault(
1539    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1540) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1541    // UPSTREAM-PARITY (globals.c xmlThrDefRegisterNodeDefault): arms the
1542    // gate, stores the callback unconditionally (NULL clears), returns the
1543    // previous one.
1544    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1545    let old = crate::xml::globals::get_register_node_default();
1546    crate::xml::globals::set_register_node_default(func);
1547    old
1548}
1549
1550/// Upstream `xmlThrDefDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
1551///
1552/// # SAFETY
1553///
1554///
1555/// - `func` must be a valid callback (or None);
1556///   the callback is invoked with the documented context pointer and
1557///   must itself uphold the same pointer invariants.
1558///
1559/// The caller must not race this call with concurrent mutation of the
1560/// same objects from other threads (per-object state is not internally
1561/// synchronized). Violating any of the above is undefined behavior.
1562///
1563/// Exercised by the C-API differential courts
1564/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1565/// courts; those pass byte-for-byte against the upstream oracle.
1566#[no_mangle]
1567pub unsafe extern "C" fn xmlThrDefDeregisterNodeDefault(
1568    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1569) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1570    // UPSTREAM-PARITY (globals.c xmlThrDefDeregisterNodeDefault): arms the
1571    // gate, stores the callback unconditionally (NULL clears), returns the
1572    // previous one.
1573    XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
1574    let old = crate::xml::globals::get_deregister_node_default();
1575    crate::xml::globals::set_deregister_node_default(func);
1576    old
1577}
1578
1579/// Upstream `xmlThrDefSetGenericErrorFunc(void *ctx, xmlGenericErrorFunc func)`.
1580///
1581/// # SAFETY
1582///
1583/// - `ctx` must be valid pointers (or NULL
1584///   where the upstream C contract allows), obtained from the
1585///   matching constructor/owner and not yet freed; the callee may
1586///   take or keep ownership exactly as the C API specifies.
1587///
1588/// - `func` must be a valid callback (or None);
1589///   the callback is invoked with the documented context pointer and
1590///   must itself uphold the same pointer invariants.
1591///
1592/// The caller must not race this call with concurrent mutation of the
1593/// same objects from other threads (per-object state is not internally
1594/// synchronized). Violating any of the above is undefined behavior.
1595///
1596/// Exercised by the C-API differential courts
1597/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1598/// courts; those pass byte-for-byte against the upstream oracle.
1599#[no_mangle]
1600pub unsafe extern "C" fn xmlThrDefSetGenericErrorFunc(
1601    ctx: *mut c_void,
1602    func: Option<xmlGenericErrorFunc>,
1603) {
1604    crate::xml::globals::set_generic_error_func(ctx, func);
1605}
1606
1607/// Upstream `xmlThrDefSetStructuredErrorFunc(void *ctx, xmlStructuredErrorFunc func)`.
1608///
1609/// # SAFETY
1610///
1611/// - `ctx` must be valid pointers (or NULL
1612///   where the upstream C contract allows), obtained from the
1613///   matching constructor/owner and not yet freed; the callee may
1614///   take or keep ownership exactly as the C API specifies.
1615///
1616/// - `func` must be a valid callback (or None);
1617///   the callback is invoked with the documented context pointer and
1618///   must itself uphold the same pointer invariants.
1619///
1620/// The caller must not race this call with concurrent mutation of the
1621/// same objects from other threads (per-object state is not internally
1622/// synchronized). Violating any of the above is undefined behavior.
1623///
1624/// Exercised by the C-API differential courts
1625/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1626/// courts; those pass byte-for-byte against the upstream oracle.
1627#[no_mangle]
1628pub unsafe extern "C" fn xmlThrDefSetStructuredErrorFunc(
1629    ctx: *mut c_void,
1630    func: Option<xmlStructuredErrorFunc>,
1631) {
1632    crate::xml::globals::set_structured_error_func(ctx, func);
1633}
1634
1635/// Upstream `xmlThrDefDefaultBufferSize(int v)`.
1636///
1637/// # SAFETY
1638///
1639/// The function touches crate-global state only; it is safe
1640/// as long as the caller respects the library's global
1641/// initialization/cleanup ordering (xmlInitParser before use,
1642/// xmlCleanupParser only after all users are done).
1643///
1644/// Violating the global lifecycle ordering, or calling this after
1645/// teardown or from a signal handler, is undefined behavior.
1646#[no_mangle]
1647pub unsafe extern "C" fn xmlThrDefDefaultBufferSize(v: c_int) -> c_int {
1648    unsafe {
1649        if v != 0 {
1650            xmlDefaultBufferSize = v;
1651        }
1652        xmlDefaultBufferSize
1653    }
1654}
1655
1656/// Upstream `xmlThrDefBufferAllocScheme(xmlBufferAllocationScheme v)`.
1657///
1658/// # SAFETY
1659///
1660/// The function touches crate-global state only; it is safe
1661/// as long as the caller respects the library's global
1662/// initialization/cleanup ordering (xmlInitParser before use,
1663/// xmlCleanupParser only after all users are done).
1664///
1665/// Violating the global lifecycle ordering, or calling this after
1666/// teardown or from a signal handler, is undefined behavior.
1667#[no_mangle]
1668pub unsafe extern "C" fn xmlThrDefBufferAllocScheme(v: c_int) -> c_int {
1669    unsafe {
1670        if v != 0 {
1671            xmlBufferAllocScheme = v;
1672        }
1673        xmlBufferAllocScheme
1674    }
1675}
1676
1677/// Upstream `xmlThrDefParserInputBufferCreateFilenameDefault(...)`.
1678///
1679/// # SAFETY
1680///
1681///
1682/// - `func` must be a valid callback (or None);
1683///   the callback is invoked with the documented context pointer and
1684///   must itself uphold the same pointer invariants.
1685///
1686/// The caller must not race this call with concurrent mutation of the
1687/// same objects from other threads (per-object state is not internally
1688/// synchronized). Violating any of the above is undefined behavior.
1689///
1690/// Exercised by the C-API differential courts
1691/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1692/// courts; those pass byte-for-byte against the upstream oracle.
1693#[no_mangle]
1694pub unsafe extern "C" fn xmlThrDefParserInputBufferCreateFilenameDefault(
1695    func: Option<
1696        unsafe extern "C" fn(
1697            *const c_char,
1698            c_int,
1699        ) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1700    >,
1701) -> Option<
1702    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1703> {
1704    if func.is_some() {
1705        crate::xml::globals::set_parser_input_buffer_create_filename_value(func);
1706    }
1707    crate::xml::globals::get_parser_input_buffer_create_filename_value()
1708}
1709
1710/// Upstream `xmlThrDefOutputBufferCreateFilenameDefault(...)`.
1711///
1712/// # SAFETY
1713///
1714///
1715/// - `func` must be a valid callback (or None);
1716///   the callback is invoked with the documented context pointer and
1717///   must itself uphold the same pointer invariants.
1718///
1719/// The caller must not race this call with concurrent mutation of the
1720/// same objects from other threads (per-object state is not internally
1721/// synchronized). Violating any of the above is undefined behavior.
1722///
1723/// Exercised by the C-API differential courts
1724/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1725/// courts; those pass byte-for-byte against the upstream oracle.
1726#[no_mangle]
1727pub unsafe extern "C" fn xmlThrDefOutputBufferCreateFilenameDefault(
1728    func: Option<
1729        unsafe extern "C" fn(
1730            *const c_char,
1731            crate::abi::structs::xmlCharEncodingHandlerPtr,
1732            c_int,
1733        ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1734    >,
1735) -> Option<
1736    unsafe extern "C" fn(
1737        *const c_char,
1738        crate::abi::structs::xmlCharEncodingHandlerPtr,
1739        c_int,
1740    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1741> {
1742    if func.is_some() {
1743        crate::xml::globals::set_output_buffer_create_filename_value(func);
1744    }
1745    crate::xml::globals::get_output_buffer_create_filename_value()
1746}
1747
1748// ═══════════════════════════════════════════════════════════════════════════════
1749// __xmlXxx() pointer accessors (upstream threads.c / globals.c)
1750// ═══════════════════════════════════════════════════════════════════════════════
1751// The deprecated thread-local API exports one `__xmlXxx(void)` accessor per
1752// global; each returns a pointer to the global so callers can read/write it.
1753
1754/// Upstream `__xmlBufferAllocScheme(void)` — returns a pointer to `xmlBufferAllocScheme`.
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 __xmlBufferAllocScheme() -> *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    core::ptr::addr_of_mut!(xmlBufferAllocScheme)
1770}
1771
1772/// Upstream `__xmlDefaultBufferSize(void)` — returns a pointer to `xmlDefaultBufferSize`.
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 __xmlDefaultBufferSize() -> *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    core::ptr::addr_of_mut!(xmlDefaultBufferSize)
1788}
1789
1790/// Upstream `__xmlDeregisterNodeDefaultValue(void)` — returns a pointer to `xmlDeregisterNodeDefaultValue`.
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 __xmlDeregisterNodeDefaultValue(
1803) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1804    // SAFETY: returning a pointer to an exported static; the caller may
1805    // read/write it exactly as with upstream's deprecated accessor.
1806    crate::xml::globals::deregister_node_ptr()
1807}
1808
1809/// Upstream `__xmlDoValidityCheckingDefaultValue(void)` — returns a pointer to `xmlDoValidityCheckingDefaultValue`.
1810///
1811/// # SAFETY
1812///
1813/// The function touches crate-global state only; it is safe
1814/// as long as the caller respects the library's global
1815/// initialization/cleanup ordering (xmlInitParser before use,
1816/// xmlCleanupParser only after all users are done).
1817///
1818/// Violating the global lifecycle ordering, or calling this after
1819/// teardown or from a signal handler, is undefined behavior.
1820#[no_mangle]
1821pub unsafe extern "C" fn __xmlDoValidityCheckingDefaultValue() -> *mut c_int {
1822    // SAFETY: returning a pointer to an exported static; the caller may
1823    // read/write it exactly as with upstream's deprecated accessor.
1824    crate::xml::globals::do_validity_ptr()
1825}
1826
1827/// Upstream `__xmlGenericError(void)` — returns a pointer to `xmlGenericError`.
1828///
1829/// # SAFETY
1830///
1831/// The function touches crate-global state only; it is safe
1832/// as long as the caller respects the library's global
1833/// initialization/cleanup ordering (xmlInitParser before use,
1834/// xmlCleanupParser only after all users are done).
1835///
1836/// Violating the global lifecycle ordering, or calling this after
1837/// teardown or from a signal handler, is undefined behavior.
1838#[no_mangle]
1839pub unsafe extern "C" fn __xmlGenericError() -> *mut Option<xmlGenericErrorFunc> {
1840    // SAFETY: returning a pointer to an exported static; the caller may
1841    // read/write it exactly as with upstream's deprecated accessor.
1842    crate::xml::globals::generic_error_ptr()
1843}
1844
1845/// Upstream `__xmlGenericErrorContext(void)` — returns a pointer to `xmlGenericErrorContext`.
1846///
1847/// # SAFETY
1848///
1849/// The function touches crate-global state only; it is safe
1850/// as long as the caller respects the library's global
1851/// initialization/cleanup ordering (xmlInitParser before use,
1852/// xmlCleanupParser only after all users are done).
1853///
1854/// Violating the global lifecycle ordering, or calling this after
1855/// teardown or from a signal handler, is undefined behavior.
1856#[no_mangle]
1857pub unsafe extern "C" fn __xmlGenericErrorContext() -> *mut *mut c_void {
1858    // SAFETY: returning a pointer to an exported static; the caller may
1859    // read/write it exactly as with upstream's deprecated accessor.
1860    crate::xml::globals::generic_error_ctx_ptr()
1861}
1862
1863/// Upstream `__xmlGetWarningsDefaultValue(void)` — returns a pointer to `xmlGetWarningsDefaultValue`.
1864///
1865/// # SAFETY
1866///
1867/// The function touches crate-global state only; it is safe
1868/// as long as the caller respects the library's global
1869/// initialization/cleanup ordering (xmlInitParser before use,
1870/// xmlCleanupParser only after all users are done).
1871///
1872/// Violating the global lifecycle ordering, or calling this after
1873/// teardown or from a signal handler, is undefined behavior.
1874#[no_mangle]
1875pub unsafe extern "C" fn __xmlGetWarningsDefaultValue() -> *mut c_int {
1876    // SAFETY: returning a pointer to an exported static; the caller may
1877    // read/write it exactly as with upstream's deprecated accessor.
1878    crate::xml::globals::get_warnings_ptr()
1879}
1880
1881/// Upstream `__xmlKeepBlanksDefaultValue(void)` — returns a pointer to `xmlKeepBlanksDefaultValue`.
1882///
1883/// # SAFETY
1884///
1885/// The function touches crate-global state only; it is safe
1886/// as long as the caller respects the library's global
1887/// initialization/cleanup ordering (xmlInitParser before use,
1888/// xmlCleanupParser only after all users are done).
1889///
1890/// Violating the global lifecycle ordering, or calling this after
1891/// teardown or from a signal handler, is undefined behavior.
1892#[no_mangle]
1893pub unsafe extern "C" fn __xmlKeepBlanksDefaultValue() -> *mut c_int {
1894    // SAFETY: returning a pointer to an exported static; the caller may
1895    // read/write it exactly as with upstream's deprecated accessor.
1896    crate::xml::globals::keep_blanks_ptr()
1897}
1898
1899/// Upstream `__xmlLineNumbersDefaultValue(void)` — returns a pointer to `xmlLineNumbersDefaultValue`.
1900///
1901/// # SAFETY
1902///
1903/// The function touches crate-global state only; it is safe
1904/// as long as the caller respects the library's global
1905/// initialization/cleanup ordering (xmlInitParser before use,
1906/// xmlCleanupParser only after all users are done).
1907///
1908/// Violating the global lifecycle ordering, or calling this after
1909/// teardown or from a signal handler, is undefined behavior.
1910#[no_mangle]
1911pub unsafe extern "C" fn __xmlLineNumbersDefaultValue() -> *mut c_int {
1912    // SAFETY: returning a pointer to an exported static; the caller may
1913    // read/write it exactly as with upstream's deprecated accessor.
1914    crate::xml::globals::line_numbers_ptr()
1915}
1916
1917/// Upstream `__xmlLoadExtDtdDefaultValue(void)` — returns a pointer to `xmlLoadExtDtdDefaultValue`.
1918///
1919/// # SAFETY
1920///
1921/// The function touches crate-global state only; it is safe
1922/// as long as the caller respects the library's global
1923/// initialization/cleanup ordering (xmlInitParser before use,
1924/// xmlCleanupParser only after all users are done).
1925///
1926/// Violating the global lifecycle ordering, or calling this after
1927/// teardown or from a signal handler, is undefined behavior.
1928#[no_mangle]
1929pub unsafe extern "C" fn __xmlLoadExtDtdDefaultValue() -> *mut c_int {
1930    // SAFETY: returning a pointer to an exported static; the caller may
1931    // read/write it exactly as with upstream's deprecated accessor.
1932    crate::xml::globals::load_ext_dtd_ptr()
1933}
1934
1935/// Upstream `__xmlOutputBufferCreateFilenameValue(void)` — returns a pointer to `xmlOutputBufferCreateFilenameValue`.
1936///
1937/// # SAFETY
1938///
1939/// The function touches crate-global state only; it is safe
1940/// as long as the caller respects the library's global
1941/// initialization/cleanup ordering (xmlInitParser before use,
1942/// xmlCleanupParser only after all users are done).
1943///
1944/// Violating the global lifecycle ordering, or calling this after
1945/// teardown or from a signal handler, is undefined behavior.
1946#[no_mangle]
1947pub unsafe extern "C" fn __xmlOutputBufferCreateFilenameValue() -> *mut Option<
1948    unsafe extern "C" fn(
1949        *const c_char,
1950        crate::abi::structs::xmlCharEncodingHandlerPtr,
1951        c_int,
1952    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1953> {
1954    // SAFETY: returning a pointer to an exported static; the caller may
1955    // read/write it exactly as with upstream's deprecated accessor.
1956    crate::xml::globals::output_create_filename_ptr()
1957}
1958
1959/// Upstream `__xmlParserDebugEntities(void)` — returns a pointer to `xmlParserDebugEntities`.
1960///
1961/// # SAFETY
1962///
1963/// The function touches crate-global state only; it is safe
1964/// as long as the caller respects the library's global
1965/// initialization/cleanup ordering (xmlInitParser before use,
1966/// xmlCleanupParser only after all users are done).
1967///
1968/// Violating the global lifecycle ordering, or calling this after
1969/// teardown or from a signal handler, is undefined behavior.
1970#[no_mangle]
1971pub unsafe extern "C" fn __xmlParserDebugEntities() -> *mut c_int {
1972    // SAFETY: returning a pointer to an exported static; the caller may
1973    // read/write it exactly as with upstream's deprecated accessor.
1974    core::ptr::addr_of_mut!(xmlParserDebugEntities)
1975}
1976
1977/// Upstream `__xmlParserInputBufferCreateFilenameValue(void)` — returns a pointer to `xmlParserInputBufferCreateFilenameValue`.
1978///
1979/// # SAFETY
1980///
1981/// The function touches crate-global state only; it is safe
1982/// as long as the caller respects the library's global
1983/// initialization/cleanup ordering (xmlInitParser before use,
1984/// xmlCleanupParser only after all users are done).
1985///
1986/// Violating the global lifecycle ordering, or calling this after
1987/// teardown or from a signal handler, is undefined behavior.
1988#[no_mangle]
1989pub unsafe extern "C" fn __xmlParserInputBufferCreateFilenameValue() -> *mut Option<
1990    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1991> {
1992    // SAFETY: returning a pointer to an exported static; the caller may
1993    // read/write it exactly as with upstream's deprecated accessor.
1994    crate::xml::globals::parser_input_create_filename_ptr()
1995}
1996
1997/// Upstream `__xmlParserVersion(void)` — returns a pointer to `xmlParserVersion`.
1998///
1999/// # SAFETY
2000///
2001/// The function touches crate-global state only; it is safe
2002/// as long as the caller respects the library's global
2003/// initialization/cleanup ordering (xmlInitParser before use,
2004/// xmlCleanupParser only after all users are done).
2005///
2006/// Violating the global lifecycle ordering, or calling this after
2007/// teardown or from a signal handler, is undefined behavior.
2008#[no_mangle]
2009pub unsafe extern "C" fn __xmlParserVersion() -> *mut *const c_char {
2010    // SAFETY: returning a pointer to an exported static; the caller may
2011    // read/write it exactly as with upstream's deprecated accessor.
2012    core::ptr::addr_of_mut!(xmlParserVersion)
2013}
2014
2015/// Upstream `__xmlPedanticParserDefaultValue(void)` — returns a pointer to `xmlPedanticParserDefaultValue`.
2016///
2017/// # SAFETY
2018///
2019/// The function touches crate-global state only; it is safe
2020/// as long as the caller respects the library's global
2021/// initialization/cleanup ordering (xmlInitParser before use,
2022/// xmlCleanupParser only after all users are done).
2023///
2024/// Violating the global lifecycle ordering, or calling this after
2025/// teardown or from a signal handler, is undefined behavior.
2026#[no_mangle]
2027pub unsafe extern "C" fn __xmlPedanticParserDefaultValue() -> *mut c_int {
2028    // SAFETY: returning a pointer to an exported static; the caller may
2029    // read/write it exactly as with upstream's deprecated accessor.
2030    crate::xml::globals::pedantic_ptr()
2031}
2032
2033/// Upstream `__xmlRegisterNodeDefaultValue(void)` — returns a pointer to `xmlRegisterNodeDefaultValue`.
2034///
2035/// # SAFETY
2036///
2037/// The function touches crate-global state only; it is safe
2038/// as long as the caller respects the library's global
2039/// initialization/cleanup ordering (xmlInitParser before use,
2040/// xmlCleanupParser only after all users are done).
2041///
2042/// Violating the global lifecycle ordering, or calling this after
2043/// teardown or from a signal handler, is undefined behavior.
2044#[no_mangle]
2045pub unsafe extern "C" fn __xmlRegisterNodeDefaultValue(
2046) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
2047    // SAFETY: returning a pointer to an exported static; the caller may
2048    // read/write it exactly as with upstream's deprecated accessor.
2049    crate::xml::globals::register_node_ptr()
2050}
2051
2052/// Upstream `__xmlStructuredError(void)` — returns a pointer to `xmlStructuredError`.
2053///
2054/// # SAFETY
2055///
2056/// The function touches crate-global state only; it is safe
2057/// as long as the caller respects the library's global
2058/// initialization/cleanup ordering (xmlInitParser before use,
2059/// xmlCleanupParser only after all users are done).
2060///
2061/// Violating the global lifecycle ordering, or calling this after
2062/// teardown or from a signal handler, is undefined behavior.
2063#[no_mangle]
2064pub unsafe extern "C" fn __xmlStructuredError() -> *mut Option<xmlStructuredErrorFunc> {
2065    // SAFETY: returning a pointer to an exported static; the caller may
2066    // read/write it exactly as with upstream's deprecated accessor.
2067    crate::xml::globals::structured_error_ptr()
2068}
2069
2070/// Upstream `__xmlStructuredErrorContext(void)` — returns a pointer to `xmlStructuredErrorContext`.
2071///
2072/// # SAFETY
2073///
2074/// The function touches crate-global state only; it is safe
2075/// as long as the caller respects the library's global
2076/// initialization/cleanup ordering (xmlInitParser before use,
2077/// xmlCleanupParser only after all users are done).
2078///
2079/// Violating the global lifecycle ordering, or calling this after
2080/// teardown or from a signal handler, is undefined behavior.
2081#[no_mangle]
2082pub unsafe extern "C" fn __xmlStructuredErrorContext() -> *mut *mut c_void {
2083    // SAFETY: returning a pointer to an exported static; the caller may
2084    // read/write it exactly as with upstream's deprecated accessor.
2085    crate::xml::globals::structured_error_ctx_ptr()
2086}
2087
2088/// Upstream `__xmlSubstituteEntitiesDefaultValue(void)` — returns a pointer to `xmlSubstituteEntitiesDefaultValue`.
2089///
2090/// # SAFETY
2091///
2092/// The function touches crate-global state only; it is safe
2093/// as long as the caller respects the library's global
2094/// initialization/cleanup ordering (xmlInitParser before use,
2095/// xmlCleanupParser only after all users are done).
2096///
2097/// Violating the global lifecycle ordering, or calling this after
2098/// teardown or from a signal handler, is undefined behavior.
2099#[no_mangle]
2100pub unsafe extern "C" fn __xmlSubstituteEntitiesDefaultValue() -> *mut c_int {
2101    // SAFETY: returning a pointer to an exported static; the caller may
2102    // read/write it exactly as with upstream's deprecated accessor.
2103    crate::xml::globals::substitute_entities_ptr()
2104}
2105
2106// ═══════════════════════════════════════════════════════════════════════════════
2107// Regression court — xmlLastError mirror concurrency (11.1-X)
2108// ═══════════════════════════════════════════════════════════════════════════════
2109//
2110// R-000135 discovery during 11.1-X: the exported `xmlLastError` mirror was
2111// deep-copied and freed without synchronization, so concurrent error raises
2112// on different threads double-freed the mirror strings. The parallel lib
2113// test suite observed this as `double free or corruption (!prev)` aborts
2114// (xml::errors tests racing with any other raising thread). The writers are
2115// serialized via LAST_ERROR_MIRROR_LOCK; these courts hammer the exact
2116// interleavings and must complete without crashing.
2117
2118#[cfg(test)]
2119mod tests {
2120    use super::*;
2121    use crate::abi::allocator::xmlMallocImpl;
2122    use crate::abi::structs::_xmlError;
2123    use crate::xml::globals;
2124    use core::ptr;
2125
2126    /// Allocate a NUL-terminated C string owned by xmlMallocImpl (the same
2127    /// allocator the thread-local error slot uses).
2128    ///
2129    /// # Safety
2130    ///
2131    /// - `s` must be a valid string; the returned pointer is
2132    ///   allocator-owned, valid for `bytes.len() + 1` bytes, and must be
2133    ///   freed with `xmlFreeImpl` exactly once.
2134    unsafe fn alloc_cstr(s: &str) -> *mut c_char {
2135        let bytes = s.as_bytes();
2136        let p = unsafe { xmlMallocImpl(bytes.len() + 1) as *mut c_char };
2137        assert!(!p.is_null(), "alloc_cstr: xmlMallocImpl failed");
2138        unsafe {
2139            ptr::copy_nonoverlapping(bytes.as_ptr(), p as *mut u8, bytes.len());
2140            *((p as *mut u8).add(bytes.len())) = 0;
2141        }
2142        p
2143    }
2144
2145    /// Build an owned `_xmlError` with distinct string fields.
2146    ///
2147    /// # Safety
2148    ///
2149    /// - Every `message`/`file`/`str1` field is an `alloc_cstr`
2150    ///   allocation that the returned struct owns; the caller must free
2151    ///   them with `xmlFreeImpl` exactly once (e.g. via
2152    ///   `reset_last_error`), or the fields leak.
2153    unsafe fn build_error(tag: &str) -> _xmlError {
2154        _xmlError {
2155            domain: 1,
2156            code: 2,
2157            message: unsafe { alloc_cstr(&format!("msg {tag}")) },
2158            level: 3,
2159            file: unsafe { alloc_cstr(&format!("file {tag}")) },
2160            line: 4,
2161            str1: unsafe { alloc_cstr(&format!("str1 {tag}")) },
2162            str2: ptr::null_mut(),
2163            str3: ptr::null_mut(),
2164            int1: 0,
2165            int2: 0,
2166            ctxt: ptr::null_mut(),
2167            node: ptr::null_mut(),
2168        }
2169    }
2170
2171    /// Concurrent sync/reset hammer: one thread raises errors while another
2172    /// resets. Before the mirror lock this double-freed the shared strings;
2173    /// the test crashes (SIGABRT) under the old code and passes now.
2174    ///
2175    /// # Safety
2176    ///
2177    /// - `build_error` allocates owned string fields with `xmlFreeImpl`;
2178    ///   `set_last_error` deep-copies them into the mirror and the
2179    ///   thread-local slot, and `reset_last_error` frees each copy exactly
2180    ///   once; the mirror is internally serialized by `LAST_ERROR_MIRROR_LOCK`.
2181    #[test]
2182    fn test_last_error_mirror_concurrent_sync_reset() {
2183        let sync = std::thread::spawn(|| {
2184            for i in 0..400 {
2185                unsafe { globals::set_last_error(build_error(&format!("sync {i}"))) };
2186            }
2187        });
2188        let reset = std::thread::spawn(|| {
2189            for _ in 0..400 {
2190                globals::reset_last_error();
2191            }
2192        });
2193        sync.join().unwrap();
2194        reset.join().unwrap();
2195        // Leave the mirror in a clean state for later tests. (No thread-local
2196        // assertion: the harness reuses OS threads across tests, so a prior
2197        // test's error may legitimately live in this thread's slot.)
2198        globals::reset_last_error();
2199    }
2200
2201    /// Many threads raising concurrently (the full parallel-suite shape that
2202    /// originally aborted in `test_encode_entities_reentrant_*` victims).
2203    ///
2204    /// # Safety
2205    ///
2206    /// - Each thread's `build_error` allocations are owned by the
2207    ///   thread-local last-error slot and the locked mirror; every
2208    ///   `set_last_error`/`reset_last_error` pair frees owned strings
2209    ///   exactly once, and the final `reset_last_error` leaves the mirror
2210    ///   clean.
2211    #[test]
2212    fn test_last_error_mirror_many_threads() {
2213        let mut handles = Vec::new();
2214        for t in 0..8 {
2215            handles.push(std::thread::spawn(move || {
2216                for i in 0..150 {
2217                    unsafe { globals::set_last_error(build_error(&format!("t{t} i{i}"))) };
2218                    if i % 7 == 0 {
2219                        globals::reset_last_error();
2220                    }
2221                }
2222            }));
2223        }
2224        for h in handles {
2225            h.join().unwrap();
2226        }
2227        globals::reset_last_error();
2228    }
2229}