diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 1fa1603620..5f6a08bf3d 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -32,7 +32,7 @@ use std::sync::{Arc, Mutex}; use wasmparser::{FuncValidatorAllocations, FunctionBody}; use wasmtime_environ::{ AddressMapSection, CacheStore, CompileError, FilePos, FlagValue, FunctionBodyData, FunctionLoc, - InstructionAddressMap, ModuleTranslation, ModuleTypes, PtrSize, StackMapInformation, TrapCode, + InstructionAddressMap, ModuleTranslation, ModuleTypes, PtrSize, StackMapInformation, Trap, TrapEncodingBuilder, TrapInformation, Tunables, VMOffsets, WasmFunctionInfo, }; @@ -1003,18 +1003,18 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation { TrapInformation { code_offset: offset, trap_code: match code { - ir::TrapCode::StackOverflow => TrapCode::StackOverflow, - ir::TrapCode::HeapOutOfBounds => TrapCode::HeapOutOfBounds, - ir::TrapCode::HeapMisaligned => TrapCode::HeapMisaligned, - ir::TrapCode::TableOutOfBounds => TrapCode::TableOutOfBounds, - ir::TrapCode::IndirectCallToNull => TrapCode::IndirectCallToNull, - ir::TrapCode::BadSignature => TrapCode::BadSignature, - ir::TrapCode::IntegerOverflow => TrapCode::IntegerOverflow, - ir::TrapCode::IntegerDivisionByZero => TrapCode::IntegerDivisionByZero, - ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger, - ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached, - ir::TrapCode::Interrupt => TrapCode::Interrupt, - ir::TrapCode::User(ALWAYS_TRAP_CODE) => TrapCode::AlwaysTrapAdapter, + ir::TrapCode::StackOverflow => Trap::StackOverflow, + ir::TrapCode::HeapOutOfBounds => Trap::MemoryOutOfBounds, + ir::TrapCode::HeapMisaligned => Trap::HeapMisaligned, + ir::TrapCode::TableOutOfBounds => Trap::TableOutOfBounds, + ir::TrapCode::IndirectCallToNull => Trap::IndirectCallToNull, + ir::TrapCode::BadSignature => Trap::BadSignature, + ir::TrapCode::IntegerOverflow => Trap::IntegerOverflow, + ir::TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero, + ir::TrapCode::BadConversionToInteger => Trap::BadConversionToInteger, + ir::TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached, + ir::TrapCode::Interrupt => Trap::Interrupt, + ir::TrapCode::User(ALWAYS_TRAP_CODE) => Trap::AlwaysTrapAdapter, // these should never be emitted by wasmtime-cranelift ir::TrapCode::User(_) => unreachable!(), diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs index 7fe7cec7e2..c2769c7fe6 100644 --- a/crates/environ/src/trap_encoding.rs +++ b/crates/environ/src/trap_encoding.rs @@ -2,6 +2,7 @@ use crate::obj::ELF_WASMTIME_TRAPS; use object::write::{Object, StandardSegment}; use object::{Bytes, LittleEndian, SectionKind, U32Bytes}; use std::convert::TryFrom; +use std::fmt; use std::ops::Range; /// A helper structure to build the custom-encoded section of a wasmtime @@ -26,29 +27,30 @@ pub struct TrapInformation { pub code_offset: u32, /// Code of the trap. - pub trap_code: TrapCode, + pub trap_code: Trap, } -/// A trap code describing the reason for a trap. -/// -/// All trap instructions have an explicit trap code. +// 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)] -#[repr(u8)] -pub enum TrapCode { +#[allow(missing_docs)] +pub enum Trap { /// The current stack space was exhausted. StackOverflow, - /// A `heap_addr` instruction detected an out-of-bounds error. - /// - /// Note that not all out-of-bounds heap accesses are reported this way; - /// some are detected by a segmentation fault on the heap unmapped or - /// offset-guard pages. - HeapOutOfBounds, + /// An out-of-bounds memory access. + MemoryOutOfBounds, /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address. HeapMisaligned, - /// A `table_addr` instruction detected an out-of-bounds error. + /// An out-of-bounds access to a table. TableOutOfBounds, /// Indirect call to a null table entry. @@ -70,15 +72,45 @@ pub enum TrapCode { UnreachableCodeReached, /// Execution has potentially run too long and may be interrupted. - /// This trap is resumable. Interrupt, - /// Used for the component model when functions are lifted/lowered in a way - /// that generates a function that always traps. + /// 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, - // if adding a variant here be sure to update the `check!` macro below + + /// When wasm code is configured to consume fuel and it runs out of fuel + /// then this trap will be raised. + OutOfFuel, } +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 {} + impl TrapEncodingBuilder { /// Appends trap information about a function into this section. /// @@ -136,7 +168,7 @@ impl TrapEncodingBuilder { /// The `section` provided is expected to have been built by /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative /// offset within the text section of the compilation image. -pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { +pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { let mut section = Bytes(section); // NB: this matches the encoding written by `append_to` above. let count = section.read::>().ok()?; @@ -164,16 +196,16 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { // FIXME: this could use some sort of derive-like thing to avoid having to // deduplicate the names here. // - // This simply converts from the `trap`, a `u8`, to the `TrapCode` enum. + // This simply converts from the `trap`, a `u8`, to the `Trap` enum. macro_rules! check { - ($($name:ident)*) => ($(if trap == TrapCode::$name as u8 { - return Some(TrapCode::$name); + ($($name:ident)*) => ($(if trap == Trap::$name as u8 { + return Some(Trap::$name); })*); } check! { StackOverflow - HeapOutOfBounds + MemoryOutOfBounds HeapMisaligned TableOutOfBounds IndirectCallToNull @@ -184,6 +216,7 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { UnreachableCodeReached Interrupt AlwaysTrapAdapter + OutOfFuel } if cfg!(debug_assertions) { diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 5675a79cd7..305d595580 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -30,7 +30,7 @@ use wasmtime_environ::{ packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex, GlobalInit, HostPtr, MemoryIndex, Module, PrimaryMap, SignatureIndex, TableIndex, - TableInitialization, TrapCode, VMOffsets, WasmType, + TableInitialization, Trap, VMOffsets, WasmType, }; mod allocator; @@ -580,7 +580,7 @@ impl Instance { dst: u32, src: u32, len: u32, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { // TODO: this `clone()` shouldn't be necessary but is used for now to // inform `rustc` that the lifetime of the elements here are // disconnected from the lifetime of `self`. @@ -602,7 +602,7 @@ impl Instance { dst: u32, src: u32, len: u32, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-init let table = unsafe { &mut *self.get_table(table_index) }; @@ -612,7 +612,7 @@ impl Instance { .and_then(|s| s.get(..usize::try_from(len).unwrap())) { Some(elements) => elements, - None => return Err(TrapCode::TableOutOfBounds), + None => return Err(Trap::TableOutOfBounds), }; match table.element_type() { @@ -662,7 +662,7 @@ impl Instance { src_index: MemoryIndex, src: u64, len: u64, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { // https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-memory-copy let src_mem = self.get_memory(src_index); @@ -684,8 +684,8 @@ impl Instance { Ok(()) } - fn validate_inbounds(&self, max: usize, ptr: u64, len: u64) -> Result { - let oob = || TrapCode::HeapOutOfBounds; + fn validate_inbounds(&self, max: usize, ptr: u64, len: u64) -> Result { + let oob = || Trap::MemoryOutOfBounds; let end = ptr .checked_add(len) .and_then(|i| usize::try_from(i).ok()) @@ -708,7 +708,7 @@ impl Instance { dst: u64, val: u8, len: u64, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { let memory = self.get_memory(memory_index); let dst = self.validate_inbounds(memory.current_length(), dst, len)?; @@ -738,7 +738,7 @@ impl Instance { dst: u64, src: u32, len: u32, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { let range = match self.module().passive_data_map.get(&data_index).cloned() { Some(range) if !self.dropped_data.contains(data_index) => range, _ => 0..0, @@ -757,7 +757,7 @@ impl Instance { dst: u64, src: u32, len: u32, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-memory-init let memory = self.get_memory(memory_index); diff --git a/crates/runtime/src/instance/allocator.rs b/crates/runtime/src/instance/allocator.rs index ebf4f80b0d..9f514b252a 100644 --- a/crates/runtime/src/instance/allocator.rs +++ b/crates/runtime/src/instance/allocator.rs @@ -13,8 +13,8 @@ use std::sync::Arc; use thiserror::Error; use wasmtime_environ::{ DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization, - MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, TrapCode, - VMOffsets, WasmType, WASM_PAGE_SIZE, + MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, Trap, VMOffsets, + WasmType, WASM_PAGE_SIZE, }; #[cfg(feature = "pooling-allocator")] @@ -105,7 +105,7 @@ pub enum InstantiationError { /// A trap ocurred during instantiation, after linking. #[error("Trap occurred during instantiation")] - Trap(TrapCode), + Trap(Trap), /// A limit on how many instances are supported has been reached. #[error("Limit of {0} concurrent instances has been reached")] @@ -386,7 +386,7 @@ fn initialize_memories(instance: &mut Instance, module: &Module) -> Result<(), I }, ); if !ok { - return Err(InstantiationError::Trap(TrapCode::HeapOutOfBounds)); + return Err(InstantiationError::Trap(Trap::MemoryOutOfBounds)); } Ok(()) diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index 6a7fb956ee..e4f6dcfd84 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -63,7 +63,7 @@ use anyhow::Result; use std::mem; use std::ptr::{self, NonNull}; use wasmtime_environ::{ - DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TrapCode, + DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, Trap, }; /// Actually public trampolines which are used by the runtime as the entrypoint @@ -228,7 +228,7 @@ unsafe fn table_fill( // `VMCallerCheckedAnyfunc` until we look at the table's element type. val: *mut u8, len: u32, -) -> Result<(), TrapCode> { +) -> Result<(), Trap> { let instance = (*vmctx).instance_mut(); let table_index = TableIndex::from_u32(table_index); let table = &mut *instance.get_table(table_index); @@ -259,7 +259,7 @@ unsafe fn table_copy( dst: u32, src: u32, len: u32, -) -> Result<(), TrapCode> { +) -> Result<(), Trap> { let dst_table_index = TableIndex::from_u32(dst_table_index); let src_table_index = TableIndex::from_u32(src_table_index); let instance = (*vmctx).instance_mut(); @@ -278,7 +278,7 @@ unsafe fn table_init( dst: u32, src: u32, len: u32, -) -> Result<(), TrapCode> { +) -> Result<(), Trap> { let table_index = TableIndex::from_u32(table_index); let elem_index = ElemIndex::from_u32(elem_index); let instance = (*vmctx).instance_mut(); @@ -300,7 +300,7 @@ unsafe fn memory_copy( src_index: u32, src: u64, len: u64, -) -> Result<(), TrapCode> { +) -> Result<(), Trap> { let src_index = MemoryIndex::from_u32(src_index); let dst_index = MemoryIndex::from_u32(dst_index); let instance = (*vmctx).instance_mut(); @@ -314,7 +314,7 @@ unsafe fn memory_fill( dst: u64, val: u32, len: u64, -) -> Result<(), TrapCode> { +) -> Result<(), Trap> { let memory_index = MemoryIndex::from_u32(memory_index); let instance = (*vmctx).instance_mut(); instance.memory_fill(memory_index, dst, val as u8, len) @@ -328,7 +328,7 @@ unsafe fn memory_init( dst: u64, src: u32, len: u32, -) -> Result<(), TrapCode> { +) -> Result<(), Trap> { let memory_index = MemoryIndex::from_u32(memory_index); let data_index = DataIndex::from_u32(data_index); let instance = (*vmctx).instance_mut(); @@ -498,14 +498,14 @@ unsafe fn validate_atomic_addr( addr: u64, access_size: u64, access_alignment: u64, -) -> Result<(), TrapCode> { +) -> Result<(), Trap> { debug_assert!(access_alignment.is_power_of_two()); - ensure!(addr % access_alignment == 0, TrapCode::HeapMisaligned); + ensure!(addr % access_alignment == 0, Trap::HeapMisaligned); let length = u64::try_from(instance.get_memory(memory).current_length()).unwrap(); ensure!( addr.saturating_add(access_size) < length, - TrapCode::HeapOutOfBounds + Trap::MemoryOutOfBounds ); Ok(()) diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index 70bea7014f..ce73898866 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -8,7 +8,7 @@ use anyhow::{bail, format_err, Error, Result}; use std::convert::{TryFrom, TryInto}; use std::ops::Range; use std::ptr; -use wasmtime_environ::{TablePlan, TrapCode, WasmType, FUNCREF_INIT_BIT, FUNCREF_MASK}; +use wasmtime_environ::{TablePlan, Trap, WasmType, FUNCREF_INIT_BIT, FUNCREF_MASK}; /// An element going into or coming out of a table. /// @@ -267,7 +267,7 @@ impl Table { &mut self, dst: u32, items: impl ExactSizeIterator, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { assert!(self.element_type() == TableElementType::Func); let elements = match self @@ -276,7 +276,7 @@ impl Table { .and_then(|s| s.get_mut(..items.len())) { Some(elements) => elements, - None => return Err(TrapCode::TableOutOfBounds), + None => return Err(Trap::TableOutOfBounds), }; for (item, slot) in items.zip(elements) { @@ -290,14 +290,14 @@ impl Table { /// Fill `table[dst..dst + len]` with `val`. /// /// Returns a trap error on out-of-bounds accesses. - pub fn fill(&mut self, dst: u32, val: TableElement, len: u32) -> Result<(), TrapCode> { + pub fn fill(&mut self, dst: u32, val: TableElement, len: u32) -> Result<(), Trap> { let start = dst as usize; let end = start .checked_add(len as usize) - .ok_or_else(|| TrapCode::TableOutOfBounds)?; + .ok_or_else(|| Trap::TableOutOfBounds)?; if end > self.size() as usize { - return Err(TrapCode::TableOutOfBounds); + return Err(Trap::TableOutOfBounds); } debug_assert!(self.type_matches(&val)); @@ -412,7 +412,7 @@ impl Table { dst_index: u32, src_index: u32, len: u32, - ) -> Result<(), TrapCode> { + ) -> Result<(), Trap> { // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy if src_index @@ -422,7 +422,7 @@ impl Table { .checked_add(len) .map_or(true, |m| m > (*dst_table).size()) { - return Err(TrapCode::TableOutOfBounds); + return Err(Trap::TableOutOfBounds); } debug_assert!( diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index f9772df2aa..215bfddc06 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -10,7 +10,6 @@ use std::cell::{Cell, UnsafeCell}; use std::mem::MaybeUninit; use std::ptr; use std::sync::Once; -use wasmtime_environ::TrapCode; pub use self::backtrace::Backtrace; pub use self::tls::{tls_eager_initialize, TlsRestore}; @@ -112,7 +111,7 @@ pub unsafe fn raise_user_trap(error: Error, needs_backtrace: bool) -> ! { /// Only safe to call when wasm code is on the stack, aka `catch_traps` must /// have been previously called. Additionally no Rust destructors can be on the /// stack. They will be skipped and not executed. -pub unsafe fn raise_lib_trap(trap: TrapCode) -> ! { +pub unsafe fn raise_lib_trap(trap: wasmtime_environ::Trap) -> ! { raise_trap(TrapReason::Wasm(trap)) } @@ -153,7 +152,7 @@ pub enum TrapReason { Jit(usize), /// A trap raised from a wasm libcall - Wasm(TrapCode), + Wasm(wasmtime_environ::Trap), } impl TrapReason { @@ -185,8 +184,8 @@ impl From for TrapReason { } } -impl From for TrapReason { - fn from(code: TrapCode) -> Self { +impl From for TrapReason { + fn from(code: wasmtime_environ::Trap) -> Self { TrapReason::Wasm(code) } } diff --git a/crates/wasmtime/src/component/func/host.rs b/crates/wasmtime/src/component/func/host.rs index 023acba474..a30e01bbd3 100644 --- a/crates/wasmtime/src/component/func/host.rs +++ b/crates/wasmtime/src/component/func/host.rs @@ -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(memory: &[u8], ptr: &ValRaw) -> Result 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), } } diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 177edc18a2..50eb511398 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -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(()) diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index ecc3f22b44..43cc546e36 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -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( @@ -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( ); 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), } } diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index 970fb030aa..a0c5869cca 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -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::()`. 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(), } })?; diff --git a/crates/wasmtime/src/module/registry.rs b/crates/wasmtime/src/module/registry.rs index d57ad218ce..21bae8c5a8 100644 --- a/crates/wasmtime/src/module/registry.rs +++ b/crates/wasmtime/src/module/registry.rs @@ -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 { + pub fn lookup_trap_code(&self, pc: usize) -> Option { let (code, offset) = self.code(pc)?; wasmtime_environ::lookup_trap_code(code.code.code_memory().trap_data(), offset) } diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index 8799cf85e4..587990759c 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -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( // 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 diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 7b2631e9bc..8c1d21aa44 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -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::().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::().is_none(); - wasmtime_runtime::raise_user_trap(error, needs_backtrace) - } - - #[cold] // traps are exceptional, this helps move handling off the main path - pub(crate) fn from_runtime_box( - store: &StoreOpaque, - runtime_trap: Box, - ) -> Error { - let wasmtime_runtime::Trap { reason, backtrace } = *runtime_trap; - let (error, pc) = match reason { - // For user-defined errors they're already an `anyhow::Error` so no - // conversion is really necessary here, but a `backtrace` may have - // been captured so it's attempted to get inserted here. - // - // If the error is actually a `Trap` then the backtrace is inserted - // directly into the `Trap` since there's storage there for it. - // Otherwise though this represents a host-defined error which isn't - // using a `Trap` but instead some other condition that was fatal to - // wasm itself. In that situation the backtrace is inserted as - // contextual information on error using `error.context(...)` to - // provide useful information to debug with for the embedder/caller, - // otherwise the information about what the wasm was doing when the - // error was generated would be lost. - wasmtime_runtime::TrapReason::User { - error, - needs_backtrace, - } => { - debug_assert!(needs_backtrace == backtrace.is_some()); - (error, None) - } - wasmtime_runtime::TrapReason::Jit(pc) => { - let code = store - .modules() - .lookup_trap_code(pc) - .unwrap_or(TrapCode::StackOverflow); - (Trap::from_env(code).into(), Some(pc)) - } - wasmtime_runtime::TrapReason::Wasm(trap_code) => { - (Trap::from_env(trap_code).into(), None) - } - }; - match backtrace { - Some(bt) => { - let bt = WasmBacktrace::new(store, bt, pc); - if bt.wasm_trace.is_empty() { - error - } else { - error.context(bt) - } - } - None => error, +#[cold] // traps are exceptional, this helps move handling off the main path +pub(crate) fn from_runtime_box( + store: &StoreOpaque, + runtime_trap: Box, +) -> Error { + let wasmtime_runtime::Trap { reason, backtrace } = *runtime_trap; + let (error, pc) = match reason { + // For user-defined errors they're already an `anyhow::Error` so no + // conversion is really necessary here, but a `backtrace` may have + // been captured so it's attempted to get inserted here. + // + // If the error is actually a `Trap` then the backtrace is inserted + // directly into the `Trap` since there's storage there for it. + // Otherwise though this represents a host-defined error which isn't + // using a `Trap` but instead some other condition that was fatal to + // wasm itself. In that situation the backtrace is inserted as + // contextual information on error using `error.context(...)` to + // provide useful information to debug with for the embedder/caller, + // otherwise the information about what the wasm was doing when the + // error was generated would be lost. + wasmtime_runtime::TrapReason::User { + error, + needs_backtrace, + } => { + debug_assert!(needs_backtrace == backtrace.is_some()); + (error, None) } - } - - /// 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. ///