From 2afaac5181f4b73e86fac39d095c84a9b8e59129 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 2 Nov 2022 11:29:31 -0500 Subject: [PATCH] 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` now work with `Result` instead where the error is `anyhow::Error`. This includes functions such as: * Host-defined functions in a `Linker` * `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` to `-> Result` 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 --- Cargo.lock | 1 + cranelift/codegen/src/ir/immediates.rs | 4 +- crates/bench-api/src/lib.rs | 17 +- crates/c-api/src/func.rs | 58 +- crates/c-api/src/instance.rs | 4 +- crates/c-api/src/trap.rs | 134 ++-- crates/c-api/src/vec.rs | 27 +- crates/fuzzing/src/oracles.rs | 14 +- crates/fuzzing/src/oracles/diff_v8.rs | 19 +- crates/fuzzing/src/oracles/diff_wasmi.rs | 33 +- crates/fuzzing/src/oracles/diff_wasmtime.rs | 12 +- crates/fuzzing/src/oracles/stacks.rs | 16 +- crates/wasi-common/src/error.rs | 16 + crates/wasi-common/src/lib.rs | 2 +- crates/wasi-common/src/snapshots/preview_0.rs | 9 +- crates/wasi-common/src/snapshots/preview_1.rs | 16 +- crates/wasi-nn/src/witx.rs | 6 +- crates/wasi/src/lib.rs | 2 +- crates/wasmtime/src/component/func/options.rs | 5 +- crates/wasmtime/src/config.rs | 36 +- crates/wasmtime/src/externals.rs | 6 +- crates/wasmtime/src/func.rs | 163 +++-- crates/wasmtime/src/func/typed.rs | 24 +- crates/wasmtime/src/instance.rs | 9 +- crates/wasmtime/src/lib.rs | 6 +- crates/wasmtime/src/linker.rs | 18 +- crates/wasmtime/src/store.rs | 60 +- crates/wasmtime/src/trampoline/func.rs | 4 +- crates/wasmtime/src/trap.rs | 591 ++++++++---------- crates/wast/src/wast.rs | 34 +- crates/wiggle/generate/src/funcs.rs | 4 +- crates/wiggle/generate/src/lib.rs | 2 +- crates/wiggle/generate/src/module_trait.rs | 2 +- crates/wiggle/generate/src/wasmtime.rs | 8 +- crates/wiggle/src/lib.rs | 15 +- crates/wiggle/test-helpers/Cargo.toml | 1 + .../wiggle/test-helpers/examples/tracing.rs | 6 +- crates/wiggle/tests/errors.rs | 16 +- crates/wiggle/tests/wasi.rs | 2 +- crates/wiggle/tests/wasmtime_sync.rs | 2 +- examples/interrupt.rs | 4 +- src/commands/run.rs | 31 +- tests/all/async_functions.rs | 16 +- tests/all/call_hook.rs | 58 +- tests/all/cli_tests.rs | 12 +- tests/all/component_model/async.rs | 4 +- tests/all/component_model/func.rs | 14 +- tests/all/component_model/import.rs | 50 +- tests/all/component_model/post_return.rs | 4 +- tests/all/component_model/strings.rs | 17 +- tests/all/custom_signal_handler.rs | 16 +- tests/all/fuel.rs | 9 +- tests/all/func.rs | 48 +- tests/all/host_funcs.rs | 45 +- tests/all/iloop.rs | 32 +- tests/all/import_calling_export.rs | 7 +- tests/all/linker.rs | 2 +- tests/all/pooling_allocator.rs | 28 +- tests/all/stack_overflow.rs | 8 +- tests/all/traps.rs | 285 +++++---- 60 files changed, 1043 insertions(+), 1051 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9762689cb..ece6862f7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3877,6 +3877,7 @@ dependencies = [ name = "wiggle-test" version = "0.21.0" dependencies = [ + "anyhow", "env_logger 0.9.0", "proptest", "thiserror", diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs index 4421f1fdaa..3b3f703235 100644 --- a/cranelift/codegen/src/ir/immediates.rs +++ b/cranelift/codegen/src/ir/immediates.rs @@ -473,7 +473,7 @@ impl FromStr for Offset32 { /// containing the bit pattern. /// /// We specifically avoid using a f32 here since some architectures may silently alter floats. -/// See: https://github.com/bytecodealliance/wasmtime/pull/2251#discussion_r498508646 +/// See: /// /// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but /// [PartialOrd] respects IEEE754 semantics. @@ -488,7 +488,7 @@ pub struct Ieee32(u32); /// containing the bit pattern. /// /// We specifically avoid using a f64 here since some architectures may silently alter floats. -/// See: https://github.com/bytecodealliance/wasmtime/pull/2251#discussion_r498508646 +/// See: /// /// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but /// [PartialOrd] respects IEEE754 semantics. diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index a946fdb6b5..498236f64d 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -136,14 +136,14 @@ mod unsafe_send_sync; use crate::unsafe_send_sync::UnsafeSendSync; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use std::os::raw::{c_int, c_void}; use std::slice; use std::{env, path::PathBuf}; use target_lexicon::Triple; use wasmtime::{Config, Engine, Instance, Linker, Module, Store}; use wasmtime_cli_flags::{CommonOptions, WasiModules}; -use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; +use wasmtime_wasi::{sync::WasiCtxBuilder, I32Exit, WasiCtx}; pub type ExitCode = c_int; pub const OK: ExitCode = 0; @@ -539,14 +539,13 @@ impl BenchState { Err(trap) => { // Since _start will likely return by using the system `exit` call, we must // check the trap code to see if it actually represents a successful exit. - match trap.i32_exit_status() { - Some(0) => Ok(()), - Some(n) => Err(anyhow!("_start exited with a non-zero code: {}", n)), - None => Err(anyhow!( - "executing the benchmark resulted in a trap: {}", - trap - )), + if let Some(exit) = trap.downcast_ref::() { + if exit.0 == 0 { + return Ok(()); + } } + + Err(trap) } } } diff --git a/crates/c-api/src/func.rs b/crates/c-api/src/func.rs index 3d3f5ee3cf..68cebd0c42 100644 --- a/crates/c-api/src/func.rs +++ b/crates/c-api/src/func.rs @@ -3,6 +3,8 @@ use crate::{ wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t, wasmtime_error_t, wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext, CStoreContextMut, }; +use anyhow::{Error, Result}; +use std::any::Any; use std::ffi::c_void; use std::mem::{self, MaybeUninit}; use std::panic::{self, AssertUnwindSafe}; @@ -67,7 +69,7 @@ unsafe fn create_function( let mut out_results: wasm_val_vec_t = vec![wasm_val_t::default(); results.len()].into(); let out = func(¶ms, &mut out_results); if let Some(trap) = out { - return Err(trap.trap.clone()); + return Err(trap.error); } let out_results = out_results.as_slice(); @@ -152,24 +154,25 @@ pub unsafe extern "C" fn wasm_func_call( } ptr::null_mut() } - Ok(Err(trap)) => match trap.downcast::() { - Ok(trap) => Box::into_raw(Box::new(wasm_trap_t::new(trap))), - Err(err) => Box::into_raw(Box::new(wasm_trap_t::new(err.into()))), - }, + Ok(Err(err)) => Box::into_raw(Box::new(wasm_trap_t::new(err))), Err(panic) => { - let trap = if let Some(msg) = panic.downcast_ref::() { - Trap::new(msg) - } else if let Some(msg) = panic.downcast_ref::<&'static str>() { - Trap::new(*msg) - } else { - Trap::new("rust panic happened") - }; - let trap = Box::new(wasm_trap_t::new(trap)); + let err = error_from_panic(panic); + let trap = Box::new(wasm_trap_t::new(err)); Box::into_raw(trap) } } } +fn error_from_panic(panic: Box) -> Error { + if let Some(msg) = panic.downcast_ref::() { + Error::msg(msg.clone()) + } else if let Some(msg) = panic.downcast_ref::<&'static str>() { + Error::msg(*msg) + } else { + Error::msg("rust panic happened") + } +} + #[no_mangle] pub unsafe extern "C" fn wasm_func_type(f: &wasm_func_t) -> Box { Box::new(wasm_functype_t::new(f.func().ty(f.ext.store.context()))) @@ -235,7 +238,7 @@ pub(crate) unsafe fn c_callback_to_rust_fn( callback: wasmtime_func_callback_t, data: *mut c_void, finalizer: Option, -) -> impl Fn(Caller<'_, crate::StoreData>, &[Val], &mut [Val]) -> Result<(), Trap> { +) -> impl Fn(Caller<'_, crate::StoreData>, &[Val], &mut [Val]) -> Result<()> { let foreign = crate::ForeignData { data, finalizer }; move |mut caller, params, results| { drop(&foreign); // move entire foreign into this closure @@ -264,7 +267,7 @@ pub(crate) unsafe fn c_callback_to_rust_fn( out_results.len(), ); if let Some(trap) = out { - return Err(trap.trap); + return Err(trap.error); } // Translate the `wasmtime_val_t` results into the `results` space @@ -299,14 +302,14 @@ pub(crate) unsafe fn c_unchecked_callback_to_rust_fn( callback: wasmtime_func_unchecked_callback_t, data: *mut c_void, finalizer: Option, -) -> impl Fn(Caller<'_, crate::StoreData>, &mut [ValRaw]) -> Result<(), Trap> { +) -> impl Fn(Caller<'_, crate::StoreData>, &mut [ValRaw]) -> Result<()> { let foreign = crate::ForeignData { data, finalizer }; move |caller, values| { drop(&foreign); // move entire foreign into this closure let mut caller = wasmtime_caller_t { caller }; match callback(foreign.data, &mut caller, values.as_mut_ptr(), values.len()) { None => Ok(()), - Some(trap) => Err(trap.trap), + Some(trap) => Err(trap.error), } } } @@ -348,22 +351,17 @@ pub unsafe extern "C" fn wasmtime_func_call( store.data_mut().wasm_val_storage = params; None } - Ok(Err(trap)) => match trap.downcast::() { - Ok(trap) => { + Ok(Err(trap)) => { + if trap.is::() { *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(trap))); None - } - Err(err) => Some(Box::new(wasmtime_error_t::from(err))), - }, - Err(panic) => { - let trap = if let Some(msg) = panic.downcast_ref::() { - Trap::new(msg) - } else if let Some(msg) = panic.downcast_ref::<&'static str>() { - Trap::new(*msg) } else { - Trap::new("rust panic happened") - }; - *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(trap))); + Some(Box::new(wasmtime_error_t::from(trap))) + } + } + Err(panic) => { + let err = error_from_panic(panic); + *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(err))); None } } diff --git a/crates/c-api/src/instance.rs b/crates/c-api/src/instance.rs index 4897520bed..2f93661ecd 100644 --- a/crates/c-api/src/instance.rs +++ b/crates/c-api/src/instance.rs @@ -41,7 +41,7 @@ pub unsafe extern "C" fn wasm_instance_new( ))), Err(e) => { if let Some(ptr) = result { - *ptr = Box::into_raw(Box::new(wasm_trap_t::new(e.into()))); + *ptr = Box::into_raw(Box::new(wasm_trap_t::new(e))); } None } @@ -100,7 +100,7 @@ pub(crate) fn handle_instantiate( } Err(e) => match e.downcast::() { Ok(trap) => { - *trap_ptr = Box::into_raw(Box::new(wasm_trap_t::new(trap))); + *trap_ptr = Box::into_raw(Box::new(wasm_trap_t::new(trap.into()))); None } Err(e) => Some(Box::new(e.into())), diff --git a/crates/c-api/src/trap.rs b/crates/c-api/src/trap.rs index 6f60709fe4..f6881c5454 100644 --- a/crates/c-api/src/trap.rs +++ b/crates/c-api/src/trap.rs @@ -1,25 +1,37 @@ use crate::{wasm_frame_vec_t, wasm_instance_t, wasm_name_t, wasm_store_t}; +use anyhow::{anyhow, Error}; use once_cell::unsync::OnceCell; -use wasmtime::{Trap, TrapCode}; +use wasmtime::{Trap, WasmBacktrace}; #[repr(C)] -#[derive(Clone)] pub struct wasm_trap_t { - pub(crate) trap: Trap, + pub(crate) error: Error, +} + +// This is currently only needed for the `wasm_trap_copy` API in the C API. +// +// For now the impl here is "fake it til you make it" since this is losing +// context by only cloning the error string. +impl Clone for wasm_trap_t { + fn clone(&self) -> wasm_trap_t { + wasm_trap_t { + error: anyhow!("{:?}", self.error), + } + } } wasmtime_c_api_macros::declare_ref!(wasm_trap_t); impl wasm_trap_t { - pub(crate) fn new(trap: Trap) -> wasm_trap_t { - wasm_trap_t { trap: trap } + pub(crate) fn new(error: Error) -> wasm_trap_t { + wasm_trap_t { error } } } #[repr(C)] #[derive(Clone)] -pub struct wasm_frame_t { - trap: Trap, +pub struct wasm_frame_t<'a> { + trace: &'a WasmBacktrace, idx: usize, func_name: OnceCell>, module_name: OnceCell>, @@ -40,7 +52,7 @@ pub extern "C" fn wasm_trap_new( } let message = String::from_utf8_lossy(&message[..message.len() - 1]); Box::new(wasm_trap_t { - trap: Trap::new(message), + error: Error::msg(message.into_owned()), }) } @@ -49,24 +61,28 @@ pub unsafe extern "C" fn wasmtime_trap_new(message: *const u8, len: usize) -> Bo let bytes = crate::slice_from_raw_parts(message, len); let message = String::from_utf8_lossy(&bytes); Box::new(wasm_trap_t { - trap: Trap::new(message), + error: Error::msg(message.into_owned()), }) } #[no_mangle] pub extern "C" fn wasm_trap_message(trap: &wasm_trap_t, out: &mut wasm_message_t) { let mut buffer = Vec::new(); - buffer.extend_from_slice(trap.trap.to_string().as_bytes()); + buffer.extend_from_slice(format!("{:?}", trap.error).as_bytes()); buffer.reserve_exact(1); buffer.push(0); out.set_buffer(buffer); } #[no_mangle] -pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option> { - if raw.trap.trace().unwrap_or(&[]).len() > 0 { +pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option>> { + let trace = match raw.error.downcast_ref::() { + Some(trap) => trap, + None => return None, + }; + if trace.frames().len() > 0 { Some(Box::new(wasm_frame_t { - trap: raw.trap.clone(), + trace, idx: 0, func_name: OnceCell::new(), module_name: OnceCell::new(), @@ -77,11 +93,15 @@ pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option(raw: &'a wasm_trap_t, out: &mut wasm_frame_vec_t<'a>) { + let trace = match raw.error.downcast_ref::() { + Some(trap) => trap, + None => return out.set_buffer(Vec::new()), + }; + let vec = (0..trace.frames().len()) .map(|idx| { Some(Box::new(wasm_frame_t { - trap: raw.trap.clone(), + trace, idx, func_name: OnceCell::new(), module_name: OnceCell::new(), @@ -93,50 +113,54 @@ pub extern "C" fn wasm_trap_trace(raw: &wasm_trap_t, out: &mut wasm_frame_vec_t) #[no_mangle] pub extern "C" fn wasmtime_trap_code(raw: &wasm_trap_t, code: &mut i32) -> bool { - match raw.trap.trap_code() { - Some(c) => { - *code = match c { - TrapCode::StackOverflow => 0, - TrapCode::MemoryOutOfBounds => 1, - TrapCode::HeapMisaligned => 2, - TrapCode::TableOutOfBounds => 3, - TrapCode::IndirectCallToNull => 4, - TrapCode::BadSignature => 5, - TrapCode::IntegerOverflow => 6, - TrapCode::IntegerDivisionByZero => 7, - TrapCode::BadConversionToInteger => 8, - TrapCode::UnreachableCodeReached => 9, - TrapCode::Interrupt => 10, - _ => unreachable!(), - }; - true - } - None => false, - } + let trap = match raw.error.downcast_ref::() { + Some(trap) => trap, + None => return false, + }; + *code = match trap { + Trap::StackOverflow => 0, + Trap::MemoryOutOfBounds => 1, + Trap::HeapMisaligned => 2, + Trap::TableOutOfBounds => 3, + Trap::IndirectCallToNull => 4, + Trap::BadSignature => 5, + Trap::IntegerOverflow => 6, + Trap::IntegerDivisionByZero => 7, + Trap::BadConversionToInteger => 8, + Trap::UnreachableCodeReached => 9, + Trap::Interrupt => 10, + _ => unreachable!(), + }; + true } #[no_mangle] pub extern "C" fn wasmtime_trap_exit_status(raw: &wasm_trap_t, status: &mut i32) -> bool { - match raw.trap.i32_exit_status() { - Some(i) => { - *status = i; - true - } - None => false, + #[cfg(feature = "wasi")] + if let Some(exit) = raw.error.downcast_ref::() { + *status = exit.0; + return true; } + + // Squash unused warnings in wasi-disabled builds. + drop((raw, status)); + + false } #[no_mangle] -pub extern "C" fn wasm_frame_func_index(frame: &wasm_frame_t) -> u32 { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx].func_index() +pub extern "C" fn wasm_frame_func_index(frame: &wasm_frame_t<'_>) -> u32 { + frame.trace.frames()[frame.idx].func_index() } #[no_mangle] -pub extern "C" fn wasmtime_frame_func_name(frame: &wasm_frame_t) -> Option<&wasm_name_t> { +pub extern "C" fn wasmtime_frame_func_name<'a>( + frame: &'a wasm_frame_t<'_>, +) -> Option<&'a wasm_name_t> { frame .func_name .get_or_init(|| { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] + frame.trace.frames()[frame.idx] .func_name() .map(|s| wasm_name_t::from(s.to_string().into_bytes())) }) @@ -144,11 +168,13 @@ pub extern "C" fn wasmtime_frame_func_name(frame: &wasm_frame_t) -> Option<&wasm } #[no_mangle] -pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wasm_name_t> { +pub extern "C" fn wasmtime_frame_module_name<'a>( + frame: &'a wasm_frame_t<'_>, +) -> Option<&'a wasm_name_t> { frame .module_name .get_or_init(|| { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] + frame.trace.frames()[frame.idx] .module_name() .map(|s| wasm_name_t::from(s.to_string().into_bytes())) }) @@ -156,25 +182,25 @@ pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wa } #[no_mangle] -pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] +pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t<'_>) -> usize { + frame.trace.frames()[frame.idx] .func_offset() .unwrap_or(usize::MAX) } #[no_mangle] -pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t) -> *mut wasm_instance_t { +pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t<'_>) -> *mut wasm_instance_t { unimplemented!("wasm_frame_instance") } #[no_mangle] -pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] +pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t<'_>) -> usize { + frame.trace.frames()[frame.idx] .module_offset() .unwrap_or(usize::MAX) } #[no_mangle] -pub extern "C" fn wasm_frame_copy(frame: &wasm_frame_t) -> Box { +pub extern "C" fn wasm_frame_copy<'a>(frame: &wasm_frame_t<'a>) -> Box> { Box::new(frame.clone()) } diff --git a/crates/c-api/src/vec.rs b/crates/c-api/src/vec.rs index 77835b4150..7a5aae734f 100644 --- a/crates/c-api/src/vec.rs +++ b/crates/c-api/src/vec.rs @@ -19,7 +19,7 @@ impl wasm_name_t { macro_rules! declare_vecs { ( $(( - name: $name:ident, + name: $name:ident $(<$lt:tt>)?, ty: $elem_ty:ty, new: $new:ident, empty: $empty:ident, @@ -29,12 +29,12 @@ macro_rules! declare_vecs { ))* ) => {$( #[repr(C)] - pub struct $name { + pub struct $name $(<$lt>)? { size: usize, data: *mut $elem_ty, } - impl $name { + impl$(<$lt>)? $name $(<$lt>)? { pub fn set_buffer(&mut self, buffer: Vec<$elem_ty>) { let mut vec = buffer.into_boxed_slice(); self.size = vec.len(); @@ -79,13 +79,13 @@ macro_rules! declare_vecs { } } - impl Clone for $name { + impl$(<$lt>)? Clone for $name $(<$lt>)? { fn clone(&self) -> Self { self.as_slice().to_vec().into() } } - impl From> for $name { + impl$(<$lt>)? From> for $name $(<$lt>)? { fn from(vec: Vec<$elem_ty>) -> Self { let mut vec = vec.into_boxed_slice(); let result = $name { @@ -97,7 +97,7 @@ macro_rules! declare_vecs { } } - impl Drop for $name { + impl$(<$lt>)? Drop for $name $(<$lt>)? { fn drop(&mut self) { drop(self.take()); } @@ -115,8 +115,8 @@ macro_rules! declare_vecs { } #[no_mangle] - pub unsafe extern "C" fn $new( - out: &mut $name, + pub unsafe extern "C" fn $new $(<$lt>)? ( + out: &mut $name $(<$lt>)?, size: usize, ptr: *const $elem_ty, ) { @@ -125,12 +125,15 @@ macro_rules! declare_vecs { } #[no_mangle] - pub extern "C" fn $copy(out: &mut $name, src: &$name) { + pub extern "C" fn $copy $(<$lt>)? ( + out: &mut $name $(<$lt>)?, + src: &$name $(<$lt>)?, + ) { out.set_buffer(src.as_slice().to_vec()); } #[no_mangle] - pub extern "C" fn $delete(out: &mut $name) { + pub extern "C" fn $delete $(<$lt>)? (out: &mut $name $(<$lt>)?) { out.take(); } )*}; @@ -228,8 +231,8 @@ declare_vecs! { delete: wasm_val_vec_delete, ) ( - name: wasm_frame_vec_t, - ty: Option>, + name: wasm_frame_vec_t<'a>, + ty: Option>>, new: wasm_frame_vec_new, empty: wasm_frame_vec_new_empty, uninit: wasm_frame_vec_new_uninitialized, diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index 21e35723f0..90fa8b6edf 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -374,8 +374,7 @@ pub fn differential( // falls through to checking the intermediate state otherwise. (Err(lhs), Err(rhs)) => { let err = rhs.downcast::().expect("not a trap"); - let poisoned = err.trap_code() == Some(TrapCode::StackOverflow) - || lhs_engine.is_stack_overflow(&lhs); + let poisoned = err == Trap::StackOverflow || lhs_engine.is_stack_overflow(&lhs); if poisoned { return Ok(false); @@ -675,14 +674,9 @@ pub fn table_ops( .downcast::() .unwrap(); - match trap.trap_code() { - Some(TrapCode::TableOutOfBounds) => {} - None if trap - .to_string() - .contains("all fuel consumed by WebAssembly") => {} - _ => { - panic!("unexpected trap: {}", trap); - } + match trap { + Trap::TableOutOfBounds | Trap::OutOfFuel => {} + _ => panic!("unexpected trap: {trap}"), } // Do a final GC after running the Wasm. diff --git a/crates/fuzzing/src/oracles/diff_v8.rs b/crates/fuzzing/src/oracles/diff_v8.rs index 8b8efd79d2..1d03c20041 100644 --- a/crates/fuzzing/src/oracles/diff_v8.rs +++ b/crates/fuzzing/src/oracles/diff_v8.rs @@ -5,7 +5,6 @@ use std::cell::RefCell; use std::rc::Rc; use std::sync::Once; use wasmtime::Trap; -use wasmtime::TrapCode; pub struct V8Engine { isolate: Rc>, @@ -91,14 +90,14 @@ impl DiffEngine for V8Engine { v8 ); }; - match wasmtime.trap_code() { - Some(TrapCode::MemoryOutOfBounds) => { + match wasmtime { + Trap::MemoryOutOfBounds => { return verify_v8(&[ "memory access out of bounds", "data segment is out of bounds", ]) } - Some(TrapCode::UnreachableCodeReached) => { + Trap::UnreachableCodeReached => { return verify_v8(&[ "unreachable", // All the wasms we test use wasm-smith's @@ -113,10 +112,10 @@ impl DiffEngine for V8Engine { "Maximum call stack size exceeded", ]); } - Some(TrapCode::IntegerDivisionByZero) => { + Trap::IntegerDivisionByZero => { return verify_v8(&["divide by zero", "remainder by zero"]) } - Some(TrapCode::StackOverflow) => { + Trap::StackOverflow => { return verify_v8(&[ "call stack size exceeded", // Similar to the above comment in `UnreachableCodeReached` @@ -128,15 +127,15 @@ impl DiffEngine for V8Engine { "unreachable", ]); } - Some(TrapCode::IndirectCallToNull) => return verify_v8(&["null function"]), - Some(TrapCode::TableOutOfBounds) => { + Trap::IndirectCallToNull => return verify_v8(&["null function"]), + Trap::TableOutOfBounds => { return verify_v8(&[ "table initializer is out of bounds", "table index is out of bounds", ]) } - Some(TrapCode::BadSignature) => return verify_v8(&["function signature mismatch"]), - Some(TrapCode::IntegerOverflow) | Some(TrapCode::BadConversionToInteger) => { + Trap::BadSignature => return verify_v8(&["function signature mismatch"]), + Trap::IntegerOverflow | Trap::BadConversionToInteger => { return verify_v8(&[ "float unrepresentable in integer range", "divide result unrepresentable", diff --git a/crates/fuzzing/src/oracles/diff_wasmi.rs b/crates/fuzzing/src/oracles/diff_wasmi.rs index 01ff817c4b..0864d34d54 100644 --- a/crates/fuzzing/src/oracles/diff_wasmi.rs +++ b/crates/fuzzing/src/oracles/diff_wasmi.rs @@ -3,7 +3,7 @@ use crate::generators::{Config, DiffValue, DiffValueType}; use crate::oracles::engine::{DiffEngine, DiffInstance}; use anyhow::{Context, Error, Result}; -use wasmtime::{Trap, TrapCode}; +use wasmtime::Trap; /// A wrapper for `wasmi` as a [`DiffEngine`]. pub struct WasmiEngine { @@ -55,8 +55,8 @@ impl DiffEngine for WasmiEngine { // Wasmtime reports as a `MemoryOutOfBounds`. Some(wasmi::Error::Memory(msg)) => { assert_eq!( - trap.trap_code(), - Some(TrapCode::MemoryOutOfBounds), + *trap, + Trap::MemoryOutOfBounds, "wasmtime error did not match wasmi: {msg}" ); return; @@ -77,10 +77,7 @@ impl DiffEngine for WasmiEngine { .expect(&format!("not a trap: {:?}", err)), }; assert!(wasmi.as_code().is_some()); - assert_eq!( - wasmi.as_code().map(wasmi_to_wasmtime_trap_code), - trap.trap_code(), - ); + assert_eq!(wasmi_to_wasmtime_trap_code(wasmi.as_code().unwrap()), *trap); } fn is_stack_overflow(&self, err: &Error) -> bool { @@ -97,18 +94,18 @@ impl DiffEngine for WasmiEngine { } /// Converts `wasmi` trap code to `wasmtime` trap code. -fn wasmi_to_wasmtime_trap_code(trap: wasmi::core::TrapCode) -> wasmtime::TrapCode { - use wasmi::core::TrapCode as WasmiTrapCode; +fn wasmi_to_wasmtime_trap_code(trap: wasmi::core::TrapCode) -> Trap { + use wasmi::core::TrapCode; match trap { - WasmiTrapCode::Unreachable => TrapCode::UnreachableCodeReached, - WasmiTrapCode::MemoryAccessOutOfBounds => TrapCode::MemoryOutOfBounds, - WasmiTrapCode::TableAccessOutOfBounds => TrapCode::TableOutOfBounds, - WasmiTrapCode::ElemUninitialized => TrapCode::IndirectCallToNull, - WasmiTrapCode::DivisionByZero => TrapCode::IntegerDivisionByZero, - WasmiTrapCode::IntegerOverflow => TrapCode::IntegerOverflow, - WasmiTrapCode::InvalidConversionToInt => TrapCode::BadConversionToInteger, - WasmiTrapCode::StackOverflow => TrapCode::StackOverflow, - WasmiTrapCode::UnexpectedSignature => TrapCode::BadSignature, + TrapCode::Unreachable => Trap::UnreachableCodeReached, + TrapCode::MemoryAccessOutOfBounds => Trap::MemoryOutOfBounds, + TrapCode::TableAccessOutOfBounds => Trap::TableOutOfBounds, + TrapCode::ElemUninitialized => Trap::IndirectCallToNull, + TrapCode::DivisionByZero => Trap::IntegerDivisionByZero, + TrapCode::IntegerOverflow => Trap::IntegerOverflow, + TrapCode::InvalidConversionToInt => Trap::BadConversionToInteger, + TrapCode::StackOverflow => Trap::StackOverflow, + TrapCode::UnexpectedSignature => Trap::BadSignature, } } diff --git a/crates/fuzzing/src/oracles/diff_wasmtime.rs b/crates/fuzzing/src/oracles/diff_wasmtime.rs index 29d0e08475..f2a31aee1f 100644 --- a/crates/fuzzing/src/oracles/diff_wasmtime.rs +++ b/crates/fuzzing/src/oracles/diff_wasmtime.rs @@ -6,7 +6,7 @@ use crate::oracles::engine::DiffInstance; use crate::oracles::{compile_module, engine::DiffEngine, StoreLimits}; use anyhow::{Context, Error, Result}; use arbitrary::Unstructured; -use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, TrapCode, Val}; +use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, Val}; /// A wrapper for using Wasmtime as a [`DiffEngine`]. pub struct WasmtimeEngine { @@ -45,18 +45,12 @@ impl DiffEngine for WasmtimeEngine { let trap2 = err .downcast_ref::() .expect(&format!("not a trap: {:?}", err)); - assert_eq!( - trap.trap_code(), - trap2.trap_code(), - "{}\nis not equal to\n{}", - trap, - trap2 - ); + assert_eq!(trap, trap2, "{}\nis not equal to\n{}", trap, trap2); } fn is_stack_overflow(&self, err: &Error) -> bool { match err.downcast_ref::() { - Some(trap) => trap.trap_code() == Some(TrapCode::StackOverflow), + Some(trap) => *trap == Trap::StackOverflow, None => false, } } diff --git a/crates/fuzzing/src/oracles/stacks.rs b/crates/fuzzing/src/oracles/stacks.rs index 54dde5801f..3216ed10c8 100644 --- a/crates/fuzzing/src/oracles/stacks.rs +++ b/crates/fuzzing/src/oracles/stacks.rs @@ -1,4 +1,5 @@ use crate::generators::Stacks; +use anyhow::{bail, Result}; use wasmtime::*; /// Run the given `Stacks` test case and assert that the host's view of the Wasm @@ -17,7 +18,7 @@ pub fn check_stacks(stacks: Stacks) -> usize { .func_wrap( "host", "check_stack", - |mut caller: Caller<'_, ()>| -> Result<(), Trap> { + |mut caller: Caller<'_, ()>| -> Result<()> { let fuel = caller .get_export("fuel") .expect("should export `fuel`") @@ -26,7 +27,7 @@ pub fn check_stacks(stacks: Stacks) -> usize { let fuel_left = fuel.get(&mut caller).unwrap_i32(); if fuel_left == 0 { - return Err(Trap::new("out of fuel")); + bail!(Trap::OutOfFuel); } fuel.set(&mut caller, Val::I32(fuel_left - 1)).unwrap(); @@ -59,7 +60,7 @@ pub fn check_stacks(stacks: Stacks) -> usize { for input in stacks.inputs().iter().copied() { log::debug!("input: {}", input); if let Err(trap) = run.call(&mut store, (input.into(),)) { - log::debug!("trap: {}", trap); + log::debug!("trap: {:?}", trap); let get_stack = instance .get_typed_func::<(), (u32, u32), _>(&mut store, "get_stack") .expect("should export `get_stack` function as expected"); @@ -72,9 +73,10 @@ pub fn check_stacks(stacks: Stacks) -> usize { .get_memory(&mut store, "memory") .expect("should have `memory` export"); - let host_trace = trap.trace().unwrap(); + let host_trace = trap.downcast_ref::().unwrap().frames(); + let trap = trap.downcast_ref::().unwrap(); max_stack_depth = max_stack_depth.max(host_trace.len()); - assert_stack_matches(&mut store, memory, ptr, len, host_trace, trap.trap_code()); + assert_stack_matches(&mut store, memory, ptr, len, host_trace, *trap); } } max_stack_depth @@ -87,7 +89,7 @@ fn assert_stack_matches( ptr: u32, len: u32, host_trace: &[FrameInfo], - trap_code: Option, + trap: Trap, ) { let mut data = vec![0; len as usize]; memory @@ -108,7 +110,7 @@ fn assert_stack_matches( // be able to see the exact function that triggered the stack overflow. In // this situation the host trace is asserted to be one larger and then the // top frame (first) of the host trace is discarded. - let host_trace = if trap_code == Some(TrapCode::StackOverflow) { + let host_trace = if trap == Trap::StackOverflow { assert_eq!(host_trace.len(), wasm_trace.len() + 1); &host_trace[1..] } else { diff --git a/crates/wasi-common/src/error.rs b/crates/wasi-common/src/error.rs index eed892ed06..d16909e031 100644 --- a/crates/wasi-common/src/error.rs +++ b/crates/wasi-common/src/error.rs @@ -24,6 +24,7 @@ //! `anyhow::Result::context` to aid in debugging of errors. pub use anyhow::{Context, Error}; +use std::fmt; /// Internal error type for the `wasi-common` crate. /// @@ -138,3 +139,18 @@ impl ErrorExt for Error { ErrorKind::Perm.into() } } + +/// An error returned from the `proc_exit` host syscall. +/// +/// Embedders can test if an error returned from wasm is this error, in which +/// case it may signal a non-fatal trap. +#[derive(Debug)] +pub struct I32Exit(pub i32); + +impl fmt::Display for I32Exit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Exited with i32 exit status {}", self.0) + } +} + +impl std::error::Error for I32Exit {} diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index 0f86c560ca..6f9e949cbe 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -66,7 +66,7 @@ pub use cap_rand::RngCore; pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock}; pub use ctx::WasiCtx; pub use dir::WasiDir; -pub use error::{Context, Error, ErrorExt, ErrorKind}; +pub use error::{Context, Error, ErrorExt, ErrorKind, I32Exit}; pub use file::WasiFile; pub use sched::{Poll, WasiSched}; pub use string_array::StringArrayError; diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index 3fbf9379bb..d04b471c18 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -6,6 +6,7 @@ use crate::sched::{ use crate::snapshots::preview_1::types as snapshot1_types; use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; use crate::{Error, ErrorExt, WasiCtx}; +use anyhow::Result; use cap_std::time::Duration; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; @@ -28,10 +29,10 @@ impl wiggle::GuestErrorType for types::Errno { } impl types::UserErrorConversion for WasiCtx { - fn errno_from_error(&mut self, e: Error) -> Result { + fn errno_from_error(&mut self, e: Error) -> Result { debug!("Error: {:?}", e); - e.try_into() - .map_err(|e| wasmtime::Trap::new(format!("{:?}", e))) + let errno = e.try_into()?; + Ok(errno) } } @@ -932,7 +933,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { Ok(num_results.try_into().expect("results fit into memory")) } - async fn proc_exit(&mut self, status: types::Exitcode) -> wasmtime::Trap { + async fn proc_exit(&mut self, status: types::Exitcode) -> anyhow::Error { Snapshot1::proc_exit(self, status).await } diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 664a0508e1..ca7dde519d 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -8,9 +8,9 @@ use crate::{ subscription::{RwEventFlags, SubscriptionResult}, Poll, Userdata, }, - Error, ErrorExt, ErrorKind, SystemTimeSpec, WasiCtx, + Error, ErrorExt, ErrorKind, I32Exit, SystemTimeSpec, WasiCtx, }; -use anyhow::Context; +use anyhow::{anyhow, Context, Result}; use cap_std::time::{Duration, SystemClock}; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; @@ -35,10 +35,10 @@ impl wiggle::GuestErrorType for types::Errno { } impl types::UserErrorConversion for WasiCtx { - fn errno_from_error(&mut self, e: Error) -> Result { + fn errno_from_error(&mut self, e: Error) -> Result { debug!("Error: {:?}", e); - e.try_into() - .map_err(|e| wasmtime::Trap::new(format!("{:?}", e))) + let errno = e.try_into()?; + Ok(errno) } } @@ -1214,12 +1214,12 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(num_results.try_into().expect("results fit into memory")) } - async fn proc_exit(&mut self, status: types::Exitcode) -> wasmtime::Trap { + async fn proc_exit(&mut self, status: types::Exitcode) -> anyhow::Error { // Check that the status is within WASI's range. if status < 126 { - wasmtime::Trap::i32_exit(status as i32) + I32Exit(status as i32).into() } else { - wasmtime::Trap::new("exit with invalid exit status outside of [0..126)") + anyhow!("exit with invalid exit status outside of [0..126)") } } diff --git a/crates/wasi-nn/src/witx.rs b/crates/wasi-nn/src/witx.rs index b4c27734c6..e7c877bd90 100644 --- a/crates/wasi-nn/src/witx.rs +++ b/crates/wasi-nn/src/witx.rs @@ -1,6 +1,7 @@ //! Contains the macro-generated implementation of wasi-nn from the its witx definition file. use crate::ctx::WasiNnCtx; use crate::ctx::WasiNnError; +use anyhow::Result; // Generate the traits and types of wasi-nn in several Rust modules (e.g. `types`). wiggle::from_witx!({ @@ -11,10 +12,7 @@ wiggle::from_witx!({ use types::NnErrno; impl<'a> types::UserErrorConversion for WasiNnCtx { - fn nn_errno_from_wasi_nn_error( - &mut self, - e: WasiNnError, - ) -> Result { + fn nn_errno_from_wasi_nn_error(&mut self, e: WasiNnError) -> Result { eprintln!("Host error: {:?}", e); match e { WasiNnError::BackendError(_) => unimplemented!(), diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 1774423978..308fc2d0d4 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -7,7 +7,7 @@ //! Individual snapshots are available through //! `wasmtime_wasi::snapshots::preview_{0, 1}::Wasi::new(&Store, Rc>)`. -pub use wasi_common::{Error, WasiCtx, WasiDir, WasiFile}; +pub use wasi_common::{Error, I32Exit, WasiCtx, WasiDir, WasiFile}; /// Re-export the commonly used wasi-cap-std-sync crate here. This saves /// consumers of this library from having to keep additional dependencies diff --git a/crates/wasmtime/src/component/func/options.rs b/crates/wasmtime/src/component/func/options.rs index 7c07e59004..f2df2bba5a 100644 --- a/crates/wasmtime/src/component/func/options.rs +++ b/crates/wasmtime/src/component/func/options.rs @@ -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)) diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index e5f3fde341..b29fe23226 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`. + /// 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 diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 953801d273..11368216c2 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -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(()) diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 571ab528d9..587fd8e566 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -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( store: impl AsContextMut, 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( mut store: impl AsContextMut, 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> + Send + 'a> + ) -> Box> + 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`](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, 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( 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; + ) -> Result; #[doc(hidden)] fn func_type(params: impl Iterator) -> 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 WasmRet for T @@ -1367,17 +1434,13 @@ where { type Abi = ::Abi; type Retptr = (); - type Fallible = Result; + type Fallible = Result; fn compatible_with_store(&self, store: &StoreOpaque) -> bool { ::compatible_with_store(self, store) } - unsafe fn into_abi_for_ret( - self, - store: &mut StoreOpaque, - _retptr: (), - ) -> Result { + unsafe fn into_abi_for_ret(self, store: &mut StoreOpaque, _retptr: ()) -> Result { Ok(::into_abi(self, store)) } @@ -1389,16 +1452,16 @@ where T::abi_into_raw(f(()), ptr); } - fn into_fallible(self) -> Result { + fn into_fallible(self) -> Result { Ok(self) } - fn fallible_from_trap(trap: Trap) -> Result { - Err(trap) + fn fallible_from_error(error: Error) -> Result { + Err(error) } } -unsafe impl WasmRet for Result +unsafe impl WasmRet for Result where T: WasmRet, { @@ -1417,7 +1480,7 @@ where self, store: &mut StoreOpaque, retptr: Self::Retptr, - ) -> Result { + ) -> Result { 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 { + fn into_fallible(self) -> Result { self } - fn fallible_from_trap(trap: Trap) -> Result { - Err(trap) + fn fallible_from_error(error: Error) -> Result { + 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; + type Fallible = Result; #[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 { + unsafe fn into_abi_for_ret(self, _store: &mut StoreOpaque, ptr: Self::Retptr) -> Result { 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 { + fn into_fallible(self) -> Result { Ok(self) } #[inline] - fn fallible_from_trap(trap: Trap) -> Result { - Err(trap) + fn fallible_from_error(error: Error) -> Result { + 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( 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( 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::::with(caller_vmctx, |mut caller| { diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs index 83565829e0..321aa36630 100644 --- a/crates/wasmtime/src/func/typed.rs +++ b/crates/wasmtime/src/func/typed.rs @@ -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 { + /// + /// [`Trap`]: crate::Trap + pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result { 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( &self, mut store: impl AsContextMut, params: Params, - ) -> Result + ) -> Result where T: Send, { @@ -120,7 +132,7 @@ where store: &mut StoreContextMut<'_, T>, func: ptr::NonNull, params: Params, - ) -> Result { + ) -> Result { // 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") } } }; diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index 3a7523efdf..cb7897ad3a 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, { @@ -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(), } })?; diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 189dc5d878..2ec92d1445 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -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); diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index 2c269c1d23..ba78908983 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -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 Linker { 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 Linker { 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 Linker { 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 Linker { Caller<'a, T>, &'a [Val], &'a mut [Val], - ) -> Box> + Send + 'a> + ) -> Box> + Send + 'a> + Send + Sync + 'static, @@ -714,8 +714,7 @@ impl Linker { .unwrap() .into_func() .unwrap() - .call(&mut caller, params, results) - .map_err(|error| error.downcast::().unwrap())?; + .call(&mut caller, params, results)?; Ok(()) }, @@ -781,8 +780,7 @@ impl Linker { .into_func() .unwrap() .call_async(&mut caller, params, results) - .await - .map_err(|error| error.downcast::().unwrap())?; + .await?; Ok(()) }) }, diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 5a77d408b8..ae0753ad3a 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -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 { pub trait CallHookHandler: 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 { - Sync(Box Result<(), crate::Trap> + Send + Sync>), + Sync(Box Result<()> + Send + Sync>), #[cfg(feature = "async")] Async(Box + Send + Sync>), } @@ -331,8 +331,7 @@ pub struct StoreOpaque { #[cfg(feature = "async")] struct AsyncState { - current_suspend: - UnsafeCell<*const wasmtime_fiber::Suspend, (), Result<(), Trap>>>, + current_suspend: UnsafeCell<*const wasmtime_fiber::Suspend, (), Result<()>>>, current_poll_cx: UnsafeCell<*mut Context<'static>>, } @@ -722,7 +721,7 @@ impl Store { /// 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 StoreInner { &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 StoreInner { 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 StoreContextMut<'_, T> { pub(crate) async fn on_fiber( &mut self, func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send, - ) -> Result + ) -> Result where T: Send, { @@ -1530,11 +1529,7 @@ impl 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 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 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 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 { // We need to carry over this `cx` into our fiber's runtime @@ -1699,7 +1693,7 @@ impl 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 StoreContextMut<'_, T> { #[cfg(feature = "async")] pub struct AsyncCx { - current_suspend: *mut *const wasmtime_fiber::Suspend, (), Result<(), Trap>>, + current_suspend: *mut *const wasmtime_fiber::Suspend, (), Result<()>>, current_poll_cx: *mut *mut Context<'static>, } @@ -1748,7 +1742,7 @@ impl AsyncCx { pub unsafe fn block_on( &self, mut future: Pin<&mut (dyn Future + Send)>, - ) -> Result { + ) -> Result { // 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 wasmtime_runtime::Store for StoreInner { 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 wasmtime_runtime::Store for StoreInner { #[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 { 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 diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index a80babdb83..9de79001a4 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -21,7 +21,7 @@ unsafe extern "C" fn stub_fn( 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( engine: &Engine, ) -> Result<(Box, 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() diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index b9c2ed2421..710b3fdd3b 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -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, -} - -/// 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), - - /// 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`](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. /// -/// 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::().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: +// +// * `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::().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, + ) -> 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::().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, - 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, @@ -219,292 +362,76 @@ impl TrapBacktrace { hint_wasm_backtrace_details_env, } } -} -struct TrapInner { - reason: TrapReason, - backtrace: OnceCell, -} - -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>(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::() - .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, - ) -> 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) -> 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) -> 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 { - 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 { - 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::>(), - // ..)` 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(""); + write!(f, " {:>3}: ", i)?; - for (i, frame) in trace.iter().enumerate() { - let name = frame.module_name().unwrap_or(""); - 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, "")?, - } + 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, "")?, + } + 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 for Trap { - fn from(e: anyhow::Error) -> Trap { - match e.downcast::() { - Ok(trap) => trap, - Err(e) => Box::::from(e).into(), - } - } -} - -impl From> for Trap { - fn from(e: Box) -> 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.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, diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index a8460ab2c5..0a4754840e 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -2,7 +2,7 @@ use crate::component; use crate::core; use crate::spectest::*; -use anyhow::{anyhow, bail, Context as _, Result}; +use anyhow::{anyhow, bail, Context as _, Error, Result}; use std::path::Path; use std::str; use wasmtime::*; @@ -24,7 +24,7 @@ pub struct WastContext { enum Outcome { Ok(T), - Trap(Trap), + Trap(Error), } impl Outcome { @@ -35,7 +35,7 @@ impl Outcome { } } - fn into_result(self) -> Result { + fn into_result(self) -> Result { match self { Outcome::Ok(t) => Ok(t), Outcome::Trap(t) => Err(t), @@ -111,22 +111,24 @@ impl WastContext { fn instantiate_module(&mut self, module: &[u8]) -> Result> { let module = Module::new(self.store.engine(), module)?; - let instance = match self.core_linker.instantiate(&mut self.store, &module) { - Ok(i) => i, - Err(e) => return e.downcast::().map(Outcome::Trap), - }; - Ok(Outcome::Ok(instance)) + Ok( + match self.core_linker.instantiate(&mut self.store, &module) { + Ok(i) => Outcome::Ok(i), + Err(e) => Outcome::Trap(e), + }, + ) } #[cfg(feature = "component-model")] fn instantiate_component(&mut self, module: &[u8]) -> Result> { let engine = self.store.engine(); let module = component::Component::new(engine, module)?; - let instance = match self.component_linker.instantiate(&mut self.store, &module) { - Ok(i) => i, - Err(e) => return e.downcast::().map(Outcome::Trap), - }; - Ok(Outcome::Ok(instance)) + Ok( + match self.component_linker.instantiate(&mut self.store, &module) { + Ok(i) => Outcome::Ok(i), + Err(e) => Outcome::Trap(e), + }, + ) } /// Register "spectest" which is used by the spec testsuite. @@ -174,7 +176,7 @@ impl WastContext { let mut results = vec![Val::null(); func.ty(&self.store).results().len()]; Ok(match func.call(&mut self.store, &values, &mut results) { Ok(()) => Outcome::Ok(Results::Core(results.into())), - Err(e) => Outcome::Trap(e.downcast()?), + Err(e) => Outcome::Trap(e), }) } #[cfg(feature = "component-model")] @@ -200,7 +202,7 @@ impl WastContext { func.post_return(&mut self.store)?; Outcome::Ok(Results::Component(results.into())) } - Err(e) => Outcome::Trap(e.downcast()?), + Err(e) => Outcome::Trap(e), }) } } @@ -330,7 +332,7 @@ impl WastContext { Outcome::Ok(values) => bail!("expected trap, got {:?}", values), Outcome::Trap(t) => t, }; - let actual = trap.to_string(); + let actual = format!("{trap:?}"); if actual.contains(expected) // `bulk-memory-operations/bulk.wast` checks for a message that // specifies which element is uninitialized, but our traps don't diff --git a/crates/wiggle/generate/src/funcs.rs b/crates/wiggle/generate/src/funcs.rs index bdcc7a0ea4..7fd5957969 100644 --- a/crates/wiggle/generate/src/funcs.rs +++ b/crates/wiggle/generate/src/funcs.rs @@ -101,7 +101,7 @@ fn _define_func( ctx: &mut (impl #(#bounds)+*), memory: &dyn #rt::GuestMemory, #(#abi_params),* - ) -> Result<#abi_ret, #rt::wasmtime_crate::Trap> { + ) -> #rt::anyhow::Result<#abi_ret> { use std::convert::TryFrom as _; #traced_body } @@ -127,7 +127,7 @@ fn _define_func( ctx: &'a mut (impl #(#bounds)+*), memory: &'a dyn #rt::GuestMemory, #(#abi_params),* - ) -> impl std::future::Future> + 'a { + ) -> impl std::future::Future> + 'a { use std::convert::TryFrom as _; #traced_body } diff --git a/crates/wiggle/generate/src/lib.rs b/crates/wiggle/generate/src/lib.rs index 09698d0227..9b5cea7579 100644 --- a/crates/wiggle/generate/src/lib.rs +++ b/crates/wiggle/generate/src/lib.rs @@ -43,7 +43,7 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings) let abi_typename = names.type_ref(&errtype.abi_type(), anon_lifetime()); let user_typename = errtype.typename(); let methodname = names.user_error_conversion_method(&errtype); - quote!(fn #methodname(&mut self, e: super::#user_typename) -> Result<#abi_typename, #rt::wasmtime_crate::Trap>;) + quote!(fn #methodname(&mut self, e: super::#user_typename) -> #rt::anyhow::Result<#abi_typename>;) }); let user_error_conversion = quote! { pub trait UserErrorConversion { diff --git a/crates/wiggle/generate/src/module_trait.rs b/crates/wiggle/generate/src/module_trait.rs index cd277bbf17..be9a47ad35 100644 --- a/crates/wiggle/generate/src/module_trait.rs +++ b/crates/wiggle/generate/src/module_trait.rs @@ -45,7 +45,7 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings }); let result = match f.results.len() { - 0 if f.noreturn => quote!(#rt::wasmtime_crate::Trap), + 0 if f.noreturn => quote!(#rt::anyhow::Error), 0 => quote!(()), 1 => { let (ok, err) = match &**f.results[0].tref.type_() { diff --git a/crates/wiggle/generate/src/wasmtime.rs b/crates/wiggle/generate/src/wasmtime.rs index d80bacec44..50ece48207 100644 --- a/crates/wiggle/generate/src/wasmtime.rs +++ b/crates/wiggle/generate/src/wasmtime.rs @@ -114,9 +114,7 @@ fn generate_func( let body = quote! { let mem = match caller.get_export("memory") { Some(#rt::wasmtime_crate::Extern::Memory(m)) => m, - _ => { - return Err(#rt::wasmtime_crate::Trap::new("missing required memory export")); - } + _ => #rt::anyhow::bail!("missing required memory export"), }; let (mem , ctx) = mem.data_and_store_mut(&mut caller); let ctx = get_cx(ctx); @@ -143,7 +141,7 @@ fn generate_func( linker.func_wrap( #module_str, #field_str, - move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> Result<#ret_ty, #rt::wasmtime_crate::Trap> { + move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> { let result = async { #body }; #rt::run_in_dummy_executor(result)? }, @@ -156,7 +154,7 @@ fn generate_func( linker.func_wrap( #module_str, #field_str, - move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> Result<#ret_ty, #rt::wasmtime_crate::Trap> { + move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> { #body }, )?; diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs index d5f232bb31..9867e893f0 100644 --- a/crates/wiggle/src/lib.rs +++ b/crates/wiggle/src/lib.rs @@ -1,3 +1,4 @@ +use anyhow::{bail, Result}; use std::fmt; use std::slice; use std::str; @@ -909,17 +910,7 @@ impl Pointee for str { } } -impl From for wasmtime_crate::Trap { - fn from(err: GuestError) -> wasmtime_crate::Trap { - wasmtime_crate::Trap::from( - Box::new(err) as Box - ) - } -} - -pub fn run_in_dummy_executor( - future: F, -) -> Result { +pub fn run_in_dummy_executor(future: F) -> Result { use std::pin::Pin; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; @@ -929,7 +920,7 @@ pub fn run_in_dummy_executor( match f.as_mut().poll(&mut cx) { Poll::Ready(val) => return Ok(val), Poll::Pending => - return Err(wasmtime_crate::Trap::new("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store")) + bail!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store"), } fn dummy_waker() -> Waker { diff --git a/crates/wiggle/test-helpers/Cargo.toml b/crates/wiggle/test-helpers/Cargo.toml index 058cf06f6a..a09a7a510b 100644 --- a/crates/wiggle/test-helpers/Cargo.toml +++ b/crates/wiggle/test-helpers/Cargo.toml @@ -16,6 +16,7 @@ proptest = "1.0.0" wiggle = { path = "..", features = ["tracing_log"] } [dev-dependencies] +anyhow = { workspace = true } thiserror = "1.0" tracing = "0.1.26" tracing-subscriber = { version = "0.3.1", default-features = false, features = ['fmt'] } diff --git a/crates/wiggle/test-helpers/examples/tracing.rs b/crates/wiggle/test-helpers/examples/tracing.rs index dd2556a43a..07c361d985 100644 --- a/crates/wiggle/test-helpers/examples/tracing.rs +++ b/crates/wiggle/test-helpers/examples/tracing.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use wiggle_test::{impl_errno, HostMemory, WasiCtx}; /// The `errors` argument to the wiggle gives us a hook to map a rich error @@ -32,10 +33,7 @@ impl_errno!(types::Errno); /// When the `errors` mapping in witx is non-empty, we need to impl the /// types::UserErrorConversion trait that wiggle generates from that mapping. impl<'a> types::UserErrorConversion for WasiCtx<'a> { - fn errno_from_rich_error( - &mut self, - e: RichError, - ) -> Result { + fn errno_from_rich_error(&mut self, e: RichError) -> Result { wiggle::tracing::debug!( rich_error = wiggle::tracing::field::debug(&e), "error conversion" diff --git a/crates/wiggle/tests/errors.rs b/crates/wiggle/tests/errors.rs index 5b10a5479b..737b7ea5a4 100644 --- a/crates/wiggle/tests/errors.rs +++ b/crates/wiggle/tests/errors.rs @@ -1,5 +1,6 @@ /// Execute the wiggle guest conversion code to exercise it mod convert_just_errno { + use anyhow::Result; use wiggle_test::{impl_errno, HostMemory, WasiCtx}; /// The `errors` argument to the wiggle gives us a hook to map a rich error @@ -31,10 +32,7 @@ mod convert_just_errno { /// When the `errors` mapping in witx is non-empty, we need to impl the /// types::UserErrorConversion trait that wiggle generates from that mapping. impl<'a> types::UserErrorConversion for WasiCtx<'a> { - fn errno_from_rich_error( - &mut self, - e: RichError, - ) -> Result { + fn errno_from_rich_error(&mut self, e: RichError) -> Result { // WasiCtx can collect a Vec log so we can test this. We're // logging the Display impl that `thiserror::Error` provides us. self.log.borrow_mut().push(e.to_string()); @@ -105,6 +103,7 @@ mod convert_just_errno { /// we use two distinct error types. mod convert_multiple_error_types { pub use super::convert_just_errno::RichError; + use anyhow::Result; use wiggle_test::{impl_errno, WasiCtx}; /// Test that we can map multiple types of errors. @@ -143,16 +142,13 @@ mod convert_multiple_error_types { // each member of the `errors` mapping. // Bodies elided. impl<'a> types::UserErrorConversion for WasiCtx<'a> { - fn errno_from_rich_error( - &mut self, - _e: RichError, - ) -> Result { + fn errno_from_rich_error(&mut self, _e: RichError) -> Result { unimplemented!() } fn errno2_from_another_rich_error( &mut self, _e: AnotherRichError, - ) -> Result { + ) -> Result { unimplemented!() } } @@ -165,7 +161,7 @@ mod convert_multiple_error_types { fn bar(&mut self, _: u32) -> Result<(), AnotherRichError> { unimplemented!() } - fn baz(&mut self, _: u32) -> wiggle::wasmtime_crate::Trap { + fn baz(&mut self, _: u32) -> anyhow::Error { unimplemented!() } } diff --git a/crates/wiggle/tests/wasi.rs b/crates/wiggle/tests/wasi.rs index f51897ca91..9a0783c155 100644 --- a/crates/wiggle/tests/wasi.rs +++ b/crates/wiggle/tests/wasi.rs @@ -314,7 +314,7 @@ impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> { unimplemented!("poll_oneoff") } - fn proc_exit(&mut self, _rval: types::Exitcode) -> wiggle::wasmtime_crate::Trap { + fn proc_exit(&mut self, _rval: types::Exitcode) -> anyhow::Error { unimplemented!("proc_exit") } diff --git a/crates/wiggle/tests/wasmtime_sync.rs b/crates/wiggle/tests/wasmtime_sync.rs index 3410e530ea..43c303286b 100644 --- a/crates/wiggle/tests/wasmtime_sync.rs +++ b/crates/wiggle/tests/wasmtime_sync.rs @@ -132,7 +132,7 @@ fn test_async_host_func_pending() { ) .unwrap_err(); assert!( - format!("{}", trap).contains("Cannot wait on pending future"), + format!("{:?}", trap).contains("Cannot wait on pending future"), "expected get a pending future Trap from dummy executor, got: {}", trap ); diff --git a/examples/interrupt.rs b/examples/interrupt.rs index 70d7965a3a..749a633045 100644 --- a/examples/interrupt.rs +++ b/examples/interrupt.rs @@ -26,10 +26,10 @@ fn main() -> Result<()> { }); println!("Entering infinite loop ..."); - let trap = run.call(&mut store, ()).unwrap_err(); + let err = run.call(&mut store, ()).unwrap_err(); println!("trap received..."); - assert!(trap.trap_code().unwrap() == TrapCode::Interrupt); + assert_eq!(err.downcast::()?, Trap::Interrupt); Ok(()) } diff --git a/src/commands/run.rs b/src/commands/run.rs index 8185bf7e00..4dc9732d59 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -15,6 +15,7 @@ use std::{ use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType}; use wasmtime_cli_flags::{CommonOptions, WasiModules}; use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder}; +use wasmtime_wasi::I32Exit; #[cfg(feature = "wasi-nn")] use wasmtime_wasi_nn::WasiNnCtx; @@ -211,24 +212,25 @@ impl RunCommand { { Ok(()) => (), Err(e) => { - // If the program exited because of a non-zero exit status, print - // a message and exit. - if let Some(trap) = e.downcast_ref::() { + // If a specific WASI error code was requested then that's + // forwarded through to the process here without printing any + // extra error information. + if let Some(exit) = e.downcast_ref::() { // Print the error message in the usual way. - if let Some(status) = trap.i32_exit_status() { - // On Windows, exit status 3 indicates an abort (see below), - // so return 1 indicating a non-zero status to avoid ambiguity. - if cfg!(windows) && status >= 3 { - process::exit(1); - } - process::exit(status); + // On Windows, exit status 3 indicates an abort (see below), + // so return 1 indicating a non-zero status to avoid ambiguity. + if cfg!(windows) && exit.0 >= 3 { + process::exit(1); } + process::exit(exit.0); + } + // If the program exited because of a trap, return an error code + // to the outside environment indicating a more severe problem + // than a simple failure. + if e.is::() { eprintln!("Error: {:?}", e); - // If the program exited because of a trap, return an error code - // to the outside environment indicating a more severe problem - // than a simple failure. if cfg!(unix) { // On Unix, return the error code of an abort. process::exit(128 + libc::SIGABRT); @@ -238,6 +240,9 @@ impl RunCommand { process::exit(3); } } + + // Otherwise fall back on Rust's default error printing/return + // code. return Err(e); } } diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs index f741d86e07..ee57764fa0 100644 --- a/tests/all/async_functions.rs +++ b/tests/all/async_functions.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, bail, Result}; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; @@ -440,7 +440,7 @@ async fn resume_separate_thread() { let func = Func::wrap0_async(&mut store, |_| { Box::new(async { tokio::task::yield_now().await; - Err::<(), _>(wasmtime::Trap::new("test")) + Err::<(), _>(anyhow!("test")) }) }); let result = Instance::new_async(&mut store, &module, &[func.into()]).await; @@ -493,7 +493,7 @@ async fn resume_separate_thread3() { // situation we'll set up the TLS info so it's in place while the body of // the function executes... let mut store = Store::new(&Engine::default(), None); - let f = Func::wrap(&mut store, move |mut caller: Caller<'_, _>| { + let f = Func::wrap(&mut store, move |mut caller: Caller<'_, _>| -> Result<()> { // ... and the execution of this host-defined function (while the TLS // info is initialized), will set up a recursive call into wasm. This // recursive call will be done asynchronously so we can suspend it @@ -536,7 +536,7 @@ async fn resume_separate_thread3() { // ... all in all this function will need access to the original TLS // information to raise the trap. This TLS information should be // restored even though the asynchronous execution is suspended. - Err::<(), _>(wasmtime::Trap::new("")) + bail!("") }); assert!(f.call(&mut store, &[], &mut []).is_err()); } @@ -561,8 +561,12 @@ async fn recursive_async() -> Result<()> { // ... but calls that actually stack overflow should indeed stack // overflow - let err = overflow.call_async(&mut caller, ()).await.unwrap_err(); - assert_eq!(err.trap_code(), Some(TrapCode::StackOverflow)); + let err = overflow + .call_async(&mut caller, ()) + .await + .unwrap_err() + .downcast::()?; + assert_eq!(err, Trap::StackOverflow); Ok(()) }) }); diff --git a/tests/all/call_hook.rs b/tests/all/call_hook.rs index d30125181a..f28771a2d2 100644 --- a/tests/all/call_hook.rs +++ b/tests/all/call_hook.rs @@ -1,4 +1,4 @@ -use anyhow::Error; +use anyhow::{bail, Error, Result}; use std::future::Future; use std::pin::Pin; use std::task::{self, Poll}; @@ -424,12 +424,12 @@ fn trapping() -> Result<(), Error> { linker.func_wrap( "host", "f", - |mut caller: Caller, action: i32, recur: i32| -> Result<(), Trap> { + |mut caller: Caller, action: i32, recur: i32| -> Result<()> { assert_eq!(caller.data().context.last(), Some(&Context::Host)); assert_eq!(caller.data().calls_into_host, caller.data().calls_into_wasm); match action { - TRAP_IN_F => return Err(Trap::new("trapping in f")), + TRAP_IN_F => bail!("trapping in f"), TRAP_NEXT_CALL_HOST => caller.data_mut().trap_next_call_host = true, TRAP_NEXT_RETURN_HOST => caller.data_mut().trap_next_return_host = true, TRAP_NEXT_CALL_WASM => caller.data_mut().trap_next_call_wasm = true, @@ -485,7 +485,7 @@ fn trapping() -> Result<(), Error> { }; let (s, e) = run(TRAP_IN_F, false); - assert!(e.unwrap().to_string().starts_with("trapping in f")); + assert!(format!("{:?}", e.unwrap()).contains("trapping in f")); assert_eq!(s.calls_into_host, 1); assert_eq!(s.returns_from_host, 1); assert_eq!(s.calls_into_wasm, 1); @@ -501,10 +501,7 @@ fn trapping() -> Result<(), Error> { // trap in next call to host. recur, so the second call into host traps: let (s, e) = run(TRAP_NEXT_CALL_HOST, true); - assert!(e - .unwrap() - .to_string() - .starts_with("call_hook: trapping on CallingHost")); + assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingHost")); assert_eq!(s.calls_into_host, 2); assert_eq!(s.returns_from_host, 1); assert_eq!(s.calls_into_wasm, 2); @@ -512,10 +509,7 @@ fn trapping() -> Result<(), Error> { // trap in the return from host. should trap right away, without recursion let (s, e) = run(TRAP_NEXT_RETURN_HOST, false); - assert!(e - .unwrap() - .to_string() - .starts_with("call_hook: trapping on ReturningFromHost")); + assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromHost")); assert_eq!(s.calls_into_host, 1); assert_eq!(s.returns_from_host, 1); assert_eq!(s.calls_into_wasm, 1); @@ -531,10 +525,7 @@ fn trapping() -> Result<(), Error> { // trap in next call to wasm. recur, so the second call into wasm traps: let (s, e) = run(TRAP_NEXT_CALL_WASM, true); - assert!(e - .unwrap() - .to_string() - .starts_with("call_hook: trapping on CallingWasm")); + assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingWasm")); assert_eq!(s.calls_into_host, 1); assert_eq!(s.returns_from_host, 1); assert_eq!(s.calls_into_wasm, 2); @@ -542,10 +533,7 @@ fn trapping() -> Result<(), Error> { // trap in the return from wasm. should trap right away, without recursion let (s, e) = run(TRAP_NEXT_RETURN_WASM, false); - assert!(e - .unwrap() - .to_string() - .starts_with("call_hook: trapping on ReturningFromWasm")); + assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromWasm")); assert_eq!(s.calls_into_host, 1); assert_eq!(s.returns_from_host, 1); assert_eq!(s.calls_into_wasm, 1); @@ -560,11 +548,7 @@ async fn basic_async_hook() -> Result<(), Error> { #[async_trait::async_trait] impl CallHookHandler for HandlerR { - async fn handle_call_event( - &self, - obj: &mut State, - ch: CallHook, - ) -> Result<(), wasmtime::Trap> { + async fn handle_call_event(&self, obj: &mut State, ch: CallHook) -> Result<()> { State::call_hook(obj, ch) } } @@ -638,13 +622,9 @@ async fn timeout_async_hook() -> Result<(), Error> { #[async_trait::async_trait] impl CallHookHandler for HandlerR { - async fn handle_call_event( - &self, - obj: &mut State, - ch: CallHook, - ) -> Result<(), wasmtime::Trap> { + async fn handle_call_event(&self, obj: &mut State, ch: CallHook) -> Result<()> { if obj.calls_into_host > 200 { - return Err(wasmtime::Trap::new("timeout")); + bail!("timeout"); } match ch { @@ -718,11 +698,7 @@ async fn drop_suspended_async_hook() -> Result<(), Error> { #[async_trait::async_trait] impl CallHookHandler for Handler { - async fn handle_call_event( - &self, - state: &mut u32, - _ch: CallHook, - ) -> Result<(), wasmtime::Trap> { + async fn handle_call_event(&self, state: &mut u32, _ch: CallHook) -> Result<()> { assert_eq!(*state, 0); *state += 1; let _dec = Decrement(state); @@ -861,12 +837,12 @@ impl Default for State { impl State { // This implementation asserts that hooks are always called in a stack-like manner. - fn call_hook(&mut self, s: CallHook) -> Result<(), Trap> { + fn call_hook(&mut self, s: CallHook) -> Result<()> { match s { CallHook::CallingHost => { self.calls_into_host += 1; if self.trap_next_call_host { - return Err(Trap::new("call_hook: trapping on CallingHost")); + bail!("call_hook: trapping on CallingHost"); } else { self.context.push(Context::Host); } @@ -875,7 +851,7 @@ impl State { Some(Context::Host) => { self.returns_from_host += 1; if self.trap_next_return_host { - return Err(Trap::new("call_hook: trapping on ReturningFromHost")); + bail!("call_hook: trapping on ReturningFromHost"); } } c => panic!( @@ -886,7 +862,7 @@ impl State { CallHook::CallingWasm => { self.calls_into_wasm += 1; if self.trap_next_call_wasm { - return Err(Trap::new("call_hook: trapping on CallingWasm")); + bail!("call_hook: trapping on CallingWasm"); } else { self.context.push(Context::Wasm); } @@ -895,7 +871,7 @@ impl State { Some(Context::Wasm) => { self.returns_from_wasm += 1; if self.trap_next_return_wasm { - return Err(Trap::new("call_hook: trapping on ReturningFromWasm")); + bail!("call_hook: trapping on ReturningFromWasm"); } } c => panic!( diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs index 42520a5238..0e002bd267 100644 --- a/tests/all/cli_tests.rs +++ b/tests/all/cli_tests.rs @@ -252,11 +252,7 @@ fn exit125_wasi_snapshot1() -> Result<()> { fn exit126_wasi_snapshot0() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?; let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; - if cfg!(windows) { - assert_eq!(output.status.code().unwrap(), 3); - } else { - assert_eq!(output.status.code().unwrap(), 128 + libc::SIGABRT); - } + assert_eq!(output.status.code().unwrap(), 1); assert!(output.stdout.is_empty()); assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); Ok(()) @@ -267,11 +263,7 @@ fn exit126_wasi_snapshot0() -> Result<()> { fn exit126_wasi_snapshot1() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?; let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "--disable-cache"])?; - if cfg!(windows) { - assert_eq!(output.status.code().unwrap(), 3); - } else { - assert_eq!(output.status.code().unwrap(), 128 + libc::SIGABRT); - } + assert_eq!(output.status.code().unwrap(), 1); assert!(output.stdout.is_empty()); assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); Ok(()) diff --git a/tests/all/component_model/async.rs b/tests/all/component_model/async.rs index 4098d84cae..816a02f1e1 100644 --- a/tests/all/component_model/async.rs +++ b/tests/all/component_model/async.rs @@ -1,6 +1,6 @@ use anyhow::Result; use wasmtime::component::*; -use wasmtime::{Store, StoreContextMut, Trap, TrapCode}; +use wasmtime::{Store, StoreContextMut, Trap}; /// This is super::func::thunks, except with an async store. #[tokio::test] @@ -38,7 +38,7 @@ async fn smoke() -> Result<()> { .call_async(&mut store, ()) .await .unwrap_err(); - assert!(err.downcast::()?.trap_code() == Some(TrapCode::UnreachableCodeReached)); + assert_eq!(err.downcast::()?, Trap::UnreachableCodeReached); Ok(()) } diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index 5c3027a5ef..fded264cbc 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -3,7 +3,7 @@ use anyhow::Result; use std::rc::Rc; use std::sync::Arc; use wasmtime::component::*; -use wasmtime::{Store, StoreContextMut, Trap, TrapCode}; +use wasmtime::{Store, StoreContextMut, Trap}; const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000; const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000; @@ -37,7 +37,7 @@ fn thunks() -> Result<()> { .get_typed_func::<(), (), _>(&mut store, "thunk-trap")? .call(&mut store, ()) .unwrap_err(); - assert!(err.downcast::()?.trap_code() == Some(TrapCode::UnreachableCodeReached)); + assert_eq!(err.downcast::()?, Trap::UnreachableCodeReached); Ok(()) } @@ -1099,7 +1099,7 @@ fn some_traps() -> Result<()> { .call(&mut store, (&[],)) .unwrap_err() .downcast::()?; - assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached)); + assert_eq!(err, Trap::UnreachableCodeReached); // This should fail when calling the allocator function for the argument let err = instance(&mut store)? @@ -1107,7 +1107,7 @@ fn some_traps() -> Result<()> { .call(&mut store, ("",)) .unwrap_err() .downcast::()?; - assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached)); + assert_eq!(err, Trap::UnreachableCodeReached); // This should fail when calling the allocator function for the space // to store the arguments (before arguments are even lowered) @@ -1119,7 +1119,7 @@ fn some_traps() -> Result<()> { .call(&mut store, ("", "", "", "", "", "", "", "", "", "")) .unwrap_err() .downcast::()?; - assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached)); + assert_eq!(err, Trap::UnreachableCodeReached); // Assert that when the base pointer returned by malloc is out of bounds // that errors are reported as such. Both empty and lists with contents @@ -2375,8 +2375,8 @@ fn errors_that_poison_instance() -> Result<()> { Err(e) => e, }; assert_eq!( - err.downcast::().unwrap().trap_code(), - Some(TrapCode::UnreachableCodeReached) + err.downcast::().unwrap(), + Trap::UnreachableCodeReached ); } diff --git a/tests/all/component_model/import.rs b/tests/all/component_model/import.rs index 79cabe6ba3..9f2654ba74 100644 --- a/tests/all/component_model/import.rs +++ b/tests/all/component_model/import.rs @@ -2,7 +2,7 @@ use super::REALLOC_AND_FREE; use anyhow::Result; use std::ops::Deref; use wasmtime::component::*; -use wasmtime::{Store, StoreContextMut, Trap}; +use wasmtime::{Store, StoreContextMut, WasmBacktrace}; #[test] fn can_compile() -> Result<()> { @@ -256,15 +256,13 @@ fn attempt_to_leave_during_malloc() -> Result<()> { .instantiate(&mut store, &component)? .get_typed_func::<(), (), _>(&mut store, "run")? .call(&mut store, ()) - .unwrap_err() - .downcast::()?; + .unwrap_err(); assert!( - trap.to_string().contains("cannot leave component instance"), - "bad trap: {}", - trap, + format!("{trap:?}").contains("cannot leave component instance"), + "bad trap: {trap:?}", ); - let trace = trap.trace().unwrap(); + let trace = trap.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 4); // This was our entry point... @@ -294,12 +292,10 @@ fn attempt_to_leave_during_malloc() -> Result<()> { .instantiate(&mut store, &component)? .get_typed_func::<(&str,), (), _>(&mut store, "take-string")? .call(&mut store, ("x",)) - .unwrap_err() - .downcast::()?; + .unwrap_err(); assert!( - trap.to_string().contains("cannot leave component instance"), - "bad trap: {}", - trap, + format!("{trap:?}").contains("cannot leave component instance"), + "bad trap: {trap:?}", ); Ok(()) } @@ -344,10 +340,8 @@ fn attempt_to_reenter_during_host() -> Result<()> { let func = store.data_mut().func.take().unwrap(); let trap = func.call(&mut store, ()).unwrap_err(); assert!( - trap.to_string() - .contains("cannot reenter component instance"), - "bad trap: {}", - trap, + format!("{trap:?}").contains("cannot reenter component instance"), + "bad trap: {trap:?}", ); Ok(()) }, @@ -372,10 +366,8 @@ fn attempt_to_reenter_during_host() -> Result<()> { let func = store.data_mut().func.take().unwrap(); let trap = func.call(&mut store, &[], &mut []).unwrap_err(); assert!( - trap.to_string() - .contains("cannot reenter component instance"), - "bad trap: {}", - trap, + format!("{trap:?}").contains("cannot reenter component instance"), + "bad trap: {trap:?}", ); Ok(()) }, @@ -733,16 +725,22 @@ fn bad_import_alignment() -> Result<()> { .instantiate(&mut store, &component)? .get_typed_func::<(), (), _>(&mut store, "unaligned-retptr")? .call(&mut store, ()) - .unwrap_err() - .downcast::()?; - assert!(trap.to_string().contains("pointer not aligned"), "{}", trap); + .unwrap_err(); + assert!( + format!("{:?}", trap).contains("pointer not aligned"), + "{}", + trap + ); let trap = linker .instantiate(&mut store, &component)? .get_typed_func::<(), (), _>(&mut store, "unaligned-argptr")? .call(&mut store, ()) - .unwrap_err() - .downcast::()?; - assert!(trap.to_string().contains("pointer not aligned"), "{}", trap); + .unwrap_err(); + assert!( + format!("{:?}", trap).contains("pointer not aligned"), + "{}", + trap + ); Ok(()) } diff --git a/tests/all/component_model/post_return.rs b/tests/all/component_model/post_return.rs index 9079cc78d2..56bfa51661 100644 --- a/tests/all/component_model/post_return.rs +++ b/tests/all/component_model/post_return.rs @@ -1,6 +1,6 @@ use anyhow::Result; use wasmtime::component::*; -use wasmtime::{Store, StoreContextMut, Trap, TrapCode}; +use wasmtime::{Store, StoreContextMut, Trap}; #[test] fn invalid_api() -> Result<()> { @@ -284,7 +284,7 @@ fn trap_in_post_return_poisons_instance() -> Result<()> { let f = instance.get_typed_func::<(), (), _>(&mut store, "f")?; f.call(&mut store, ())?; let trap = f.post_return(&mut store).unwrap_err().downcast::()?; - assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)); + assert_eq!(trap, Trap::UnreachableCodeReached); let err = f.call(&mut store, ()).unwrap_err(); assert!( err.to_string() diff --git a/tests/all/component_model/strings.rs b/tests/all/component_model/strings.rs index fca18ebc49..7f85b67fea 100644 --- a/tests/all/component_model/strings.rs +++ b/tests/all/component_model/strings.rs @@ -1,7 +1,7 @@ use super::REALLOC_AND_FREE; use anyhow::Result; use wasmtime::component::{Component, Linker}; -use wasmtime::{Engine, Store, StoreContextMut, Trap, TrapCode}; +use wasmtime::{Engine, Store, StoreContextMut, Trap}; const UTF16_TAG: u32 = 1 << 31; @@ -248,7 +248,7 @@ fn test_ptr_out_of_bounds(engine: &Engine, src: &str, dst: &str) -> Result<()> { .err() .unwrap() .downcast::()?; - assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)); + assert_eq!(trap, Trap::UnreachableCodeReached); Ok(()) }; @@ -322,7 +322,7 @@ fn test_ptr_overflow(engine: &Engine, src: &str, dst: &str) -> Result<()> { .call(&mut store, (size,)) .unwrap_err() .downcast::()?; - assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)); + assert_eq!(trap, Trap::UnreachableCodeReached); Ok(()) }; @@ -422,7 +422,7 @@ fn test_realloc_oob(engine: &Engine, src: &str, dst: &str) -> Result<()> { let instance = Linker::new(engine).instantiate(&mut store, &component)?; let func = instance.get_typed_func::<(), (), _>(&mut store, "f")?; let trap = func.call(&mut store, ()).unwrap_err().downcast::()?; - assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)); + assert_eq!(trap, Trap::UnreachableCodeReached); Ok(()) } @@ -498,9 +498,8 @@ fn test_invalid_string_encoding( let trap = test_raw_when_encoded(engine, src, dst, bytes, len)?.unwrap(); let src = src.replace("latin1+", ""); assert!( - trap.to_string() - .contains(&format!("invalid {src} encoding")), - "bad error: {}", + format!("{:?}", trap).contains(&format!("invalid {src} encoding")), + "bad error: {:?}", trap, ); Ok(()) @@ -524,7 +523,7 @@ fn test_raw_when_encoded( dst: &str, bytes: &[u8], len: u32, -) -> Result> { +) -> Result> { let component = format!( r#" (component @@ -574,6 +573,6 @@ fn test_raw_when_encoded( let func = instance.get_typed_func::<(&[u8], u32), (), _>(&mut store, "f")?; match func.call(&mut store, (bytes, len)) { Ok(_) => Ok(None), - Err(e) => Ok(Some(e.downcast()?)), + Err(e) => Ok(Some(e)), } } diff --git a/tests/all/custom_signal_handler.rs b/tests/all/custom_signal_handler.rs index 91077b12b9..22cf654162 100644 --- a/tests/all/custom_signal_handler.rs +++ b/tests/all/custom_signal_handler.rs @@ -170,12 +170,7 @@ mod tests { let trap = invoke_export(&mut store, instance, "read_out_of_bounds") .unwrap_err() .downcast::()?; - assert!( - trap.to_string() - .contains("wasm trap: out of bounds memory access"), - "bad trap message: {:?}", - trap.to_string() - ); + assert_eq!(trap, Trap::MemoryOutOfBounds); } // these invoke wasmtime_call_trampoline from callable.rs @@ -192,10 +187,11 @@ mod tests { let read_out_of_bounds_func = instance.get_typed_func::<(), i32, _>(&mut store, "read_out_of_bounds")?; println!("calling read_out_of_bounds..."); - let trap = read_out_of_bounds_func.call(&mut store, ()).unwrap_err(); - assert!(trap - .to_string() - .contains("wasm trap: out of bounds memory access")); + let trap = read_out_of_bounds_func + .call(&mut store, ()) + .unwrap_err() + .downcast::()?; + assert_eq!(trap, Trap::MemoryOutOfBounds); } Ok(()) } diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs index ccc683e63d..a42c3084d9 100644 --- a/tests/all/fuel.rs +++ b/tests/all/fuel.rs @@ -117,7 +117,7 @@ fn iloop() { store.add_fuel(10_000).unwrap(); let error = Instance::new(&mut store, &module, &[]).err().unwrap(); assert!( - error.to_string().contains("all fuel consumed"), + format!("{:?}", error).contains("all fuel consumed"), "bad error: {}", error ); @@ -173,8 +173,11 @@ fn host_function_consumes_all() { let export = instance .get_typed_func::<(), (), _>(&mut store, "") .unwrap(); - let trap = export.call(&mut store, ()).err().unwrap().to_string(); - assert!(trap.contains("all fuel consumed"), "bad error: {}", trap); + let trap = export.call(&mut store, ()).unwrap_err(); + assert!( + format!("{trap:?}").contains("all fuel consumed"), + "bad error: {trap:?}" + ); } #[test] diff --git a/tests/all/func.rs b/tests/all/func.rs index 79a0efbbe4..d56d44e500 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; use std::sync::Arc; use wasmtime::*; @@ -17,15 +17,13 @@ fn func_constructors() { Func::wrap(&mut store, || -> Option { None }); Func::wrap(&mut store, || -> Option { None }); - Func::wrap(&mut store, || -> Result<(), Trap> { loop {} }); - Func::wrap(&mut store, || -> Result { loop {} }); - Func::wrap(&mut store, || -> Result { loop {} }); - Func::wrap(&mut store, || -> Result { loop {} }); - Func::wrap(&mut store, || -> Result { loop {} }); - Func::wrap(&mut store, || -> Result, Trap> { - loop {} - }); - Func::wrap(&mut store, || -> Result, Trap> { loop {} }); + Func::wrap(&mut store, || -> Result<()> { loop {} }); + Func::wrap(&mut store, || -> Result { loop {} }); + Func::wrap(&mut store, || -> Result { loop {} }); + Func::wrap(&mut store, || -> Result { loop {} }); + Func::wrap(&mut store, || -> Result { loop {} }); + Func::wrap(&mut store, || -> Result> { loop {} }); + Func::wrap(&mut store, || -> Result> { loop {} }); } #[test] @@ -222,15 +220,9 @@ fn import_works() -> Result<()> { #[test] fn trap_smoke() -> Result<()> { let mut store = Store::<()>::default(); - let f = Func::wrap(&mut store, || -> Result<(), Trap> { - Err(Trap::new("test")) - }); - let err = f - .call(&mut store, &[], &mut []) - .unwrap_err() - .downcast::()?; + let f = Func::wrap(&mut store, || -> Result<()> { bail!("test") }); + let err = f.call(&mut store, &[], &mut []).unwrap_err(); assert!(err.to_string().contains("test")); - assert!(err.i32_exit_status().is_none()); Ok(()) } @@ -244,11 +236,8 @@ fn trap_import() -> Result<()> { )?; let mut store = Store::<()>::default(); let module = Module::new(store.engine(), &wasm)?; - let import = Func::wrap(&mut store, || -> Result<(), Trap> { Err(Trap::new("foo")) }); - let trap = Instance::new(&mut store, &module, &[import.into()]) - .err() - .unwrap() - .downcast::()?; + let import = Func::wrap(&mut store, || -> Result<()> { bail!("foo") }); + let trap = Instance::new(&mut store, &module, &[import.into()]).unwrap_err(); assert!(trap.to_string().contains("foo")); Ok(()) } @@ -451,10 +440,7 @@ fn func_write_nothing() -> anyhow::Result<()> { let mut store = Store::<()>::default(); let ty = FuncType::new(None, Some(ValType::I32)); let f = Func::new(&mut store, ty, |_, _, _| Ok(())); - let err = f - .call(&mut store, &[], &mut [Val::I32(0)]) - .unwrap_err() - .downcast::()?; + let err = f.call(&mut store, &[], &mut [Val::I32(0)]).unwrap_err(); assert!(err .to_string() .contains("function attempted to return an incompatible value")); @@ -488,7 +474,7 @@ fn return_cross_store_value() -> anyhow::Result<()> { let run = instance.get_func(&mut store1, "run").unwrap(); let result = run.call(&mut store1, &[], &mut [Val::I32(0)]); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("cross-`Store`")); + assert!(format!("{:?}", result.unwrap_err()).contains("cross-`Store`")); Ok(()) } @@ -620,9 +606,9 @@ fn trap_doesnt_leak() -> anyhow::Result<()> { // test that `Func::wrap` is correct let canary1 = Canary::default(); let dtor1_run = canary1.0.clone(); - let f1 = Func::wrap(&mut store, move || -> Result<(), Trap> { + let f1 = Func::wrap(&mut store, move || -> Result<()> { drop(&canary1); - Err(Trap::new("")) + bail!("") }); assert!(f1.typed::<(), (), _>(&store)?.call(&mut store, ()).is_err()); assert!(f1.call(&mut store, &[], &mut []).is_err()); @@ -632,7 +618,7 @@ fn trap_doesnt_leak() -> anyhow::Result<()> { let dtor2_run = canary2.0.clone(); let f2 = Func::new(&mut store, FuncType::new(None, None), move |_, _, _| { drop(&canary2); - Err(Trap::new("")) + bail!("") }); assert!(f2.typed::<(), (), _>(&store)?.call(&mut store, ()).is_err()); assert!(f2.call(&mut store, &[], &mut []).is_err()); diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs index 0c33c60a90..9b96b69ee7 100644 --- a/tests/all/host_funcs.rs +++ b/tests/all/host_funcs.rs @@ -1,7 +1,8 @@ -use anyhow::Result; +use anyhow::{bail, Result}; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use wasmtime::*; use wasmtime_wasi::sync::WasiCtxBuilder; +use wasmtime_wasi::I32Exit; #[test] #[should_panic = "cannot use `func_new_async` without enabling async support"] @@ -33,13 +34,13 @@ fn wrap_func() -> Result<()> { linker.func_wrap("m3", "", || -> Option { None })?; linker.func_wrap("m3", "f", || -> Option { None })?; - linker.func_wrap("", "f1", || -> Result<(), Trap> { loop {} })?; - linker.func_wrap("", "f2", || -> Result { loop {} })?; - linker.func_wrap("", "f3", || -> Result { loop {} })?; - linker.func_wrap("", "f4", || -> Result { loop {} })?; - linker.func_wrap("", "f5", || -> Result { loop {} })?; - linker.func_wrap("", "f6", || -> Result, Trap> { loop {} })?; - linker.func_wrap("", "f7", || -> Result, Trap> { loop {} })?; + linker.func_wrap("", "f1", || -> Result<()> { loop {} })?; + linker.func_wrap("", "f2", || -> Result { loop {} })?; + linker.func_wrap("", "f3", || -> Result { loop {} })?; + linker.func_wrap("", "f4", || -> Result { loop {} })?; + linker.func_wrap("", "f5", || -> Result { loop {} })?; + linker.func_wrap("", "f6", || -> Result> { loop {} })?; + linker.func_wrap("", "f7", || -> Result> { loop {} })?; Ok(()) } @@ -444,19 +445,15 @@ fn call_wasm_many_args() -> Result<()> { fn trap_smoke() -> Result<()> { let engine = Engine::default(); let mut linker = Linker::<()>::new(&engine); - linker.func_wrap("", "", || -> Result<(), Trap> { Err(Trap::new("test")) })?; + linker.func_wrap("", "", || -> Result<()> { bail!("test") })?; let mut store = Store::new(&engine, ()); let f = linker.get(&mut store, "", "").unwrap().into_func().unwrap(); - let err = f - .call(&mut store, &[], &mut []) - .unwrap_err() - .downcast::()?; + let err = f.call(&mut store, &[], &mut []).unwrap_err(); assert!(err.to_string().contains("test")); - assert!(err.i32_exit_status().is_none()); Ok(()) } @@ -472,16 +469,12 @@ fn trap_import() -> Result<()> { let engine = Engine::default(); let mut linker = Linker::new(&engine); - linker.func_wrap("", "", || -> Result<(), Trap> { Err(Trap::new("foo")) })?; + linker.func_wrap("", "", || -> Result<()> { bail!("foo") })?; let module = Module::new(&engine, &wasm)?; let mut store = Store::new(&engine, ()); - let trap = linker - .instantiate(&mut store, &module) - .err() - .unwrap() - .downcast::()?; + let trap = linker.instantiate(&mut store, &module).unwrap_err(); assert!(trap.to_string().contains("foo")); @@ -607,10 +600,7 @@ fn func_return_nothing() -> Result<()> { let mut store = Store::new(&engine, ()); let f = linker.get(&mut store, "", "").unwrap().into_func().unwrap(); - let err = f - .call(&mut store, &[], &mut [Val::I32(0)]) - .unwrap_err() - .downcast::()?; + let err = f.call(&mut store, &[], &mut [Val::I32(0)]).unwrap_err(); assert!(err .to_string() .contains("function attempted to return an incompatible value")); @@ -725,8 +715,11 @@ fn wasi_imports() -> Result<()> { let instance = linker.instantiate(&mut store, &module)?; let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?; - let trap = start.call(&mut store, ()).unwrap_err(); - assert_eq!(trap.i32_exit_status(), Some(123)); + let exit = start + .call(&mut store, ()) + .unwrap_err() + .downcast::()?; + assert_eq!(exit.0, 123); Ok(()) } diff --git a/tests/all/iloop.rs b/tests/all/iloop.rs index 1947a59c44..68614ddd77 100644 --- a/tests/all/iloop.rs +++ b/tests/all/iloop.rs @@ -31,12 +31,8 @@ fn loops_interruptable() -> anyhow::Result<()> { let instance = Instance::new(&mut store, &module, &[])?; let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?; store.engine().increment_epoch(); - let trap = iloop.call(&mut store, ()).unwrap_err(); - assert!( - trap.trap_code().unwrap() == TrapCode::Interrupt, - "bad message: {}", - trap - ); + let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; + assert_eq!(trap, Trap::Interrupt); Ok(()) } @@ -48,12 +44,8 @@ fn functions_interruptable() -> anyhow::Result<()> { let instance = Instance::new(&mut store, &module, &[func.into()])?; let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?; store.engine().increment_epoch(); - let trap = iloop.call(&mut store, ()).unwrap_err(); - assert!( - trap.trap_code().unwrap() == TrapCode::Interrupt, - "{}", - trap.to_string() - ); + let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; + assert_eq!(trap, Trap::Interrupt); Ok(()) } @@ -98,15 +90,11 @@ fn loop_interrupt_from_afar() -> anyhow::Result<()> { // Enter the infinitely looping function and assert that our interrupt // handle does indeed actually interrupt the function. let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?; - let trap = iloop.call(&mut store, ()).unwrap_err(); + let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; STOP.store(true, SeqCst); thread.join().unwrap(); assert!(HITS.load(SeqCst) > NUM_HITS); - assert!( - trap.trap_code().unwrap() == TrapCode::Interrupt, - "bad message: {}", - trap.to_string() - ); + assert_eq!(trap, Trap::Interrupt); Ok(()) } @@ -138,14 +126,10 @@ fn function_interrupt_from_afar() -> anyhow::Result<()> { // Enter the infinitely looping function and assert that our interrupt // handle does indeed actually interrupt the function. let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?; - let trap = iloop.call(&mut store, ()).unwrap_err(); + let trap = iloop.call(&mut store, ()).unwrap_err().downcast::()?; STOP.store(true, SeqCst); thread.join().unwrap(); assert!(HITS.load(SeqCst) > NUM_HITS); - assert!( - trap.trap_code().unwrap() == TrapCode::Interrupt, - "bad message: {}", - trap.to_string() - ); + assert_eq!(trap, Trap::Interrupt); Ok(()) } diff --git a/tests/all/import_calling_export.rs b/tests/all/import_calling_export.rs index dd7342a054..5b17d6937e 100644 --- a/tests/all/import_calling_export.rs +++ b/tests/all/import_calling_export.rs @@ -82,10 +82,7 @@ fn test_returns_incorrect_type() -> Result<()> { let mut result = [Val::I32(0)]; let trap = run_func .call(&mut store, &[], &mut result) - .expect_err("the execution should fail") - .downcast::()?; - assert!(trap - .to_string() - .contains("function attempted to return an incompatible value")); + .expect_err("the execution should fail"); + assert!(format!("{:?}", trap).contains("function attempted to return an incompatible value")); Ok(()) } diff --git a/tests/all/linker.rs b/tests/all/linker.rs index cc8e060afd..9728d757f5 100644 --- a/tests/all/linker.rs +++ b/tests/all/linker.rs @@ -32,7 +32,7 @@ fn link_twice_bad() -> Result<()> { linker.func_wrap("f", "", || {})?; assert!(linker.func_wrap("f", "", || {}).is_err()); assert!(linker - .func_wrap("f", "", || -> Result<(), Trap> { loop {} }) + .func_wrap("f", "", || -> Result<()> { loop {} }) .is_err()); // globals diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs index e00a55b2f8..9b389cfd1c 100644 --- a/tests/all/pooling_allocator.rs +++ b/tests/all/pooling_allocator.rs @@ -167,20 +167,32 @@ fn memory_guard_page_trap() -> Result<()> { let m = instance.get_memory(&mut store, "m").unwrap(); let f = instance.get_typed_func::(&mut store, "f")?; - let trap = f.call(&mut store, 0).expect_err("function should trap"); - assert!(trap.to_string().contains("out of bounds")); + let trap = f + .call(&mut store, 0) + .expect_err("function should trap") + .downcast::()?; + assert_eq!(trap, Trap::MemoryOutOfBounds); - let trap = f.call(&mut store, 1).expect_err("function should trap"); - assert!(trap.to_string().contains("out of bounds")); + let trap = f + .call(&mut store, 1) + .expect_err("function should trap") + .downcast::()?; + assert_eq!(trap, Trap::MemoryOutOfBounds); m.grow(&mut store, 1).expect("memory should grow"); f.call(&mut store, 0).expect("function should not trap"); - let trap = f.call(&mut store, 65536).expect_err("function should trap"); - assert!(trap.to_string().contains("out of bounds")); + let trap = f + .call(&mut store, 65536) + .expect_err("function should trap") + .downcast::()?; + assert_eq!(trap, Trap::MemoryOutOfBounds); - let trap = f.call(&mut store, 65537).expect_err("function should trap"); - assert!(trap.to_string().contains("out of bounds")); + let trap = f + .call(&mut store, 65537) + .expect_err("function should trap") + .downcast::()?; + assert_eq!(trap, Trap::MemoryOutOfBounds); m.grow(&mut store, 1).expect("memory should grow"); f.call(&mut store, 65536).expect("function should not trap"); diff --git a/tests/all/stack_overflow.rs b/tests/all/stack_overflow.rs index 52ff8e2381..9cd90eb9c8 100644 --- a/tests/all/stack_overflow.rs +++ b/tests/all/stack_overflow.rs @@ -28,12 +28,8 @@ fn host_always_has_some_stack() -> anyhow::Result<()> { // Make sure that our function traps and the trap says that the call stack // has been exhausted. - let trap = foo.call(&mut store, ()).unwrap_err(); - assert!( - trap.to_string().contains("call stack exhausted"), - "{}", - trap.to_string() - ); + let trap = foo.call(&mut store, ()).unwrap_err().downcast::()?; + assert_eq!(trap, Trap::StackOverflow); // Additionally, however, and this is the crucial test, make sure that the // host function actually completed. If HITS is 1 then we entered but didn't diff --git a/tests/all/traps.rs b/tests/all/traps.rs index 74831fb554..c01ddb88e6 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{bail, Error, Result}; use std::panic::{self, AssertUnwindSafe}; use std::process::Command; use wasmtime::*; @@ -15,7 +15,75 @@ fn test_trap_return() -> Result<()> { let module = Module::new(store.engine(), wat)?; let hello_type = FuncType::new(None, None); - let hello_func = Func::new(&mut store, hello_type, |_, _, _| Err(Trap::new("test 123"))); + let hello_func = Func::new(&mut store, hello_type, |_, _, _| bail!("test 123")); + + let instance = Instance::new(&mut store, &module, &[hello_func.into()])?; + let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; + + let e = run_func.call(&mut store, ()).unwrap_err(); + assert!(format!("{e:?}").contains("test 123")); + + assert!( + e.downcast_ref::().is_some(), + "error should contain a WasmBacktrace" + ); + + Ok(()) +} + +#[test] +fn test_anyhow_error_return() -> Result<()> { + let mut store = Store::<()>::default(); + let wat = r#" + (module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) + ) + "#; + + let module = Module::new(store.engine(), wat)?; + let hello_type = FuncType::new(None, None); + let hello_func = Func::new(&mut store, hello_type, |_, _, _| { + Err(anyhow::Error::msg("test 1234")) + }); + + let instance = Instance::new(&mut store, &module, &[hello_func.into()])?; + let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; + + let e = run_func.call(&mut store, ()).unwrap_err(); + assert!(!e.to_string().contains("test 1234")); + assert!(format!("{:?}", e).contains("Caused by:\n test 1234")); + + assert!(e.downcast_ref::().is_none()); + assert!(e.downcast_ref::().is_some()); + + Ok(()) +} + +#[test] +fn test_trap_return_downcast() -> Result<()> { + let mut store = Store::<()>::default(); + let wat = r#" + (module + (func $hello (import "" "hello")) + (func (export "run") (call $hello)) + ) + "#; + + #[derive(Debug)] + struct MyTrap; + impl std::fmt::Display for MyTrap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "my trap") + } + } + impl std::error::Error for MyTrap {} + + let module = Module::new(store.engine(), wat)?; + let hello_type = FuncType::new(None, None); + let hello_func = Func::new(&mut store, hello_type, |_, _, _| { + Err(anyhow::Error::from(MyTrap)) + }); let instance = Instance::new(&mut store, &module, &[hello_func.into()])?; let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; @@ -24,7 +92,19 @@ fn test_trap_return() -> Result<()> { .call(&mut store, ()) .err() .expect("error calling function"); - assert!(e.to_string().contains("test 123")); + let dbg = format!("{:?}", e); + println!("{}", dbg); + + assert!(!e.to_string().contains("my trap")); + assert!(dbg.contains("Caused by:\n my trap")); + + e.downcast_ref::() + .expect("error downcasts to MyTrap"); + let bt = e + .downcast_ref::() + .expect("error downcasts to WasmBacktrace"); + assert_eq!(bt.frames().len(), 1); + println!("{:?}", bt); Ok(()) } @@ -43,12 +123,9 @@ fn test_trap_trace() -> Result<()> { let instance = Instance::new(&mut store, &module, &[])?; let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; - let e = run_func - .call(&mut store, ()) - .err() - .expect("error calling function"); + let e = run_func.call(&mut store, ()).unwrap_err(); - let trace = e.trace().expect("backtrace is available"); + let trace = e.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 2); assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); assert_eq!(trace[0].func_index(), 1); @@ -60,11 +137,7 @@ fn test_trap_trace() -> Result<()> { assert_eq!(trace[1].func_name(), None); assert_eq!(trace[1].func_offset(), Some(1)); assert_eq!(trace[1].module_offset(), Some(0x21)); - assert!( - e.to_string().contains("unreachable"), - "wrong message: {}", - e.to_string() - ); + assert_eq!(e.downcast::()?, Trap::UnreachableCodeReached); Ok(()) } @@ -123,11 +196,9 @@ fn test_trap_through_host() -> Result<()> { &module, &[host_func_a.into(), host_func_b.into()], )?; - let a = instance - .get_typed_func::<(), (), _>(&mut store, "a") - .unwrap(); + let a = instance.get_typed_func::<(), (), _>(&mut store, "a")?; let err = a.call(&mut store, ()).unwrap_err(); - let trace = err.trace().expect("backtrace is available"); + let trace = err.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 3); assert_eq!(trace[0].func_name(), Some("c")); assert_eq!(trace[1].func_name(), Some("b")); @@ -153,12 +224,8 @@ fn test_trap_backtrace_disabled() -> Result<()> { let instance = Instance::new(&mut store, &module, &[])?; let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; - let e = run_func - .call(&mut store, ()) - .err() - .expect("error calling function"); - - assert!(e.trace().is_none(), "backtraces should be disabled"); + let e = run_func.call(&mut store, ()).unwrap_err(); + assert!(e.downcast_ref::().is_none()); Ok(()) } @@ -174,24 +241,21 @@ fn test_trap_trace_cb() -> Result<()> { "#; let fn_type = FuncType::new(None, None); - let fn_func = Func::new(&mut store, fn_type, |_, _, _| Err(Trap::new("cb throw"))); + let fn_func = Func::new(&mut store, fn_type, |_, _, _| bail!("cb throw")); let module = Module::new(store.engine(), wat)?; let instance = Instance::new(&mut store, &module, &[fn_func.into()])?; let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; - let e = run_func - .call(&mut store, ()) - .err() - .expect("error calling function"); + let e = run_func.call(&mut store, ()).unwrap_err(); - let trace = e.trace().expect("backtrace is available"); + let trace = e.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 2); assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); assert_eq!(trace[0].func_index(), 2); assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); assert_eq!(trace[1].func_index(), 1); - assert!(e.to_string().contains("cb throw")); + assert!(format!("{e:?}").contains("cb throw")); Ok(()) } @@ -209,19 +273,16 @@ fn test_trap_stack_overflow() -> Result<()> { let instance = Instance::new(&mut store, &module, &[])?; let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; - let e = run_func - .call(&mut store, ()) - .err() - .expect("error calling function"); + let e = run_func.call(&mut store, ()).unwrap_err(); - let trace = e.trace().expect("backtrace is available"); + let trace = e.downcast_ref::().unwrap().frames(); assert!(trace.len() >= 32); for i in 0..trace.len() { assert_eq!(trace[i].module_name().unwrap(), "rec_mod"); assert_eq!(trace[i].func_index(), 0); assert_eq!(trace[i].func_name(), Some("run")); } - assert!(e.to_string().contains("call stack exhausted")); + assert_eq!(e.downcast::()?, Trap::StackOverflow); Ok(()) } @@ -242,19 +303,18 @@ fn trap_display_pretty() -> Result<()> { let instance = Instance::new(&mut store, &module, &[])?; let run_func = instance.get_typed_func::<(), (), _>(&mut store, "bar")?; - let e = run_func - .call(&mut store, ()) - .err() - .expect("error calling function"); + let e = run_func.call(&mut store, ()).unwrap_err(); assert_eq!( - e.to_string(), + format!("{:?}", e), "\ -wasm trap: wasm `unreachable` instruction executed -wasm backtrace: +error while executing at wasm backtrace: 0: 0x23 - m!die 1: 0x27 - m! 2: 0x2c - m!foo 3: 0x31 - m! + +Caused by: + wasm trap: wasm `unreachable` instruction executed\ " ); Ok(()) @@ -287,21 +347,20 @@ fn trap_display_multi_module() -> Result<()> { let instance = Instance::new(&mut store, &module, &[bar])?; let bar2 = instance.get_typed_func::<(), (), _>(&mut store, "bar2")?; - let e = bar2 - .call(&mut store, ()) - .err() - .expect("error calling function"); + let e = bar2.call(&mut store, ()).unwrap_err(); assert_eq!( - e.to_string(), + format!("{e:?}"), "\ -wasm trap: wasm `unreachable` instruction executed -wasm backtrace: +error while executing at wasm backtrace: 0: 0x23 - a!die 1: 0x27 - a! 2: 0x2c - a!foo 3: 0x31 - a! 4: 0x29 - b!middle 5: 0x2e - b! + +Caused by: + wasm trap: wasm `unreachable` instruction executed\ " ); Ok(()) @@ -321,15 +380,9 @@ fn trap_start_function_import() -> Result<()> { let module = Module::new(store.engine(), &binary)?; let sig = FuncType::new(None, None); - let func = Func::new(&mut store, sig, |_, _, _| Err(Trap::new("user trap"))); - let err = Instance::new(&mut store, &module, &[func.into()]) - .err() - .unwrap(); - assert!(err - .downcast_ref::() - .unwrap() - .to_string() - .contains("user trap")); + let func = Func::new(&mut store, sig, |_, _, _| bail!("user trap")); + let err = Instance::new(&mut store, &module, &[func.into()]).unwrap_err(); + assert!(format!("{err:?}").contains("user trap")); Ok(()) } @@ -417,7 +470,7 @@ fn rust_catch_panic_import() -> Result<()> { let instance = Instance::new(&mut store, &module, &[panic.into(), catch_panic.into()])?; let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?; let trap = run.call(&mut store, ()).unwrap_err(); - let trace = trap.trace().unwrap(); + let trace = trap.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 1); assert_eq!(trace[0].func_index(), 3); assert_eq!(num_panics.load(std::sync::atomic::Ordering::SeqCst), 2); @@ -536,18 +589,20 @@ fn start_trap_pretty() -> Result<()> { let module = Module::new(store.engine(), wat)?; let e = match Instance::new(&mut store, &module, &[]) { Ok(_) => panic!("expected failure"), - Err(e) => e.downcast::()?, + Err(e) => e, }; assert_eq!( - e.to_string(), + format!("{e:?}"), "\ -wasm trap: wasm `unreachable` instruction executed -wasm backtrace: +error while executing at wasm backtrace: 0: 0x1d - m!die 1: 0x21 - m! 2: 0x26 - m!foo 3: 0x2b - m!start + +Caused by: + wasm trap: wasm `unreachable` instruction executed\ " ); Ok(()) @@ -568,15 +623,15 @@ fn present_after_module_drop() -> Result<()> { assert_trap(func.call(&mut store, ()).unwrap_err()); return Ok(()); - fn assert_trap(t: Trap) { - println!("{}", t); - let trace = t.trace().expect("backtrace is available"); + fn assert_trap(t: Error) { + println!("{:?}", t); + let trace = t.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 1); assert_eq!(trace[0].func_index(), 0); } } -fn assert_trap_code(wat: &str, code: wasmtime::TrapCode) { +fn assert_trap_code(wat: &str, code: wasmtime::Trap) { let mut store = Store::<()>::default(); let module = Module::new(store.engine(), wat).unwrap(); @@ -585,7 +640,7 @@ fn assert_trap_code(wat: &str, code: wasmtime::TrapCode) { Err(e) => e, }; let trap = err.downcast_ref::().unwrap(); - assert_eq!(trap.trap_code(), Some(code)); + assert_eq!(*trap, code); } #[test] @@ -598,7 +653,7 @@ fn heap_out_of_bounds_trap() { (start $start) ) "#, - TrapCode::MemoryOutOfBounds, + Trap::MemoryOutOfBounds, ); assert_trap_code( @@ -609,7 +664,7 @@ fn heap_out_of_bounds_trap() { (start $start) ) "#, - TrapCode::MemoryOutOfBounds, + Trap::MemoryOutOfBounds, ); } @@ -660,13 +715,11 @@ fn parse_dwarf_info() -> Result<()> { ); linker.module(&mut store, "", &module)?; let run = linker.get_default(&mut store, "")?; - let trap = run - .call(&mut store, &[], &mut []) - .unwrap_err() - .downcast::()?; + let trap = run.call(&mut store, &[], &mut []).unwrap_err(); let mut found = false; - for frame in trap.trace().expect("backtrace is available") { + let frames = trap.downcast_ref::().unwrap().frames(); + for frame in frames { for symbol in frame.symbols() { if let Some(file) = symbol.file() { if file.ends_with("input.rs") { @@ -698,16 +751,15 @@ fn no_hint_even_with_dwarf_info() -> Result<()> { ) "#, )?; - let trap = Instance::new(&mut store, &module, &[]) - .err() - .unwrap() - .downcast::()?; + let trap = Instance::new(&mut store, &module, &[]).unwrap_err(); assert_eq!( - trap.to_string(), + format!("{trap:?}"), "\ -wasm trap: wasm `unreachable` instruction executed -wasm backtrace: +error while executing at wasm backtrace: 0: 0x1a - !start + +Caused by: + wasm trap: wasm `unreachable` instruction executed\ " ); Ok(()) @@ -732,17 +784,16 @@ fn hint_with_dwarf_info() -> Result<()> { ) "#, )?; - let trap = Instance::new(&mut store, &module, &[]) - .err() - .unwrap() - .downcast::()?; + let trap = Instance::new(&mut store, &module, &[]).unwrap_err(); assert_eq!( - trap.to_string(), + format!("{trap:?}"), "\ -wasm trap: wasm `unreachable` instruction executed -wasm backtrace: +error while executing at wasm backtrace: 0: 0x1a - !start note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information + +Caused by: + wasm trap: wasm `unreachable` instruction executed\ " ); Ok(()) @@ -794,12 +845,9 @@ fn traps_without_address_map() -> Result<()> { let instance = Instance::new(&mut store, &module, &[])?; let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; - let e = run_func - .call(&mut store, ()) - .err() - .expect("error calling function"); + let e = run_func.call(&mut store, ()).unwrap_err(); - let trace = e.trace().expect("backtrace is available"); + let trace = e.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 2); assert_eq!(trace[0].func_name(), Some("hello")); assert_eq!(trace[0].func_index(), 1); @@ -846,16 +894,13 @@ fn catch_trap_calling_across_stores() -> Result<()> { .get_typed_func::<(), (), _>(&mut data.child_store, "trap") .expect("trap function should be exported"); - let trap = func - .call(&mut data.child_store, ()) - .err() - .expect("should trap"); + let trap = func.call(&mut data.child_store, ()).unwrap_err(); assert!( - trap.to_string().contains("unreachable"), - "trap should contain 'unreachable', got: {trap}" + format!("{trap:?}").contains("unreachable"), + "trap should contain 'unreachable', got: {trap:?}" ); - let trace = trap.trace().unwrap(); + let trace = trap.downcast_ref::().unwrap().frames(); assert_eq!(trace.len(), 1); assert_eq!(trace[0].func_name(), Some("trap")); @@ -965,7 +1010,7 @@ async fn async_then_sync_trap() -> Result<()> { .unwrap(); let trap = a.call_async(&mut async_store, ()).await.unwrap_err(); - let trace = trap.trace().unwrap(); + let trace = trap.downcast_ref::().unwrap().frames(); // We don't support cross-store or cross-engine symbolication currently, so // the other frames are ignored. assert_eq!(trace.len(), 1); @@ -1022,25 +1067,21 @@ async fn sync_then_async_trap() -> Result<()> { let sync_module = Module::new(sync_store.engine(), wat)?; let mut sync_linker = Linker::new(sync_store.engine()); - sync_linker.func_wrap( - "", - "b", - move |mut caller: Caller| -> Result<(), Trap> { - log::info!("Called `b`..."); - let async_instance = caller.data().async_instance; - let async_store = &mut caller.data_mut().async_store; + sync_linker.func_wrap("", "b", move |mut caller: Caller| -> Result<()> { + log::info!("Called `b`..."); + let async_instance = caller.data().async_instance; + let async_store = &mut caller.data_mut().async_store; - log::info!("Calling `c`..."); - let c = async_instance - .get_typed_func::<(), (), _>(&mut *async_store, "c") - .unwrap(); - tokio::task::block_in_place(|| { - tokio::runtime::Handle::current() - .block_on(async move { c.call_async(async_store, ()).await }) - })?; - Ok(()) - }, - )?; + log::info!("Calling `c`..."); + let c = async_instance + .get_typed_func::<(), (), _>(&mut *async_store, "c") + .unwrap(); + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current() + .block_on(async move { c.call_async(async_store, ()).await }) + })?; + Ok(()) + })?; let sync_instance = sync_linker.instantiate(&mut sync_store, &sync_module)?; @@ -1050,7 +1091,7 @@ async fn sync_then_async_trap() -> Result<()> { .unwrap(); let trap = a.call(&mut sync_store, ()).unwrap_err(); - let trace = trap.trace().unwrap(); + let trace = trap.downcast_ref::().unwrap().frames(); // We don't support cross-store or cross-engine symbolication currently, so // the other frames are ignored. assert_eq!(trace.len(), 1);