holochain_wasmer_common/
result.rs

1use holochain_serialized_bytes::prelude::*;
2use thiserror::Error;
3
4/// Enum of all possible ERROR states that wasm can encounter.
5///
6/// Used in [`wasm_error!`] for specifying the error type and message.
7#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
8pub enum WasmErrorInner {
9    /// While moving pointers and lengths across the host/guest, we hit an unsafe
10    /// conversion such as a negative pointer or out of bounds value.
11    PointerMap,
12    /// These bytes failed to deserialize.
13    /// The host should provide nice debug info and context that the wasm guest won't have.
14    #[serde(with = "serde_bytes")]
15    Deserialize(Vec<u8>),
16    /// Something failed to serialize.
17    /// This should be rare or impossible for almost everything that implements `Serialize`.
18    Serialize(SerializedBytesError),
19    /// Somehow we errored while erroring.
20    /// For example, maybe we failed to serialize an error while attempting to serialize an error.
21    ErrorWhileError,
22    /// Something went wrong while writing or reading bytes to/from wasm memory.
23    /// Whatever this is, it is very bad and probably not recoverable.
24    Memory,
25    /// Error with guest logic that the host doesn't know about.
26    Guest(String),
27    /// Error with host logic that the guest doesn't know about.
28    Host(String),
29    /// Something to do with host logic that the guest doesn't know about
30    /// AND wasm execution MUST immediately halt.
31    /// The `Vec<u8>` holds the encoded data as though the guest had returned.
32    HostShortCircuit(Vec<u8>),
33    /// Wasmer failed to build a Module from wasm byte code.
34    /// With the feature `wasmer_sys` enabled, building a Module includes compiling the wasm.
35    ModuleBuild(String),
36    /// Wasmer failed to serialize a Module to bytes.
37    ModuleSerialize(String),
38    /// Wasmer failed to deserialize a Module from bytes.
39    ModuleDeserialize(String),
40    /// The host failed to call a function in the guest.
41    CallError(String),
42}
43
44impl WasmErrorInner {
45    /// Some errors indicate the wasm guest is potentially corrupt and so the
46    /// host MUST NOT reuse it (e.g. in a cache of wasm instances). Other errors
47    /// MAY NOT invalidate an instance cache on the host.
48    pub fn maybe_corrupt(&self) -> bool {
49        match self {
50            // Similar to bad memory errors.
51            Self::PointerMap
52            // Should never happen but best not to cache this.
53            | Self::ErrorWhileError
54            // Bad memory is bad memory.
55            | Self::Memory
56            // Failing to build, serialize, or deserialize the wasmer Module means we cannot use it.
57            | Self::ModuleBuild(_)
58            | Self::ModuleSerialize(_)
59            | Self::ModuleDeserialize(_)
60            // This is ambiguous so best to treat as potentially corrupt.
61            | Self::CallError(_)
62             => true,
63            // (De)serialization simply means some input/output data was
64            // unrecognisable somehow, it doesn't corrupt the guest memory.
65            Self::Deserialize(_)
66            | Self::Serialize(_)
67            // Guest/host errors are regular errors that simply pass through the
68            // guest as any other data as some `Result` value, it doesn't corrupt
69            // guest memory.
70            | Self::Guest(_)
71            | Self::Host(_)
72            // As long as `consume_bytes_from_guest` is used by the host it will
73            // have already cleaned up all memory leaks in the guest before the
74            // host can even run something that may fail and short circuit.
75            | Self::HostShortCircuit(_)=> false,
76        }
77    }
78}
79
80impl From<std::num::TryFromIntError> for WasmErrorInner {
81    fn from(_: std::num::TryFromIntError) -> Self {
82        Self::PointerMap
83    }
84}
85
86impl From<std::array::TryFromSliceError> for WasmErrorInner {
87    fn from(_: std::array::TryFromSliceError) -> Self {
88        Self::Memory
89    }
90}
91
92impl From<SerializedBytesError> for WasmErrorInner {
93    fn from(error: SerializedBytesError) -> Self {
94        Self::Serialize(error)
95    }
96}
97
98/// Wraps a WasmErrorInner with a file and line number.
99/// The easiest way to generate this is with the `wasm_error!` macro that will
100/// insert the correct file/line and can create strings by forwarding args to
101/// the `format!` macro.
102#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Error)]
103#[rustfmt::skip]
104pub struct WasmError {
105    pub file: String,
106    pub line: u32,
107    pub error: WasmErrorInner,
108}
109
110/// Helper macro for returning an error from a WASM.
111///
112/// Automatically included in the error are the file and the line number where the
113/// error occurred. The error type is one of the [`WasmErrorInner`] variants.
114///
115/// This macro is the recommended way of returning an error from a Zome function.
116///
117/// If a single expression is passed to `wasm_error!` the result will be converted
118/// to a `WasmErrorInner` via `into()` so string and `WasmErrorInner` values are
119/// both supported directly.
120///
121/// If a list of arguments is passed to `wasm_error!` it will first be forwarded
122/// to `format!` and then the resultant string converted to `WasmErrorInner`.
123///
124/// As the string->WasmErrorInner conversion is handled by a call to into, the
125/// feature `error_as_host` can be used so that `WasmErrorInner::Host` is produced
126/// by the macro from any passed/generated string.
127///
128/// # Examples
129///
130/// ```ignore
131/// Err(wasm_error!(WasmErrorInner::Guest("entry not found".to_string())));
132/// Err(wasm_error!("entry not found"));
133/// Err(wasm_error!("{} {}", "entry", "not found"));
134/// Err(wasm_error!(WasmErrorInner::Host("some host error".into())));
135/// ```
136#[macro_export]
137macro_rules! wasm_error {
138    ($e:expr) => {
139        $crate::WasmError {
140            // On Windows the `file!()` macro returns a path with inconsistent formatting:
141            // from the workspace to the package root it uses backwards-slashes,
142            // then within the package it uses forwards-slashes.
143            // i.e. "test-crates\\wasm_core\\src/wasm.rs"
144            //
145            // To remedy this we normalize the formatting here.
146            file: file!().replace('\\', "/").to_string(),
147            line: line!(),
148            error: $e.into(),
149        }
150    };
151    ($($arg:tt)*) => {{
152        $crate::wasm_error!(std::format!($($arg)*))
153    }};
154}
155
156impl From<WasmError> for String {
157    fn from(e: WasmError) -> Self {
158        format!("{}", e)
159    }
160}
161
162impl std::fmt::Display for WasmError {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        write!(f, "{:?}", self)
165    }
166}
167
168/// Allows ? in a TryFrom context downstream.
169impl From<core::convert::Infallible> for WasmError {
170    fn from(_: core::convert::Infallible) -> WasmError {
171        unreachable!()
172    }
173}
174
175#[cfg(not(feature = "error_as_host"))]
176impl From<String> for WasmErrorInner {
177    fn from(s: String) -> Self {
178        Self::Guest(s)
179    }
180}
181
182#[cfg(feature = "error_as_host")]
183impl From<String> for WasmErrorInner {
184    fn from(s: String) -> Self {
185        Self::Host(s)
186    }
187}
188
189impl From<&str> for WasmErrorInner {
190    fn from(s: &str) -> Self {
191        s.to_string().into()
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    #[cfg(not(feature = "error_as_host"))]
201    fn wasm_error_macro_guest() {
202        assert_eq!(
203            wasm_error!("foo").error,
204            WasmErrorInner::Guest("foo".into()),
205        );
206
207        assert_eq!(
208            wasm_error!("{} {}", "foo", "bar").error,
209            WasmErrorInner::Guest("foo bar".into())
210        );
211
212        assert_eq!(
213            wasm_error!(WasmErrorInner::Host("foo".into())).error,
214            WasmErrorInner::Host("foo".into()),
215        );
216    }
217
218    #[test]
219    #[cfg(feature = "error_as_host")]
220    fn wasm_error_macro_host() {
221        assert_eq!(wasm_error!("foo").error, WasmErrorInner::Host("foo".into()),);
222
223        assert_eq!(
224            wasm_error!("{} {}", "foo", "bar").error,
225            WasmErrorInner::Host("foo bar".into())
226        );
227
228        assert_eq!(
229            wasm_error!(WasmErrorInner::Guest("foo".into())).error,
230            WasmErrorInner::Guest("foo".into()),
231        );
232    }
233}