diff --git a/core/src/host_error.rs b/core/src/host_error.rs index 0b8722d09b..0bf9bd1289 100644 --- a/core/src/host_error.rs +++ b/core/src/host_error.rs @@ -37,7 +37,8 @@ use downcast_rs::{impl_downcast, DowncastSync}; /// /// // Get a reference to the concrete error /// match failable_fn() { -/// Err(Trap::Host(host_error)) => { +/// Err(trap) if trap.is_host() => { +/// let host_error = trap.as_host().unwrap(); /// let my_error: &MyError = host_error.downcast_ref::().unwrap(); /// assert_eq!(my_error.code, 1312); /// } @@ -48,7 +49,7 @@ use downcast_rs::{impl_downcast, DowncastSync}; /// match failable_fn() { /// Err(err) => { /// let my_error = match err { -/// Trap::Host(host_error) => host_error.downcast::().unwrap(), +/// trap if trap.is_host() => trap.into_host().unwrap().downcast::().unwrap(), /// unexpected => panic!("expected host error but found: {}", unexpected), /// }; /// assert_eq!(my_error.code, 1312); diff --git a/core/src/trap.rs b/core/src/trap.rs index 6f3f03962f..ad5b6359b9 100644 --- a/core/src/trap.rs +++ b/core/src/trap.rs @@ -10,43 +10,136 @@ use std::error::Error as StdError; /// Under some conditions, wasm execution may produce a `Trap`, which immediately aborts execution. /// Traps can't be handled by WebAssembly code, but are reported to the embedder. #[derive(Debug)] -pub enum Trap { +pub struct Trap { + /// The internal data structure of a [`Trap`]. + inner: Box, +} + +#[test] +fn trap_size() { + assert_eq!( + core::mem::size_of::(), + core::mem::size_of::<*const ()>() + ); +} + +/// The internal of a [`Trap`]. +#[derive(Debug)] +enum TrapInner { /// Traps during Wasm execution. Code(TrapCode), /// Traps and errors during host execution. Host(Box), } +impl TrapInner { + /// Returns `true` if `self` trap originating from host code. + #[inline] + pub fn is_host(&self) -> bool { + matches!(self, TrapInner::Host(_)) + } + + /// Returns `true` if `self` trap originating from Wasm code. + #[inline] + pub fn is_code(&self) -> bool { + matches!(self, TrapInner::Code(_)) + } + + /// Returns a shared reference to the [`HostError`] if any. + #[inline] + pub fn as_host(&self) -> Option<&dyn HostError> { + if let Self::Host(host_error) = self { + return Some(&**host_error); + } + None + } + + /// Returns an exclusive reference to the [`HostError`] if any. + #[inline] + pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> { + if let Self::Host(host_error) = self { + return Some(&mut **host_error); + } + None + } + + /// Converts into the [`HostError`] if any. + #[inline] + pub fn into_host(self) -> Option> { + if let Self::Host(host_error) = self { + return Some(host_error); + } + None + } + + /// Returns the [`TrapCode`] traps originating from Wasm execution. + #[inline] + pub fn as_code(&self) -> Option { + if let Self::Code(trap_code) = self { + return Some(*trap_code); + } + None + } +} + impl Trap { + /// Create a new [`Trap`] from the [`TrapInner`]. + fn new(inner: TrapInner) -> Self { + Self { + inner: Box::new(inner), + } + } + /// Wraps the host error in a [`Trap`]. - #[inline] + #[cold] pub fn host(host_error: U) -> Self where U: HostError + Sized, { - Self::Host(Box::new(host_error)) + Self::new(TrapInner::Host(Box::new(host_error))) } /// Returns `true` if `self` trap originating from host code. #[inline] pub fn is_host(&self) -> bool { - matches!(self, Self::Host(_)) + self.inner.is_host() + } + + /// Returns `true` if `self` trap originating from Wasm code. + #[inline] + pub fn is_code(&self) -> bool { + self.inner.is_code() + } + + /// Returns a shared reference to the [`HostError`] if any. + #[inline] + pub fn as_host(&self) -> Option<&dyn HostError> { + self.inner.as_host() + } + + /// Returns an exclusive reference to the [`HostError`] if any. + #[inline] + pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> { + self.inner.as_host_mut() + } + + /// Converts into the [`HostError`] if any. + #[inline] + pub fn into_host(self) -> Option> { + self.inner.into_host() } /// Returns the [`TrapCode`] traps originating from Wasm execution. #[inline] - pub fn code(&self) -> Option { - if let Self::Code(trap_code) = self { - return Some(*trap_code); - } - None + pub fn as_code(&self) -> Option { + self.inner.as_code() } } impl From for Trap { - #[inline] + #[cold] fn from(error: TrapCode) -> Self { - Self::Code(error) + Self::new(TrapInner::Code(error)) } } @@ -56,23 +149,29 @@ where { #[inline] fn from(e: U) -> Self { - Trap::host(e) + Self::host(e) } } -impl Display for Trap { +impl Display for TrapInner { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Trap::Code(trap_code) => Display::fmt(trap_code, f), - Trap::Host(host_error) => Display::fmt(host_error, f), + Self::Code(trap_code) => Display::fmt(trap_code, f), + Self::Host(host_error) => Display::fmt(host_error, f), } } } +impl Display for Trap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&self.inner, f) + } +} + #[cfg(feature = "std")] impl StdError for Trap { fn description(&self) -> &str { - self.code().map(|code| code.trap_message()).unwrap_or("") + self.as_code().map(|code| code.trap_message()).unwrap_or("") } } diff --git a/wasmi_v1/benches/benches.rs b/wasmi_v1/benches/benches.rs index bfc3f8afd4..48833475e4 100644 --- a/wasmi_v1/benches/benches.rs +++ b/wasmi_v1/benches/benches.rs @@ -10,7 +10,7 @@ use self::bench::{ use criterion::{criterion_group, criterion_main, Bencher, Criterion}; use std::{slice, time::Duration}; use wasmi as v1; -use wasmi::core::{Trap, Value}; +use wasmi::core::Value; const WASM_KERNEL: &str = "benches/wasm/wasm_kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm"; @@ -341,15 +341,18 @@ fn bench_execute_recursive_trap_v1(c: &mut Criterion) { .and_then(v1::Extern::into_func) .unwrap(); let mut result = [Value::I32(0)]; - b.iter(|| { let error = bench_call .call(&mut store, &[Value::I32(1000)], &mut result) .unwrap_err(); - assert!(matches!( - error, - v1::Error::Trap(Trap::Code(v1::core::TrapCode::Unreachable)) - )); + match error { + v1::Error::Trap(trap) => assert_matches::assert_matches!( + trap.as_code(), + Some(v1::core::TrapCode::Unreachable), + "expected unreachable trap", + ), + _ => panic!("expected unreachable trap"), + } }) }); } diff --git a/wasmi_v1/tests/spec/run.rs b/wasmi_v1/tests/spec/run.rs index ac72f8232f..f32fc54b3e 100644 --- a/wasmi_v1/tests/spec/run.rs +++ b/wasmi_v1/tests/spec/run.rs @@ -1,7 +1,7 @@ use super::{error::TestError, TestContext, TestDescriptor}; use anyhow::Result; use wasmi::{Config, Error as WasmiError}; -use wasmi_core::{Trap, Value, F32, F64}; +use wasmi_core::{Value, F32, F64}; use wast::{ lexer::Lexer, parser::ParseBuffer, @@ -200,9 +200,10 @@ fn execute_directives(wast: Wast, test_context: &mut TestContext) -> Result<()> /// - If the trap message of the `error` is not as expected. fn assert_trap(test_context: &TestContext, span: Span, error: TestError, message: &str) { match error { - TestError::Wasmi(WasmiError::Trap(Trap::Code(trap_code))) => { + TestError::Wasmi(WasmiError::Trap(trap)) if trap.is_code() => { + let code = trap.as_code().unwrap(); assert_eq!( - trap_code.trap_message(), + code.trap_message(), message, "{}: the directive trapped as expected but with an unexpected message", test_context.spanned(span),