Return anyhow::Error from host functions instead of Trap, redesign Trap (#5149)
* Return `anyhow::Error` from host functions instead of `Trap` This commit refactors how errors are modeled when returned from host functions and additionally refactors how custom errors work with `Trap`. At a high level functions in Wasmtime that previously worked with `Result<T, Trap>` now work with `Result<T>` instead where the error is `anyhow::Error`. This includes functions such as: * Host-defined functions in a `Linker<T>` * `TypedFunc::call` * Host-related callbacks like call hooks Errors are now modeled primarily as `anyhow::Error` throughout Wasmtime. This subsequently removes the need for `Trap` to have the ability to represent all host-defined errors as it previously did. Consequently the `From` implementations for any error into a `Trap` have been removed here and the only embedder-defined way to create a `Trap` is to use `Trap::new` with a custom string. After this commit the distinction between a `Trap` and a host error is the wasm backtrace that it contains. Previously all errors in host functions would flow through a `Trap` and get a wasm backtrace attached to them, but now this only happens if a `Trap` itself is created meaning that arbitrary host-defined errors flowing from a host import to the other side won't get backtraces attached. Some internals of Wasmtime itself were updated or preserved to use `Trap::new` to capture a backtrace where it seemed useful, such as when fuel runs out. The main motivation for this commit is that it now enables hosts to thread a concrete error type from a host function all the way through to where a wasm function was invoked. Previously this could not be done since the host error was wrapped in a `Trap` that didn't provide the ability to get at the internals. A consequence of this commit is that when a host error is returned that isn't a `Trap` we'll capture a backtrace and then won't have a `Trap` to attach it to. To avoid losing the contextual information this commit uses the `Error::context` method to attach the backtrace as contextual information to ensure that the backtrace is itself not lost. This is a breaking change for likely all users of Wasmtime, but it's hoped to be a relatively minor change to workaround. Most use cases can likely change `-> Result<T, Trap>` to `-> Result<T>` and otherwise explicit creation of a `Trap` is largely no longer necessary. * Fix some doc links * add some tests and make a backtrace type public (#55) * Trap: avoid a trailing newline in the Display impl which in turn ends up with three newlines between the end of the backtrace and the `Caused by` in the anyhow Debug impl * make BacktraceContext pub, and add tests showing downcasting behavior of anyhow::Error to traps or backtraces * Remove now-unnecesary `Trap` downcasts in `Linker::module` * Fix test output expectations * Remove `Trap::i32_exit` This commit removes special-handling in the `wasmtime::Trap` type for the i32 exit code required by WASI. This is now instead modeled as a specific `I32Exit` error type in the `wasmtime-wasi` crate which is returned by the `proc_exit` hostcall. Embedders which previously tested for i32 exits now downcast to the `I32Exit` value. * Remove the `Trap::new` constructor This commit removes the ability to create a trap with an arbitrary error message. The purpose of this commit is to continue the prior trend of leaning into the `anyhow::Error` type instead of trying to recreate it with `Trap`. A subsequent simplification to `Trap` after this commit is that `Trap` will simply be an `enum` of trap codes with no extra information. This commit is doubly-motivated by the desire to always use the new `BacktraceContext` type instead of sometimes using that and sometimes using `Trap`. Most of the changes here were around updating `Trap::new` calls to `bail!` calls instead. Tests which assert particular error messages additionally often needed to use the `:?` formatter instead of the `{}` formatter because the prior formats the whole `anyhow::Error` and the latter only formats the top-most error, which now contains the backtrace. * Merge `Trap` and `TrapCode` With prior refactorings there's no more need for `Trap` to be opaque or otherwise contain a backtrace. This commit parse down `Trap` to simply an `enum` which was the old `TrapCode`. All various tests and such were updated to handle this. The main consequence of this commit is that all errors have a `BacktraceContext` context attached to them. This unfortunately means that the backtrace is printed first before the error message or trap code, but given all the prior simplifications that seems worth it at this time. * Rename `BacktraceContext` to `WasmBacktrace` This feels like a better name given how this has turned out, and additionally this commit removes having both `WasmBacktrace` and `BacktraceContext`. * Soup up documentation for errors and traps * Fix build of the C API Co-authored-by: Pat Hickey <pat@moreproductive.org>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
use crate::store::{StoreId, StoreOpaque};
|
||||
use crate::StoreContextMut;
|
||||
use crate::Trap;
|
||||
use anyhow::{bail, Result};
|
||||
use std::ptr::NonNull;
|
||||
use wasmtime_environ::component::StringEncoding;
|
||||
@@ -97,7 +96,7 @@ impl Options {
|
||||
};
|
||||
|
||||
if result % old_align != 0 {
|
||||
bail!(Trap::new("realloc return: result not aligned"));
|
||||
bail!("realloc return: result not aligned");
|
||||
}
|
||||
let result = usize::try_from(result)?;
|
||||
|
||||
@@ -105,7 +104,7 @@ impl Options {
|
||||
|
||||
let result_slice = match memory.get_mut(result..).and_then(|s| s.get_mut(..new_size)) {
|
||||
Some(end) => end,
|
||||
None => bail!(Trap::new("realloc return: beyond end of memory")),
|
||||
None => bail!("realloc return: beyond end of memory"),
|
||||
};
|
||||
|
||||
Ok((result_slice, result))
|
||||
|
||||
@@ -381,15 +381,30 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether backtraces exist in a `Trap`.
|
||||
/// Configures whether [`WasmBacktrace`] will be present in the context of
|
||||
/// errors returned from Wasmtime.
|
||||
///
|
||||
/// 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.
|
||||
/// 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.
|
||||
///
|
||||
/// When disabled, wasm backtrace details are ignored, and [`crate::Trap::trace()`]
|
||||
/// will always return `None`.
|
||||
/// 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
|
||||
|
||||
@@ -464,7 +464,7 @@ impl Table {
|
||||
let table = Table::from_wasmtime_table(wasmtime_export, store);
|
||||
(*table.wasmtime_table(store, std::iter::empty()))
|
||||
.fill(0, init, ty.minimum())
|
||||
.map_err(|c| Trap::new_wasm(c, None))?;
|
||||
.map_err(|c| Trap::from_env(c))?;
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
@@ -653,7 +653,7 @@ impl Table {
|
||||
let src_table = src_table.wasmtime_table(store, src_range);
|
||||
unsafe {
|
||||
runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
|
||||
.map_err(|c| Trap::new_wasm(c, None))?;
|
||||
.map_err(|c| Trap::from_env(c))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -683,7 +683,7 @@ impl Table {
|
||||
unsafe {
|
||||
(*table)
|
||||
.fill(dst, val, len)
|
||||
.map_err(|c| Trap::new_wasm(c, None))?;
|
||||
.map_err(|c| Trap::from_env(c))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, StoreContext,
|
||||
StoreContextMut, Trap, Val, ValRaw, ValType,
|
||||
};
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use anyhow::{bail, Context as _, Error, Result};
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
@@ -290,7 +290,7 @@ macro_rules! generate_wrap_async_func {
|
||||
|
||||
match unsafe { async_cx.block_on(future.as_mut()) } {
|
||||
Ok(ret) => ret.into_fallible(),
|
||||
Err(e) => R::fallible_from_trap(e),
|
||||
Err(e) => R::fallible_from_error(e),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -327,12 +327,28 @@ 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<T>(
|
||||
store: impl AsContextMut<Data = T>,
|
||||
ty: FuncType,
|
||||
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let ty_clone = ty.clone();
|
||||
unsafe {
|
||||
@@ -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
|
||||
@@ -365,7 +386,7 @@ impl Func {
|
||||
pub unsafe fn new_unchecked<T>(
|
||||
mut store: impl AsContextMut<Data = T>,
|
||||
ty: FuncType,
|
||||
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let store = store.as_context_mut().0;
|
||||
let host = HostFunc::new_unchecked(store.engine(), ty, func);
|
||||
@@ -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
|
||||
///
|
||||
/// ```
|
||||
@@ -443,7 +469,7 @@ impl Func {
|
||||
Caller<'a, T>,
|
||||
&'a [Val],
|
||||
&'a mut [Val],
|
||||
) -> Box<dyn Future<Output = Result<(), Trap>> + Send + 'a>
|
||||
) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
@@ -505,7 +531,7 @@ impl Func {
|
||||
/// | `T` | `T` | a single return value |
|
||||
/// | `(T1, T2, ...)` | `T1 T2 ...` | multiple returns |
|
||||
///
|
||||
/// Note that all return types can also be wrapped in `Result<_, Trap>` to
|
||||
/// Note that all return types can also be wrapped in `Result<_>` to
|
||||
/// indicate that the host function can generate a trap as well as possibly
|
||||
/// returning a value.
|
||||
///
|
||||
@@ -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<T>`](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
|
||||
@@ -581,7 +619,7 @@ impl Func {
|
||||
/// let add = Func::wrap(&mut store, |a: i32, b: i32| {
|
||||
/// match a.checked_add(b) {
|
||||
/// Some(i) => Ok(i),
|
||||
/// None => Err(Trap::new("overflow")),
|
||||
/// None => anyhow::bail!("overflow"),
|
||||
/// }
|
||||
/// });
|
||||
/// let module = Module::new(
|
||||
@@ -652,7 +690,7 @@ impl Func {
|
||||
/// let log_str = Func::wrap(&mut store, |mut caller: Caller<'_, ()>, ptr: i32, len: i32| {
|
||||
/// let mem = match caller.get_export("memory") {
|
||||
/// Some(Extern::Memory(mem)) => mem,
|
||||
/// _ => return Err(Trap::new("failed to find host memory")),
|
||||
/// _ => anyhow::bail!("failed to find host memory"),
|
||||
/// };
|
||||
/// let data = mem.data(&caller)
|
||||
/// .get(ptr as u32 as usize..)
|
||||
@@ -660,9 +698,9 @@ impl Func {
|
||||
/// let string = match data {
|
||||
/// Some(data) => match str::from_utf8(data) {
|
||||
/// Ok(s) => s,
|
||||
/// Err(_) => return Err(Trap::new("invalid utf-8")),
|
||||
/// Err(_) => anyhow::bail!("invalid utf-8"),
|
||||
/// },
|
||||
/// None => return Err(Trap::new("pointer/length out of bounds")),
|
||||
/// None => anyhow::bail!("pointer/length out of bounds"),
|
||||
/// };
|
||||
/// assert_eq!(string, "Hello, world!");
|
||||
/// println!("{}", string);
|
||||
@@ -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
|
||||
@@ -808,7 +875,7 @@ impl Func {
|
||||
&self,
|
||||
mut store: impl AsContextMut,
|
||||
params_and_returns: *mut ValRaw,
|
||||
) -> Result<(), Trap> {
|
||||
) -> Result<()> {
|
||||
let mut store = store.as_context_mut();
|
||||
let data = &store.0.store_data()[self.0];
|
||||
let anyfunc = data.export().anyfunc;
|
||||
@@ -821,7 +888,7 @@ impl Func {
|
||||
anyfunc: NonNull<VMCallerCheckedAnyfunc>,
|
||||
trampoline: VMTrampoline,
|
||||
params_and_returns: *mut ValRaw,
|
||||
) -> Result<(), Trap> {
|
||||
) -> Result<()> {
|
||||
invoke_wasm_and_catch_traps(store, |caller| {
|
||||
let trampoline = wasmtime_runtime::prepare_host_to_wasm_trampoline(caller, trampoline);
|
||||
trampoline(
|
||||
@@ -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
|
||||
@@ -1024,8 +1095,8 @@ impl Func {
|
||||
mut caller: Caller<'_, T>,
|
||||
ty: &FuncType,
|
||||
values_vec: &mut [ValRaw],
|
||||
func: &dyn Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap>,
|
||||
) -> Result<(), Trap> {
|
||||
func: &dyn Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
// Translate the raw JIT arguments in `values_vec` into a `Val` which
|
||||
// we'll be passing as a slice. The storage for our slice-of-`Val` we'll
|
||||
// be taking from the `Store`. We preserve our slice back into the
|
||||
@@ -1064,14 +1135,10 @@ impl Func {
|
||||
// values, and we need to catch that here.
|
||||
for (i, (ret, ty)) in results.iter().zip(ty.results()).enumerate() {
|
||||
if ret.ty() != ty {
|
||||
return Err(Trap::new(
|
||||
"function attempted to return an incompatible value",
|
||||
));
|
||||
bail!("function attempted to return an incompatible value");
|
||||
}
|
||||
if !ret.comes_from_same_store(caller.store.0) {
|
||||
return Err(Trap::new(
|
||||
"cross-`Store` values are not currently supported",
|
||||
));
|
||||
bail!("cross-`Store` values are not currently supported");
|
||||
}
|
||||
unsafe {
|
||||
values_vec[i] = ret.to_raw(&mut caller.store);
|
||||
@@ -1224,7 +1291,7 @@ impl Func {
|
||||
pub(crate) fn invoke_wasm_and_catch_traps<T>(
|
||||
store: &mut StoreContextMut<'_, T>,
|
||||
closure: impl FnMut(*mut VMContext),
|
||||
) -> Result<(), Trap> {
|
||||
) -> Result<()> {
|
||||
unsafe {
|
||||
let exit = enter_wasm(store);
|
||||
|
||||
@@ -1342,7 +1409,7 @@ pub unsafe trait WasmRet {
|
||||
self,
|
||||
store: &mut StoreOpaque,
|
||||
ptr: Self::Retptr,
|
||||
) -> Result<Self::Abi, Trap>;
|
||||
) -> Result<Self::Abi>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn func_type(params: impl Iterator<Item = ValType>) -> FuncType;
|
||||
@@ -1358,7 +1425,7 @@ pub unsafe trait WasmRet {
|
||||
#[doc(hidden)]
|
||||
fn into_fallible(self) -> Self::Fallible;
|
||||
#[doc(hidden)]
|
||||
fn fallible_from_trap(trap: Trap) -> Self::Fallible;
|
||||
fn fallible_from_error(error: Error) -> Self::Fallible;
|
||||
}
|
||||
|
||||
unsafe impl<T> WasmRet for T
|
||||
@@ -1367,17 +1434,13 @@ where
|
||||
{
|
||||
type Abi = <T as WasmTy>::Abi;
|
||||
type Retptr = ();
|
||||
type Fallible = Result<T, Trap>;
|
||||
type Fallible = Result<T>;
|
||||
|
||||
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
|
||||
<Self as WasmTy>::compatible_with_store(self, store)
|
||||
}
|
||||
|
||||
unsafe fn into_abi_for_ret(
|
||||
self,
|
||||
store: &mut StoreOpaque,
|
||||
_retptr: (),
|
||||
) -> Result<Self::Abi, Trap> {
|
||||
unsafe fn into_abi_for_ret(self, store: &mut StoreOpaque, _retptr: ()) -> Result<Self::Abi> {
|
||||
Ok(<Self as WasmTy>::into_abi(self, store))
|
||||
}
|
||||
|
||||
@@ -1389,16 +1452,16 @@ where
|
||||
T::abi_into_raw(f(()), ptr);
|
||||
}
|
||||
|
||||
fn into_fallible(self) -> Result<T, Trap> {
|
||||
fn into_fallible(self) -> Result<T> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn fallible_from_trap(trap: Trap) -> Result<T, Trap> {
|
||||
Err(trap)
|
||||
fn fallible_from_error(error: Error) -> Result<T> {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> WasmRet for Result<T, Trap>
|
||||
unsafe impl<T> WasmRet for Result<T>
|
||||
where
|
||||
T: WasmRet,
|
||||
{
|
||||
@@ -1417,7 +1480,7 @@ where
|
||||
self,
|
||||
store: &mut StoreOpaque,
|
||||
retptr: Self::Retptr,
|
||||
) -> Result<Self::Abi, Trap> {
|
||||
) -> Result<Self::Abi> {
|
||||
self.and_then(|val| val.into_abi_for_ret(store, retptr))
|
||||
}
|
||||
|
||||
@@ -1429,12 +1492,12 @@ where
|
||||
T::wrap_trampoline(ptr, f)
|
||||
}
|
||||
|
||||
fn into_fallible(self) -> Result<T, Trap> {
|
||||
fn into_fallible(self) -> Result<T> {
|
||||
self
|
||||
}
|
||||
|
||||
fn fallible_from_trap(trap: Trap) -> Result<T, Trap> {
|
||||
Err(trap)
|
||||
fn fallible_from_error(error: Error) -> Result<T> {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1448,7 +1511,7 @@ macro_rules! impl_wasm_host_results {
|
||||
{
|
||||
type Abi = <($($t::Abi,)*) as HostAbi>::Abi;
|
||||
type Retptr = <($($t::Abi,)*) as HostAbi>::Retptr;
|
||||
type Fallible = Result<Self, Trap>;
|
||||
type Fallible = Result<Self>;
|
||||
|
||||
#[inline]
|
||||
fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
|
||||
@@ -1457,7 +1520,7 @@ macro_rules! impl_wasm_host_results {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn into_abi_for_ret(self, _store: &mut StoreOpaque, ptr: Self::Retptr) -> Result<Self::Abi, Trap> {
|
||||
unsafe fn into_abi_for_ret(self, _store: &mut StoreOpaque, ptr: Self::Retptr) -> Result<Self::Abi> {
|
||||
let ($($t,)*) = self;
|
||||
let abi = ($($t.into_abi(_store),)*);
|
||||
Ok(<($($t::Abi,)*) as HostAbi>::into_abi(abi, ptr))
|
||||
@@ -1480,13 +1543,13 @@ macro_rules! impl_wasm_host_results {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_fallible(self) -> Result<Self, Trap> {
|
||||
fn into_fallible(self) -> Result<Self> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fallible_from_trap(trap: Trap) -> Result<Self, Trap> {
|
||||
Err(trap)
|
||||
fn fallible_from_error(error: Error) -> Result<Self> {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1844,7 +1907,7 @@ macro_rules! impl_into_func {
|
||||
let ret = {
|
||||
panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
if let Err(trap) = caller.store.0.call_hook(CallHook::CallingHost) {
|
||||
return R::fallible_from_trap(trap);
|
||||
return R::fallible_from_error(trap);
|
||||
}
|
||||
$(let $args = $args::from_abi($args, caller.store.0);)*
|
||||
let r = func(
|
||||
@@ -1852,7 +1915,7 @@ macro_rules! impl_into_func {
|
||||
$( $args, )*
|
||||
);
|
||||
if let Err(trap) = caller.store.0.call_hook(CallHook::ReturningFromHost) {
|
||||
return R::fallible_from_trap(trap);
|
||||
return R::fallible_from_error(trap);
|
||||
}
|
||||
r.into_fallible()
|
||||
}))
|
||||
@@ -1986,7 +2049,7 @@ impl HostFunc {
|
||||
pub fn new<T>(
|
||||
engine: &Engine,
|
||||
ty: FuncType,
|
||||
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let ty_clone = ty.clone();
|
||||
unsafe {
|
||||
@@ -2001,7 +2064,7 @@ impl HostFunc {
|
||||
pub unsafe fn new_unchecked<T>(
|
||||
engine: &Engine,
|
||||
ty: FuncType,
|
||||
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let func = move |caller_vmctx, values: &mut [ValRaw]| {
|
||||
Caller::<T>::with(caller_vmctx, |mut caller| {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{invoke_wasm_and_catch_traps, HostAbi};
|
||||
use crate::store::{AutoAssertNoGc, StoreOpaque};
|
||||
use crate::{AsContextMut, ExternRef, Func, FuncType, StoreContextMut, Trap, ValRaw, ValType};
|
||||
use crate::{AsContextMut, ExternRef, Func, FuncType, StoreContextMut, ValRaw, ValType};
|
||||
use anyhow::{bail, Result};
|
||||
use std::marker;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
@@ -68,11 +68,17 @@ 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
|
||||
/// connected to an asynchronous store.
|
||||
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Results, Trap> {
|
||||
///
|
||||
/// [`Trap`]: crate::Trap
|
||||
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Results> {
|
||||
let mut store = store.as_context_mut();
|
||||
assert!(
|
||||
!store.0.async_support(),
|
||||
@@ -89,17 +95,23 @@ 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
|
||||
/// connected to a synchronous store.
|
||||
///
|
||||
/// [`Trap`]: crate::Trap
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
|
||||
pub async fn call_async<T>(
|
||||
&self,
|
||||
mut store: impl AsContextMut<Data = T>,
|
||||
params: Params,
|
||||
) -> Result<Results, Trap>
|
||||
) -> Result<Results>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
@@ -120,7 +132,7 @@ where
|
||||
store: &mut StoreContextMut<'_, T>,
|
||||
func: ptr::NonNull<VMCallerCheckedAnyfunc>,
|
||||
params: Params,
|
||||
) -> Result<Results, Trap> {
|
||||
) -> Result<Results> {
|
||||
// double-check that params/results match for this function's type in
|
||||
// debug mode.
|
||||
if cfg!(debug_assertions) {
|
||||
@@ -150,9 +162,7 @@ where
|
||||
match params.into_abi(&mut store) {
|
||||
Some(abi) => abi,
|
||||
None => {
|
||||
return Err(Trap::new(
|
||||
"attempt to pass cross-`Store` value to Wasm as function argument",
|
||||
))
|
||||
bail!("attempt to pass cross-`Store` value to Wasm as function argument")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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::<Trap>()`.
|
||||
/// check for trap errors, you can use `error.downcast::<Trap>()`. 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<Instance, Error> {
|
||||
) -> Result<Instance> {
|
||||
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<Data = T>,
|
||||
module: &Module,
|
||||
imports: &[Extern],
|
||||
) -> Result<Instance, Error>
|
||||
) -> Result<Instance>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
@@ -324,7 +325,7 @@ impl Instance {
|
||||
)
|
||||
.map_err(|e| -> Error {
|
||||
match e {
|
||||
InstantiationError::Trap(trap) => Trap::new_wasm(trap, None).into(),
|
||||
InstantiationError::Trap(trap) => Trap::from_env(trap).into(),
|
||||
other => other.into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
//! // module which called this host function.
|
||||
//! let mem = match caller.get_export("memory") {
|
||||
//! Some(Extern::Memory(mem)) => mem,
|
||||
//! _ => return Err(Trap::new("failed to find host memory")),
|
||||
//! _ => anyhow::bail!("failed to find host memory"),
|
||||
//! };
|
||||
//!
|
||||
//! // Use the `ptr` and `len` values to get a subslice of the wasm-memory
|
||||
@@ -351,9 +351,9 @@
|
||||
//! let string = match data {
|
||||
//! Some(data) => match str::from_utf8(data) {
|
||||
//! Ok(s) => s,
|
||||
//! Err(_) => return Err(Trap::new("invalid utf-8")),
|
||||
//! Err(_) => anyhow::bail!("invalid utf-8"),
|
||||
//! },
|
||||
//! None => return Err(Trap::new("pointer/length out of bounds")),
|
||||
//! None => anyhow::bail!("pointer/length out of bounds"),
|
||||
//! };
|
||||
//! assert_eq!(string, "Hello, world!");
|
||||
//! println!("{}", string);
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::instance::InstancePre;
|
||||
use crate::store::StoreOpaque;
|
||||
use crate::{
|
||||
AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, Instance,
|
||||
IntoFunc, Module, StoreContextMut, Trap, Val, ValRaw,
|
||||
IntoFunc, Module, StoreContextMut, Val, ValRaw,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use log::warn;
|
||||
@@ -149,7 +149,7 @@ macro_rules! generate_wrap_async_func {
|
||||
let mut future = Pin::from(func(caller, $($args),*));
|
||||
match unsafe { async_cx.block_on(future.as_mut()) } {
|
||||
Ok(ret) => ret.into_fallible(),
|
||||
Err(e) => R::fallible_from_trap(e),
|
||||
Err(e) => R::fallible_from_error(e),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -271,7 +271,7 @@ impl<T> Linker<T> {
|
||||
import.name(),
|
||||
);
|
||||
self.func_new(import.module(), import.name(), func_ty, move |_, _, _| {
|
||||
Err(Trap::new(err_msg.clone()))
|
||||
bail!("{err_msg}")
|
||||
})?;
|
||||
}
|
||||
}
|
||||
@@ -348,7 +348,7 @@ impl<T> Linker<T> {
|
||||
module: &str,
|
||||
name: &str,
|
||||
ty: FuncType,
|
||||
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
|
||||
) -> Result<&mut Self> {
|
||||
let func = HostFunc::new(&self.engine, ty, func);
|
||||
let key = self.import_key(module, Some(name));
|
||||
@@ -366,7 +366,7 @@ impl<T> Linker<T> {
|
||||
module: &str,
|
||||
name: &str,
|
||||
ty: FuncType,
|
||||
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
|
||||
) -> Result<&mut Self> {
|
||||
let func = HostFunc::new_unchecked(&self.engine, ty, func);
|
||||
let key = self.import_key(module, Some(name));
|
||||
@@ -391,7 +391,7 @@ impl<T> Linker<T> {
|
||||
Caller<'a, T>,
|
||||
&'a [Val],
|
||||
&'a mut [Val],
|
||||
) -> Box<dyn Future<Output = Result<(), Trap>> + Send + 'a>
|
||||
) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
@@ -714,8 +714,7 @@ impl<T> Linker<T> {
|
||||
.unwrap()
|
||||
.into_func()
|
||||
.unwrap()
|
||||
.call(&mut caller, params, results)
|
||||
.map_err(|error| error.downcast::<Trap>().unwrap())?;
|
||||
.call(&mut caller, params, results)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
@@ -781,8 +780,7 @@ impl<T> Linker<T> {
|
||||
.into_func()
|
||||
.unwrap()
|
||||
.call_async(&mut caller, params, results)
|
||||
.await
|
||||
.map_err(|error| error.downcast::<Trap>().unwrap())?;
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
use crate::linker::Definition;
|
||||
use crate::module::BareModuleInfo;
|
||||
use crate::{module::ModuleRegistry, Engine, Module, Trap, Val, ValRaw};
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
@@ -219,11 +219,11 @@ enum ResourceLimiterInner<T> {
|
||||
pub trait CallHookHandler<T>: Send {
|
||||
/// A callback to run when wasmtime is about to enter a host call, or when about to
|
||||
/// exit the hostcall.
|
||||
async fn handle_call_event(&self, t: &mut T, ch: CallHook) -> Result<(), crate::Trap>;
|
||||
async fn handle_call_event(&self, t: &mut T, ch: CallHook) -> Result<()>;
|
||||
}
|
||||
|
||||
enum CallHookInner<T> {
|
||||
Sync(Box<dyn FnMut(&mut T, CallHook) -> Result<(), crate::Trap> + Send + Sync>),
|
||||
Sync(Box<dyn FnMut(&mut T, CallHook) -> Result<()> + Send + Sync>),
|
||||
#[cfg(feature = "async")]
|
||||
Async(Box<dyn CallHookHandler<T> + Send + Sync>),
|
||||
}
|
||||
@@ -331,8 +331,7 @@ pub struct StoreOpaque {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
struct AsyncState {
|
||||
current_suspend:
|
||||
UnsafeCell<*const wasmtime_fiber::Suspend<Result<(), Trap>, (), Result<(), Trap>>>,
|
||||
current_suspend: UnsafeCell<*const wasmtime_fiber::Suspend<Result<()>, (), Result<()>>>,
|
||||
current_poll_cx: UnsafeCell<*mut Context<'static>>,
|
||||
}
|
||||
|
||||
@@ -722,7 +721,7 @@ impl<T> Store<T> {
|
||||
/// to host or wasm code as the trap propagates to the root call.
|
||||
pub fn call_hook(
|
||||
&mut self,
|
||||
hook: impl FnMut(&mut T, CallHook) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
hook: impl FnMut(&mut T, CallHook) -> Result<()> + Send + Sync + 'static,
|
||||
) {
|
||||
self.inner.call_hook = Some(CallHookInner::Sync(Box::new(hook)));
|
||||
}
|
||||
@@ -1094,7 +1093,7 @@ impl<T> StoreInner<T> {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
pub fn call_hook(&mut self, s: CallHook) -> Result<(), Trap> {
|
||||
pub fn call_hook(&mut self, s: CallHook) -> Result<()> {
|
||||
match &mut self.call_hook {
|
||||
Some(CallHookInner::Sync(hook)) => hook(&mut self.data, s),
|
||||
|
||||
@@ -1103,7 +1102,7 @@ impl<T> StoreInner<T> {
|
||||
Ok(self
|
||||
.inner
|
||||
.async_cx()
|
||||
.ok_or(Trap::new("couldn't grab async_cx for call hook"))?
|
||||
.ok_or_else(|| anyhow!("couldn't grab async_cx for call hook"))?
|
||||
.block_on(handler.handle_call_event(&mut self.data, s).as_mut())??)
|
||||
},
|
||||
|
||||
@@ -1354,7 +1353,7 @@ impl StoreOpaque {
|
||||
/// This only works on async futures and stores, and assumes that we're
|
||||
/// executing on a fiber. This will yield execution back to the caller once.
|
||||
#[cfg(feature = "async")]
|
||||
fn async_yield_impl(&mut self) -> Result<(), Trap> {
|
||||
fn async_yield_impl(&mut self) -> Result<()> {
|
||||
// Small future that yields once and then returns ()
|
||||
#[derive(Default)]
|
||||
struct Yield {
|
||||
@@ -1380,7 +1379,7 @@ impl StoreOpaque {
|
||||
|
||||
let mut future = Yield::default();
|
||||
|
||||
// When control returns, we have a `Result<(), Trap>` passed
|
||||
// When control returns, we have a `Result<()>` passed
|
||||
// in from the host fiber. If this finished successfully then
|
||||
// we were resumed normally via a `poll`, so keep going. If
|
||||
// the future was dropped while we were yielded, then we need
|
||||
@@ -1518,7 +1517,7 @@ impl<T> StoreContextMut<'_, T> {
|
||||
pub(crate) async fn on_fiber<R>(
|
||||
&mut self,
|
||||
func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send,
|
||||
) -> Result<R, Trap>
|
||||
) -> Result<R>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
@@ -1530,11 +1529,7 @@ impl<T> StoreContextMut<'_, T> {
|
||||
let future = {
|
||||
let current_poll_cx = self.0.async_state.current_poll_cx.get();
|
||||
let current_suspend = self.0.async_state.current_suspend.get();
|
||||
let stack = self
|
||||
.engine()
|
||||
.allocator()
|
||||
.allocate_fiber_stack()
|
||||
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
|
||||
let stack = self.engine().allocator().allocate_fiber_stack()?;
|
||||
|
||||
let engine = self.engine().clone();
|
||||
let slot = &mut slot;
|
||||
@@ -1558,8 +1553,7 @@ impl<T> StoreContextMut<'_, T> {
|
||||
*slot = Some(func(self));
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
|
||||
})?;
|
||||
|
||||
// Once we have the fiber representing our synchronous computation, we
|
||||
// wrap that in a custom future implementation which does the
|
||||
@@ -1575,7 +1569,7 @@ impl<T> StoreContextMut<'_, T> {
|
||||
return Ok(slot.unwrap());
|
||||
|
||||
struct FiberFuture<'a> {
|
||||
fiber: wasmtime_fiber::Fiber<'a, Result<(), Trap>, (), Result<(), Trap>>,
|
||||
fiber: wasmtime_fiber::Fiber<'a, Result<()>, (), Result<()>>,
|
||||
current_poll_cx: *mut *mut Context<'static>,
|
||||
engine: Engine,
|
||||
}
|
||||
@@ -1644,7 +1638,7 @@ impl<T> StoreContextMut<'_, T> {
|
||||
unsafe impl Send for FiberFuture<'_> {}
|
||||
|
||||
impl Future for FiberFuture<'_> {
|
||||
type Output = Result<(), Trap>;
|
||||
type Output = Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
// We need to carry over this `cx` into our fiber's runtime
|
||||
@@ -1699,7 +1693,7 @@ impl<T> StoreContextMut<'_, T> {
|
||||
impl Drop for FiberFuture<'_> {
|
||||
fn drop(&mut self) {
|
||||
if !self.fiber.done() {
|
||||
let result = self.fiber.resume(Err(Trap::new("future dropped")));
|
||||
let result = self.fiber.resume(Err(anyhow!("future dropped")));
|
||||
// This resumption with an error should always complete the
|
||||
// fiber. While it's technically possible for host code to catch
|
||||
// the trap and re-resume, we'd ideally like to signal that to
|
||||
@@ -1719,7 +1713,7 @@ impl<T> StoreContextMut<'_, T> {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub struct AsyncCx {
|
||||
current_suspend: *mut *const wasmtime_fiber::Suspend<Result<(), Trap>, (), Result<(), Trap>>,
|
||||
current_suspend: *mut *const wasmtime_fiber::Suspend<Result<()>, (), Result<()>>,
|
||||
current_poll_cx: *mut *mut Context<'static>,
|
||||
}
|
||||
|
||||
@@ -1748,7 +1742,7 @@ impl AsyncCx {
|
||||
pub unsafe fn block_on<U>(
|
||||
&self,
|
||||
mut future: Pin<&mut (dyn Future<Output = U> + Send)>,
|
||||
) -> Result<U, Trap> {
|
||||
) -> Result<U> {
|
||||
// Take our current `Suspend` context which was configured as soon as
|
||||
// our fiber started. Note that we must load it at the front here and
|
||||
// save it on our stack frame. While we're polling the future other
|
||||
@@ -1896,14 +1890,14 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
||||
|
||||
fn out_of_gas(&mut self) -> Result<(), anyhow::Error> {
|
||||
return match &mut self.out_of_gas_behavior {
|
||||
OutOfGas::Trap => Err(anyhow::Error::new(OutOfGasError)),
|
||||
OutOfGas::Trap => Err(Trap::OutOfFuel.into()),
|
||||
#[cfg(feature = "async")]
|
||||
OutOfGas::InjectFuel {
|
||||
injection_count,
|
||||
fuel_to_inject,
|
||||
} => {
|
||||
if *injection_count == 0 {
|
||||
return Err(anyhow::Error::new(OutOfGasError));
|
||||
return Err(Trap::OutOfFuel.into());
|
||||
}
|
||||
*injection_count -= 1;
|
||||
let fuel = *fuel_to_inject;
|
||||
@@ -1916,25 +1910,11 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
||||
#[cfg(not(feature = "async"))]
|
||||
OutOfGas::InjectFuel { .. } => unreachable!(),
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OutOfGasError;
|
||||
|
||||
impl fmt::Display for OutOfGasError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("all fuel consumed by WebAssembly")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for OutOfGasError {}
|
||||
}
|
||||
|
||||
fn new_epoch(&mut self) -> Result<u64, anyhow::Error> {
|
||||
return match &mut self.epoch_deadline_behavior {
|
||||
EpochDeadline::Trap => {
|
||||
let trap = Trap::new_wasm(wasmtime_environ::TrapCode::Interrupt, None);
|
||||
Err(anyhow::Error::from(trap))
|
||||
}
|
||||
EpochDeadline::Trap => Err(Trap::Interrupt.into()),
|
||||
EpochDeadline::Callback(callback) => {
|
||||
let delta = callback(&mut self.data)?;
|
||||
// Set a new deadline and return the new epoch deadline so
|
||||
|
||||
@@ -21,7 +21,7 @@ unsafe extern "C" fn stub_fn<F>(
|
||||
values_vec: *mut ValRaw,
|
||||
values_vec_len: usize,
|
||||
) where
|
||||
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<(), Trap> + 'static,
|
||||
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + 'static,
|
||||
{
|
||||
// Here we are careful to use `catch_unwind` to ensure Rust panics don't
|
||||
// unwind past us. The primary reason for this is that Rust considers it UB
|
||||
@@ -110,7 +110,7 @@ pub fn create_function<F>(
|
||||
engine: &Engine,
|
||||
) -> Result<(Box<VMHostFuncContext>, VMSharedSignatureIndex, VMTrampoline)>
|
||||
where
|
||||
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<(), Trap> + Send + Sync + 'static,
|
||||
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
|
||||
{
|
||||
let mut obj = engine
|
||||
.compiler()
|
||||
|
||||
@@ -1,59 +1,81 @@
|
||||
use crate::store::StoreOpaque;
|
||||
use crate::Module;
|
||||
use once_cell::sync::OnceCell;
|
||||
use anyhow::Error;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::{EntityRef, FilePos, TrapCode as EnvTrapCode};
|
||||
use wasmtime_environ::{EntityRef, FilePos, TrapCode};
|
||||
use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
|
||||
|
||||
/// A struct representing an aborted instruction execution, with a message
|
||||
/// indicating the cause.
|
||||
#[derive(Clone)]
|
||||
pub struct Trap {
|
||||
inner: Arc<TrapInner>,
|
||||
}
|
||||
|
||||
/// State describing the occasion which evoked a trap.
|
||||
#[derive(Debug)]
|
||||
enum TrapReason {
|
||||
/// An error message describing a trap.
|
||||
Message(String),
|
||||
|
||||
/// An `i32` exit status describing an explicit program exit.
|
||||
I32Exit(i32),
|
||||
|
||||
/// A structured error describing a trap.
|
||||
Error(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
/// A specific code for a trap triggered while executing WASM.
|
||||
InstructionTrap(TrapCode),
|
||||
}
|
||||
|
||||
impl fmt::Display for TrapReason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TrapReason::Message(s) => write!(f, "{}", s),
|
||||
TrapReason::I32Exit(status) => write!(f, "Exited with i32 exit status {}", status),
|
||||
TrapReason::Error(e) => write!(f, "{}", e),
|
||||
TrapReason::InstructionTrap(code) => write!(f, "wasm trap: {}", code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trap code describing the reason for a trap.
|
||||
/// Representation of a WebAssembly trap and what caused it to occur.
|
||||
///
|
||||
/// All trap instructions have an explicit trap code.
|
||||
/// 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.
|
||||
///
|
||||
/// The code can be accessed from the c-api, where the possible values are translated
|
||||
/// into enum values defined there:
|
||||
/// # Errors in Wasmtime
|
||||
///
|
||||
/// * `wasm_trap_code` in c-api/src/trap.rs, and
|
||||
/// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
|
||||
/// Error-handling in Wasmtime is primarily done through the [`anyhow`] crate
|
||||
/// where most results are a [`Result<T>`](anyhow::Result) which is an alias for
|
||||
/// [`Result<T, anyhow::Error>`](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.
|
||||
///
|
||||
/// These need to be kept in sync.
|
||||
/// 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::<Trap>().unwrap(), Trap::UnreachableCodeReached);
|
||||
/// assert!(error.root_cause().is::<Trap>());
|
||||
///
|
||||
/// let overflow = instance.get_typed_func::<(), (), _>(&mut store, "overflow")?;
|
||||
/// let error = overflow.call(&mut store, ()).unwrap_err();
|
||||
/// assert_eq!(*error.downcast_ref::<Trap>().unwrap(), Trap::StackOverflow);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
//
|
||||
// The code can be accessed from the c-api, where the possible values are translated
|
||||
// into enum values defined there:
|
||||
//
|
||||
// * `wasm_trap_code` in c-api/src/trap.rs, and
|
||||
// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
|
||||
//
|
||||
// These need to be kept in sync.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum TrapCode {
|
||||
pub enum Trap {
|
||||
/// The current stack space was exhausted.
|
||||
StackOverflow,
|
||||
|
||||
@@ -93,31 +115,97 @@ pub enum TrapCode {
|
||||
/// generates a function that always traps and, when called, produces this
|
||||
/// flavor of trap.
|
||||
AlwaysTrapAdapter,
|
||||
|
||||
/// When wasm code is configured to consume fuel and it runs out of fuel
|
||||
/// then this trap will be raised.
|
||||
///
|
||||
/// For more information see
|
||||
/// [`Config::consume_fuel`](crate::Config::consume_fuel).
|
||||
OutOfFuel,
|
||||
}
|
||||
|
||||
impl TrapCode {
|
||||
/// Panics if `code` is `EnvTrapCode::User`.
|
||||
fn from_non_user(code: EnvTrapCode) -> Self {
|
||||
impl Trap {
|
||||
// Same safety requirements and caveats as
|
||||
// `wasmtime_runtime::raise_user_trap`.
|
||||
pub(crate) unsafe fn raise(error: anyhow::Error) -> ! {
|
||||
let needs_backtrace = error.downcast_ref::<WasmBacktrace>().is_none();
|
||||
wasmtime_runtime::raise_user_trap(error, needs_backtrace)
|
||||
}
|
||||
|
||||
#[cold] // traps are exceptional, this helps move handling off the main path
|
||||
pub(crate) fn from_runtime_box(
|
||||
store: &StoreOpaque,
|
||||
runtime_trap: Box<wasmtime_runtime::Trap>,
|
||||
) -> Error {
|
||||
let wasmtime_runtime::Trap { reason, backtrace } = *runtime_trap;
|
||||
let (error, pc) = match reason {
|
||||
// For user-defined errors they're already an `anyhow::Error` so no
|
||||
// conversion is really necessary here, but a `backtrace` may have
|
||||
// been captured so it's attempted to get inserted here.
|
||||
//
|
||||
// If the error is actually a `Trap` then the backtrace is inserted
|
||||
// directly into the `Trap` since there's storage there for it.
|
||||
// Otherwise though this represents a host-defined error which isn't
|
||||
// using a `Trap` but instead some other condition that was fatal to
|
||||
// wasm itself. In that situation the backtrace is inserted as
|
||||
// contextual information on error using `error.context(...)` to
|
||||
// provide useful information to debug with for the embedder/caller,
|
||||
// otherwise the information about what the wasm was doing when the
|
||||
// error was generated would be lost.
|
||||
wasmtime_runtime::TrapReason::User {
|
||||
error,
|
||||
needs_backtrace,
|
||||
} => {
|
||||
debug_assert!(needs_backtrace == backtrace.is_some());
|
||||
(error, None)
|
||||
}
|
||||
wasmtime_runtime::TrapReason::Jit(pc) => {
|
||||
let code = store
|
||||
.modules()
|
||||
.lookup_trap_code(pc)
|
||||
.unwrap_or(TrapCode::StackOverflow);
|
||||
(Trap::from_env(code).into(), Some(pc))
|
||||
}
|
||||
wasmtime_runtime::TrapReason::Wasm(trap_code) => {
|
||||
(Trap::from_env(trap_code).into(), None)
|
||||
}
|
||||
};
|
||||
match backtrace {
|
||||
Some(bt) => {
|
||||
let bt = WasmBacktrace::new(store, bt, pc);
|
||||
if bt.wasm_trace.is_empty() {
|
||||
error
|
||||
} else {
|
||||
error.context(bt)
|
||||
}
|
||||
}
|
||||
None => error,
|
||||
}
|
||||
}
|
||||
|
||||
/// Panics if `code` is `TrapCode::User`.
|
||||
pub(crate) fn from_env(code: TrapCode) -> Self {
|
||||
match code {
|
||||
EnvTrapCode::StackOverflow => TrapCode::StackOverflow,
|
||||
EnvTrapCode::HeapOutOfBounds => TrapCode::MemoryOutOfBounds,
|
||||
EnvTrapCode::HeapMisaligned => TrapCode::HeapMisaligned,
|
||||
EnvTrapCode::TableOutOfBounds => TrapCode::TableOutOfBounds,
|
||||
EnvTrapCode::IndirectCallToNull => TrapCode::IndirectCallToNull,
|
||||
EnvTrapCode::BadSignature => TrapCode::BadSignature,
|
||||
EnvTrapCode::IntegerOverflow => TrapCode::IntegerOverflow,
|
||||
EnvTrapCode::IntegerDivisionByZero => TrapCode::IntegerDivisionByZero,
|
||||
EnvTrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
||||
EnvTrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
||||
EnvTrapCode::Interrupt => TrapCode::Interrupt,
|
||||
EnvTrapCode::AlwaysTrapAdapter => TrapCode::AlwaysTrapAdapter,
|
||||
TrapCode::StackOverflow => Trap::StackOverflow,
|
||||
TrapCode::HeapOutOfBounds => Trap::MemoryOutOfBounds,
|
||||
TrapCode::HeapMisaligned => Trap::HeapMisaligned,
|
||||
TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
|
||||
TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
|
||||
TrapCode::BadSignature => Trap::BadSignature,
|
||||
TrapCode::IntegerOverflow => Trap::IntegerOverflow,
|
||||
TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
|
||||
TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
|
||||
TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
|
||||
TrapCode::Interrupt => Trap::Interrupt,
|
||||
TrapCode::AlwaysTrapAdapter => Trap::AlwaysTrapAdapter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TrapCode {
|
||||
impl fmt::Display for Trap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use TrapCode::*;
|
||||
use Trap::*;
|
||||
|
||||
let desc = match self {
|
||||
StackOverflow => "call stack exhausted",
|
||||
MemoryOutOfBounds => "out of bounds memory access",
|
||||
@@ -131,20 +219,75 @@ impl fmt::Display for TrapCode {
|
||||
UnreachableCodeReached => "wasm `unreachable` instruction executed",
|
||||
Interrupt => "interrupt",
|
||||
AlwaysTrapAdapter => "degenerate component adapter called",
|
||||
OutOfFuel => "all fuel consumed by WebAssembly",
|
||||
};
|
||||
write!(f, "{}", desc)
|
||||
write!(f, "wasm trap: {desc}")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Trap {}
|
||||
|
||||
/// 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::<WasmBacktrace>().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(crate) struct TrapBacktrace {
|
||||
pub struct WasmBacktrace {
|
||||
wasm_trace: Vec<FrameInfo>,
|
||||
runtime_trace: wasmtime_runtime::Backtrace,
|
||||
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,
|
||||
}
|
||||
|
||||
impl TrapBacktrace {
|
||||
pub fn new(
|
||||
impl WasmBacktrace {
|
||||
fn new(
|
||||
store: &StoreOpaque,
|
||||
runtime_trace: wasmtime_runtime::Backtrace,
|
||||
trap_pc: Option<usize>,
|
||||
@@ -219,292 +362,76 @@ impl TrapBacktrace {
|
||||
hint_wasm_backtrace_details_env,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TrapInner {
|
||||
reason: TrapReason,
|
||||
backtrace: OnceCell<TrapBacktrace>,
|
||||
}
|
||||
|
||||
fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
||||
(t, t)
|
||||
}
|
||||
|
||||
impl Trap {
|
||||
/// Creates a new `Trap` with `message`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let trap = wasmtime::Trap::new("unexpected error");
|
||||
/// assert!(trap.to_string().contains("unexpected error"));
|
||||
/// ```
|
||||
#[cold] // traps are exceptional, this helps move handling off the main path
|
||||
pub fn new<I: Into<String>>(message: I) -> Self {
|
||||
let reason = TrapReason::Message(message.into());
|
||||
Trap::new_with_trace(reason, None)
|
||||
}
|
||||
|
||||
/// Creates a new `Trap` representing an explicit program exit with a classic `i32`
|
||||
/// exit status value.
|
||||
#[cold] // see Trap::new
|
||||
pub fn i32_exit(status: i32) -> Self {
|
||||
Trap::new_with_trace(TrapReason::I32Exit(status), None)
|
||||
}
|
||||
|
||||
// Same safety requirements and caveats as
|
||||
// `wasmtime_runtime::raise_user_trap`.
|
||||
pub(crate) unsafe fn raise(error: anyhow::Error) -> ! {
|
||||
let needs_backtrace = error
|
||||
.downcast_ref::<Trap>()
|
||||
.map_or(true, |trap| trap.trace().is_none());
|
||||
wasmtime_runtime::raise_user_trap(error, needs_backtrace)
|
||||
}
|
||||
|
||||
#[cold] // see Trap::new
|
||||
pub(crate) fn from_runtime_box(
|
||||
store: &StoreOpaque,
|
||||
runtime_trap: Box<wasmtime_runtime::Trap>,
|
||||
) -> Self {
|
||||
Self::from_runtime(store, *runtime_trap)
|
||||
}
|
||||
|
||||
#[cold] // see Trap::new
|
||||
pub(crate) fn from_runtime(store: &StoreOpaque, runtime_trap: wasmtime_runtime::Trap) -> Self {
|
||||
let wasmtime_runtime::Trap { reason, backtrace } = runtime_trap;
|
||||
match reason {
|
||||
wasmtime_runtime::TrapReason::User {
|
||||
error,
|
||||
needs_backtrace,
|
||||
} => {
|
||||
let trap = Trap::from(error);
|
||||
if let Some(backtrace) = backtrace {
|
||||
debug_assert!(needs_backtrace);
|
||||
debug_assert!(trap.inner.backtrace.get().is_none());
|
||||
trap.record_backtrace(TrapBacktrace::new(store, backtrace, None));
|
||||
}
|
||||
trap
|
||||
}
|
||||
wasmtime_runtime::TrapReason::Jit(pc) => {
|
||||
let code = store
|
||||
.modules()
|
||||
.lookup_trap_code(pc)
|
||||
.unwrap_or(EnvTrapCode::StackOverflow);
|
||||
let backtrace = backtrace.map(|bt| TrapBacktrace::new(store, bt, Some(pc)));
|
||||
Trap::new_wasm(code, backtrace)
|
||||
}
|
||||
wasmtime_runtime::TrapReason::Wasm(trap_code) => {
|
||||
let backtrace = backtrace.map(|bt| TrapBacktrace::new(store, bt, None));
|
||||
Trap::new_wasm(trap_code, backtrace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cold] // see Trap::new
|
||||
pub(crate) fn new_wasm(code: EnvTrapCode, backtrace: Option<TrapBacktrace>) -> Self {
|
||||
let code = TrapCode::from_non_user(code);
|
||||
Trap::new_with_trace(TrapReason::InstructionTrap(code), backtrace)
|
||||
}
|
||||
|
||||
/// Creates a new `Trap`.
|
||||
/// * `reason` - this is the wasmtime-internal reason for why this trap is
|
||||
/// being created.
|
||||
///
|
||||
/// * `backtrace` - this is a captured backtrace from when the trap
|
||||
/// occurred. Contains the native backtrace, and the backtrace of
|
||||
/// WebAssembly frames.
|
||||
fn new_with_trace(reason: TrapReason, backtrace: Option<TrapBacktrace>) -> Self {
|
||||
let backtrace = if let Some(bt) = backtrace {
|
||||
OnceCell::with_value(bt)
|
||||
} else {
|
||||
OnceCell::new()
|
||||
};
|
||||
Trap {
|
||||
inner: Arc::new(TrapInner { reason, backtrace }),
|
||||
}
|
||||
}
|
||||
|
||||
/// If the trap was the result of an explicit program exit with a classic
|
||||
/// `i32` exit status value, return the value, otherwise return `None`.
|
||||
pub fn i32_exit_status(&self) -> Option<i32> {
|
||||
match self.inner.reason {
|
||||
TrapReason::I32Exit(status) => Some(status),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays the error reason for this trap.
|
||||
///
|
||||
/// In particular, it differs from this struct's `Display` by *only*
|
||||
/// showing the reason, and not the full backtrace. This is useful to
|
||||
/// customize the way the trap is reported, for instance to display a short
|
||||
/// message for user-facing errors.
|
||||
pub fn display_reason<'a>(&'a self) -> impl fmt::Display + 'a {
|
||||
&self.inner.reason
|
||||
}
|
||||
|
||||
/// Returns a list of function frames in WebAssembly code that led to this
|
||||
/// trap happening.
|
||||
///
|
||||
/// This function return an `Option` of a list of frames to indicate that
|
||||
/// wasm frames are not always available. Frames will never be available if
|
||||
/// backtraces are disabled via
|
||||
/// [`Config::wasm_backtrace`](crate::Config::wasm_backtrace). Frames will
|
||||
/// also not be available for freshly-created traps. WebAssembly frames are
|
||||
/// currently only captured when the trap reaches wasm itself to get raised
|
||||
/// across a wasm boundary.
|
||||
pub fn trace(&self) -> Option<&[FrameInfo]> {
|
||||
self.inner
|
||||
.backtrace
|
||||
.get()
|
||||
.as_ref()
|
||||
.map(|bt| bt.wasm_trace.as_slice())
|
||||
}
|
||||
|
||||
/// Code of a trap that happened while executing a WASM instruction.
|
||||
/// If the trap was triggered by a host export this will be `None`.
|
||||
pub fn trap_code(&self) -> Option<TrapCode> {
|
||||
match self.inner.reason {
|
||||
TrapReason::InstructionTrap(code) => Some(code),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn record_backtrace(&self, backtrace: TrapBacktrace) {
|
||||
// When a trap is created on top of the wasm stack, the trampoline will
|
||||
// re-raise it via
|
||||
// `wasmtime_runtime::raise_user_trap(trap.into::<Box<dyn Error>>(),
|
||||
// ..)` after `panic::catch_unwind`. We don't want to overwrite the
|
||||
// first backtrace recorded, as it is most precise. However, this should
|
||||
// never happen in the first place because we thread `needs_backtrace`
|
||||
// booleans throuch all calls to `raise_user_trap` to avoid capturing
|
||||
// unnecessary backtraces! So debug assert that we don't ever capture
|
||||
// unnecessary backtraces.
|
||||
let result = self.inner.backtrace.try_insert(backtrace);
|
||||
debug_assert!(result.is_ok());
|
||||
/// Returns a list of function frames in WebAssembly this backtrace
|
||||
/// represents.
|
||||
pub fn frames(&self) -> &[FrameInfo] {
|
||||
self.wasm_trace.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Trap {
|
||||
impl fmt::Display for WasmBacktrace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut f = f.debug_struct("Trap");
|
||||
f.field("reason", &self.inner.reason);
|
||||
if let Some(backtrace) = self.inner.backtrace.get() {
|
||||
f.field("wasm_trace", &backtrace.wasm_trace)
|
||||
.field("runtime_trace", &backtrace.runtime_trace);
|
||||
}
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
writeln!(f, "error while executing at wasm backtrace:")?;
|
||||
|
||||
impl fmt::Display for Trap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.inner.reason)?;
|
||||
|
||||
if let Some(trace) = self.trace() {
|
||||
if trace.is_empty() {
|
||||
return Ok(());
|
||||
let mut needs_newline = false;
|
||||
for (i, frame) in self.wasm_trace.iter().enumerate() {
|
||||
// Avoid putting a trailing newline on the output
|
||||
if needs_newline {
|
||||
writeln!(f, "")?;
|
||||
} else {
|
||||
needs_newline = true;
|
||||
}
|
||||
writeln!(f, "\nwasm backtrace:")?;
|
||||
let name = frame.module_name().unwrap_or("<unknown>");
|
||||
write!(f, " {:>3}: ", i)?;
|
||||
|
||||
for (i, frame) in trace.iter().enumerate() {
|
||||
let name = frame.module_name().unwrap_or("<unknown>");
|
||||
write!(f, " {:>3}: ", i)?;
|
||||
if let Some(offset) = frame.module_offset() {
|
||||
write!(f, "{:#6x} - ", offset)?;
|
||||
}
|
||||
|
||||
if let Some(offset) = frame.module_offset() {
|
||||
write!(f, "{:#6x} - ", offset)?;
|
||||
}
|
||||
|
||||
let write_raw_func_name = |f: &mut fmt::Formatter<'_>| {
|
||||
demangle_function_name_or_index(
|
||||
f,
|
||||
frame.func_name(),
|
||||
frame.func_index() as usize,
|
||||
)
|
||||
};
|
||||
if frame.symbols().is_empty() {
|
||||
write!(f, "{}!", name)?;
|
||||
write_raw_func_name(f)?;
|
||||
writeln!(f, "")?;
|
||||
} else {
|
||||
for (i, symbol) in frame.symbols().iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " - ")?;
|
||||
} else {
|
||||
// ...
|
||||
}
|
||||
match symbol.name() {
|
||||
Some(name) => demangle_function_name(f, name)?,
|
||||
None if i == 0 => write_raw_func_name(f)?,
|
||||
None => write!(f, "<inlined function>")?,
|
||||
}
|
||||
let write_raw_func_name = |f: &mut fmt::Formatter<'_>| {
|
||||
demangle_function_name_or_index(f, frame.func_name(), frame.func_index() as usize)
|
||||
};
|
||||
if frame.symbols().is_empty() {
|
||||
write!(f, "{}!", name)?;
|
||||
write_raw_func_name(f)?;
|
||||
} else {
|
||||
for (i, symbol) in frame.symbols().iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " - ")?;
|
||||
} else {
|
||||
// ...
|
||||
}
|
||||
match symbol.name() {
|
||||
Some(name) => demangle_function_name(f, name)?,
|
||||
None if i == 0 => write_raw_func_name(f)?,
|
||||
None => write!(f, "<inlined function>")?,
|
||||
}
|
||||
if let Some(file) = symbol.file() {
|
||||
writeln!(f, "")?;
|
||||
if let Some(file) = symbol.file() {
|
||||
write!(f, " at {}", file)?;
|
||||
if let Some(line) = symbol.line() {
|
||||
write!(f, ":{}", line)?;
|
||||
if let Some(col) = symbol.column() {
|
||||
write!(f, ":{}", col)?;
|
||||
}
|
||||
write!(f, " at {}", file)?;
|
||||
if let Some(line) = symbol.line() {
|
||||
write!(f, ":{}", line)?;
|
||||
if let Some(col) = symbol.column() {
|
||||
write!(f, ":{}", col)?;
|
||||
}
|
||||
}
|
||||
writeln!(f, "")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self
|
||||
.inner
|
||||
.backtrace
|
||||
.get()
|
||||
.map(|t| t.hint_wasm_backtrace_details_env)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
writeln!(f, "note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information")?;
|
||||
}
|
||||
}
|
||||
if self.hint_wasm_backtrace_details_env {
|
||||
write!(f, "\nnote: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Trap {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match &self.inner.reason {
|
||||
TrapReason::Error(e) => e.source(),
|
||||
TrapReason::I32Exit(_) | TrapReason::Message(_) | TrapReason::InstructionTrap(_) => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for Trap {
|
||||
fn from(e: anyhow::Error) -> Trap {
|
||||
match e.downcast::<Trap>() {
|
||||
Ok(trap) => trap,
|
||||
Err(e) => Box::<dyn std::error::Error + Send + Sync>::from(e).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn std::error::Error + Send + Sync>> for Trap {
|
||||
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Trap {
|
||||
// If the top-level error is already a trap, don't be redundant and just return it.
|
||||
if let Some(trap) = e.downcast_ref::<Trap>() {
|
||||
trap.clone()
|
||||
} else {
|
||||
let reason = TrapReason::Error(e.into());
|
||||
Trap::new_with_trace(reason, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Description of a frame in a backtrace for a [`Trap`].
|
||||
/// Description of a frame in a backtrace for a [`WasmBacktrace`].
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// [`Trap`]: crate::Trap
|
||||
/// 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<String>,
|
||||
|
||||
Reference in New Issue
Block a user