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