From c111eb2a503bf3ab06756f9e9db2b82caf66e0f0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 1 Nov 2022 12:15:18 -0700 Subject: [PATCH] Soup up documentation for errors and traps --- crates/wasmtime/src/config.rs | 40 ++++++--- crates/wasmtime/src/func.rs | 79 +++++++++++++++++- crates/wasmtime/src/func/typed.rs | 8 ++ crates/wasmtime/src/instance.rs | 7 +- crates/wasmtime/src/trap.rs | 130 ++++++++++++++++++++++++++---- 5 files changed, 231 insertions(+), 33 deletions(-) diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index e5f3fde34145..b29fe2322683 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -381,15 +381,30 @@ impl Config { self } - /// Configures whether backtraces exist in a `Trap`. - /// - /// Enabled by default, this feature builds in support to - /// generate backtraces at runtime for WebAssembly modules. This means that - /// unwinding information is compiled into wasm modules and necessary runtime - /// dependencies are enabled as well. - /// - /// When disabled, wasm backtrace details are ignored, and [`crate::Trap::trace()`] - /// will always return `None`. + /// Configures whether [`WasmBacktrace`] will be present in the context of + /// errors returned from Wasmtime. + /// + /// A backtrace may be collected whenever an error is returned from a host + /// function call through to WebAssembly or when WebAssembly itself hits a + /// trap condition, such as an out-of-bounds memory access. This flag + /// indicates, in these conditions, whether the backtrace is collected or + /// not. + /// + /// Currently wasm backtraces are implemented through frame pointer walking. + /// This means that collecting a backtrace is expected to be a fast and + /// relatively cheap operation. Additionally backtrace collection is + /// suitable in concurrent environments since one thread capturing a + /// backtrace won't block other threads. + /// + /// Collected backtraces are attached via [`anyhow::Error::context`] to + /// errors returned from host functions. The [`WasmBacktrace`] type can be + /// acquired via [`anyhow::Error::downcast_ref`] to inspect the backtrace. + /// When this option is disabled then this context is never applied to + /// errors coming out of wasm. + /// + /// This option is `true` by default. + /// + /// [`WasmBacktrace`]: crate::WasmBacktrace #[deprecated = "Backtraces will always be enabled in future Wasmtime releases; if this \ causes problems for you, please file an issue."] pub fn wasm_backtrace(&mut self, enable: bool) -> &mut Self { @@ -429,13 +444,16 @@ impl Config { /// This configuration option only exists to help third-party stack /// capturing mechanisms, such as the system's unwinder or the `backtrace` /// crate, determine how to unwind through Wasm frames. It does not affect - /// whether Wasmtime can capture Wasm backtraces or not, or whether - /// [`Trap::trace`][crate::Trap::trace] returns `Some` or `None`. + /// whether Wasmtime can capture Wasm backtraces or not. The presence of + /// [`WasmBacktrace`] is controlled by the [`Config::wasm_backtrace`] + /// option. /// /// Note that native unwind information is always generated when targeting /// Windows, since the Windows ABI requires it. /// /// This option defaults to `true`. + /// + /// [`WasmBacktrace`]: crate::WasmBacktrace pub fn native_unwind_info(&mut self, enable: bool) -> &mut Self { self.native_unwind_info = enable; self diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 3d285a854183..587fd8e566bd 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -327,6 +327,22 @@ impl Func { /// /// For more information about `Send + Sync + 'static` requirements on the /// `func`, see [`Func::wrap`](#why-send--sync--static). + /// + /// # Errors + /// + /// The host-provided function here returns a + /// [`Result<()>`](anyhow::Result). If the function returns `Ok(())` then + /// that indicates that the host function completed successfully and wrote + /// the result into the `&mut [Val]` argument. + /// + /// If the function returns `Err(e)`, however, then this is equivalent to + /// the host function triggering a trap for wasm. WebAssembly execution is + /// immediately halted and the original caller of [`Func::call`], for + /// example, will receive the error returned here (possibly with + /// [`WasmBacktrace`](crate::WasmBacktrace) context information attached). + /// + /// For more information about errors in Wasmtime see the [`Trap`] + /// documentation. #[cfg(compiler)] #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs pub fn new( @@ -355,6 +371,11 @@ impl Func { /// [`Func::new`] or [`Func::wrap`]. The [`Func::wrap`] API, in particular, /// is both safer and faster than this API. /// + /// # Errors + /// + /// See [`Func::new`] for the behavior of returning an error from the host + /// function provided here. + /// /// # Unsafety /// /// This function is not safe because it's not known at compile time that @@ -392,6 +413,11 @@ impl Func { /// This function will panic if `store` is not associated with an [async /// config](crate::Config::async_support). /// + /// # Errors + /// + /// See [`Func::new`] for the behavior of returning an error from the host + /// function provided here. + /// /// # Examples /// /// ``` @@ -543,6 +569,18 @@ impl Func { /// actually closing over any values. These zero-sized types will use the /// context from [`Caller`] for host-defined information. /// + /// # Errors + /// + /// The closure provided here to `wrap` can optionally return a + /// [`Result`](anyhow::Result). Returning `Ok(t)` represents the host + /// function successfully completing with the `t` result. Returning + /// `Err(e)`, however, is equivalent to raising a custom wasm trap. + /// Execution of WebAssembly does not resume and the stack is unwound to the + /// original caller of the function where the error is returned. + /// + /// For more information about errors in Wasmtime see the [`Trap`] + /// documentation. + /// /// # Examples /// /// First up we can see how simple wasm imports can be implemented, such @@ -750,16 +788,41 @@ impl Func { /// Invokes this function with the `params` given and writes returned values /// to `results`. /// - /// The `params` here must match the type signature of this `Func`, or a - /// trap will occur. If a trap occurs while executing this function, then a - /// trap will also be returned. Additionally `results` must have the same - /// length as the number of results for this function. + /// The `params` here must match the type signature of this `Func`, or an + /// error will occur. Additionally `results` must have the same + /// length as the number of results for this function. Calling this function + /// will synchronously execute the WebAssembly function referenced to get + /// the results. + /// + /// This function will return `Ok(())` if execution completed without a trap + /// or error of any kind. In this situation the results will be written to + /// the provided `results` array. + /// + /// # Errors + /// + /// Any error which occurs throughout the execution of the function will be + /// returned as `Err(e)`. The [`Error`](anyhow::Error) type can be inspected + /// for the precise error cause such as: + /// + /// * [`Trap`] - indicates that a wasm trap happened and execution was + /// halted. + /// * [`WasmBacktrace`] - optionally included on errors for backtrace + /// information of the trap/error. + /// * Other string-based errors to indicate issues such as type errors with + /// `params`. + /// * Any host-originating error originally returned from a function defined + /// via [`Func::new`], for example. + /// + /// Errors typically indicate that execution of WebAssembly was halted + /// mid-way and did not complete after the error condition happened. /// /// # Panics /// /// This function will panic if called on a function belonging to an async /// store. Asynchronous stores must always use `call_async`. /// initiates a panic. Also panics if `store` does not own this function. + /// + /// [`WasmBacktrace`]: crate::WasmBacktrace pub fn call( &self, mut store: impl AsContextMut, @@ -788,6 +851,10 @@ impl Func { /// invoked many times with new `ExternRef` values and no other GC happens /// via any other means then no values will get collected. /// + /// # Errors + /// + /// For more information about errors see the [`Func::call`] documentation. + /// /// # Unsafety /// /// This function is unsafe because the `params_and_returns` argument is not @@ -878,6 +945,10 @@ impl Func { /// For more information see the documentation on [asynchronous /// configs](crate::Config::async_support). /// + /// # Errors + /// + /// For more information on errors see the [`Func::call`] documentation. + /// /// # Panics /// /// Panics if this is called on a function in a synchronous store. This diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs index ecc5c15c5b32..321aa3663056 100644 --- a/crates/wasmtime/src/func/typed.rs +++ b/crates/wasmtime/src/func/typed.rs @@ -68,6 +68,10 @@ where /// For more information, see the [`Func::typed`] and [`Func::call`] /// documentation. /// + /// # Errors + /// + /// For more information on errors see the documentation on [`Func::call`]. + /// /// # Panics /// /// This function will panic if it is called when the underlying [`Func`] is @@ -91,6 +95,10 @@ where /// For more information, see the [`Func::typed`] and [`Func::call_async`] /// documentation. /// + /// # Errors + /// + /// For more information on errors see the documentation on [`Func::call`]. + /// /// # Panics /// /// This function will panic if it is called when the underlying [`Func`] is diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index de6b33f50cf2..cb7897ad3abe 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -88,7 +88,8 @@ impl Instance { /// /// When instantiation fails it's recommended to inspect the return value to /// see why it failed, or bubble it upwards. If you'd like to specifically - /// check for trap errors, you can use `error.downcast::()`. + /// check for trap errors, you can use `error.downcast::()`. For more + /// about error handling see the [`Trap`] documentation. /// /// # Panics /// @@ -102,7 +103,7 @@ impl Instance { mut store: impl AsContextMut, module: &Module, imports: &[Extern], - ) -> Result { + ) -> Result { let mut store = store.as_context_mut(); let imports = Instance::typecheck_externs(store.0, module, imports)?; // Note that the unsafety here should be satisfied by the call to @@ -134,7 +135,7 @@ impl Instance { mut store: impl AsContextMut, module: &Module, imports: &[Extern], - ) -> Result + ) -> Result where T: Send, { diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 7840726d6a36..7f6faa13bfa7 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -5,13 +5,66 @@ use std::fmt; use wasmtime_environ::{EntityRef, FilePos, TrapCode}; use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index}; -/// Description of a trap which can happen in WebAssembly, halting wasm -/// execution. +/// Representation of a WebAssembly trap and what caused it to occur. /// -/// This enumeration is a list of all possible traps that can happen in wasm, in -/// addition to some Wasmtime-specific trap codes listed here as well. This type -/// will be returned as an error when a wasm function hits one of these trap -/// conditions through the [`anyhow::Error`] wrapper. +/// WebAssembly traps happen explicitly for instructions such as `unreachable` +/// but can also happen as side effects of other instructions such as `i32.load` +/// loading an out-of-bounds address. Traps halt the execution of WebAssembly +/// and cause an error to be returned to the host. This enumeration is a list of +/// all possible traps that can happen in wasm, in addition to some +/// Wasmtime-specific trap codes listed here as well. +/// +/// # Errors in Wasmtime +/// +/// Error-handling in Wasmtime is primarily done through the [`anyhow`] crate +/// where most results are a [`Result`](anyhow::Result) which is an alias for +/// [`Result`](std::result::Result). Errors in Wasmtime are +/// represented with [`anyhow::Error`] which acts as a container for any type of +/// error in addition to optional context for this error. The "base" error or +/// [`anyhow::Error::root_cause`] is a [`Trap`] whenever WebAssembly hits a +/// trap, or otherwise it's whatever the host created the error with when +/// returning an error for a host call. +/// +/// Any error which happens while WebAssembly is executing will also, by +/// default, capture a backtrace of the wasm frames while executing. This +/// backtrace is represented with a [`WasmBacktrace`] instance and is attached +/// to the [`anyhow::Error`] return value as a +/// [`context`](anyhow::Error::context). Inspecting a [`WasmBacktrace`] can be +/// done with the [`downcast_ref`](anyhow::Error::downcast_ref) function. For +/// information on this see the [`WasmBacktrace`] documentation. +/// +/// # Examples +/// +/// ``` +/// # use wasmtime::*; +/// # use anyhow::Result; +/// # fn main() -> Result<()> { +/// let engine = Engine::default(); +/// let module = Module::new( +/// &engine, +/// r#" +/// (module +/// (func (export "trap") +/// unreachable) +/// (func $overflow (export "overflow") +/// call $overflow) +/// ) +/// "#, +/// )?; +/// let mut store = Store::new(&engine, ()); +/// let instance = Instance::new(&mut store, &module, &[])?; +/// +/// let trap = instance.get_typed_func::<(), (), _>(&mut store, "trap")?; +/// let error = trap.call(&mut store, ()).unwrap_err(); +/// assert_eq!(*error.downcast_ref::().unwrap(), Trap::UnreachableCodeReached); +/// assert!(error.root_cause().is::()); +/// +/// let overflow = instance.get_typed_func::<(), (), _>(&mut store, "overflow")?; +/// let error = overflow.call(&mut store, ()).unwrap_err(); +/// assert_eq!(*error.downcast_ref::().unwrap(), Trap::StackOverflow); +/// # Ok(()) +/// # } +/// ``` // // The code can be accessed from the c-api, where the possible values are translated // into enum values defined there: @@ -174,13 +227,63 @@ impl fmt::Display for Trap { impl std::error::Error for Trap {} -/// todo +/// Representation of a backtrace of function frames in a WebAssembly module for +/// where an error happened. +/// +/// This structure is attached to the [`anyhow::Error`] returned from many +/// Wasmtime functions that execute WebAssembly such as [`Instance::new`] or +/// [`Func::call`]. This can be acquired with the [`anyhow::Error::downcast`] +/// family of methods to programmatically inspect the backtrace. Otherwise since +/// it's part of the error returned this will get printed along with the rest of +/// the error when the error is logged. +/// +/// Capturing of wasm backtraces can be configured through the +/// [`Config::wasm_backtrace`](crate::Config::wasm_backtrace) method. +/// +/// For more information about errors in wasmtime see the documentation of the +/// [`Trap`] type. +/// +/// [`Func::call`]: crate::Func::call +/// [`Instance::new`]: crate::Instance::new +/// +/// # Examples +/// +/// ``` +/// # use wasmtime::*; +/// # use anyhow::Result; +/// # fn main() -> Result<()> { +/// let engine = Engine::default(); +/// let module = Module::new( +/// &engine, +/// r#" +/// (module +/// (func $start (export "run") +/// call $trap) +/// (func $trap +/// unreachable) +/// ) +/// "#, +/// )?; +/// let mut store = Store::new(&engine, ()); +/// let instance = Instance::new(&mut store, &module, &[])?; +/// let func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; +/// let error = func.call(&mut store, ()).unwrap_err(); +/// let bt = error.downcast_ref::().unwrap(); +/// let frames = bt.frames(); +/// assert_eq!(frames.len(), 2); +/// assert_eq!(frames[0].func_name(), Some("trap")); +/// assert_eq!(frames[1].func_name(), Some("start")); +/// # Ok(()) +/// # } +/// ``` #[derive(Debug)] pub struct WasmBacktrace { wasm_trace: Vec, + hint_wasm_backtrace_details_env: bool, + // This is currently only present for the `Debug` implementation for extra + // context. #[allow(dead_code)] runtime_trace: wasmtime_runtime::Backtrace, - hint_wasm_backtrace_details_env: bool, } impl WasmBacktrace { @@ -324,14 +427,11 @@ impl fmt::Display for WasmBacktrace { } } -/// Description of a frame in a backtrace for a [`Trap`] or [`BacktraceContext`]. -/// -/// Whenever a WebAssembly trap occurs an instance of [`Trap`] is created. Each -/// [`Trap`] has a backtrace of the WebAssembly frames that led to the trap, and -/// each frame is described by this structure. +/// Description of a frame in a backtrace for a [`WasmBacktrace`]. /// -/// [`Trap`]: crate::Trap -/// [`BacktraceContext`]: crate::BacktraceContext +/// Whenever an error happens while WebAssembly is executing a +/// [`WasmBacktrace`] will be attached to the error returned which can be used +/// to acquire this `FrameInfo`. For more information see [`WasmBacktrace`]. #[derive(Debug)] pub struct FrameInfo { module_name: Option,