Deduplicate listings of traps in Wasmtime (#5299)
This commit replaces `wasmtime_environ::TrapCode` with `wasmtime::Trap`. This is possible with past refactorings which slimmed down the `Trap` definition in the `wasmtime` crate to a simple `enum`. This means that there's one less place that all the various trap opcodes need to be listed in Wasmtime.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use crate::component::func::{Memory, MemoryMut, Options};
|
||||
use crate::component::storage::slice_to_storage_mut;
|
||||
use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Type, Val};
|
||||
use crate::{AsContextMut, StoreContextMut, Trap, ValRaw};
|
||||
use crate::{AsContextMut, StoreContextMut, ValRaw};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use std::any::Any;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
@@ -270,7 +270,7 @@ fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<us
|
||||
unsafe fn handle_result(func: impl FnOnce() -> Result<()>) {
|
||||
match panic::catch_unwind(AssertUnwindSafe(func)) {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(e)) => Trap::raise(e),
|
||||
Ok(Err(e)) => crate::trap::raise(e),
|
||||
Err(e) => wasmtime_runtime::resume_panic(e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::store::{StoreData, StoreOpaque, Stored};
|
||||
use crate::trampoline::{generate_global_export, generate_table_export};
|
||||
use crate::{
|
||||
AsContext, AsContextMut, Engine, ExternRef, ExternType, Func, GlobalType, Memory, Mutability,
|
||||
SharedMemory, TableType, Trap, Val, ValType,
|
||||
SharedMemory, TableType, Val, ValType,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::mem;
|
||||
@@ -462,9 +462,7 @@ impl Table {
|
||||
let init = init.into_table_element(store, ty.element())?;
|
||||
unsafe {
|
||||
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::from_env(c))?;
|
||||
(*table.wasmtime_table(store, std::iter::empty())).fill(0, init, ty.minimum())?;
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
@@ -652,8 +650,7 @@ impl Table {
|
||||
let src_range = src_index..(src_index.checked_add(len).unwrap_or(u32::MAX));
|
||||
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::from_env(c))?;
|
||||
runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -681,9 +678,7 @@ impl Table {
|
||||
|
||||
let table = self.wasmtime_table(store, std::iter::empty());
|
||||
unsafe {
|
||||
(*table)
|
||||
.fill(dst, val, len)
|
||||
.map_err(|c| Trap::from_env(c))?;
|
||||
(*table).fill(dst, val, len)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::store::{StoreData, StoreOpaque, Stored};
|
||||
use crate::{
|
||||
AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, StoreContext,
|
||||
StoreContextMut, Trap, Val, ValRaw, ValType,
|
||||
StoreContextMut, Val, ValRaw, ValType,
|
||||
};
|
||||
use anyhow::{bail, Context as _, Error, Result};
|
||||
use std::future::Future;
|
||||
@@ -343,6 +343,8 @@ impl Func {
|
||||
///
|
||||
/// For more information about errors in Wasmtime see the [`Trap`]
|
||||
/// documentation.
|
||||
///
|
||||
/// [`Trap`]: crate::Trap
|
||||
#[cfg(compiler)]
|
||||
#[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs
|
||||
pub fn new<T>(
|
||||
@@ -581,6 +583,8 @@ impl Func {
|
||||
/// For more information about errors in Wasmtime see the [`Trap`]
|
||||
/// documentation.
|
||||
///
|
||||
/// [`Trap`]: crate::Trap
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// First up we can see how simple wasm imports can be implemented, such
|
||||
@@ -816,6 +820,8 @@ impl Func {
|
||||
/// Errors typically indicate that execution of WebAssembly was halted
|
||||
/// mid-way and did not complete after the error condition happened.
|
||||
///
|
||||
/// [`Trap`]: crate::Trap
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if called on a function belonging to an async
|
||||
@@ -1305,7 +1311,7 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
|
||||
);
|
||||
exit_wasm(store, exit);
|
||||
store.0.call_hook(CallHook::ReturningFromWasm)?;
|
||||
result.map_err(|t| Trap::from_runtime_box(store.0, t))
|
||||
result.map_err(|t| crate::trap::from_runtime_box(store.0, t))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1947,7 +1953,7 @@ macro_rules! impl_into_func {
|
||||
|
||||
match result {
|
||||
CallResult::Ok(val) => val,
|
||||
CallResult::Trap(err) => Trap::raise(err),
|
||||
CallResult::Trap(err) => crate::trap::raise(err),
|
||||
CallResult::Panic(panic) => wasmtime_runtime::resume_panic(panic),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::store::{InstanceId, StoreOpaque, Stored};
|
||||
use crate::types::matching;
|
||||
use crate::{
|
||||
AsContextMut, Engine, Export, Extern, Func, Global, Memory, Module, SharedMemory,
|
||||
StoreContextMut, Table, Trap, TypedFunc,
|
||||
StoreContextMut, Table, TypedFunc,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
||||
use std::mem;
|
||||
@@ -91,6 +91,8 @@ impl Instance {
|
||||
/// check for trap errors, you can use `error.downcast::<Trap>()`. For more
|
||||
/// about error handling see the [`Trap`] documentation.
|
||||
///
|
||||
/// [`Trap`]: crate::Trap
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if called with a store associated with a
|
||||
@@ -325,7 +327,7 @@ impl Instance {
|
||||
)
|
||||
.map_err(|e| -> Error {
|
||||
match e {
|
||||
InstantiationError::Trap(trap) => Trap::from_env(trap).into(),
|
||||
InstantiationError::Trap(trap) => trap.into(),
|
||||
other => other.into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
use crate::code::CodeObject;
|
||||
#[cfg(feature = "component-model")]
|
||||
use crate::component::Component;
|
||||
use crate::{FrameInfo, Module};
|
||||
use crate::{FrameInfo, Module, Trap};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use wasmtime_environ::TrapCode;
|
||||
use wasmtime_jit::CodeMemory;
|
||||
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
|
||||
|
||||
@@ -132,7 +131,7 @@ impl ModuleRegistry {
|
||||
}
|
||||
|
||||
/// Fetches trap information about a program counter in a backtrace.
|
||||
pub fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> {
|
||||
pub fn lookup_trap_code(&self, pc: usize) -> Option<Trap> {
|
||||
let (code, offset) = self.code(pc)?;
|
||||
wasmtime_environ::lookup_trap_code(code.code.code_memory().trap_data(), offset)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Support for a calling of an imported function.
|
||||
|
||||
use crate::{Engine, FuncType, Trap, ValRaw};
|
||||
use crate::{Engine, FuncType, ValRaw};
|
||||
use anyhow::Result;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ptr::NonNull;
|
||||
@@ -56,7 +56,7 @@ unsafe extern "C" fn stub_fn<F>(
|
||||
// call-site, which gets unwrapped in `Trap::from_runtime` later on as we
|
||||
// convert from the internal `Trap` type to our own `Trap` type in this
|
||||
// crate.
|
||||
Ok(Err(trap)) => Trap::raise(trap.into()),
|
||||
Ok(Err(trap)) => crate::trap::raise(trap.into()),
|
||||
|
||||
// And finally if the imported function panicked, then we trigger the
|
||||
// form of unwinding that's safe to jump over wasm code on all
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::store::StoreOpaque;
|
||||
use crate::Module;
|
||||
use anyhow::Error;
|
||||
use std::fmt;
|
||||
use wasmtime_environ::{EntityRef, FilePos, TrapCode};
|
||||
use wasmtime_environ::{EntityRef, FilePos};
|
||||
use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
|
||||
|
||||
/// Representation of a WebAssembly trap and what caused it to occur.
|
||||
@@ -65,168 +65,64 @@ use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
|
||||
/// # 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 Trap {
|
||||
/// The current stack space was exhausted.
|
||||
StackOverflow,
|
||||
pub use wasmtime_environ::Trap;
|
||||
|
||||
/// An out-of-bounds memory access.
|
||||
MemoryOutOfBounds,
|
||||
|
||||
/// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
|
||||
HeapMisaligned,
|
||||
|
||||
/// An out-of-bounds access to a table.
|
||||
TableOutOfBounds,
|
||||
|
||||
/// Indirect call to a null table entry.
|
||||
IndirectCallToNull,
|
||||
|
||||
/// Signature mismatch on indirect call.
|
||||
BadSignature,
|
||||
|
||||
/// An integer arithmetic operation caused an overflow.
|
||||
IntegerOverflow,
|
||||
|
||||
/// An integer division by zero.
|
||||
IntegerDivisionByZero,
|
||||
|
||||
/// Failed float-to-int conversion.
|
||||
BadConversionToInteger,
|
||||
|
||||
/// Code that was supposed to have been unreachable was reached.
|
||||
UnreachableCodeReached,
|
||||
|
||||
/// Execution has potentially run too long and may be interrupted.
|
||||
Interrupt,
|
||||
|
||||
/// When the `component-model` feature is enabled this trap represents a
|
||||
/// function that was `canon lift`'d, then `canon lower`'d, then called.
|
||||
/// This combination of creation of a function in the component model
|
||||
/// 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,
|
||||
// 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)
|
||||
}
|
||||
|
||||
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,
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Panics if `code` is `TrapCode::User`.
|
||||
pub(crate) fn from_env(code: TrapCode) -> Self {
|
||||
match code {
|
||||
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,
|
||||
wasmtime_runtime::TrapReason::Jit(pc) => {
|
||||
let code = store
|
||||
.modules()
|
||||
.lookup_trap_code(pc)
|
||||
.unwrap_or(Trap::StackOverflow);
|
||||
(code.into(), Some(pc))
|
||||
}
|
||||
wasmtime_runtime::TrapReason::Wasm(trap_code) => (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,
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Trap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use Trap::*;
|
||||
|
||||
let desc = match self {
|
||||
StackOverflow => "call stack exhausted",
|
||||
MemoryOutOfBounds => "out of bounds memory access",
|
||||
HeapMisaligned => "misaligned memory access",
|
||||
TableOutOfBounds => "undefined element: out of bounds table access",
|
||||
IndirectCallToNull => "uninitialized element",
|
||||
BadSignature => "indirect call type mismatch",
|
||||
IntegerOverflow => "integer overflow",
|
||||
IntegerDivisionByZero => "integer divide by zero",
|
||||
BadConversionToInteger => "invalid conversion to integer",
|
||||
UnreachableCodeReached => "wasm `unreachable` instruction executed",
|
||||
Interrupt => "interrupt",
|
||||
AlwaysTrapAdapter => "degenerate component adapter called",
|
||||
OutOfFuel => "all fuel consumed by WebAssembly",
|
||||
};
|
||||
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.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user