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