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
21use core::ffi::c_void;
22use std::os::raw::{c_char, c_int, c_uint};
23
24use crate::abi::callbacks::{xmlGenericErrorFunc, xmlStructuredErrorFunc};
25use crate::abi::types::xmlChar;
26
27/// Serializes writes to the exported `xmlLastError` mirror.
28///
29/// Upstream's `xmlLastError` is a bare racy global (deprecated, documented
30/// not thread-safe), and the candidate preserves the C-visible semantics:
31/// downstream C consumers read the symbol directly without a lock. The
32/// candidate's internal deep-copy/free writers are serialized here so that
33/// concurrent error raises on different threads can never free the same
34/// mirror string twice or write while another thread is freeing — observed
35/// as heap corruption (`double free or corruption (!prev)`) in the parallel
36/// lib test suite (xml::errors tests racing with any other raising thread).
37static LAST_ERROR_MIRROR_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
38
39// ═══════════════════════════════════════════════════════════════════════════════
40// Parser defaults (upstream globals.c)
41// ═══════════════════════════════════════════════════════════════════════════════
42
43/// `int xmlDoValidityCheckingDefaultValue` (default 0)
44#[no_mangle]
45pub static mut xmlDoValidityCheckingDefaultValue: c_int = 0;
46
47/// `int xmlGetWarningsDefaultValue` (default 1)
48#[no_mangle]
49pub static mut xmlGetWarningsDefaultValue: c_int = 1;
50
51/// `int xmlLoadExtDtdDefaultValue` (default 0)
52#[no_mangle]
53pub static mut xmlLoadExtDtdDefaultValue: c_int = 0;
54
55/// `int xmlPedanticParserDefaultValue` (default 0)
56#[no_mangle]
57pub static mut xmlPedanticParserDefaultValue: c_int = 0;
58
59/// `int xmlLineNumbersDefaultValue` (default 1 — upstream globals.c
60/// `xmlLineNumbersDefaultValueThrDef = 1`)
61#[no_mangle]
62pub static mut xmlLineNumbersDefaultValue: c_int = 1;
63
64/// `int xmlKeepBlanksDefaultValue` (default 1)
65#[no_mangle]
66pub static mut xmlKeepBlanksDefaultValue: c_int = 1;
67
68/// `int xmlSubstituteEntitiesDefaultValue` (default 0)
69#[no_mangle]
70pub static mut xmlSubstituteEntitiesDefaultValue: c_int = 0;
71
72/// `int xmlParserDebugEntities` (default 0)
73#[no_mangle]
74pub static mut xmlParserDebugEntities: c_int = 0;
75
76/// `int xmlIndentTreeOutput` (default 1 — upstream globals.c
77/// `xmlIndentTreeOutputThrDef = 1`)
78#[no_mangle]
79pub static mut xmlIndentTreeOutput: c_int = 1;
80
81/// `const xmlChar *xmlTreeIndentString` (default "  " — upstream globals.c
82/// `xmlTreeIndentStringThrDef = "  "`)
83#[no_mangle]
84pub static mut xmlTreeIndentString: *const xmlChar = {
85    static S: [u8; 3] = *b"  \0";
86    S.as_ptr()
87};
88
89/// `int xmlSaveNoEmptyTags` (default 0)
90#[no_mangle]
91pub static mut xmlSaveNoEmptyTags: c_int = 0;
92
93/// Upstream `xmlRegisterNodeFunc xmlRegisterNodeDefaultValue` (default NULL)
94#[no_mangle]
95pub static mut xmlRegisterNodeDefaultValue: Option<
96    unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode),
97> = None;
98
99/// Upstream static `xmlRegisterCallbacks` (tree.c): the gate that arms the
100/// node register/deregister hooks once a callback has been registered.
101pub static XML_REGISTER_CALLBACKS: core::sync::atomic::AtomicBool =
102    core::sync::atomic::AtomicBool::new(false);
103
104/// Upstream `xmlRegisterNodeCallback(node)` — invoke the registered node
105/// hook, gated by `xmlRegisterCallbacks` (tree.c).
106///
107/// # SAFETY
108///
109/// - `node` must be a valid, fully-initialised node or NULL.
110pub fn register_node_hook(node: *mut crate::abi::structs::_xmlNode) {
111    if !XML_REGISTER_CALLBACKS.load(core::sync::atomic::Ordering::Relaxed) {
112        return;
113    }
114    let hook = unsafe { xmlRegisterNodeDefaultValue };
115    if let Some(h) = hook {
116        if !node.is_null() {
117            // SAFETY: the hook is a valid C callback registered by the user.
118            unsafe { h(node) };
119        }
120    }
121}
122
123/// Upstream `xmlDeregisterNodeCallback(node)` — invoke the registered node
124/// deregister hook, gated by `xmlRegisterCallbacks` (tree.c).
125///
126/// # SAFETY
127///
128/// - `node` must be a valid node about to be freed, or NULL.
129pub fn deregister_node_hook(node: *mut crate::abi::structs::_xmlNode) {
130    if !XML_REGISTER_CALLBACKS.load(core::sync::atomic::Ordering::Relaxed) {
131        return;
132    }
133    let hook = unsafe { xmlDeregisterNodeDefaultValue };
134    if let Some(h) = hook {
135        if !node.is_null() {
136            // SAFETY: the hook is a valid C callback registered by the user.
137            unsafe { h(node) };
138        }
139    }
140}
141
142/// `xmlDeregisterNodeFunc xmlDeregisterNodeDefaultValue` (default NULL)
143#[no_mangle]
144pub static mut xmlDeregisterNodeDefaultValue: Option<
145    unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode),
146> = None;
147
148/// `const char *xmlParserVersion` — matched to the system oracle build
149/// (libxml2 2.15.3 GIT build: LIBXML_VERSION_STRING "21503" plus the
150/// upstream version extra "-GITv2.15.3").
151///
152/// SAFETY: the pointed-to string is a static, immutable, null-terminated
153/// literal; `static mut` is used because C raw pointers are not `Sync`.
154/// Reads/writes of the pointer itself are racy only if C code mutates it
155/// (upstream treats it as a constant).
156#[no_mangle]
157pub static mut xmlParserVersion: *const c_char = {
158    static V: [u8; 17] = *b"21503-GITv2.15.3\0";
159    V.as_ptr() as *const c_char
160};
161
162/// `int xmlParserMaxDepth` (default 256)
163#[no_mangle]
164pub static mut xmlParserMaxDepth: c_int = 256;
165
166// ═══════════════════════════════════════════════════════════════════════════════
167// Buffer globals (upstream tree.h / xmlIO.c)
168// ═══════════════════════════════════════════════════════════════════════════════
169
170/// `int xmlDefaultBufferSize` (default 4096)
171#[no_mangle]
172pub static mut xmlDefaultBufferSize: c_int = 4096;
173
174/// `xmlBufferAllocationScheme xmlBufferAllocScheme` (default XML_BUFFER_ALLOC_EXACT = 1;
175/// upstream globals.c `xmlBufferAllocSchemeThrDef = XML_BUFFER_ALLOC_EXACT`)
176#[no_mangle]
177pub static mut xmlBufferAllocScheme: c_int = 1;
178
179// ═══════════════════════════════════════════════════════════════════════════════
180// Error callback globals (upstream xmlerror.h)
181// ═══════════════════════════════════════════════════════════════════════════════
182
183/// `xmlGenericErrorFunc xmlGenericError` — the generic error callback.
184///
185/// Upstream defaults to `xmlGenericErrorDefaultFunc` (a variadic stderr
186/// printer, error.c); the candidate's shim below reproduces it, so a
187/// freshly-initialized library routes errors to stderr exactly like upstream.
188#[cfg(target_arch = "x86_64")]
189#[no_mangle]
190pub static mut xmlGenericError: Option<xmlGenericErrorFunc> = Some(XML_GENERIC_ERROR_DEFAULT);
191
192#[cfg(not(target_arch = "x86_64"))]
193#[no_mangle]
194pub static mut xmlGenericError: Option<xmlGenericErrorFunc> = None;
195
196/// `void *xmlGenericErrorContext` — context for the generic error callback.
197#[no_mangle]
198pub static mut xmlGenericErrorContext: *mut c_void = core::ptr::null_mut();
199
200/// `xmlStructuredErrorFunc xmlStructuredError` — the structured error callback.
201#[no_mangle]
202pub static mut xmlStructuredError: Option<xmlStructuredErrorFunc> = None;
203
204/// `void *xmlStructuredErrorContext` — context for the structured callback.
205#[no_mangle]
206pub static mut xmlStructuredErrorContext: *mut c_void = core::ptr::null_mut();
207
208// ═══════════════════════════════════════════════════════════════════════════════
209// Variadic default error handlers (upstream error.c / xsltutils.c)
210// ═══════════════════════════════════════════════════════════════════════════════
211//
212// Upstream `xmlGenericErrorDefaultFunc(void *ctx, const char *msg, ...)`
213// prints the formatted message to `xmlGenericErrorContext` (stderr when
214// NULL). Stable Rust cannot define a variadic extern fn body, so the ABI
215// entry is an x86_64 SysV inline-asm shim that materialises the caller's
216// register/stack arguments into a `va_list` and forwards to a non-variadic
217// receiver — the same pattern as `xsltTransformError`
218// (exports_xslt_util.rs) and the writer's `vfmt_shim!`. Neither default
219// function is a dynamic export upstream (both are internal); the exported
220// data globals merely point at them.
221
222/// System V AMD64 `__va_list_tag` (24 bytes) — same layout as the writer's
223/// shims and `exports_xslt_util.rs`.
224#[cfg(target_arch = "x86_64")]
225#[repr(C)]
226#[derive(Clone, Copy)]
227struct VaListTag {
228    gp_offset: c_uint,
229    fp_offset: c_uint,
230    overflow_arg_area: *mut c_void,
231    reg_save_area: *mut c_void,
232}
233
234#[cfg(target_arch = "x86_64")]
235unsafe extern "C" {
236    fn vfprintf(stream: *mut c_void, format: *const c_char, ap: *mut VaListTag) -> c_int;
237}
238
239/// The `stderr` FILE* — glibc exports the `stderr` data object, an
240/// 8-byte pointer variable whose value is `&_IO_2_1_stderr_`. Upstream
241/// `xmlGenericErrorDefaultFunc` defaults the error context to `stderr`;
242/// using the real stdio object (unbuffered, fd-2 relative) keeps writes
243/// byte-exact and honors fd-2 redirection, unlike a private `fdopen(2)`
244/// FILE* which is fully buffered and lands at exit on whatever fd 2 then
245/// points to.
246#[cfg(target_arch = "x86_64")]
247unsafe fn stderr_file() -> *mut c_void {
248    extern "C" {
249        static stderr: *mut c_void;
250    }
251    unsafe { stderr }
252}
253
254/// Variadic receiver for the `xmlGenericErrorDefaultFunc` shim (upstream
255/// error.c semantics: default the context to stderr, then `vfprintf`).
256#[cfg(target_arch = "x86_64")]
257#[no_mangle]
258pub unsafe extern "C" fn xmlGenericErrorDefaultFuncV(
259    _ctx: *mut c_void,
260    msg: *const c_char,
261    ap: *mut VaListTag,
262) -> c_int {
263    unsafe {
264        if crate::abi::data_globals::xmlGenericErrorContext.is_null() {
265            crate::abi::data_globals::xmlGenericErrorContext = stderr_file();
266        }
267        let stream = crate::abi::data_globals::xmlGenericErrorContext;
268        if msg.is_null() || stream.is_null() {
269            return 0;
270        }
271        vfprintf(stream, msg, ap)
272    }
273}
274
275/// `xmlGenericErrorDefaultFunc(void *ctx, const char *msg, ...)` — the
276/// upstream default generic error handler. Not exported dynamically (matches
277/// upstream, where the symbol is internal).
278///
279/// 2 fixed args (ctx=rdi, msg=rsi) → `gp_offset` 16; the va_list pointer is
280/// passed as the 3rd arg (rdx) of the receiver.
281#[cfg(target_arch = "x86_64")]
282pub unsafe extern "C" fn xmlGenericErrorDefaultFunc() -> c_int {
283    unsafe {
284        core::arch::asm!(
285            "sub rsp, 240",
286            "mov [rsp+0], rdi",
287            "mov [rsp+8], rsi",
288            "mov [rsp+16], rdx",
289            "mov [rsp+24], rcx",
290            "mov [rsp+32], r8",
291            "mov [rsp+40], r9",
292            "movaps [rsp+48], xmm0",
293            "movaps [rsp+64], xmm1",
294            "movaps [rsp+80], xmm2",
295            "movaps [rsp+96], xmm3",
296            "movaps [rsp+112], xmm4",
297            "movaps [rsp+128], xmm5",
298            "movaps [rsp+144], xmm6",
299            "movaps [rsp+160], xmm7",
300            "mov dword ptr [rsp+176], 16",
301            "mov dword ptr [rsp+180], 48",
302            "lea rax, [rsp+256]",
303            "mov [rsp+184], rax",
304            "lea rax, [rsp]",
305            "mov [rsp+192], rax",
306            "lea rdx, [rsp+176]",
307            "call xmlGenericErrorDefaultFuncV",
308            "add rsp, 240",
309            "add rsp, 8",
310            "ret",
311            options(noreturn),
312        );
313    }
314}
315
316/// Default value of the exported `xmlGenericError` data global.
317#[cfg(target_arch = "x86_64")]
318const XML_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
319    // SAFETY: the shim and the function-pointer type have identical ABI
320    // (a code pointer); the declared arity is a Rust-side fiction required
321    // to store a variadic entry in the non-variadic pointer type.
322    core::mem::transmute::<
323        unsafe extern "C" fn() -> c_int,
324        unsafe extern "C" fn(*mut c_void, *const c_char),
325    >(xmlGenericErrorDefaultFunc)
326};
327
328/// Variadic receiver for the `xsltGenericErrorDefaultFunc` shim (upstream
329/// xsltutils.c semantics: default the context to stderr, then `vfprintf`).
330#[cfg(target_arch = "x86_64")]
331#[no_mangle]
332pub unsafe extern "C" fn xsltGenericErrorDefaultFuncV(
333    _ctx: *mut c_void,
334    msg: *const c_char,
335    ap: *mut VaListTag,
336) -> c_int {
337    unsafe {
338        if crate::abi::data_globals::xsltGenericErrorContext.is_null() {
339            crate::abi::data_globals::xsltGenericErrorContext = stderr_file();
340        }
341        let stream = crate::abi::data_globals::xsltGenericErrorContext;
342        if msg.is_null() || stream.is_null() {
343            return 0;
344        }
345        vfprintf(stream, msg, ap)
346    }
347}
348
349/// `xsltGenericErrorDefaultFunc(void *ctx, const char *msg, ...)` — the
350/// upstream default XSLT error handler (xsltutils.c).
351#[cfg(target_arch = "x86_64")]
352pub unsafe extern "C" fn xsltGenericErrorDefaultFunc() -> c_int {
353    unsafe {
354        core::arch::asm!(
355            "sub rsp, 240",
356            "mov [rsp+0], rdi",
357            "mov [rsp+8], rsi",
358            "mov [rsp+16], rdx",
359            "mov [rsp+24], rcx",
360            "mov [rsp+32], r8",
361            "mov [rsp+40], r9",
362            "movaps [rsp+48], xmm0",
363            "movaps [rsp+64], xmm1",
364            "movaps [rsp+80], xmm2",
365            "movaps [rsp+96], xmm3",
366            "movaps [rsp+112], xmm4",
367            "movaps [rsp+128], xmm5",
368            "movaps [rsp+144], xmm6",
369            "movaps [rsp+160], xmm7",
370            "mov dword ptr [rsp+176], 16",
371            "mov dword ptr [rsp+180], 48",
372            "lea rax, [rsp+256]",
373            "mov [rsp+184], rax",
374            "lea rax, [rsp]",
375            "mov [rsp+192], rax",
376            "lea rdx, [rsp+176]",
377            "call xsltGenericErrorDefaultFuncV",
378            "add rsp, 240",
379            "add rsp, 8",
380            "ret",
381            options(noreturn),
382        );
383    }
384}
385
386/// Default value of the exported `xsltGenericError` data global.
387#[cfg(target_arch = "x86_64")]
388const XSLT_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
389    // SAFETY: as above — ABI-identical code pointer.
390    core::mem::transmute::<
391        unsafe extern "C" fn() -> c_int,
392        unsafe extern "C" fn(*mut c_void, *const c_char),
393    >(xsltGenericErrorDefaultFunc)
394};
395
396/// The built-in default generic error handler (upstream
397/// `xmlGenericErrorDefaultFunc`), for use when a caller resets the handler
398/// with NULL. Only available on x86_64 (the variadic shim is SysV-specific);
399/// on other targets there is no default (resets leave the handler unset).
400pub fn default_generic_error_func() -> Option<xmlGenericErrorFunc> {
401    #[cfg(target_arch = "x86_64")]
402    {
403        Some(XML_GENERIC_ERROR_DEFAULT)
404    }
405    #[cfg(not(target_arch = "x86_64"))]
406    {
407        None
408    }
409}
410
411// ═══════════════════════════════════════════════════════════════════════════════
412// Static strings (upstream xmlstring.h / tree.c)
413// ═══════════════════════════════════════════════════════════════════════════════
414
415/// `const xmlChar xmlStringText[]` — "text"
416#[no_mangle]
417pub static xmlStringText: [xmlChar; 5] = [b't', b'e', b'x', b't', 0];
418
419/// `const xmlChar xmlStringTextNoenc[]` — "textnoenc"
420#[no_mangle]
421pub static xmlStringTextNoenc: [xmlChar; 9] =
422    [b't', b'e', b'x', b't', b'n', b'o', b'e', b'n', b'c'];
423
424/// `const xmlChar xmlStringComment[]` — "comment"
425#[no_mangle]
426pub static xmlStringComment: [xmlChar; 8] = [b'c', b'o', b'm', b'm', b'e', b'n', b't', 0];
427
428// ═══════════════════════════════════════════════════════════════════════════════
429// XPath numeric constants (upstream xpath.c)
430// ═══════════════════════════════════════════════════════════════════════════════
431
432/// `double xmlXPathNAN` — NaN
433#[no_mangle]
434pub static xmlXPathNAN: f64 = f64::NAN;
435
436/// `double xmlXPathPINF` — +infinity
437#[no_mangle]
438pub static xmlXPathPINF: f64 = f64::INFINITY;
439
440/// `double xmlXPathNINF` — -infinity
441#[no_mangle]
442pub static xmlXPathNINF: f64 = f64::NEG_INFINITY;
443
444// ═══════════════════════════════════════════════════════════════════════════════
445// libxslt globals (upstream xslt.c / xsltutils.c / documents.c / xslt.h)
446// ═══════════════════════════════════════════════════════════════════════════════
447// xsltMaxDepth / xsltMaxVars are exported from src/xslt/transform/mod.rs
448// (they are read by the transform engine).
449
450/// `const int xsltLibxmlVersion` = LIBXML_VERSION (21503) — the libxml2
451/// version libxslt was built against (upstream xslt.c).
452#[no_mangle]
453pub static xsltLibxmlVersion: c_int = 21503;
454
455/// `xmlGenericErrorFunc xsltGenericError` — the libxslt error callback.
456/// Upstream defaults to `xsltGenericErrorDefaultFunc` (xsltutils.c, a variadic
457/// stderr printer); the candidate's shim below reproduces it (R-000135
458/// divergence now closed).
459#[cfg(target_arch = "x86_64")]
460#[no_mangle]
461pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = Some(XSLT_GENERIC_ERROR_DEFAULT);
462
463#[cfg(not(target_arch = "x86_64"))]
464#[no_mangle]
465pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = None;
466
467/// `void *xsltGenericErrorContext` (default NULL)
468#[no_mangle]
469pub static mut xsltGenericErrorContext: *mut c_void = core::ptr::null_mut();
470
471/// `void *xsltGenericDebugContext` (default NULL)
472#[no_mangle]
473pub static mut xsltGenericDebugContext: *mut c_void = core::ptr::null_mut();
474
475/// `const xmlChar xsltExtMarker[]` — empty string used to mark extension
476/// nodes (upstream transform.c).
477#[no_mangle]
478pub static xsltExtMarker: [xmlChar; 1] = [0];
479
480/// `xsltDocLoaderFunc xsltDocDefaultLoader` — the document loader callback.
481/// Upstream defaults to `xsltDocDefaultLoaderFunc`; the candidate defaults
482/// to NULL and its internal loader path is used — documented safe
483/// divergence (residual R-000135).
484#[no_mangle]
485pub static mut xsltDocDefaultLoader: Option<
486    unsafe extern "C" fn(
487        *const xmlChar,
488        *mut c_void,
489        c_int,
490        *mut crate::abi::structs::_xsltStylesheet,
491        *mut crate::abi::structs::_xsltTransformContext,
492    ) -> *mut crate::abi::structs::_xmlDoc,
493> = None;
494
495// ═══════════════════════════════════════════════════════════════════════════════
496// I/O filename callback globals (upstream xmlIO.h)
497// ═══════════════════════════════════════════════════════════════════════════════
498
499/// `xmlParserInputBufferCreateFilenameFunc xmlParserInputBufferCreateFilenameValue`
500#[no_mangle]
501pub static mut xmlParserInputBufferCreateFilenameValue: Option<
502    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
503> = None;
504
505/// `xmlOutputBufferCreateFilenameFunc xmlOutputBufferCreateFilenameValue`
506#[no_mangle]
507pub static mut xmlOutputBufferCreateFilenameValue: Option<
508    unsafe extern "C" fn(
509        *const c_char,
510        crate::abi::structs::xmlCharEncodingHandlerPtr,
511        c_int,
512    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
513> = None;
514
515// ═══════════════════════════════════════════════════════════════════════════════
516// Default SAX v1 handler structs + locator (upstream globals.c 2.15.3)
517// ═══════════════════════════════════════════════════════════════════════════════
518//
519// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` and `htmlDefaultSAXHandler`
520// (parser.h / HTMLparser.h), plus `const xmlSAXLocator xmlDefaultSAXLocator`.
521// The handler instances reproduce the upstream initializer lists exactly
522// (globals.c); every referenced xmlSAX2* entry point is a real candidate
523// export.
524
525/// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` (globals.c 2.15.3).
526#[no_mangle]
527pub static xmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
528    crate::abi::structs::_xmlSAXHandlerV1 {
529        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
530        isStandalone: Some(crate::abi::exports_xml2::xmlSAX2IsStandalone),
531        hasInternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasInternalSubset),
532        hasExternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasExternalSubset),
533        resolveEntity: Some(crate::abi::exports_xml2::xmlSAX2ResolveEntity),
534        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
535        entityDecl: Some(crate::abi::exports_xml2::xmlSAX2EntityDecl),
536        notationDecl: Some(crate::abi::exports_xml2::xmlSAX2NotationDecl),
537        attributeDecl: Some(crate::abi::exports_xml2::xmlSAX2AttributeDecl),
538        elementDecl: Some(crate::abi::exports_xml2::xmlSAX2ElementDecl),
539        unparsedEntityDecl: Some(crate::abi::exports_xml2::xmlSAX2UnparsedEntityDecl),
540        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
541        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
542        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
543        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
544        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
545        reference: Some(crate::abi::exports_xml2::xmlSAX2Reference),
546        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
547        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
548        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
549        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
550        warning: Some(crate::xml::errors::xmlParserWarning),
551        error: Some(crate::xml::errors::xmlParserError),
552        fatalError: Some(crate::xml::errors::xmlParserError),
553        getParameterEntity: Some(crate::abi::exports_xml2::xmlSAX2GetParameterEntity),
554        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
555        externalSubset: Some(crate::abi::exports_xml2::xmlSAX2ExternalSubset),
556        initialized: 1,
557    };
558
559/// `const xmlSAXHandlerV1 htmlDefaultSAXHandler` (globals.c 2.15.3).
560#[no_mangle]
561pub static htmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
562    crate::abi::structs::_xmlSAXHandlerV1 {
563        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
564        isStandalone: None,
565        hasInternalSubset: None,
566        hasExternalSubset: None,
567        resolveEntity: None,
568        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
569        entityDecl: None,
570        notationDecl: None,
571        attributeDecl: None,
572        elementDecl: None,
573        unparsedEntityDecl: None,
574        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
575        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
576        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
577        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
578        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
579        reference: None,
580        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
581        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
582        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
583        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
584        warning: Some(crate::xml::errors::xmlParserWarning),
585        error: Some(crate::xml::errors::xmlParserError),
586        fatalError: Some(crate::xml::errors::xmlParserError),
587        getParameterEntity: None,
588        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
589        externalSubset: None,
590        initialized: 1,
591    };
592
593/// `const xmlSAXLocator xmlDefaultSAXLocator` (globals.c 2.15.3).
594#[no_mangle]
595pub static xmlDefaultSAXLocator: crate::abi::callbacks::_xmlSAXLocator =
596    crate::abi::callbacks::_xmlSAXLocator {
597        getPublicId: Some(crate::abi::exports_xml2::xmlSAX2GetPublicId),
598        getSystemId: Some(crate::abi::exports_xml2::xmlSAX2GetSystemId),
599        getLineNumber: Some(crate::abi::exports_xml2::xmlSAX2GetLineNumber),
600        getColumnNumber: Some(crate::abi::exports_xml2::xmlSAX2GetColumnNumber),
601    };
602
603// ═══════════════════════════════════════════════════════════════════════════════
604// xmlLastError — the exported C global mirror of the thread-local error state
605// ═══════════════════════════════════════════════════════════════════════════════
606//
607// Upstream `XMLPUBVAR xmlError xmlLastError` (xmlerror.h). The candidate's
608// internal error state is thread-local (safe divergence, more correct than
609// upstream's racy global); this mirror is deep-copied on every error raise
610// and freed on reset, so C consumers observe upstream-equivalent lifetime
611// semantics. Residual R-000135.
612
613/// `xmlError xmlLastError` — most recent error (mirror).
614#[no_mangle]
615pub static mut xmlLastError: crate::abi::structs::_xmlError = crate::abi::structs::_xmlError {
616    domain: 0,
617    code: 0,
618    message: core::ptr::null_mut(),
619    level: 0,
620    file: core::ptr::null_mut(),
621    line: 0,
622    str1: core::ptr::null_mut(),
623    str2: core::ptr::null_mut(),
624    str3: core::ptr::null_mut(),
625    int1: 0,
626    int2: 0,
627    ctxt: core::ptr::null_mut(),
628    node: core::ptr::null_mut(),
629};
630
631/// Deep-copy `err` into the exported `xmlLastError` global.
632///
633/// The string fields are copied with `libc::malloc`/`memcpy` so the mirror
634/// owns them (previous mirror strings are freed first — upstream
635/// xmlResetError semantics).
636///
637/// # SAFETY
638///
639/// - `err` must point to a valid `_xmlError` whose string fields are
640///   NUL-terminated or NULL.
641pub unsafe fn sync_xml_last_error(err: *const crate::abi::structs::_xmlError) {
642    if err.is_null() {
643        return;
644    }
645    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
646    unsafe { sync_xml_last_error_locked(err) };
647}
648
649/// Mirror write helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
650unsafe fn sync_xml_last_error_locked(err: *const crate::abi::structs::_xmlError) {
651    unsafe {
652        reset_xml_last_error_locked();
653        let src = &*err;
654        let dst = core::ptr::addr_of_mut!(xmlLastError);
655        (*dst).domain = src.domain;
656        (*dst).code = src.code;
657        (*dst).level = src.level;
658        (*dst).line = src.line;
659        (*dst).int1 = src.int1;
660        (*dst).int2 = src.int2;
661        (*dst).ctxt = src.ctxt;
662        (*dst).node = src.node;
663        (*dst).message = dup_cstr(src.message as *const u8);
664        (*dst).file = dup_cstr(src.file as *const u8);
665        (*dst).str1 = dup_cstr(src.str1 as *const u8);
666        (*dst).str2 = dup_cstr(src.str2 as *const u8);
667        (*dst).str3 = dup_cstr(src.str3 as *const u8);
668    }
669}
670
671/// Reset the exported `xmlLastError` global, freeing owned strings
672/// (upstream xmlResetError).
673///
674/// # SAFETY
675///
676/// Only call while no other thread is reading the global (upstream has the
677/// same race; documented).
678pub unsafe fn reset_xml_last_error() {
679    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
680    unsafe { reset_xml_last_error_locked() };
681}
682
683/// Mirror reset helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
684unsafe fn reset_xml_last_error_locked() {
685    unsafe {
686        let dst = core::ptr::addr_of_mut!(xmlLastError);
687        if !(*dst).message.is_null() {
688            libc::free((*dst).message as *mut libc::c_void);
689        }
690        if !(*dst).file.is_null() {
691            libc::free((*dst).file as *mut libc::c_void);
692        }
693        if !(*dst).str1.is_null() {
694            libc::free((*dst).str1 as *mut libc::c_void);
695        }
696        if !(*dst).str2.is_null() {
697            libc::free((*dst).str2 as *mut libc::c_void);
698        }
699        if !(*dst).str3.is_null() {
700            libc::free((*dst).str3 as *mut libc::c_void);
701        }
702        *dst = crate::abi::structs::_xmlError {
703            domain: 0,
704            code: 0,
705            message: core::ptr::null_mut(),
706            level: 0,
707            file: core::ptr::null_mut(),
708            line: 0,
709            str1: core::ptr::null_mut(),
710            str2: core::ptr::null_mut(),
711            str3: core::ptr::null_mut(),
712            int1: 0,
713            int2: 0,
714            ctxt: core::ptr::null_mut(),
715            node: core::ptr::null_mut(),
716        };
717    }
718}
719
720/// Heap-copy a NUL-terminated string (NULL-safe).
721unsafe fn dup_cstr(s: *const u8) -> *mut c_char {
722    if s.is_null() {
723        return core::ptr::null_mut();
724    }
725    unsafe {
726        let len = libc::strlen(s as *const libc::c_char) as usize;
727        let p = libc::malloc(len + 1) as *mut u8;
728        if p.is_null() {
729            return core::ptr::null_mut();
730        }
731        libc::memcpy(p as *mut libc::c_void, s as *const libc::c_void, len + 1);
732        p as *mut c_char
733    }
734}
735
736// ═══════════════════════════════════════════════════════════════════════════════
737// Default accessor functions (upstream parser.h / tree.h / xmlsave.h)
738// ═══════════════════════════════════════════════════════════════════════════════
739//
740// The deprecated `xmlXxxDefault(v)` accessors set the corresponding global
741// when `v != 0` and return the (new) value — upstream semantics (they
742// predate the plain globals; the modern behavior is conditional-set-and-
743// return, see upstream globals.c / parser.c).
744
745/// Upstream `xmlKeepBlanksDefault(int v)`.
746#[no_mangle]
747pub unsafe extern "C" fn xmlKeepBlanksDefault(v: c_int) -> c_int {
748    unsafe {
749        if v != 0 {
750            xmlKeepBlanksDefaultValue = v;
751        }
752        xmlKeepBlanksDefaultValue
753    }
754}
755
756/// Upstream `xmlLineNumbersDefault(int v)`.
757#[no_mangle]
758pub unsafe extern "C" fn xmlLineNumbersDefault(v: c_int) -> c_int {
759    unsafe {
760        if v != 0 {
761            xmlLineNumbersDefaultValue = v;
762        }
763        xmlLineNumbersDefaultValue
764    }
765}
766
767/// Upstream `xmlSubstituteEntitiesDefault(int v)`.
768#[no_mangle]
769pub unsafe extern "C" fn xmlSubstituteEntitiesDefault(v: c_int) -> c_int {
770    unsafe {
771        if v != 0 {
772            xmlSubstituteEntitiesDefaultValue = v;
773        }
774        xmlSubstituteEntitiesDefaultValue
775    }
776}
777
778/// Upstream `xmlPedanticParserDefault(int v)`.
779#[no_mangle]
780pub unsafe extern "C" fn xmlPedanticParserDefault(v: c_int) -> c_int {
781    unsafe {
782        if v != 0 {
783            xmlPedanticParserDefaultValue = v;
784        }
785        xmlPedanticParserDefaultValue
786    }
787}
788
789/// Upstream `xmlDoValidityCheckingDefaultValue` accessor is the global
790/// itself; `xmlGetWarningsDefaultValue` likewise (no accessor functions
791/// exist for those in upstream 2.15).
792
793/// Upstream `xmlRegisterNodeDefault(xmlRegisterNodeFunc func)`.
794#[no_mangle]
795pub unsafe extern "C" fn xmlRegisterNodeDefault(
796    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
797) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
798    unsafe {
799        // UPSTREAM-PARITY (tree.c): registering any callback arms the
800        // xmlRegisterCallbacks gate.
801        XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
802        if func.is_some() {
803            xmlRegisterNodeDefaultValue = func;
804        }
805        xmlRegisterNodeDefaultValue
806    }
807}
808
809/// Upstream `xmlDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
810#[no_mangle]
811pub unsafe extern "C" fn xmlDeregisterNodeDefault(
812    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
813) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
814    unsafe {
815        // UPSTREAM-PARITY (tree.c): registering any callback arms the
816        // xmlRegisterCallbacks gate.
817        XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
818        if func.is_some() {
819            xmlDeregisterNodeDefaultValue = func;
820        }
821        xmlDeregisterNodeDefaultValue
822    }
823}
824
825/// Upstream `__xmlIndentTreeOutput(void)` (parser.h) — returns a pointer to
826/// the `xmlIndentTreeOutput` global.
827#[no_mangle]
828pub unsafe extern "C" fn __xmlIndentTreeOutput() -> *mut c_int {
829    unsafe { core::ptr::addr_of_mut!(xmlIndentTreeOutput) }
830}
831
832/// Upstream `__xmlSaveNoEmptyTags(void)` (parser.h) — returns a pointer to
833/// the `xmlSaveNoEmptyTags` global.
834#[no_mangle]
835pub unsafe extern "C" fn __xmlSaveNoEmptyTags() -> *mut c_int {
836    unsafe { core::ptr::addr_of_mut!(xmlSaveNoEmptyTags) }
837}
838
839/// Upstream `__xmlTreeIndentString(void)` (parser.h) — returns a pointer to
840/// the `xmlTreeIndentString` global.
841#[no_mangle]
842pub unsafe extern "C" fn __xmlTreeIndentString() -> *mut *const xmlChar {
843    unsafe { core::ptr::addr_of_mut!(xmlTreeIndentString) }
844}
845
846// ═══════════════════════════════════════════════════════════════════════════════
847// xmlThrDef* accessors (upstream threads.c / globals.c)
848// ═══════════════════════════════════════════════════════════════════════════════
849//
850// The deprecated `xmlThrDef*` family reads/writes the public globals with
851// the upstream semantics: when `v != 0` the global is set, and the (new)
852// value is returned. In upstream these were thread-local definitions before
853// the globals became plain variables; the modern behavior is exactly this
854// conditional-set-and-return on the global (see upstream globals.c).
855
856/// Upstream `xmlThrDefDoValidityCheckingDefaultValue(int v)`.
857#[no_mangle]
858pub unsafe extern "C" fn xmlThrDefDoValidityCheckingDefaultValue(v: c_int) -> c_int {
859    unsafe {
860        if v != 0 {
861            xmlDoValidityCheckingDefaultValue = v;
862        }
863        xmlDoValidityCheckingDefaultValue
864    }
865}
866
867/// Upstream `xmlThrDefGetWarningsDefaultValue(int v)`.
868#[no_mangle]
869pub unsafe extern "C" fn xmlThrDefGetWarningsDefaultValue(v: c_int) -> c_int {
870    unsafe {
871        if v != 0 {
872            xmlGetWarningsDefaultValue = v;
873        }
874        xmlGetWarningsDefaultValue
875    }
876}
877
878/// Upstream `xmlThrDefLoadExtDtdDefaultValue(int v)`.
879#[no_mangle]
880pub unsafe extern "C" fn xmlThrDefLoadExtDtdDefaultValue(v: c_int) -> c_int {
881    unsafe {
882        if v != 0 {
883            xmlLoadExtDtdDefaultValue = v;
884        }
885        xmlLoadExtDtdDefaultValue
886    }
887}
888
889/// Upstream `xmlThrDefPedanticParserDefaultValue(int v)`.
890#[no_mangle]
891pub unsafe extern "C" fn xmlThrDefPedanticParserDefaultValue(v: c_int) -> c_int {
892    unsafe {
893        if v != 0 {
894            xmlPedanticParserDefaultValue = v;
895        }
896        xmlPedanticParserDefaultValue
897    }
898}
899
900/// Upstream `xmlThrDefLineNumbersDefaultValue(int v)`.
901#[no_mangle]
902pub unsafe extern "C" fn xmlThrDefLineNumbersDefaultValue(v: c_int) -> c_int {
903    unsafe {
904        if v != 0 {
905            xmlLineNumbersDefaultValue = v;
906        }
907        xmlLineNumbersDefaultValue
908    }
909}
910
911/// Upstream `xmlThrDefKeepBlanksDefaultValue(int v)`.
912#[no_mangle]
913pub unsafe extern "C" fn xmlThrDefKeepBlanksDefaultValue(v: c_int) -> c_int {
914    unsafe {
915        if v != 0 {
916            xmlKeepBlanksDefaultValue = v;
917        }
918        xmlKeepBlanksDefaultValue
919    }
920}
921
922/// Upstream `xmlThrDefSubstituteEntitiesDefaultValue(int v)`.
923#[no_mangle]
924pub unsafe extern "C" fn xmlThrDefSubstituteEntitiesDefaultValue(v: c_int) -> c_int {
925    unsafe {
926        if v != 0 {
927            xmlSubstituteEntitiesDefaultValue = v;
928        }
929        xmlSubstituteEntitiesDefaultValue
930    }
931}
932
933/// Upstream `xmlThrDefParserDebugEntities(int v)`.
934#[no_mangle]
935pub unsafe extern "C" fn xmlThrDefParserDebugEntities(v: c_int) -> c_int {
936    unsafe {
937        if v != 0 {
938            xmlParserDebugEntities = v;
939        }
940        xmlParserDebugEntities
941    }
942}
943
944/// Upstream `xmlThrDefIndentTreeOutput(int v)`.
945#[no_mangle]
946pub unsafe extern "C" fn xmlThrDefIndentTreeOutput(v: c_int) -> c_int {
947    unsafe {
948        if v != 0 {
949            xmlIndentTreeOutput = v;
950        }
951        xmlIndentTreeOutput
952    }
953}
954
955/// Upstream `xmlThrDefTreeIndentString(const char *v)` — sets the indent
956/// string when non-NULL and returns the current pointer.
957#[no_mangle]
958pub unsafe extern "C" fn xmlThrDefTreeIndentString(v: *const c_char) -> *const c_char {
959    unsafe {
960        if !v.is_null() {
961            xmlTreeIndentString = v as *const xmlChar;
962        }
963        xmlTreeIndentString as *const c_char
964    }
965}
966
967/// Upstream `xmlThrDefSaveNoEmptyTags(int v)`.
968#[no_mangle]
969pub unsafe extern "C" fn xmlThrDefSaveNoEmptyTags(v: c_int) -> c_int {
970    unsafe {
971        if v != 0 {
972            xmlSaveNoEmptyTags = v;
973        }
974        xmlSaveNoEmptyTags
975    }
976}
977
978/// Upstream `xmlThrDefRegisterNodeDefault(xmlRegisterNodeFunc func)`.
979#[no_mangle]
980pub unsafe extern "C" fn xmlThrDefRegisterNodeDefault(
981    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
982) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
983    unsafe {
984        if func.is_some() {
985            xmlRegisterNodeDefaultValue = func;
986        }
987        xmlRegisterNodeDefaultValue
988    }
989}
990
991/// Upstream `xmlThrDefDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
992#[no_mangle]
993pub unsafe extern "C" fn xmlThrDefDeregisterNodeDefault(
994    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
995) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
996    unsafe {
997        if func.is_some() {
998            xmlDeregisterNodeDefaultValue = func;
999        }
1000        xmlDeregisterNodeDefaultValue
1001    }
1002}
1003
1004/// Upstream `xmlThrDefSetGenericErrorFunc(void *ctx, xmlGenericErrorFunc func)`.
1005#[no_mangle]
1006pub unsafe extern "C" fn xmlThrDefSetGenericErrorFunc(
1007    ctx: *mut c_void,
1008    func: Option<xmlGenericErrorFunc>,
1009) {
1010    unsafe {
1011        xmlGenericErrorContext = ctx;
1012        xmlGenericError = func;
1013    }
1014}
1015
1016/// Upstream `xmlThrDefSetStructuredErrorFunc(void *ctx, xmlStructuredErrorFunc func)`.
1017#[no_mangle]
1018pub unsafe extern "C" fn xmlThrDefSetStructuredErrorFunc(
1019    ctx: *mut c_void,
1020    func: Option<xmlStructuredErrorFunc>,
1021) {
1022    unsafe {
1023        xmlStructuredErrorContext = ctx;
1024        xmlStructuredError = func;
1025    }
1026}
1027
1028/// Upstream `xmlThrDefDefaultBufferSize(int v)`.
1029#[no_mangle]
1030pub unsafe extern "C" fn xmlThrDefDefaultBufferSize(v: c_int) -> c_int {
1031    unsafe {
1032        if v != 0 {
1033            xmlDefaultBufferSize = v;
1034        }
1035        xmlDefaultBufferSize
1036    }
1037}
1038
1039/// Upstream `xmlThrDefBufferAllocScheme(xmlBufferAllocationScheme v)`.
1040#[no_mangle]
1041pub unsafe extern "C" fn xmlThrDefBufferAllocScheme(v: c_int) -> c_int {
1042    unsafe {
1043        if v != 0 {
1044            xmlBufferAllocScheme = v;
1045        }
1046        xmlBufferAllocScheme
1047    }
1048}
1049
1050/// Upstream `xmlThrDefParserInputBufferCreateFilenameDefault(...)`.
1051#[no_mangle]
1052pub unsafe extern "C" fn xmlThrDefParserInputBufferCreateFilenameDefault(
1053    func: Option<
1054        unsafe extern "C" fn(
1055            *const c_char,
1056            c_int,
1057        ) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1058    >,
1059) -> Option<
1060    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1061> {
1062    unsafe {
1063        if func.is_some() {
1064            xmlParserInputBufferCreateFilenameValue = func;
1065        }
1066        xmlParserInputBufferCreateFilenameValue
1067    }
1068}
1069
1070/// Upstream `xmlThrDefOutputBufferCreateFilenameDefault(...)`.
1071#[no_mangle]
1072pub unsafe extern "C" fn xmlThrDefOutputBufferCreateFilenameDefault(
1073    func: Option<
1074        unsafe extern "C" fn(
1075            *const c_char,
1076            crate::abi::structs::xmlCharEncodingHandlerPtr,
1077            c_int,
1078        ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1079    >,
1080) -> Option<
1081    unsafe extern "C" fn(
1082        *const c_char,
1083        crate::abi::structs::xmlCharEncodingHandlerPtr,
1084        c_int,
1085    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1086> {
1087    unsafe {
1088        if func.is_some() {
1089            xmlOutputBufferCreateFilenameValue = func;
1090        }
1091        xmlOutputBufferCreateFilenameValue
1092    }
1093}
1094
1095// ═══════════════════════════════════════════════════════════════════════════════
1096// __xmlXxx() pointer accessors (upstream threads.c / globals.c)
1097// ═══════════════════════════════════════════════════════════════════════════════
1098// The deprecated thread-local API exports one `__xmlXxx(void)` accessor per
1099// global; each returns a pointer to the global so callers can read/write it.
1100
1101/// Upstream `__xmlBufferAllocScheme(void)` — returns a pointer to `xmlBufferAllocScheme`.
1102#[no_mangle]
1103pub unsafe extern "C" fn __xmlBufferAllocScheme() -> *mut c_int {
1104    // SAFETY: returning a pointer to an exported static; the caller may
1105    // read/write it exactly as with upstream's deprecated accessor.
1106    unsafe { core::ptr::addr_of_mut!(xmlBufferAllocScheme) }
1107}
1108
1109/// Upstream `__xmlDefaultBufferSize(void)` — returns a pointer to `xmlDefaultBufferSize`.
1110#[no_mangle]
1111pub unsafe extern "C" fn __xmlDefaultBufferSize() -> *mut c_int {
1112    // SAFETY: returning a pointer to an exported static; the caller may
1113    // read/write it exactly as with upstream's deprecated accessor.
1114    unsafe { core::ptr::addr_of_mut!(xmlDefaultBufferSize) }
1115}
1116
1117/// Upstream `__xmlDeregisterNodeDefaultValue(void)` — returns a pointer to `xmlDeregisterNodeDefaultValue`.
1118#[no_mangle]
1119pub unsafe extern "C" fn __xmlDeregisterNodeDefaultValue(
1120) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1121    // SAFETY: returning a pointer to an exported static; the caller may
1122    // read/write it exactly as with upstream's deprecated accessor.
1123    unsafe { core::ptr::addr_of_mut!(xmlDeregisterNodeDefaultValue) }
1124}
1125
1126/// Upstream `__xmlDoValidityCheckingDefaultValue(void)` — returns a pointer to `xmlDoValidityCheckingDefaultValue`.
1127#[no_mangle]
1128pub unsafe extern "C" fn __xmlDoValidityCheckingDefaultValue() -> *mut c_int {
1129    // SAFETY: returning a pointer to an exported static; the caller may
1130    // read/write it exactly as with upstream's deprecated accessor.
1131    unsafe { core::ptr::addr_of_mut!(xmlDoValidityCheckingDefaultValue) }
1132}
1133
1134/// Upstream `__xmlGenericError(void)` — returns a pointer to `xmlGenericError`.
1135#[no_mangle]
1136pub unsafe extern "C" fn __xmlGenericError() -> *mut Option<xmlGenericErrorFunc> {
1137    // SAFETY: returning a pointer to an exported static; the caller may
1138    // read/write it exactly as with upstream's deprecated accessor.
1139    unsafe { core::ptr::addr_of_mut!(xmlGenericError) }
1140}
1141
1142/// Upstream `__xmlGenericErrorContext(void)` — returns a pointer to `xmlGenericErrorContext`.
1143#[no_mangle]
1144pub unsafe extern "C" fn __xmlGenericErrorContext() -> *mut *mut c_void {
1145    // SAFETY: returning a pointer to an exported static; the caller may
1146    // read/write it exactly as with upstream's deprecated accessor.
1147    unsafe { core::ptr::addr_of_mut!(xmlGenericErrorContext) }
1148}
1149
1150/// Upstream `__xmlGetWarningsDefaultValue(void)` — returns a pointer to `xmlGetWarningsDefaultValue`.
1151#[no_mangle]
1152pub unsafe extern "C" fn __xmlGetWarningsDefaultValue() -> *mut c_int {
1153    // SAFETY: returning a pointer to an exported static; the caller may
1154    // read/write it exactly as with upstream's deprecated accessor.
1155    unsafe { core::ptr::addr_of_mut!(xmlGetWarningsDefaultValue) }
1156}
1157
1158/// Upstream `__xmlKeepBlanksDefaultValue(void)` — returns a pointer to `xmlKeepBlanksDefaultValue`.
1159#[no_mangle]
1160pub unsafe extern "C" fn __xmlKeepBlanksDefaultValue() -> *mut c_int {
1161    // SAFETY: returning a pointer to an exported static; the caller may
1162    // read/write it exactly as with upstream's deprecated accessor.
1163    unsafe { core::ptr::addr_of_mut!(xmlKeepBlanksDefaultValue) }
1164}
1165
1166/// Upstream `__xmlLineNumbersDefaultValue(void)` — returns a pointer to `xmlLineNumbersDefaultValue`.
1167#[no_mangle]
1168pub unsafe extern "C" fn __xmlLineNumbersDefaultValue() -> *mut c_int {
1169    // SAFETY: returning a pointer to an exported static; the caller may
1170    // read/write it exactly as with upstream's deprecated accessor.
1171    unsafe { core::ptr::addr_of_mut!(xmlLineNumbersDefaultValue) }
1172}
1173
1174/// Upstream `__xmlLoadExtDtdDefaultValue(void)` — returns a pointer to `xmlLoadExtDtdDefaultValue`.
1175#[no_mangle]
1176pub unsafe extern "C" fn __xmlLoadExtDtdDefaultValue() -> *mut c_int {
1177    // SAFETY: returning a pointer to an exported static; the caller may
1178    // read/write it exactly as with upstream's deprecated accessor.
1179    unsafe { core::ptr::addr_of_mut!(xmlLoadExtDtdDefaultValue) }
1180}
1181
1182/// Upstream `__xmlOutputBufferCreateFilenameValue(void)` — returns a pointer to `xmlOutputBufferCreateFilenameValue`.
1183#[no_mangle]
1184pub unsafe extern "C" fn __xmlOutputBufferCreateFilenameValue() -> *mut Option<
1185    unsafe extern "C" fn(
1186        *const c_char,
1187        crate::abi::structs::xmlCharEncodingHandlerPtr,
1188        c_int,
1189    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1190> {
1191    // SAFETY: returning a pointer to an exported static; the caller may
1192    // read/write it exactly as with upstream's deprecated accessor.
1193    unsafe { core::ptr::addr_of_mut!(xmlOutputBufferCreateFilenameValue) }
1194}
1195
1196/// Upstream `__xmlParserDebugEntities(void)` — returns a pointer to `xmlParserDebugEntities`.
1197#[no_mangle]
1198pub unsafe extern "C" fn __xmlParserDebugEntities() -> *mut c_int {
1199    // SAFETY: returning a pointer to an exported static; the caller may
1200    // read/write it exactly as with upstream's deprecated accessor.
1201    unsafe { core::ptr::addr_of_mut!(xmlParserDebugEntities) }
1202}
1203
1204/// Upstream `__xmlParserInputBufferCreateFilenameValue(void)` — returns a pointer to `xmlParserInputBufferCreateFilenameValue`.
1205#[no_mangle]
1206pub unsafe extern "C" fn __xmlParserInputBufferCreateFilenameValue() -> *mut Option<
1207    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1208> {
1209    // SAFETY: returning a pointer to an exported static; the caller may
1210    // read/write it exactly as with upstream's deprecated accessor.
1211    unsafe { core::ptr::addr_of_mut!(xmlParserInputBufferCreateFilenameValue) }
1212}
1213
1214/// Upstream `__xmlParserVersion(void)` — returns a pointer to `xmlParserVersion`.
1215#[no_mangle]
1216pub unsafe extern "C" fn __xmlParserVersion() -> *mut *const c_char {
1217    // SAFETY: returning a pointer to an exported static; the caller may
1218    // read/write it exactly as with upstream's deprecated accessor.
1219    unsafe { core::ptr::addr_of_mut!(xmlParserVersion) }
1220}
1221
1222/// Upstream `__xmlPedanticParserDefaultValue(void)` — returns a pointer to `xmlPedanticParserDefaultValue`.
1223#[no_mangle]
1224pub unsafe extern "C" fn __xmlPedanticParserDefaultValue() -> *mut c_int {
1225    // SAFETY: returning a pointer to an exported static; the caller may
1226    // read/write it exactly as with upstream's deprecated accessor.
1227    unsafe { core::ptr::addr_of_mut!(xmlPedanticParserDefaultValue) }
1228}
1229
1230/// Upstream `__xmlRegisterNodeDefaultValue(void)` — returns a pointer to `xmlRegisterNodeDefaultValue`.
1231#[no_mangle]
1232pub unsafe extern "C" fn __xmlRegisterNodeDefaultValue(
1233) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1234    // SAFETY: returning a pointer to an exported static; the caller may
1235    // read/write it exactly as with upstream's deprecated accessor.
1236    unsafe { core::ptr::addr_of_mut!(xmlRegisterNodeDefaultValue) }
1237}
1238
1239/// Upstream `__xmlStructuredError(void)` — returns a pointer to `xmlStructuredError`.
1240#[no_mangle]
1241pub unsafe extern "C" fn __xmlStructuredError() -> *mut Option<xmlStructuredErrorFunc> {
1242    // SAFETY: returning a pointer to an exported static; the caller may
1243    // read/write it exactly as with upstream's deprecated accessor.
1244    unsafe { core::ptr::addr_of_mut!(xmlStructuredError) }
1245}
1246
1247/// Upstream `__xmlStructuredErrorContext(void)` — returns a pointer to `xmlStructuredErrorContext`.
1248#[no_mangle]
1249pub unsafe extern "C" fn __xmlStructuredErrorContext() -> *mut *mut c_void {
1250    // SAFETY: returning a pointer to an exported static; the caller may
1251    // read/write it exactly as with upstream's deprecated accessor.
1252    unsafe { core::ptr::addr_of_mut!(xmlStructuredErrorContext) }
1253}
1254
1255/// Upstream `__xmlSubstituteEntitiesDefaultValue(void)` — returns a pointer to `xmlSubstituteEntitiesDefaultValue`.
1256#[no_mangle]
1257pub unsafe extern "C" fn __xmlSubstituteEntitiesDefaultValue() -> *mut c_int {
1258    // SAFETY: returning a pointer to an exported static; the caller may
1259    // read/write it exactly as with upstream's deprecated accessor.
1260    unsafe { core::ptr::addr_of_mut!(xmlSubstituteEntitiesDefaultValue) }
1261}
1262
1263// ═══════════════════════════════════════════════════════════════════════════════
1264// Regression court — xmlLastError mirror concurrency (11.1-X)
1265// ═══════════════════════════════════════════════════════════════════════════════
1266//
1267// R-000135 discovery during 11.1-X: the exported `xmlLastError` mirror was
1268// deep-copied and freed without synchronization, so concurrent error raises
1269// on different threads double-freed the mirror strings. The parallel lib
1270// test suite observed this as `double free or corruption (!prev)` aborts
1271// (xml::errors tests racing with any other raising thread). The writers are
1272// serialized via LAST_ERROR_MIRROR_LOCK; these courts hammer the exact
1273// interleavings and must complete without crashing.
1274
1275#[cfg(test)]
1276mod tests {
1277    use super::*;
1278    use crate::abi::allocator::xmlMallocImpl;
1279    use crate::abi::structs::_xmlError;
1280    use crate::xml::globals;
1281    use core::ptr;
1282
1283    /// Allocate a NUL-terminated C string owned by xmlMallocImpl (the same
1284    /// allocator the thread-local error slot uses).
1285    unsafe fn alloc_cstr(s: &str) -> *mut c_char {
1286        let bytes = s.as_bytes();
1287        let p = unsafe { xmlMallocImpl(bytes.len() + 1) as *mut c_char };
1288        assert!(!p.is_null(), "alloc_cstr: xmlMallocImpl failed");
1289        unsafe {
1290            ptr::copy_nonoverlapping(bytes.as_ptr(), p as *mut u8, bytes.len());
1291            *((p as *mut u8).add(bytes.len())) = 0;
1292        }
1293        p
1294    }
1295
1296    /// Build an owned `_xmlError` with distinct string fields.
1297    unsafe fn build_error(tag: &str) -> _xmlError {
1298        _xmlError {
1299            domain: 1,
1300            code: 2,
1301            message: unsafe { alloc_cstr(&format!("msg {tag}")) },
1302            level: 3,
1303            file: unsafe { alloc_cstr(&format!("file {tag}")) },
1304            line: 4,
1305            str1: unsafe { alloc_cstr(&format!("str1 {tag}")) },
1306            str2: ptr::null_mut(),
1307            str3: ptr::null_mut(),
1308            int1: 0,
1309            int2: 0,
1310            ctxt: ptr::null_mut(),
1311            node: ptr::null_mut(),
1312        }
1313    }
1314
1315    /// Concurrent sync/reset hammer: one thread raises errors while another
1316    /// resets. Before the mirror lock this double-freed the shared strings;
1317    /// the test crashes (SIGABRT) under the old code and passes now.
1318    #[test]
1319    fn test_last_error_mirror_concurrent_sync_reset() {
1320        let sync = std::thread::spawn(|| {
1321            for i in 0..400 {
1322                unsafe { globals::set_last_error(build_error(&format!("sync {i}"))) };
1323            }
1324        });
1325        let reset = std::thread::spawn(|| {
1326            for _ in 0..400 {
1327                globals::reset_last_error();
1328            }
1329        });
1330        sync.join().unwrap();
1331        reset.join().unwrap();
1332        // Leave the mirror in a clean state for later tests. (No thread-local
1333        // assertion: the harness reuses OS threads across tests, so a prior
1334        // test's error may legitimately live in this thread's slot.)
1335        globals::reset_last_error();
1336    }
1337
1338    /// Many threads raising concurrently (the full parallel-suite shape that
1339    /// originally aborted in `test_encode_entities_reentrant_*` victims).
1340    #[test]
1341    fn test_last_error_mirror_many_threads() {
1342        let mut handles = Vec::new();
1343        for t in 0..8 {
1344            handles.push(std::thread::spawn(move || {
1345                for i in 0..150 {
1346                    unsafe { globals::set_last_error(build_error(&format!("t{t} i{i}"))) };
1347                    if i % 7 == 0 {
1348                        globals::reset_last_error();
1349                    }
1350                }
1351            }));
1352        }
1353        for h in handles {
1354            h.join().unwrap();
1355        }
1356        globals::reset_last_error();
1357    }
1358}