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}