diff --git a/Cargo.lock b/Cargo.lock index 33ed37bc27c..0bc637862f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5174,6 +5174,7 @@ dependencies = [ "js-sys", "macro-wasmer-universal-test", "more-asserts", + "rustc-demangle", "serde", "serde-wasm-bindgen", "target-lexicon 0.12.6", @@ -5353,7 +5354,6 @@ dependencies = [ "memmap2", "more-asserts", "region", - "rustc-demangle", "serde", "serde_bytes", "smallvec", diff --git a/lib/api/Cargo.toml b/lib/api/Cargo.toml index 810e7079c6a..6c7d3a6aa3b 100644 --- a/lib/api/Cargo.toml +++ b/lib/api/Cargo.toml @@ -31,6 +31,7 @@ bytes = "1" # - Optional shared dependencies. wat = { version = "1.0", optional = true } tracing = { version = "0.1", optional = true } +rustc-demangle = "0.1" # Dependencies and Development Dependencies for `sys`. [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/lib/api/src/errors.rs b/lib/api/src/errors.rs index a216cdda459..76771beb802 100644 --- a/lib/api/src/errors.rs +++ b/lib/api/src/errors.rs @@ -1,8 +1,35 @@ #[cfg(feature = "js")] -pub use crate::js::errors::{LinkError, RuntimeError}; +use crate::js::trap::Trap; +use std::fmt; +use std::sync::Arc; use thiserror::Error; +use wasmer_types::{FrameInfo, TrapCode}; #[cfg(feature = "sys")] -pub use wasmer_compiler::{LinkError, RuntimeError}; +use wasmer_vm::Trap; + +use wasmer_types::ImportError; + +/// The WebAssembly.LinkError object indicates an error during +/// module instantiation (besides traps from the start function). +/// +/// This is based on the [link error][link-error] API. +/// +/// [link-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(Error))] +#[cfg_attr(feature = "std", error("Link error: {0}"))] +pub enum LinkError { + /// An error occurred when checking the import types. + #[cfg_attr(feature = "std", error("Error while importing {0:?}.{1:?}: {2}"))] + Import(String, String, ImportError), + + /// A trap ocurred during linking. + #[cfg_attr(feature = "std", error("RuntimeError occurred during linking: {0}"))] + Trap(#[source] RuntimeError), + /// Insufficient resources available for linking. + #[cfg_attr(feature = "std", error("Insufficient resources: {0}"))] + Resource(String), +} /// An error while instantiating a module. /// @@ -38,3 +65,199 @@ pub enum InstantiationError { #[cfg_attr(feature = "std", error("incorrect OS or architecture"))] DifferentArchOS, } + +/// A struct representing an aborted instruction execution, with a message +/// indicating the cause. +#[derive(Clone)] +pub struct RuntimeError { + pub(crate) inner: Arc, +} + +#[derive(Debug)] +struct RuntimeStringError { + details: String, +} + +impl RuntimeStringError { + fn new(msg: String) -> RuntimeStringError { + RuntimeStringError { details: msg } + } +} + +impl fmt::Display for RuntimeStringError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.details) + } +} + +impl std::error::Error for RuntimeStringError { + fn description(&self) -> &str { + &self.details + } +} + +pub(crate) struct RuntimeErrorInner { + /// The source error + pub(crate) source: Trap, + /// The trap code (if any) + trap_code: Option, + /// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`). + wasm_trace: Vec, +} + +impl RuntimeError { + /// Creates a new generic `RuntimeError` with the given `message`. + /// + /// # Example + /// ``` + /// let trap = wasmer::RuntimeError::new("unexpected error"); + /// assert_eq!("unexpected error", trap.message()); + /// ``` + pub fn new>(message: I) -> Self { + let msg = message.into(); + let source = RuntimeStringError::new(msg); + Self::user(Box::new(source)) + } + + /// Creates `RuntimeError` from an error and a WasmTrace + /// + /// # Example + /// ```ignore + /// let wasm_trace = vec![wasmer_types::FrameInfo::new( + /// "my_module".to_string(), + /// 0, + /// Some("my_function".to_string()), + /// 0.into(), + /// 2.into() + /// )]; + /// let trap = wasmer::RuntimeError::new_from_source(my_error, wasm_trace, None); + /// assert_eq!("unexpected error", trap.message()); + /// ``` + pub fn new_from_source( + source: Trap, + wasm_trace: Vec, + trap_code: Option, + ) -> Self { + // println!("CREATING ERROR FROM TRAP {}", source); + Self { + inner: Arc::new(RuntimeErrorInner { + source, + wasm_trace, + trap_code, + }), + } + } + + /// Creates a custom user Error. + /// + /// This error object can be passed through Wasm frames and later retrieved + /// using the `downcast` method. + pub fn user(error: Box) -> Self { + match error.downcast::() { + Ok(err) => *err, + Err(error) => error.into(), + } + } + + /// Returns a reference the `message` stored in `Trap`. + pub fn message(&self) -> String { + if let Some(trap_code) = self.inner.trap_code { + trap_code.message().to_string() + } else { + self.inner.source.to_string() + } + } + + /// Returns a list of function frames in WebAssembly code that led to this + /// trap happening. + pub fn trace(&self) -> &[FrameInfo] { + &self.inner.wasm_trace + } + + /// Returns trap code, if it's a Trap + pub fn to_trap(self) -> Option { + self.inner.trap_code + } + + // /// Returns trap code, if it's a Trap + // pub fn to_source(self) -> &'static Trap { + // &self.inner.as_ref().source + // } + + /// Attempts to downcast the `RuntimeError` to a concrete type. + pub fn downcast(self) -> Result { + match Arc::try_unwrap(self.inner) { + Ok(inner) if inner.source.is::() => Ok(inner.source.downcast::().unwrap()), + Ok(inner) => Err(Self { + inner: Arc::new(inner), + }), + Err(inner) => Err(Self { inner }), + } + } + + /// Attempts to downcast the `RuntimeError` to a concrete type. + pub fn downcast_ref(&self) -> Option<&T> { + self.inner.as_ref().source.downcast_ref::() + } + + /// Returns true if the `RuntimeError` is the same as T + pub fn is(&self) -> bool { + self.inner.source.is::() + } +} + +impl fmt::Debug for RuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RuntimeError") + .field("source", &self.inner.source) + .field("wasm_trace", &self.inner.wasm_trace) + .finish() + } +} + +impl fmt::Display for RuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "RuntimeError: {}", self.message())?; + let trace = self.trace(); + if trace.is_empty() { + return Ok(()); + } + for frame in self.trace().iter() { + let name = frame.module_name(); + let func_index = frame.func_index(); + writeln!(f)?; + write!(f, " at ")?; + match frame.function_name() { + Some(name) => match rustc_demangle::try_demangle(name) { + Ok(name) => write!(f, "{}", name)?, + Err(_) => write!(f, "{}", name)?, + }, + None => write!(f, "")?, + } + write!( + f, + " ({}[{}]:0x{:x})", + name, + func_index, + frame.module_offset() + )?; + } + Ok(()) + } +} + +impl std::error::Error for RuntimeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.inner.source.source() + } +} + +impl From> for RuntimeError { + fn from(error: Box) -> Self { + match error.downcast::() { + // The error is already a RuntimeError, we return it directly + Ok(runtime_error) => *runtime_error, + Err(error) => Trap::user(error).into(), + } + } +} diff --git a/lib/api/src/js/errors.rs b/lib/api/src/js/errors.rs index 2f93a80b119..b4f6e02049f 100644 --- a/lib/api/src/js/errors.rs +++ b/lib/api/src/js/errors.rs @@ -1,27 +1,28 @@ -use crate::js::lib::std::string::String; -pub use crate::js::trap::RuntimeError; -#[cfg(feature = "std")] -use thiserror::Error; -use wasmer_types::ImportError; +use crate::js::trap::Trap; +use crate::RuntimeError; +use wasm_bindgen::prelude::*; -/// The WebAssembly.LinkError object indicates an error during -/// module instantiation (besides traps from the start function). -/// -/// This is based on the [link error][link-error] API. -/// -/// [link-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError -#[derive(Debug)] -#[cfg_attr(feature = "std", derive(Error))] -#[cfg_attr(feature = "std", error("Link error: {0}"))] -pub enum LinkError { - /// An error occurred when checking the import types. - #[cfg_attr(feature = "std", error("Error while importing {0:?}.{1:?}: {2}"))] - Import(String, String, ImportError), +impl From for RuntimeError { + fn from(trap: Trap) -> Self { + if trap.is::() { + return trap.downcast::().unwrap(); + } + let wasm_trace = vec![]; + let trap_code = None; + // let (wasm_trace, trap_code) = wasmer_compiler::get_trace_and_trapcode(&trap); + RuntimeError::new_from_source(trap, wasm_trace, trap_code) + } +} + +impl From for JsValue { + fn from(_err: RuntimeError) -> Self { + // err.inner.source.into() + unimplemented!(); + } +} - /// A trap ocurred during linking. - #[cfg_attr(feature = "std", error("RuntimeError occurred during linking: {0}"))] - Trap(#[source] RuntimeError), - /// Insufficient resources available for linking. - #[cfg_attr(feature = "std", error("Insufficient resources: {0}"))] - Resource(String), +pub(crate) fn raise(error: Box) -> ! { + let error = Trap::user(error); + let js_error: JsValue = error.into(); + wasm_bindgen::throw_val(js_error) } diff --git a/lib/api/src/js/externals/function.rs b/lib/api/src/js/externals/function.rs index 8ed64561849..46157e419a5 100644 --- a/lib/api/src/js/externals/function.rs +++ b/lib/api/src/js/externals/function.rs @@ -394,10 +394,10 @@ macro_rules! impl_host_function { Ok(Ok(result)) => return result.into_c_struct(&mut store), #[allow(deprecated)] #[cfg(feature = "std")] - Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), + Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)), #[cfg(feature = "core")] #[allow(deprecated)] - Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), + Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)), Err(_panic) => unimplemented!(), } } @@ -442,10 +442,10 @@ macro_rules! impl_host_function { Ok(Ok(result)) => return result.into_c_struct(&mut store), #[cfg(feature = "std")] #[allow(deprecated)] - Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), + Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)), #[cfg(feature = "core")] #[allow(deprecated)] - Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), + Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)), Err(_panic) => unimplemented!(), } } diff --git a/lib/api/src/js/mod.rs b/lib/api/src/js/mod.rs index d8c82b7d28b..fe10e935af0 100644 --- a/lib/api/src/js/mod.rs +++ b/lib/api/src/js/mod.rs @@ -34,7 +34,7 @@ pub(crate) mod module; #[cfg(feature = "wasm-types-polyfill")] mod module_info_polyfill; pub(crate) mod store; -mod trap; +pub(crate) mod trap; pub(crate) mod typed_function; pub(crate) mod vm; mod wasm_bindgen_polyfill; diff --git a/lib/api/src/js/trap.rs b/lib/api/src/js/trap.rs index e6221109609..8a0ef2d940b 100644 --- a/lib/api/src/js/trap.rs +++ b/lib/api/src/js/trap.rs @@ -1,265 +1,74 @@ +use crate::RuntimeError; use std::error::Error; use std::fmt; -use std::sync::Arc; + use wasm_bindgen::{prelude::*, JsValue}; use wasm_bindgen_downcast::DowncastJS; -pub trait CoreError: fmt::Debug + fmt::Display { - fn source(&self) -> Option<&(dyn CoreError + 'static)> { - None - } - - fn type_id(&self) -> core::any::TypeId - where - Self: 'static, - { - core::any::TypeId::of::() - } - - fn description(&self) -> &str { - "description() is deprecated; use Display" - } - fn cause(&self) -> Option<&dyn CoreError> { - self.source() - } -} - -impl CoreError for T {} - -impl dyn CoreError + 'static { - /// Returns `true` if the inner type is the same as `T`. - #[allow(dead_code)] - pub fn core_is_equal(&self) -> bool { - let t = core::any::TypeId::of::(); - let concrete = self.type_id(); - t == concrete - } -} - -impl dyn CoreError + Send + Sync + 'static { - /// Returns `true` if the inner type is the same as `T`. - #[allow(dead_code)] - pub fn core_is_equal(&self) -> bool { - let t = core::any::TypeId::of::(); - let concrete = self.type_id(); - t == concrete - } -} - -impl dyn CoreError + Send { - #[inline] - /// Attempts to downcast the box to a concrete type. - #[allow(dead_code)] - pub fn downcast_core( - self: Box, - ) -> Result, Box> { - let err: Box = self; - ::downcast_core(err).map_err(|s| unsafe { - // Reapply the `Send` marker. - core::mem::transmute::, Box>(s) - }) - } -} - -impl dyn CoreError + Send + Sync { - #[inline] - /// Attempts to downcast the box to a concrete type. - #[allow(dead_code)] - pub fn downcast_core(self: Box) -> Result, Box> { - let err: Box = self; - ::downcast_core(err).map_err(|s| unsafe { - // Reapply the `Send + Sync` marker. - core::mem::transmute::, Box>(s) - }) - } -} - -impl dyn CoreError { - #[inline] - /// Attempts to downcast the box to a concrete type. - #[allow(dead_code)] - pub fn downcast_core( - self: Box, - ) -> Result, Box> { - if self.core_is_equal::() { - unsafe { - let raw: *mut dyn CoreError = Box::into_raw(self); - Ok(Box::from_raw(raw as *mut T)) - } - } else { - Err(self) - } - } -} - -/// A struct representing an aborted instruction execution, with a message -/// indicating the cause. -#[wasm_bindgen] -#[derive(Clone, DowncastJS)] -pub struct WasmerRuntimeError { - inner: Arc, -} - -/// This type is the same as `WasmerRuntimeError`. -/// -/// We use the `WasmerRuntimeError` name to not collide with the -/// `RuntimeError` in JS. -pub type RuntimeError = WasmerRuntimeError; - -impl PartialEq for RuntimeError { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.inner, &other.inner) - } -} - -/// The source of the `RuntimeError`. #[derive(Debug)] -enum RuntimeErrorSource { - Generic(String), - #[cfg(feature = "std")] +enum InnerTrap { User(Box), - #[cfg(feature = "core")] - User(Box), Js(JsValue), } -/// This is a hack to ensure the error type is Send+Sync -unsafe impl Send for RuntimeErrorSource {} -unsafe impl Sync for RuntimeErrorSource {} - -impl fmt::Display for RuntimeErrorSource { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Generic(s) => write!(f, "{}", s), - Self::User(s) => write!(f, "{}", s), - Self::Js(s) => write!(f, "{:?}", s), - } - } +/// A struct representing a Trap +#[wasm_bindgen] +#[derive(Debug, DowncastJS)] +pub struct Trap { + inner: InnerTrap, } -impl RuntimeError { - /// Creates a new generic `RuntimeError` with the given `message`. - /// - /// # Example - /// ``` - /// let trap = wasmer_compiler::RuntimeError::new("unexpected error"); - /// assert_eq!("unexpected error", trap.message()); - /// ``` - pub fn new>(message: I) -> Self { - RuntimeError { - inner: Arc::new(RuntimeErrorSource::Generic(message.into())), - } - } - - /// Raises a custom user Error - #[deprecated(since = "2.1.1", note = "return a Result from host functions instead")] - #[cfg(feature = "std")] - pub(crate) fn raise(error: Box) -> ! { - let error = Self::user(error); - let js_error: JsValue = error.into(); - wasm_bindgen::throw_val(js_error) - } - - /// Raises a custom user Error - #[deprecated(since = "2.1.1", note = "return a Result from host functions instead")] - #[cfg(feature = "core")] - pub(crate) fn raise(error: Box) -> ! { - let error = Self::user(error); - let js_error: JsValue = error.into(); - wasm_bindgen::throw_val(js_error) - } +unsafe impl Send for Trap {} +unsafe impl Sync for Trap {} - /// Creates a custom user Error. - /// - /// This error object can be passed through Wasm frames and later retrieved - /// using the `downcast` method. - #[cfg(feature = "std")] +impl Trap { pub fn user(error: Box) -> Self { - match error.downcast::() { - // The error is already a RuntimeError, we return it directly - Ok(runtime_error) => *runtime_error, - Err(error) => RuntimeError { - inner: Arc::new(RuntimeErrorSource::User(error)), - }, + Self { + inner: InnerTrap::User(error), } } - #[cfg(feature = "core")] - pub fn user(error: Box) -> Self { - match error.downcast_core::() { - // The error is already a RuntimeError, we return it directly - Ok(runtime_error) => *runtime_error, - Err(error) => RuntimeError { - inner: Arc::new(RuntimeErrorSource::User(error)), - }, - } - } - - /// Returns a reference the `message` stored in `Trap`. - pub fn message(&self) -> String { - format!("{}", self.inner) - } - - /// Attempts to downcast the `RuntimeError` to a concrete type. + /// Attempts to downcast the `Trap` to a concrete type. pub fn downcast(self) -> Result { - match Arc::try_unwrap(self.inner) { + match self.inner { // We only try to downcast user errors - #[cfg(feature = "std")] - Ok(RuntimeErrorSource::User(err)) if err.is::() => Ok(*err.downcast::().unwrap()), - #[cfg(feature = "core")] - Ok(RuntimeErrorSource::User(err)) if (*err).core_is_equal::() => { - Ok(*err.downcast_core::().unwrap()) - } - Ok(inner) => Err(Self { - inner: Arc::new(inner), - }), - Err(inner) => Err(Self { inner }), + InnerTrap::User(err) if err.is::() => Ok(*err.downcast::().unwrap()), + _ => Err(self), } } - /// Attempts to downcast the `RuntimeError` to a concrete type. + /// Attempts to downcast the `Trap` to a concrete type. pub fn downcast_ref(&self) -> Option<&T> { - match self.inner.as_ref() { + match &self.inner { // We only try to downcast user errors - #[cfg(feature = "std")] - RuntimeErrorSource::User(err) => err.downcast_ref::(), + InnerTrap::User(err) if err.is::() => err.downcast_ref::(), _ => None, } } - /// Returns true if the `RuntimeError` is the same as T + /// Returns true if the `Trap` is the same as T pub fn is(&self) -> bool { - match self.inner.as_ref() { - #[cfg(feature = "std")] - RuntimeErrorSource::User(err) => err.is::(), - #[cfg(feature = "core")] - RuntimeErrorSource::User(err) => (*err).core_is_equal::(), + match &self.inner { + InnerTrap::User(err) => err.is::(), _ => false, } } } -impl fmt::Debug for RuntimeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RuntimeError") - .field("source", &self.inner) - .finish() +impl std::error::Error for Trap { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.inner { + InnerTrap::User(err) => Some(&**err), + _ => None, + } } } -impl fmt::Display for RuntimeError { +impl fmt::Display for Trap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "RuntimeError: {}", self.message())?; - Ok(()) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for RuntimeError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self.inner.as_ref() { - RuntimeErrorSource::User(err) => Some(&**err), - _ => None, + match &self.inner { + InnerTrap::User(e) => write!(f, "user: {}", e), + InnerTrap::Js(value) => write!(f, "js: {:?}", value.as_string()), } } } @@ -269,8 +78,9 @@ impl From for RuntimeError { // We try to downcast the error and see if it's // an instance of RuntimeError instead, so we don't need // to re-wrap it. - WasmerRuntimeError::downcast_js(original).unwrap_or_else(|js| RuntimeError { - inner: Arc::new(RuntimeErrorSource::Js(js)), - }) + let trap = Trap::downcast_js(original).unwrap_or_else(|o| Trap { + inner: InnerTrap::Js(o), + }); + trap.into() } } diff --git a/lib/api/src/lib.rs b/lib/api/src/lib.rs index 56ea197f604..ec46693dce6 100644 --- a/lib/api/src/lib.rs +++ b/lib/api/src/lib.rs @@ -470,10 +470,10 @@ pub use wasmer_derive::ValueType; // TODO: OnCalledAction is needed for asyncify. It will be refactored with https://github.com/wasmerio/wasmer/issues/3451 pub use wasmer_types::{ is_wasm, Bytes, CompileError, CpuFeature, DeserializeError, ExportIndex, ExportType, - ExternType, FunctionType, GlobalInit, GlobalType, ImportType, LocalFunctionIndex, MemoryError, - MemoryType, MiddlewareError, Mutability, OnCalledAction, Pages, ParseCpuFeatureError, - SerializeError, TableType, Target, Type, ValueType, WasmError, WasmResult, WASM_MAX_PAGES, - WASM_MIN_PAGES, WASM_PAGE_SIZE, + ExternType, FrameInfo, FunctionType, GlobalInit, GlobalType, ImportType, LocalFunctionIndex, + MemoryError, MemoryType, MiddlewareError, Mutability, OnCalledAction, Pages, + ParseCpuFeatureError, SerializeError, TableType, Target, Type, ValueType, WasmError, + WasmResult, WASM_MAX_PAGES, WASM_MIN_PAGES, WASM_PAGE_SIZE, }; #[cfg(feature = "wat")] pub use wat::parse_bytes as wat2wasm; diff --git a/lib/api/src/sys/errors.rs b/lib/api/src/sys/errors.rs new file mode 100644 index 00000000000..0ab9622d44c --- /dev/null +++ b/lib/api/src/sys/errors.rs @@ -0,0 +1,24 @@ +use crate::{LinkError, RuntimeError}; +use wasmer_vm::Trap; + +impl From for LinkError { + fn from(other: wasmer_compiler::LinkError) -> Self { + match other { + wasmer_compiler::LinkError::Import(namespace, name, error) => { + Self::Import(namespace, name, error) + } + wasmer_compiler::LinkError::Trap(e) => Self::Trap(e.into()), + wasmer_compiler::LinkError::Resource(e) => Self::Resource(e), + } + } +} + +impl From for RuntimeError { + fn from(trap: Trap) -> Self { + if trap.is::() { + return trap.downcast::().unwrap(); + } + let (wasm_trace, trap_code) = wasmer_compiler::get_trace_and_trapcode(&trap); + RuntimeError::new_from_source(trap, wasm_trace, trap_code) + } +} diff --git a/lib/api/src/sys/externals/function.rs b/lib/api/src/sys/externals/function.rs index adfbbc9cbc7..1b773ae2ea4 100644 --- a/lib/api/src/sys/externals/function.rs +++ b/lib/api/src/sys/externals/function.rs @@ -301,7 +301,7 @@ impl Function { r }; if let Err(error) = result { - return Err(RuntimeError::from_trap(error)); + return Err(error.into()); } // Load the return values out of `values_vec`. diff --git a/lib/api/src/sys/externals/table.rs b/lib/api/src/sys/externals/table.rs index 3067094c91b..6aea055be7b 100644 --- a/lib/api/src/sys/externals/table.rs +++ b/lib/api/src/sys/externals/table.rs @@ -2,7 +2,7 @@ use crate::store::{AsStoreMut, AsStoreRef}; use crate::TableType; use crate::Value; use crate::{vm::VMExternTable, ExternRef, Function, RuntimeError}; -use wasmer_vm::{StoreHandle, TableElement, VMExtern, VMTable}; +use wasmer_vm::{StoreHandle, TableElement, Trap, VMExtern, VMTable}; #[derive(Debug, Clone)] pub struct Table { @@ -130,7 +130,7 @@ impl Table { ); VMTable::copy(dst_table, src_table, dst_index, src_index, len) } - .map_err(RuntimeError::from_trap)?; + .map_err(Into::::into)?; Ok(()) } diff --git a/lib/api/src/sys/instance.rs b/lib/api/src/sys/instance.rs index 0b5060a2853..5119dfdfc09 100644 --- a/lib/api/src/sys/instance.rs +++ b/lib/api/src/sys/instance.rs @@ -29,8 +29,8 @@ mod send_test { impl From for InstantiationError { fn from(other: wasmer_compiler::InstantiationError) -> Self { match other { - wasmer_compiler::InstantiationError::Link(e) => Self::Link(e), - wasmer_compiler::InstantiationError::Start(e) => Self::Start(e), + wasmer_compiler::InstantiationError::Link(e) => Self::Link(e.into()), + wasmer_compiler::InstantiationError::Start(e) => Self::Start(e.into()), wasmer_compiler::InstantiationError::CpuFeature(e) => Self::CpuFeature(e), } } diff --git a/lib/api/src/sys/mod.rs b/lib/api/src/sys/mod.rs index 485b87f55b6..39dbe9bcb2b 100644 --- a/lib/api/src/sys/mod.rs +++ b/lib/api/src/sys/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod engine; +pub(crate) mod errors; pub(crate) mod extern_ref; pub(crate) mod externals; pub(crate) mod instance; @@ -14,7 +15,7 @@ pub use target_lexicon::{Architecture, CallingConvention, OperatingSystem, Tripl pub use wasmer_compiler::{ wasmparser, CompilerConfig, FunctionMiddleware, MiddlewareReaderState, ModuleMiddleware, }; -pub use wasmer_compiler::{Artifact, EngineBuilder, Features, FrameInfo, Tunables}; +pub use wasmer_compiler::{Artifact, EngineBuilder, Features, Tunables}; #[cfg(feature = "cranelift")] pub use wasmer_compiler_cranelift::{Cranelift, CraneliftOptLevel}; #[cfg(feature = "llvm")] diff --git a/lib/c-api/examples/wasmer-capi-examples-runner/src/lib.rs b/lib/c-api/examples/wasmer-capi-examples-runner/src/lib.rs index 71f936b556e..7874adf7cbb 100644 --- a/lib/c-api/examples/wasmer-capi-examples-runner/src/lib.rs +++ b/lib/c-api/examples/wasmer-capi-examples-runner/src/lib.rs @@ -254,7 +254,7 @@ fn test_run() { println!("outputting batch to {}", path.display()); std::fs::write(&path, vcvars_modified).unwrap(); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); let mut vcvars = std::process::Command::new("cmd"); vcvars.arg("/C"); @@ -270,7 +270,7 @@ fn test_run() { if !output.status.success() { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stdout: {}", String::from_utf8_lossy(&output.stderr)); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to compile {test}"); } @@ -296,7 +296,7 @@ fn test_run() { println!("{output:#?}"); println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to execute {test}"); } } else { @@ -353,7 +353,7 @@ fn test_run() { if !output.status.success() { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to compile {test}: {command:#?}"); } @@ -368,7 +368,7 @@ fn test_run() { if !output.status.success() { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stdout: {}", String::from_utf8_lossy(&output.stderr)); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to execute {test} executable"); } } diff --git a/lib/c-api/tests/wasmer-c-api-test-runner/src/lib.rs b/lib/c-api/tests/wasmer-c-api-test-runner/src/lib.rs index f75456008bd..ea1234e3fd8 100644 --- a/lib/c-api/tests/wasmer-c-api-test-runner/src/lib.rs +++ b/lib/c-api/tests/wasmer-c-api-test-runner/src/lib.rs @@ -161,7 +161,7 @@ fn test_ok() { println!(); println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to invoke vcvars64.bat {test}"); } @@ -203,7 +203,7 @@ fn test_ok() { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); println!("output: {:#?}", output); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to compile {test}"); } @@ -225,7 +225,7 @@ fn test_ok() { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); println!("output: {:#?}", output); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to execute {test}"); } @@ -275,7 +275,7 @@ fn test_ok() { command.arg("-o"); command.arg(&format!("{manifest_dir}/../{test}")); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); println!("compile: {command:#?}"); // compile @@ -288,7 +288,7 @@ fn test_ok() { if !output.status.success() { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to compile {test}: {command:#?}"); } @@ -303,7 +303,7 @@ fn test_ok() { if !output.status.success() { println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - print_wasmer_root_to_stdout(&config); + // print_wasmer_root_to_stdout(&config); panic!("failed to execute {test}: {command:#?}"); } } diff --git a/lib/compiler/Cargo.toml b/lib/compiler/Cargo.toml index bbb1a11ec89..d1a53a26855 100644 --- a/lib/compiler/Cargo.toml +++ b/lib/compiler/Cargo.toml @@ -22,7 +22,6 @@ serde_bytes = { version = "0.11", optional = true } smallvec = "1.6" backtrace = "0.3" -rustc-demangle = "0.1" memmap2 = "0.5" more-asserts = "0.2" lazy_static = "1.4" diff --git a/lib/compiler/src/engine/artifact.rs b/lib/compiler/src/engine/artifact.rs index 78bcbc1a845..054e76c79c5 100644 --- a/lib/compiler/src/engine/artifact.rs +++ b/lib/compiler/src/engine/artifact.rs @@ -8,7 +8,7 @@ use crate::Features; use crate::ModuleEnvironment; use crate::{ register_frame_info, resolve_imports, FunctionExtent, GlobalFrameInfoRegistration, - InstantiationError, RuntimeError, Tunables, + InstantiationError, Tunables, }; #[cfg(feature = "static-artifact-create")] use crate::{Compiler, FunctionBodyData, ModuleTranslationState}; @@ -526,7 +526,7 @@ impl Artifact { imports, self.signatures().clone(), ) - .map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap)))?; + .map_err(InstantiationError::Start)?; Ok(handle) } @@ -551,7 +551,7 @@ impl Artifact { .collect::>(); handle .finish_instantiation(trap_handler, &data_initializers) - .map_err(|trap| InstantiationError::Start(RuntimeError::from_trap(trap))) + .map_err(InstantiationError::Start) } #[allow(clippy::type_complexity)] diff --git a/lib/compiler/src/engine/error.rs b/lib/compiler/src/engine/error.rs index 3ee5860646d..10c5e3d440d 100644 --- a/lib/compiler/src/engine/error.rs +++ b/lib/compiler/src/engine/error.rs @@ -1,8 +1,8 @@ //! The WebAssembly possible errors -#[cfg(not(target_arch = "wasm32"))] -use crate::engine::trap::RuntimeError; use thiserror::Error; pub use wasmer_types::{DeserializeError, ImportError, SerializeError}; +#[cfg(not(target_arch = "wasm32"))] +use wasmer_vm::Trap; /// The WebAssembly.LinkError object indicates an error during /// module instantiation (besides traps from the start function). @@ -19,8 +19,8 @@ pub enum LinkError { #[cfg(not(target_arch = "wasm32"))] /// A trap ocurred during linking. - #[error("RuntimeError occurred during linking: {0}")] - Trap(#[source] RuntimeError), + #[error("Trap occurred during linking: {0}")] + Trap(#[source] Trap), /// Insufficient resources available for linking. #[error("Insufficient resources: {0}")] @@ -48,5 +48,5 @@ pub enum InstantiationError { /// A runtime error occured while invoking the start function #[cfg(not(target_arch = "wasm32"))] #[error(transparent)] - Start(RuntimeError), + Start(Trap), } diff --git a/lib/compiler/src/engine/trap/error.rs b/lib/compiler/src/engine/trap/error.rs deleted file mode 100644 index 80a590690d4..00000000000 --- a/lib/compiler/src/engine/trap/error.rs +++ /dev/null @@ -1,310 +0,0 @@ -use super::frame_info::{FrameInfo, GlobalFrameInfo, FRAME_INFO}; -use backtrace::Backtrace; -use std::error::Error; -use std::fmt; -use std::sync::Arc; -use wasmer_vm::{Trap, TrapCode}; - -/// A struct representing an aborted instruction execution, with a message -/// indicating the cause. -#[derive(Clone)] -pub struct RuntimeError { - inner: Arc, -} - -pub trait CoreError: fmt::Debug + fmt::Display + core::any::Any {} - -impl CoreError for T {} - -/// The source of the `RuntimeError`. -#[derive(Debug)] -enum RuntimeErrorSource { - Generic(String), - OutOfMemory, - #[cfg(feature = "std")] - User(Box), - #[cfg(feature = "core")] - User(Box), - Trap(TrapCode), -} - -impl fmt::Display for RuntimeErrorSource { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Generic(s) => write!(f, "{}", s), - Self::User(s) => write!(f, "{}", s), - Self::OutOfMemory => write!(f, "Wasmer VM out of memory"), - Self::Trap(s) => write!(f, "{}", s.message()), - } - } -} - -struct RuntimeErrorInner { - /// The source error (this can be a custom user `Error` or a [`TrapCode`]) - source: RuntimeErrorSource, - /// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`). - wasm_trace: Vec, - /// The native backtrace - native_trace: Option, -} - -fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) { - (t, t) -} - -impl RuntimeError { - /// Creates a new generic `RuntimeError` with the given `message`. - /// - /// # Example - /// ``` - /// let trap = wasmer_compiler::RuntimeError::new("unexpected error"); - /// assert_eq!("unexpected error", trap.message()); - /// ``` - pub fn new>(message: I) -> Self { - let info = FRAME_INFO.read().unwrap(); - let msg = message.into(); - Self::new_with_trace( - &info, - None, - RuntimeErrorSource::Generic(msg), - Backtrace::new_unresolved(), - ) - } - - /// Create a new RuntimeError from a Trap. - pub fn from_trap(trap: Trap) -> Self { - let info = FRAME_INFO.read().unwrap(); - match trap { - // A user error - Trap::User(error) => { - match error.downcast::() { - // The error is already a RuntimeError, we return it directly - Ok(runtime_error) => *runtime_error, - Err(e) => Self::new_with_trace( - &info, - None, - RuntimeErrorSource::User(e), - Backtrace::new_unresolved(), - ), - } - } - // A trap caused by the VM being Out of Memory - Trap::OOM { backtrace } => { - Self::new_with_trace(&info, None, RuntimeErrorSource::OutOfMemory, backtrace) - } - // A trap caused by an error on the generated machine code for a Wasm function - Trap::Wasm { - pc, - signal_trap, - backtrace, - } => { - let code = info - .lookup_trap_info(pc) - .map_or(signal_trap.unwrap_or(TrapCode::StackOverflow), |info| { - info.trap_code - }); - Self::new_with_trace(&info, Some(pc), RuntimeErrorSource::Trap(code), backtrace) - } - // A trap triggered manually from the Wasmer runtime - Trap::Lib { - trap_code, - backtrace, - } => Self::new_with_trace(&info, None, RuntimeErrorSource::Trap(trap_code), backtrace), - } - } - - /// Creates a custom user Error. - /// - /// This error object can be passed through Wasm frames and later retrieved - /// using the `downcast` method. - #[cfg(feature = "std")] - pub fn user(error: Box) -> Self { - match error.downcast::() { - // The error is already a RuntimeError, we return it directly - Ok(runtime_error) => *runtime_error, - Err(error) => { - let info = FRAME_INFO.read().unwrap(); - Self::new_with_trace( - &info, - None, - RuntimeErrorSource::User(error), - Backtrace::new_unresolved(), - ) - } - } - } - - /// Creates a custom user Error. - /// - /// This error object can be passed through Wasm frames and later retrieved - /// using the `downcast` method. - #[cfg(feature = "core")] - pub fn user(error: Box) -> Self { - match error.downcast::() { - // The error is already a RuntimeError, we return it directly - Ok(runtime_error) => *runtime_error, - Err(error) => { - let info = FRAME_INFO.read().unwrap(); - Self::new_with_trace( - &info, - None, - RuntimeErrorSource::User(error), - Backtrace::new_unresolved(), - ) - } - } - } - - fn new_with_trace( - info: &GlobalFrameInfo, - trap_pc: Option, - source: RuntimeErrorSource, - native_trace: Backtrace, - ) -> Self { - // Let's construct the trace - let wasm_trace = native_trace - .frames() - .iter() - .filter_map(|frame| { - let pc = frame.ip() as usize; - if pc == 0 { - None - } else { - // Note that we need to be careful about the pc we pass in here to - // lookup frame information. This program counter is used to - // translate back to an original source location in the origin wasm - // module. If this pc is the exact pc that the trap happened at, - // then we look up that pc precisely. Otherwise backtrace - // information typically points at the pc *after* the call - // instruction (because otherwise it's likely a call instruction on - // the stack). In that case we want to lookup information for the - // previous instruction (the call instruction) so we subtract one as - // the lookup. - let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 }; - Some(pc_to_lookup) - } - }) - .filter_map(|pc| info.lookup_frame_info(pc)) - .collect::>(); - - Self { - inner: Arc::new(RuntimeErrorInner { - source, - wasm_trace, - native_trace: Some(native_trace), - }), - } - } - - /// Returns a reference the `message` stored in `Trap`. - pub fn message(&self) -> String { - self.inner.source.to_string() - } - - /// Returns a list of function frames in WebAssembly code that led to this - /// trap happening. - pub fn trace(&self) -> &[FrameInfo] { - &self.inner.wasm_trace - } - - /// Attempts to downcast the `RuntimeError` to a concrete type. - pub fn downcast(self) -> Result { - match Arc::try_unwrap(self.inner) { - // We only try to downcast user errors - Ok(RuntimeErrorInner { - source: RuntimeErrorSource::User(err), - .. - }) if err.is::() => Ok(*err.downcast::().unwrap()), - Ok(inner) => Err(Self { - inner: Arc::new(inner), - }), - Err(inner) => Err(Self { inner }), - } - } - - /// Attempts to downcast the `RuntimeError` to a concrete type. - pub fn downcast_ref(&self) -> Option<&T> { - match self.inner.as_ref() { - // We only try to downcast user errors - RuntimeErrorInner { - source: RuntimeErrorSource::User(err), - .. - } if err.is::() => err.downcast_ref::(), - _ => None, - } - } - - /// Returns trap code, if it's a Trap - pub fn to_trap(self) -> Option { - if let RuntimeErrorSource::Trap(trap_code) = self.inner.source { - Some(trap_code) - } else { - None - } - } - - /// Returns true if the `RuntimeError` is the same as T - pub fn is(&self) -> bool { - match &self.inner.source { - RuntimeErrorSource::User(err) => err.is::(), - _ => false, - } - } -} - -impl fmt::Debug for RuntimeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RuntimeError") - .field("source", &self.inner.source) - .field("wasm_trace", &self.inner.wasm_trace) - .field("native_trace", &self.inner.native_trace) - .finish() - } -} - -impl fmt::Display for RuntimeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "RuntimeError: {}", self.message())?; - let trace = self.trace(); - if trace.is_empty() { - return Ok(()); - } - for frame in self.trace().iter() { - let name = frame.module_name(); - let func_index = frame.func_index(); - writeln!(f)?; - write!(f, " at ")?; - match frame.function_name() { - Some(name) => match rustc_demangle::try_demangle(name) { - Ok(name) => write!(f, "{}", name)?, - Err(_) => write!(f, "{}", name)?, - }, - None => write!(f, "")?, - } - write!( - f, - " ({}[{}]:0x{:x})", - name, - func_index, - frame.module_offset() - )?; - } - Ok(()) - } -} - -impl std::error::Error for RuntimeError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match &self.inner.source { - RuntimeErrorSource::User(err) => Some(&**err), - RuntimeErrorSource::Trap(err) => Some(err), - _ => None, - } - } -} - -impl From for RuntimeError { - fn from(trap: Trap) -> Self { - Self::from_trap(trap) - } -} diff --git a/lib/compiler/src/engine/trap/frame_info.rs b/lib/compiler/src/engine/trap/frame_info.rs index a23f7607f94..4222acc1ad1 100644 --- a/lib/compiler/src/engine/trap/frame_info.rs +++ b/lib/compiler/src/engine/trap/frame_info.rs @@ -15,8 +15,9 @@ use std::cmp; use std::collections::BTreeMap; use std::sync::{Arc, RwLock}; use wasmer_types::entity::{BoxedSlice, EntityRef, PrimaryMap}; -use wasmer_types::{CompiledFunctionFrameInfo, SourceLoc, TrapInformation}; -use wasmer_types::{LocalFunctionIndex, ModuleInfo}; +use wasmer_types::{ + CompiledFunctionFrameInfo, FrameInfo, LocalFunctionIndex, ModuleInfo, TrapInformation, +}; use wasmer_vm::FunctionBodyPtr; lazy_static::lazy_static! { @@ -129,13 +130,13 @@ impl GlobalFrameInfo { None => instr_map.start_srcloc, }; let func_index = module.module.func_index(func.local_index); - Some(FrameInfo { - module_name: module.module.name(), - func_index: func_index.index() as u32, - function_name: module.module.function_names.get(&func_index).cloned(), + Some(FrameInfo::new( + module.module.name(), + func_index.index() as u32, + module.module.function_names.get(&func_index).cloned(), + instr_map.start_srcloc, instr, - func_start: instr_map.start_srcloc, - }) + )) } /// Fetches trap information about a program counter in a backtrace. @@ -240,97 +241,3 @@ pub fn register( assert!(prev.is_none()); Some(GlobalFrameInfoRegistration { key: max }) } - -/// Description of a frame in a backtrace for a [`RuntimeError::trace`](crate::RuntimeError::trace). -/// -/// Whenever a WebAssembly trap occurs an instance of [`RuntimeError`] -/// is created. Each [`RuntimeError`] has a backtrace of the -/// WebAssembly frames that led to the trap, and each frame is -/// described by this structure. -/// -/// [`RuntimeError`]: crate::RuntimeError -#[derive(Debug, Clone)] -pub struct FrameInfo { - module_name: String, - func_index: u32, - function_name: Option, - func_start: SourceLoc, - instr: SourceLoc, -} - -impl FrameInfo { - /// Creates a new [FrameInfo], useful for testing. - pub fn new( - module_name: String, - func_index: u32, - function_name: Option, - func_start: SourceLoc, - instr: SourceLoc, - ) -> Self { - Self { - module_name, - func_index, - function_name, - func_start, - instr, - } - } - - /// Returns the WebAssembly function index for this frame. - /// - /// This function index is the index in the function index space of the - /// WebAssembly module that this frame comes from. - pub fn func_index(&self) -> u32 { - self.func_index - } - - /// Returns the identifer of the module that this frame is for. - /// - /// ModuleInfo identifiers are present in the `name` section of a WebAssembly - /// binary, but this may not return the exact item in the `name` section. - /// ModuleInfo names can be overwritten at construction time or perhaps inferred - /// from file names. The primary purpose of this function is to assist in - /// debugging and therefore may be tweaked over time. - /// - /// This function returns `None` when no name can be found or inferred. - pub fn module_name(&self) -> &str { - &self.module_name - } - - /// Returns a descriptive name of the function for this frame, if one is - /// available. - /// - /// The name of this function may come from the `name` section of the - /// WebAssembly binary, or wasmer may try to infer a better name for it if - /// not available, for example the name of the export if it's exported. - /// - /// This return value is primarily used for debugging and human-readable - /// purposes for things like traps. Note that the exact return value may be - /// tweaked over time here and isn't guaranteed to be something in - /// particular about a wasm module due to its primary purpose of assisting - /// in debugging. - /// - /// This function returns `None` when no name could be inferred. - pub fn function_name(&self) -> Option<&str> { - self.function_name.as_deref() - } - - /// Returns the offset within the original wasm module this frame's program - /// counter was at. - /// - /// The offset here is the offset from the beginning of the original wasm - /// module to the instruction that this frame points to. - pub fn module_offset(&self) -> usize { - self.instr.bits() as usize - } - - /// Returns the offset from the original wasm module's function to this - /// frame's program counter. - /// - /// The offset here is the offset from the beginning of the defining - /// function of this frame (within the wasm module) to the instruction this - /// frame points to. - pub fn func_offset(&self) -> usize { - (self.instr.bits() - self.func_start.bits()) as usize - } -} diff --git a/lib/compiler/src/engine/trap/mod.rs b/lib/compiler/src/engine/trap/mod.rs index d1eac6b420b..511a35e20aa 100644 --- a/lib/compiler/src/engine/trap/mod.rs +++ b/lib/compiler/src/engine/trap/mod.rs @@ -1,7 +1,6 @@ -mod error; mod frame_info; -pub use error::RuntimeError; +mod stack; pub use frame_info::{ - register as register_frame_info, FrameInfo, FunctionExtent, GlobalFrameInfoRegistration, - FRAME_INFO, + register as register_frame_info, FunctionExtent, GlobalFrameInfoRegistration, FRAME_INFO, }; +pub use stack::get_trace_and_trapcode; diff --git a/lib/compiler/src/engine/trap/stack.rs b/lib/compiler/src/engine/trap/stack.rs new file mode 100644 index 00000000000..aa07f93a981 --- /dev/null +++ b/lib/compiler/src/engine/trap/stack.rs @@ -0,0 +1,66 @@ +use super::frame_info::{GlobalFrameInfo, FRAME_INFO}; +use backtrace::Backtrace; +use wasmer_types::{FrameInfo, TrapCode}; +use wasmer_vm::Trap; + +/// Given a `Trap`, this function returns the Wasm trace and the trap code. +pub fn get_trace_and_trapcode(trap: &Trap) -> (Vec, Option) { + let info = FRAME_INFO.read().unwrap(); + match &trap { + // A user error + Trap::User(_err) => (wasm_trace(&info, None, &Backtrace::new_unresolved()), None), + // A trap caused by the VM being Out of Memory + Trap::OOM { backtrace } => (wasm_trace(&info, None, backtrace), None), + // A trap caused by an error on the generated machine code for a Wasm function + Trap::Wasm { + pc, + signal_trap, + backtrace, + } => { + let trap_code = info + .lookup_trap_info(*pc) + .map_or(signal_trap.unwrap_or(TrapCode::StackOverflow), |info| { + info.trap_code + }); + + (wasm_trace(&info, Some(*pc), backtrace), Some(trap_code)) + } + // A trap triggered manually from the Wasmer runtime + Trap::Lib { + trap_code, + backtrace, + } => (wasm_trace(&info, None, backtrace), Some(*trap_code)), + } +} + +fn wasm_trace( + info: &GlobalFrameInfo, + trap_pc: Option, + backtrace: &Backtrace, +) -> Vec { + // Let's construct the trace + backtrace + .frames() + .iter() + .filter_map(|frame| { + let pc = frame.ip() as usize; + if pc == 0 { + None + } else { + // Note that we need to be careful about the pc we pass in here to + // lookup frame information. This program counter is used to + // translate back to an original source location in the origin wasm + // module. If this pc is the exact pc that the trap happened at, + // then we look up that pc precisely. Otherwise backtrace + // information typically points at the pc *after* the call + // instruction (because otherwise it's likely a call instruction on + // the stack). In that case we want to lookup information for the + // previous instruction (the call instruction) so we subtract one as + // the lookup. + let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 }; + Some(pc_to_lookup) + } + }) + .filter_map(|pc| info.lookup_frame_info(pc)) + .collect::>() +} diff --git a/lib/types/src/compilation/address_map.rs b/lib/types/src/compilation/address_map.rs index e9db7f467cd..2f46624b891 100644 --- a/lib/types/src/compilation/address_map.rs +++ b/lib/types/src/compilation/address_map.rs @@ -1,8 +1,8 @@ //! Data structures to provide transformation of the source // addresses of a WebAssembly module into the native code. -use super::sourceloc::SourceLoc; use crate::lib::std::vec::Vec; +use crate::SourceLoc; use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; diff --git a/lib/types/src/compilation/function.rs b/lib/types/src/compilation/function.rs index 2becb5c4652..e57476f491c 100644 --- a/lib/types/src/compilation/function.rs +++ b/lib/types/src/compilation/function.rs @@ -4,9 +4,9 @@ //! A `Compilation` contains the compiled function bodies for a WebAssembly //! module (`CompiledFunction`). -use super::trap::TrapInformation; use crate::entity::PrimaryMap; use crate::lib::std::vec::Vec; +use crate::TrapInformation; use crate::{CompiledFunctionUnwindInfo, FunctionAddressMap}; use crate::{ CustomSection, FunctionIndex, LocalFunctionIndex, Relocation, SectionIndex, SignatureIndex, diff --git a/lib/types/src/compilation/mod.rs b/lib/types/src/compilation/mod.rs index 87ad57b4dc8..624cd2de15a 100644 --- a/lib/types/src/compilation/mod.rs +++ b/lib/types/src/compilation/mod.rs @@ -5,8 +5,6 @@ pub mod function; pub mod module; pub mod relocation; pub mod section; -pub mod sourceloc; pub mod symbols; pub mod target; -pub mod trap; pub mod unwind; diff --git a/lib/types/src/compilation/symbols.rs b/lib/types/src/compilation/symbols.rs index 8cfbe50d937..1e91e44d14b 100644 --- a/lib/types/src/compilation/symbols.rs +++ b/lib/types/src/compilation/symbols.rs @@ -60,7 +60,7 @@ pub struct ModuleMetadata { pub data_initializers: Box<[OwnedDataInitializer]>, /// The function body lengths (used to find function by address) pub function_body_lengths: PrimaryMap, - /// CPU features used (See [`CpuFeature`]) + /// CPU features used (See [`CpuFeature`](crate::CpuFeature)) pub cpu_features: u64, } diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 66f5ce765cc..9bebab026d8 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -235,7 +235,7 @@ impl From for WasmError { } /// The error that can happen while parsing a `str` -/// to retrieve a [`CpuFeature`](crate::target::CpuFeature). +/// to retrieve a [`CpuFeature`](crate::CpuFeature). #[derive(Debug)] #[cfg_attr(feature = "std", derive(Error))] pub enum ParseCpuFeatureError { diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index 268c59b5ce4..3f8e4993afb 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -62,6 +62,7 @@ mod libcalls; mod memory; mod module; mod serialize; +mod stack; mod store_id; mod table; mod trapcode; @@ -125,11 +126,10 @@ pub use crate::compilation::function::{ Functions, }; pub use crate::compilation::module::CompileModuleInfo; -pub use crate::compilation::sourceloc::SourceLoc; pub use crate::compilation::symbols::{Symbol, SymbolRegistry}; -pub use crate::compilation::trap::TrapInformation; pub use crate::compilation::unwind::CompiledFunctionUnwindInfo; +pub use crate::stack::{FrameInfo, SourceLoc, TrapInformation}; pub use crate::store_id::StoreId; /// Offset in bytes from the beginning of the function. diff --git a/lib/types/src/stack/frame.rs b/lib/types/src/stack/frame.rs new file mode 100644 index 00000000000..e6cb53062a4 --- /dev/null +++ b/lib/types/src/stack/frame.rs @@ -0,0 +1,100 @@ +use crate::SourceLoc; + +/// Description of a frame in a backtrace for a [`RuntimeError`](crate::RuntimeError). +/// +/// Whenever a WebAssembly trap occurs an instance of [`RuntimeError`] +/// is created. Each [`RuntimeError`] has a backtrace of the +/// WebAssembly frames that led to the trap, and each frame is +/// described by this structure. +/// +/// [`RuntimeError`]: crate::RuntimeError +#[derive(Debug, Clone)] +pub struct FrameInfo { + /// The name of the module + module_name: String, + /// The index of the function in the module + func_index: u32, + /// The function name, if one is available. + function_name: Option, + /// The source location of the function + func_start: SourceLoc, + /// The source location of the instruction + instr: SourceLoc, +} + +impl FrameInfo { + /// Creates a new [FrameInfo], useful for testing. + pub fn new( + module_name: String, + func_index: u32, + function_name: Option, + func_start: SourceLoc, + instr: SourceLoc, + ) -> Self { + Self { + module_name, + func_index, + function_name, + func_start, + instr, + } + } + + /// Returns the WebAssembly function index for this frame. + /// + /// This function index is the index in the function index space of the + /// WebAssembly module that this frame comes from. + pub fn func_index(&self) -> u32 { + self.func_index + } + + /// Returns the identifer of the module that this frame is for. + /// + /// ModuleInfo identifiers are present in the `name` section of a WebAssembly + /// binary, but this may not return the exact item in the `name` section. + /// ModuleInfo names can be overwritten at construction time or perhaps inferred + /// from file names. The primary purpose of this function is to assist in + /// debugging and therefore may be tweaked over time. + /// + /// This function returns `None` when no name can be found or inferred. + pub fn module_name(&self) -> &str { + &self.module_name + } + + /// Returns a descriptive name of the function for this frame, if one is + /// available. + /// + /// The name of this function may come from the `name` section of the + /// WebAssembly binary, or wasmer may try to infer a better name for it if + /// not available, for example the name of the export if it's exported. + /// + /// This return value is primarily used for debugging and human-readable + /// purposes for things like traps. Note that the exact return value may be + /// tweaked over time here and isn't guaranteed to be something in + /// particular about a wasm module due to its primary purpose of assisting + /// in debugging. + /// + /// This function returns `None` when no name could be inferred. + pub fn function_name(&self) -> Option<&str> { + self.function_name.as_deref() + } + + /// Returns the offset within the original wasm module this frame's program + /// counter was at. + /// + /// The offset here is the offset from the beginning of the original wasm + /// module to the instruction that this frame points to. + pub fn module_offset(&self) -> usize { + self.instr.bits() as usize + } + + /// Returns the offset from the original wasm module's function to this + /// frame's program counter. + /// + /// The offset here is the offset from the beginning of the defining + /// function of this frame (within the wasm module) to the instruction this + /// frame points to. + pub fn func_offset(&self) -> usize { + (self.instr.bits() - self.func_start.bits()) as usize + } +} diff --git a/lib/types/src/stack/mod.rs b/lib/types/src/stack/mod.rs new file mode 100644 index 00000000000..0e36b9f495e --- /dev/null +++ b/lib/types/src/stack/mod.rs @@ -0,0 +1,9 @@ +//! Types for the stack tracing / frames. + +mod frame; +mod sourceloc; +mod trap; + +pub use frame::FrameInfo; +pub use sourceloc::SourceLoc; +pub use trap::TrapInformation; diff --git a/lib/types/src/compilation/sourceloc.rs b/lib/types/src/stack/sourceloc.rs similarity index 100% rename from lib/types/src/compilation/sourceloc.rs rename to lib/types/src/stack/sourceloc.rs diff --git a/lib/types/src/compilation/trap.rs b/lib/types/src/stack/trap.rs similarity index 100% rename from lib/types/src/compilation/trap.rs rename to lib/types/src/stack/trap.rs diff --git a/lib/types/src/vmoffsets.rs b/lib/types/src/vmoffsets.rs index 729adc1069d..4c387f737e3 100644 --- a/lib/types/src/vmoffsets.rs +++ b/lib/types/src/vmoffsets.rs @@ -4,7 +4,7 @@ //! Offsets and sizes of various structs in wasmer-vm's vmcontext //! module. -#![deny(broken_intra_doc_links)] +#![deny(rustdoc::broken_intra_doc_links)] use crate::{ FunctionIndex, GlobalIndex, LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryIndex, diff --git a/lib/vm/src/trap/trap.rs b/lib/vm/src/trap/trap.rs index 0934364b024..54239fcd924 100644 --- a/lib/vm/src/trap/trap.rs +++ b/lib/vm/src/trap/trap.rs @@ -1,72 +1,135 @@ -use backtrace::Backtrace; -use std::error::Error; -use wasmer_types::TrapCode; - -/// Stores trace message with backtrace. -#[derive(Debug)] -pub enum Trap { - /// A user-raised trap through `raise_user_trap`. - User(Box), - - /// A trap raised from the Wasm generated code - /// - /// Note: this trap is deterministic (assuming a deterministic host implementation) - Wasm { - /// The program counter in generated code where this trap happened. - pc: usize, - /// Native stack backtrace at the time the trap occurred - backtrace: Backtrace, - /// Optional trapcode associated to the signal that caused the trap - signal_trap: Option, - }, - - /// A trap raised from a wasm libcall - /// - /// Note: this trap is deterministic (assuming a deterministic host implementation) - Lib { - /// Code of the trap. - trap_code: TrapCode, - /// Native stack backtrace at the time the trap occurred - backtrace: Backtrace, - }, - - /// A trap indicating that the runtime was unable to allocate sufficient memory. - /// - /// Note: this trap is nondeterministic, since it depends on the host system. - OOM { - /// Native stack backtrace at the time the OOM occurred - backtrace: Backtrace, - }, -} - -impl Trap { - /// Construct a new Wasm trap with the given source location and backtrace. - /// - /// Internally saves a backtrace when constructed. - pub fn wasm(pc: usize, backtrace: Backtrace, signal_trap: Option) -> Self { - Self::Wasm { - pc, - backtrace, - signal_trap, - } - } - - /// Construct a new Wasm trap with the given trap code. - /// - /// Internally saves a backtrace when constructed. - pub fn lib(trap_code: TrapCode) -> Self { - let backtrace = Backtrace::new_unresolved(); - Self::Lib { - trap_code, - backtrace, - } - } - - /// Construct a new OOM trap with the given source location and trap code. - /// - /// Internally saves a backtrace when constructed. - pub fn oom() -> Self { - let backtrace = Backtrace::new_unresolved(); - Self::OOM { backtrace } - } -} +use backtrace::Backtrace; +use std::error::Error; +use std::fmt; +use wasmer_types::TrapCode; + +/// Stores trace message with backtrace. +#[derive(Debug)] +pub enum Trap { + /// A user-raised trap through `raise_user_trap`. + User(Box), + + /// A trap raised from the Wasm generated code + /// + /// Note: this trap is deterministic (assuming a deterministic host implementation) + Wasm { + /// The program counter in generated code where this trap happened. + pc: usize, + /// Native stack backtrace at the time the trap occurred + backtrace: Backtrace, + /// Optional trapcode associated to the signal that caused the trap + signal_trap: Option, + }, + + /// A trap raised from a wasm libcall + /// + /// Note: this trap is deterministic (assuming a deterministic host implementation) + Lib { + /// Code of the trap. + trap_code: TrapCode, + /// Native stack backtrace at the time the trap occurred + backtrace: Backtrace, + }, + + /// A trap indicating that the runtime was unable to allocate sufficient memory. + /// + /// Note: this trap is nondeterministic, since it depends on the host system. + OOM { + /// Native stack backtrace at the time the OOM occurred + backtrace: Backtrace, + }, +} + +fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) { + (t, t) +} + +impl Trap { + /// Construct a new Error with the given a user error. + /// + /// Internally saves a backtrace when constructed. + pub fn user(err: Box) -> Self { + Self::User(err) + } + + /// Construct a new Wasm trap with the given source location and backtrace. + /// + /// Internally saves a backtrace when constructed. + pub fn wasm(pc: usize, backtrace: Backtrace, signal_trap: Option) -> Self { + Self::Wasm { + pc, + backtrace, + signal_trap, + } + } + + /// Returns trap code, if it's a Trap + pub fn to_trap(self) -> Option { + unimplemented!() + } + + /// Construct a new Wasm trap with the given trap code. + /// + /// Internally saves a backtrace when constructed. + pub fn lib(trap_code: TrapCode) -> Self { + let backtrace = Backtrace::new_unresolved(); + Self::Lib { + trap_code, + backtrace, + } + } + + /// Construct a new OOM trap with the given source location and trap code. + /// + /// Internally saves a backtrace when constructed. + pub fn oom() -> Self { + let backtrace = Backtrace::new_unresolved(); + Self::OOM { backtrace } + } + + /// Attempts to downcast the `Trap` to a concrete type. + pub fn downcast(self) -> Result { + match self { + // We only try to downcast user errors + Trap::User(err) if err.is::() => Ok(*err.downcast::().unwrap()), + _ => Err(self), + } + } + + /// Attempts to downcast the `Trap` to a concrete type. + pub fn downcast_ref(&self) -> Option<&T> { + match &self { + // We only try to downcast user errors + Trap::User(err) if err.is::() => err.downcast_ref::(), + _ => None, + } + } + + /// Returns true if the `Trap` is the same as T + pub fn is(&self) -> bool { + match self { + Trap::User(err) => err.is::(), + _ => false, + } + } +} + +impl std::error::Error for Trap { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self { + Trap::User(err) => Some(&**err), + _ => None, + } + } +} + +impl fmt::Display for Trap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::User(e) => write!(f, "{}", e), + Self::Lib { .. } => write!(f, "lib"), + Self::Wasm { .. } => write!(f, "wasm"), + Self::OOM { .. } => write!(f, "Wasmer VM out of memory"), + } + } +} diff --git a/tests/compilers/issues.rs b/tests/compilers/issues.rs index 8ce799687fe..471e465c30c 100644 --- a/tests/compilers/issues.rs +++ b/tests/compilers/issues.rs @@ -264,6 +264,26 @@ fn regression_gpr_exhaustion_for_calls(mut config: crate::Config) -> Result<()> Ok(()) } +#[compiler_test(issues)] +fn test_start(mut config: crate::Config) -> Result<()> { + let mut store = config.store(); + let mut env = FunctionEnv::new(&mut store, ()); + let imports: Imports = imports! {}; + let wat = r#" + (module (func $main (unreachable)) (start $main)) + "#; + let module = Module::new(&store, wat)?; + let instance = Instance::new(&mut store, &module, &imports); + assert!(instance.is_err()); + if let InstantiationError::Start(err) = instance.unwrap_err() { + assert_eq!(err.message(), "unreachable"); + } else { + panic!("_start should have failed with an unreachable error") + } + + Ok(()) +} + #[compiler_test(issues)] fn test_popcnt(mut config: crate::Config) -> Result<()> { let mut store = config.store();