Path: blob/main/crates/environ/src/trap_encoding.rs
1691 views
use core::fmt;1use object::{Bytes, LittleEndian, U32Bytes};23/// Information about trap.4#[derive(Debug, PartialEq, Eq, Clone)]5pub struct TrapInformation {6/// The offset of the trapping instruction in native code.7///8/// This is relative to the beginning of the function.9pub code_offset: u32,1011/// Code of the trap.12pub trap_code: Trap,13}1415// The code can be accessed from the c-api, where the possible values are16// translated into enum values defined there:17//18// * `wasm_trap_code` in c-api/src/trap.rs, and19// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.20//21// These need to be kept in sync.22#[non_exhaustive]23#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]24#[expect(missing_docs, reason = "self-describing variants")]25pub enum Trap {26/// The current stack space was exhausted.27StackOverflow,2829/// An out-of-bounds memory access.30MemoryOutOfBounds,3132/// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.33HeapMisaligned,3435/// An out-of-bounds access to a table.36TableOutOfBounds,3738/// Indirect call to a null table entry.39IndirectCallToNull,4041/// Signature mismatch on indirect call.42BadSignature,4344/// An integer arithmetic operation caused an overflow.45IntegerOverflow,4647/// An integer division by zero.48IntegerDivisionByZero,4950/// Failed float-to-int conversion.51BadConversionToInteger,5253/// Code that was supposed to have been unreachable was reached.54UnreachableCodeReached,5556/// Execution has potentially run too long and may be interrupted.57Interrupt,5859/// When the `component-model` feature is enabled this trap represents a60/// function that was `canon lift`'d, then `canon lower`'d, then called.61/// This combination of creation of a function in the component model62/// generates a function that always traps and, when called, produces this63/// flavor of trap.64AlwaysTrapAdapter,6566/// When wasm code is configured to consume fuel and it runs out of fuel67/// then this trap will be raised.68OutOfFuel,6970/// Used to indicate that a trap was raised by atomic wait operations on non shared memory.71AtomicWaitNonSharedMemory,7273/// Call to a null reference.74NullReference,7576/// Attempt to access beyond the bounds of an array.77ArrayOutOfBounds,7879/// Attempted an allocation that was too large to succeed.80AllocationTooLarge,8182/// Attempted to cast a reference to a type that it is not an instance of.83CastFailure,8485/// When the `component-model` feature is enabled this trap represents a86/// scenario where one component tried to call another component but it87/// would have violated the reentrance rules of the component model,88/// triggering a trap instead.89CannotEnterComponent,9091/// Async-lifted export failed to produce a result by calling `task.return`92/// before returning `STATUS_DONE` and/or after all host tasks completed.93NoAsyncResult,9495/// We are suspending to a tag for which there is no active handler.96UnhandledTag,9798/// Attempt to resume a continuation twice.99ContinuationAlreadyConsumed,100101/// A Pulley opcode was executed at runtime when the opcode was disabled at102/// compile time.103DisabledOpcode,104105/// Async event loop deadlocked; i.e. it cannot make further progress given106/// that all host tasks have completed and any/all host-owned stream/future107/// handles have been dropped.108AsyncDeadlock,109// if adding a variant here be sure to update the `check!` macro below110}111112impl Trap {113/// Converts a byte back into a `Trap` if its in-bounds114pub fn from_u8(byte: u8) -> Option<Trap> {115// FIXME: this could use some sort of derive-like thing to avoid having to116// deduplicate the names here.117//118// This simply converts from the a `u8`, to the `Trap` enum.119macro_rules! check {120($($name:ident)*) => ($(if byte == Trap::$name as u8 {121return Some(Trap::$name);122})*);123}124125check! {126StackOverflow127MemoryOutOfBounds128HeapMisaligned129TableOutOfBounds130IndirectCallToNull131BadSignature132IntegerOverflow133IntegerDivisionByZero134BadConversionToInteger135UnreachableCodeReached136Interrupt137AlwaysTrapAdapter138OutOfFuel139AtomicWaitNonSharedMemory140NullReference141ArrayOutOfBounds142AllocationTooLarge143CastFailure144CannotEnterComponent145NoAsyncResult146UnhandledTag147ContinuationAlreadyConsumed148DisabledOpcode149AsyncDeadlock150}151152None153}154}155156impl fmt::Display for Trap {157fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {158use Trap::*;159160let desc = match self {161StackOverflow => "call stack exhausted",162MemoryOutOfBounds => "out of bounds memory access",163HeapMisaligned => "unaligned atomic",164TableOutOfBounds => "undefined element: out of bounds table access",165IndirectCallToNull => "uninitialized element",166BadSignature => "indirect call type mismatch",167IntegerOverflow => "integer overflow",168IntegerDivisionByZero => "integer divide by zero",169BadConversionToInteger => "invalid conversion to integer",170UnreachableCodeReached => "wasm `unreachable` instruction executed",171Interrupt => "interrupt",172AlwaysTrapAdapter => "degenerate component adapter called",173OutOfFuel => "all fuel consumed by WebAssembly",174AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",175NullReference => "null reference",176ArrayOutOfBounds => "out of bounds array access",177AllocationTooLarge => "allocation size too large",178CastFailure => "cast failure",179CannotEnterComponent => "cannot enter component instance",180NoAsyncResult => "async-lifted export failed to produce a result",181UnhandledTag => "unhandled tag",182ContinuationAlreadyConsumed => "continuation already consumed",183DisabledOpcode => "pulley opcode disabled at compile time was executed",184AsyncDeadlock => "deadlock detected: event loop cannot make further progress",185};186write!(f, "wasm trap: {desc}")187}188}189190impl core::error::Error for Trap {}191192/// Decodes the provided trap information section and attempts to find the trap193/// code corresponding to the `offset` specified.194///195/// The `section` provided is expected to have been built by196/// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative197/// offset within the text section of the compilation image.198pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {199let (offsets, traps) = parse(section)?;200201// The `offsets` table is sorted in the trap section so perform a binary202// search of the contents of this section to find whether `offset` is an203// entry in the section. Note that this is a precise search because trap pcs204// should always be precise as well as our metadata about them, which means205// we expect an exact match to correspond to a trap opcode.206//207// Once an index is found within the `offsets` array then that same index is208// used to lookup from the `traps` list of bytes to get the trap code byte209// corresponding to this offset.210let offset = u32::try_from(offset).ok()?;211let index = offsets212.binary_search_by_key(&offset, |val| val.get(LittleEndian))213.ok()?;214debug_assert!(index < traps.len());215let byte = *traps.get(index)?;216217let trap = Trap::from_u8(byte);218debug_assert!(trap.is_some(), "missing mapping for {byte}");219trap220}221222fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> {223let mut section = Bytes(section);224// NB: this matches the encoding written by `append_to` above.225let count = section.read::<U32Bytes<LittleEndian>>().ok()?;226let count = usize::try_from(count.get(LittleEndian)).ok()?;227let (offsets, traps) =228object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;229debug_assert_eq!(traps.len(), count);230Some((offsets, traps))231}232233/// Returns an iterator over all of the traps encoded in `section`, which should234/// have been produced by `TrapEncodingBuilder`.235pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> {236let (offsets, traps) = parse(section)?;237Some(238offsets239.iter()240.zip(traps)241.map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())),242)243}244245246