Skip to main content

libxml_rs/xslt/errors/
mod.rs

1//! XSLT error handling (§33, §85 Phase 8).
2//!
3//! Defines error domains, error levels, error handler types, and the
4//! public API for reporting and retrieving XSLT errors.
5//!
6//! # Phase 8 status
7//!
8//! Constants and function types are fully defined. Functions are stubbed
9//! and will be implemented as part of Phase 8.
10
11use crate::abi::structs::*;
12use std::os::raw::c_int;
13use std::ptr;
14
15// ── Error domains ─────────────────────────────────────────────────────────
16//
17// These constants identify the category of an XSLT error.
18// Source: xslt.h / xsltInternals.h (libxslt 1.1.45).
19
20/// No error.
21pub const XSLT_ERR_NONE: c_int = 0;
22
23/// Unknown error.
24pub const XSLT_ERR_UNKNOWN: c_int = 1;
25
26/// Missing required namespace.
27pub const XSLT_ERR_MISSING_NAMESPACE: c_int = 2;
28
29/// Invalid namespace.
30pub const XSLT_ERR_INVALID_NAMESPACE: c_int = 3;
31
32/// Missing required attribute.
33pub const XSLT_ERR_MISSING_ATTRIBUTE: c_int = 4;
34
35/// Invalid attribute value.
36pub const XSLT_ERR_INVALID_ATTRIBUTE: c_int = 5;
37
38/// Missing required element.
39pub const XSLT_ERR_MISSING_ELEMENT: c_int = 6;
40
41/// Invalid element.
42pub const XSLT_ERR_INVALID_ELEMENT: c_int = 7;
43
44/// Missing match attribute.
45pub const XSLT_ERR_MISSING_MATCH: c_int = 8;
46
47/// Missing name attribute.
48pub const XSLT_ERR_MISSING_NAME: c_int = 9;
49
50/// Missing select attribute.
51pub const XSLT_ERR_MISSING_SELECT: c_int = 10;
52
53/// Missing test attribute.
54pub const XSLT_ERR_MISSING_TEST: c_int = 11;
55
56/// Missing use attribute.
57pub const XSLT_ERR_MISSING_USE: c_int = 12;
58
59/// Invalid match pattern.
60pub const XSLT_ERR_INVALID_MATCH: c_int = 13;
61
62/// Invalid select expression.
63pub const XSLT_ERR_INVALID_SELECT: c_int = 14;
64
65/// Invalid test expression.
66pub const XSLT_ERR_INVALID_TEST: c_int = 15;
67
68/// Invalid use expression.
69pub const XSLT_ERR_INVALID_USE: c_int = 16;
70
71/// Missing namespace.
72pub const XSLT_ERR_MISSING_NS: c_int = 17;
73
74/// Cyclic reference detected.
75pub const XSLT_ERR_CYCLIC_REFERENCE: c_int = 18;
76
77/// Recursion limit exceeded.
78pub const XSLT_ERR_RECURSION: c_int = 19;
79
80/// Internal XSLT error.
81pub const XSLT_ERR_INTERNAL: c_int = 20;
82
83// ── Error levels ──────────────────────────────────────────────────────────
84//
85// These constants indicate the severity of an XSLT error.
86// Source: xslt.h (libxslt 1.1.45).
87
88/// No error level (unset).
89pub const XSLT_ERR_LEVEL_NONE: c_int = 0;
90
91/// Warning — non-fatal issue.
92pub const XSLT_ERR_LEVEL_WARNING: c_int = 1;
93
94/// Error — processing may continue but results may be incomplete.
95pub const XSLT_ERR_LEVEL_ERROR: c_int = 2;
96
97/// Fatal error — processing cannot continue.
98pub const XSLT_ERR_LEVEL_FATAL: c_int = 3;
99
100// ── Error handler types ───────────────────────────────────────────────────
101
102/// Global XSLT error handler function type.
103///
104/// Matches the upstream `xsltTransformErrorFunc` typedef:
105/// ```c
106/// typedef void (*xsltTransformErrorFunc)(void *ctxt, void *ctx,
107///                                        xsltStylesheetPtr style,
108///                                        const xmlChar *msg, ...);
109/// ```
110pub type xsltTransformErrorFunc = Option<
111    unsafe extern "C" fn(
112        *mut std::ffi::c_void,
113        *mut std::ffi::c_void,
114        *mut _xsltStylesheet,
115        *const crate::abi::types::xmlChar,
116        ...
117    ),
118>;
119
120// ── Public API ────────────────────────────────────────────────────────────
121
122/// The last XSLT error message (thread-local).
123use std::sync::Mutex;
124
125static LAST_XSLT_ERROR: Mutex<Option<Vec<u8>>> = Mutex::new(None);
126
127/// Global debug handler (upstream xsltGenericDebug).
128static mut XSLT_GENERIC_DEBUG: Option<
129    unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char),
130> = None;
131
132/// Set the generic debug handler (upstream `xsltSetGenericDebugFunc`).
133///
134/// # UPSTREAM-PARITY
135///
136/// ```c
137/// void xsltSetGenericDebugFunc(void *ctx, xmlGenericErrorFunc handler);
138/// ```
139///
140/// With a NULL handler, messages go to `stderr`; with a NULL context they
141/// are suppressed (upstream's default debug handler checks the context).
142///
143/// # SAFETY
144///
145/// - `ctx` must be valid pointers (or NULL
146///   where the upstream C contract allows), obtained from the
147///   matching constructor/owner and not yet freed; the callee may
148///   take or keep ownership exactly as the C API specifies.
149///
150/// - `handler` must be a valid callback (or None);
151///   the callback is invoked with the documented context pointer and
152///   must itself uphold the same pointer invariants.
153///
154/// The caller must not race this call with concurrent mutation of the
155/// same objects from other threads (per-object state is not internally
156/// synchronized). Violating any of the above is undefined behavior.
157///
158/// Exercised by the C-API differential courts
159/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
160/// courts; those pass byte-for-byte against the upstream oracle.
161#[no_mangle]
162pub unsafe extern "C" fn xsltSetGenericDebugFunc(
163    ctx: *mut std::ffi::c_void,
164    handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
165) {
166    unsafe {
167        XSLT_GENERIC_DEBUG_CONTEXT = ctx;
168        if handler.is_some() {
169            XSLT_GENERIC_DEBUG = handler;
170        }
171    }
172}
173
174static mut XSLT_GENERIC_DEBUG_CONTEXT: *mut std::ffi::c_void = std::ptr::null_mut();
175
176/// Emit a generic debug message (upstream xsltGenericDebug).
177///
178/// # SAFETY
179///
180/// - `ctx`, `msg` must be valid pointers (or NULL
181///   where the upstream C contract allows), obtained from the
182///   matching constructor/owner and not yet freed; the callee may
183///   take or keep ownership exactly as the C API specifies.
184///
185/// The caller must not race this call with concurrent mutation of the
186/// same objects from other threads (per-object state is not internally
187/// synchronized). Violating any of the above is undefined behavior.
188///
189/// Exercised by the C-API differential courts
190/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
191/// courts; those pass byte-for-byte against the upstream oracle.
192#[no_mangle]
193pub unsafe extern "C" fn xsltGenericDebug(
194    ctx: *mut std::ffi::c_void,
195    msg: *const std::os::raw::c_char,
196) {
197    if ctx.is_null() || msg.is_null() {
198        return;
199    }
200    let len = libc::strlen(msg);
201    libc::write(2, msg as *const libc::c_void, len);
202}
203
204/// Set the transform error handler for a context.
205///
206/// Registers a per-context error handler that will be called for every
207/// error reported during the transformation. Pass `None` to restore the
208/// default handler.
209///
210/// # Parameters
211///
212/// * `ctxt`   — The transform context, or `std::ptr::null_mut()` for the
213///   global handler.
214/// * `ctx`    — Opaque user-data pointer passed to the handler.
215/// * `handler` — The error handler function, or `None` to reset.
216pub fn xsltSetTransformErrorFunc(
217    ctxt: *mut _xsltTransformContext,
218    ctx: *mut std::ffi::c_void,
219    handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
220) {
221    if ctxt.is_null() {
222        return;
223    }
224    // SAFETY: ctxt must be a valid _xsltTransformContext.
225    unsafe {
226        (*ctxt).error = handler;
227        (*ctxt).errctx = ctx;
228    }
229}
230
231/// Report an XSLT error.
232///
233/// Faithful port of upstream xsltutils.c `xsltTransformError`: the
234/// transform context is moved to the error state, the error context line
235/// is printed (upstream `xsltPrintErrorContext`), and the message is
236/// emitted verbatim through the registered handler or stderr. Messages
237/// carry their own trailing newline, exactly as upstream's do — no
238/// newline is added here.
239///
240/// The upstream signature is variadic (`const char *msg, ...`); the
241/// candidate's callers format the message before calling (a `%s`/`%d`
242/// placeholder is never expanded by this function).
243///
244/// # Parameters
245///
246/// * `ctxt`  — The transform context (may be null).
247/// * `style` — The stylesheet (may be null).
248/// * `inst`  — The instruction node that triggered the error (may be null).
249/// * `msg`   — The message, NUL-terminated, typically ending in `\n`.
250pub fn xsltTransformError(
251    ctxt: *mut _xsltTransformContext,
252    style: *mut _xsltStylesheet,
253    inst: *mut _xmlNode,
254    msg: *const std::os::raw::c_char,
255) {
256    if msg.is_null() {
257        return;
258    }
259    // SAFETY: msg must be a valid NUL-terminated C string.
260    let bytes =
261        unsafe { core::slice::from_raw_parts(msg as *const u8, libc::strlen(msg) as usize) };
262    let text = String::from_utf8_lossy(bytes).into_owned();
263
264    // Record the last error (the raw message, as upstream stores the
265    // formatted message).
266    if let Ok(mut last) = LAST_XSLT_ERROR.lock() {
267        *last = Some(text.clone().into_bytes());
268    }
269
270    // UPSTREAM-PARITY (xsltutils.c xsltTransformError): an error moves the
271    // transform context out of the OK state.
272    if !ctxt.is_null() {
273        // SAFETY: ctxt must be a valid _xsltTransformContext.
274        let ctx = unsafe { &mut *ctxt };
275        if ctx.state == crate::xslt::transform::XSLT_STATE_OK {
276            ctx.state = crate::xslt::transform::XSLT_STATE_ERROR;
277        }
278        let mut node = inst;
279        if node.is_null() {
280            node = ctx.inst;
281        }
282        // Build the context line (xsltPrintErrorContext) and the full
283        // message, then emit through the handler if one is registered.
284        let context_line = print_error_context(ctxt, style, node);
285        let full = format!("{}{}", context_line, text);
286        let mut cmsg = full.into_bytes();
287        let msg_len = cmsg.len();
288        cmsg.push(0);
289        let ctx = unsafe { &*ctxt };
290        if let Some(handler) = ctx.error {
291            unsafe { handler(ctx.errctx, cmsg.as_ptr() as *const std::os::raw::c_char) };
292            return;
293        }
294        let _ = unsafe { libc::write(2, cmsg.as_ptr() as *const libc::c_void, msg_len) };
295        return;
296    }
297
298    // No transform context: compile-time errors and standalone messages.
299    // (Upstream xsltPrintErrorContext is still invoked with NULL ctxt and
300    // the given style/node.)
301    let context_line = print_error_context(ptr::null_mut(), style, inst);
302    let full = format!("{}{}", context_line, text);
303    let mut cmsg = full.into_bytes();
304    let msg_len = cmsg.len();
305    cmsg.push(0);
306    let _ = unsafe { libc::write(2, cmsg.as_ptr() as *const libc::c_void, msg_len) };
307    let _ = style;
308}
309
310/// Build the error context line printed before an XSLT error message
311/// (upstream xsltutils.c `xsltPrintErrorContext`). The line is one of:
312///
313/// ```text
314/// error\n
315/// error: file F\n
316/// error: file F line N\n
317/// error: file F element E\n
318/// error: file F line N element E\n
319/// error: element E\n
320/// compilation error ... / runtime error ...
321/// ```
322fn print_error_context(
323    ctxt: *mut _xsltTransformContext,
324    style: *mut _xsltStylesheet,
325    node: *mut _xmlNode,
326) -> String {
327    let mut line = 0i64;
328    let mut file: *const std::os::raw::c_char = ptr::null();
329    let mut name: *const std::os::raw::c_char = ptr::null();
330
331    if !node.is_null() {
332        // SAFETY: node must be valid.
333        let node_ref = unsafe { &*node };
334        if node_ref.type_ == crate::abi::types::xmlElementType::XML_DOCUMENT_NODE as c_int
335            || node_ref.type_ == crate::abi::types::xmlElementType::XML_HTML_DOCUMENT_NODE as c_int
336        {
337            let doc = node as *mut crate::abi::structs::_xmlDoc;
338            // SAFETY: doc->URL is a valid NUL-terminated string or NULL.
339            file = unsafe { (*doc).URL } as *const std::os::raw::c_char;
340        } else {
341            line = crate::abi::exports_xml2::xmlGetLineNo(node) as i64;
342            // SAFETY: node->doc must be valid while the node is alive.
343            let doc = { node_ref.doc };
344            if !doc.is_null() {
345                file = unsafe { (*doc).URL } as *const std::os::raw::c_char;
346            }
347            name = node_ref.name as *const std::os::raw::c_char;
348        }
349    }
350
351    let errtype = if !ctxt.is_null() {
352        "runtime error"
353    } else if !style.is_null() {
354        "compilation error"
355    } else {
356        "error"
357    };
358
359    let s = |p: *const std::os::raw::c_char| -> String {
360        if p.is_null() {
361            String::new()
362        } else {
363            unsafe { std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned() }
364        }
365    };
366    let file_s = s(file);
367    let name_s = s(name);
368    let has_file = !file.is_null();
369    let has_name = !name.is_null();
370
371    if has_file && line != 0 && has_name {
372        format!(
373            "{}: file {} line {} element {}\n",
374            errtype, file_s, line, name_s
375        )
376    } else if has_file && has_name {
377        format!("{}: file {} element {}\n", errtype, file_s, name_s)
378    } else if has_file && line != 0 {
379        format!("{}: file {} line {}\n", errtype, file_s, line)
380    } else if has_file {
381        format!("{}: file {}\n", errtype, file_s)
382    } else if has_name {
383        format!("{}: element {}\n", errtype, name_s)
384    } else {
385        format!("{}\n", errtype)
386    }
387}
388
389/// Get the last XSLT error message as a NUL-terminated heap string.
390///
391/// Returns a pointer to the last error message, or `std::ptr::null_mut()`
392/// if no error has occurred. The caller frees with `libc::free`.
393pub fn xsltGetLastError() -> *mut std::ffi::c_void {
394    let guard = match LAST_XSLT_ERROR.lock() {
395        Ok(g) => g,
396        Err(_) => return std::ptr::null_mut(),
397    };
398    match guard.as_ref() {
399        Some(bytes) => {
400            let len = bytes.len();
401            // SAFETY: malloc returns writable memory or NULL.
402            let p = unsafe { libc::malloc(len + 1) } as *mut u8;
403            if p.is_null() {
404                return std::ptr::null_mut();
405            }
406            unsafe {
407                core::ptr::copy_nonoverlapping(bytes.as_ptr(), p, len);
408                *p.add(len) = 0;
409            }
410            p as *mut std::ffi::c_void
411        }
412        None => std::ptr::null_mut(),
413    }
414}