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:
Alex Crichton
2022-11-18 16:04:38 -06:00
committed by GitHub
parent 9b7c5e316d
commit 7a31c5b07c
14 changed files with 178 additions and 248 deletions

View File

@@ -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),
}
}

View File

@@ -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(())

View File

@@ -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),
}
}

View File

@@ -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(),
}
})?;

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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.
///