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