Skip to main content

wasm3x/
error.rs

1//! Error handling for the safe Wasm3 bindings.
2
3use core::fmt;
4use std::ffi::CStr;
5
6use wasm3x_sys as ffi;
7
8/// A specialized [`Result`](core::result::Result) type for this crate.
9pub type Result<T, E = Error> = core::result::Result<T, E>;
10
11/// An error that can occur when working with the Wasm3 interpreter.
12#[derive(Debug)]
13pub struct Error(Box<ErrorRepr>);
14
15#[derive(Debug)]
16enum ErrorRepr {
17    /// An error reported by the Wasm3 C-API (`M3Result`).
18    Wasm3(String),
19    /// A trap raised during execution of a Wasm function.
20    Trap(String),
21    /// A type or arity mismatch detected by the safe wrapper.
22    Mismatch(String),
23    /// An error raised by a host function.
24    Host(Box<dyn std::error::Error + Send + Sync + 'static>),
25    /// A generic wrapper error with a custom message.
26    Message(String),
27}
28
29impl Error {
30    /// Creates a new error from a custom message.
31    pub fn new(message: impl Into<String>) -> Self {
32        Self(Box::new(ErrorRepr::Message(message.into())))
33    }
34
35    /// Creates a host error wrapping an arbitrary error value.
36    ///
37    /// Use this to signal a trap from within a host function.
38    pub fn host<E>(error: E) -> Self
39    where
40        E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
41    {
42        Self(Box::new(ErrorRepr::Host(error.into())))
43    }
44
45    /// Returns `true` if this error originates from a host function.
46    pub fn is_host(&self) -> bool {
47        matches!(*self.0, ErrorRepr::Host(_))
48    }
49
50    /// Returns `true` if this error represents a Wasm execution trap.
51    pub fn is_trap(&self) -> bool {
52        matches!(*self.0, ErrorRepr::Trap(_))
53    }
54
55    pub(crate) fn mismatch(message: impl Into<String>) -> Self {
56        Self(Box::new(ErrorRepr::Mismatch(message.into())))
57    }
58
59    /// Converts a raw `M3Result` into a [`Result`].
60    ///
61    /// A null pointer denotes success. A non-null pointer references a static
62    /// C string owned by the Wasm3 library.
63    pub(crate) fn from_ffi(result: ffi::M3Result) -> Result<()> {
64        if result.is_null() {
65            return Ok(());
66        }
67        Err(Self(Box::new(ErrorRepr::Wasm3(ffi_message(result)))))
68    }
69
70    /// Converts a raw `M3Result` originating from a `m3_Call` into a trap error,
71    /// enriched with `m3_GetErrorInfo` details when available.
72    pub(crate) fn from_trap(runtime: ffi::IM3Runtime, result: ffi::M3Result) -> Self {
73        debug_assert!(!result.is_null());
74        let mut message = ffi_message(result);
75        // Try to enrich the message with backtrace-style error info.
76        unsafe {
77            let mut info: ffi::M3ErrorInfo = core::mem::zeroed();
78            ffi::m3_GetErrorInfo(runtime, &mut info);
79            if !info.message.is_null() {
80                let detail = CStr::from_ptr(info.message).to_string_lossy();
81                if !detail.is_empty() && !message.contains(detail.as_ref()) {
82                    message = format!("{message}: {detail}");
83                }
84            }
85        }
86        Self(Box::new(ErrorRepr::Trap(message)))
87    }
88}
89
90/// Reads a non-null `M3Result` into an owned `String`.
91fn ffi_message(result: ffi::M3Result) -> String {
92    // SAFETY: Wasm3 error results are static, nul-terminated C strings.
93    unsafe { CStr::from_ptr(result) }
94        .to_string_lossy()
95        .into_owned()
96}
97
98impl fmt::Display for Error {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match &*self.0 {
101            ErrorRepr::Wasm3(msg) => write!(f, "wasm3 error: {msg}"),
102            ErrorRepr::Trap(msg) => write!(f, "wasm trap: {msg}"),
103            ErrorRepr::Mismatch(msg) => write!(f, "type mismatch: {msg}"),
104            ErrorRepr::Host(err) => write!(f, "host error: {err}"),
105            ErrorRepr::Message(msg) => f.write_str(msg),
106        }
107    }
108}
109
110impl std::error::Error for Error {
111    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
112        match &*self.0 {
113            ErrorRepr::Host(err) => Some(&**err),
114            _ => None,
115        }
116    }
117}