Add Trap::trap_code (#2309)
* add Trap::trap_code * Add non-exhaustive wasmtime::TrapCode * wasmtime: Better document TrapCode * move and refactor test
This commit is contained in:
@@ -12,9 +12,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
||||||
pub enum TrapCode {
|
pub enum TrapCode {
|
||||||
/// The current stack space was exhausted.
|
/// The current stack space was exhausted.
|
||||||
///
|
|
||||||
/// On some platforms, a stack overflow may also be indicated by a segmentation fault from the
|
|
||||||
/// stack guard page.
|
|
||||||
StackOverflow,
|
StackOverflow,
|
||||||
|
|
||||||
/// A `heap_addr` instruction detected an out-of-bounds error.
|
/// A `heap_addr` instruction detected an out-of-bounds error.
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ pub use crate::linker::*;
|
|||||||
pub use crate::module::Module;
|
pub use crate::module::Module;
|
||||||
pub use crate::r#ref::ExternRef;
|
pub use crate::r#ref::ExternRef;
|
||||||
pub use crate::runtime::*;
|
pub use crate::runtime::*;
|
||||||
pub use crate::trap::Trap;
|
pub use crate::trap::*;
|
||||||
pub use crate::types::*;
|
pub use crate::types::*;
|
||||||
pub use crate::values::*;
|
pub use crate::values::*;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::FrameInfo;
|
|||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::ir::TrapCode;
|
use wasmtime_environ::ir;
|
||||||
|
|
||||||
/// A struct representing an aborted instruction execution, with a message
|
/// A struct representing an aborted instruction execution, with a message
|
||||||
/// indicating the cause.
|
/// indicating the cause.
|
||||||
@@ -23,6 +23,9 @@ enum TrapReason {
|
|||||||
|
|
||||||
/// A structured error describing a trap.
|
/// A structured error describing a trap.
|
||||||
Error(Box<dyn std::error::Error + Send + Sync>),
|
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 {
|
impl fmt::Display for TrapReason {
|
||||||
@@ -31,10 +34,91 @@ impl fmt::Display for TrapReason {
|
|||||||
TrapReason::Message(s) => write!(f, "{}", s),
|
TrapReason::Message(s) => write!(f, "{}", s),
|
||||||
TrapReason::I32Exit(status) => write!(f, "Exited with i32 exit status {}", status),
|
TrapReason::I32Exit(status) => write!(f, "Exited with i32 exit status {}", status),
|
||||||
TrapReason::Error(e) => write!(f, "{}", e),
|
TrapReason::Error(e) => write!(f, "{}", e),
|
||||||
|
TrapReason::InstructionTrap(code) => write!(f, "wasm trap: {}", code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trap code describing the reason for a trap.
|
||||||
|
///
|
||||||
|
/// All trap instructions have an explicit trap code.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
|
pub enum TrapCode {
|
||||||
|
/// The current stack space was exhausted.
|
||||||
|
StackOverflow,
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrapCode {
|
||||||
|
/// Panics if `code` is `ir::TrapCode::User`.
|
||||||
|
fn from_non_user(code: ir::TrapCode) -> Self {
|
||||||
|
match code {
|
||||||
|
ir::TrapCode::StackOverflow => TrapCode::StackOverflow,
|
||||||
|
ir::TrapCode::HeapOutOfBounds => TrapCode::MemoryOutOfBounds,
|
||||||
|
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(_) => panic!("Called `TrapCode::from_non_user` with user code"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TrapCode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
use TrapCode::*;
|
||||||
|
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 => "unreachable",
|
||||||
|
Interrupt => "interrupt",
|
||||||
|
};
|
||||||
|
write!(f, "{}", desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct TrapInner {
|
struct TrapInner {
|
||||||
reason: TrapReason,
|
reason: TrapReason,
|
||||||
wasm_trace: Vec<FrameInfo>,
|
wasm_trace: Vec<FrameInfo>,
|
||||||
@@ -82,9 +166,9 @@ impl Trap {
|
|||||||
let mut code = info
|
let mut code = info
|
||||||
.lookup_trap_info(pc)
|
.lookup_trap_info(pc)
|
||||||
.map(|info| info.trap_code)
|
.map(|info| info.trap_code)
|
||||||
.unwrap_or(TrapCode::StackOverflow);
|
.unwrap_or(ir::TrapCode::StackOverflow);
|
||||||
if maybe_interrupted && code == TrapCode::StackOverflow {
|
if maybe_interrupted && code == ir::TrapCode::StackOverflow {
|
||||||
code = TrapCode::Interrupt;
|
code = ir::TrapCode::Interrupt;
|
||||||
}
|
}
|
||||||
Trap::new_wasm(&info, Some(pc), code, backtrace)
|
Trap::new_wasm(&info, Some(pc), code, backtrace)
|
||||||
}
|
}
|
||||||
@@ -102,26 +186,11 @@ impl Trap {
|
|||||||
fn new_wasm(
|
fn new_wasm(
|
||||||
info: &GlobalFrameInfo,
|
info: &GlobalFrameInfo,
|
||||||
trap_pc: Option<usize>,
|
trap_pc: Option<usize>,
|
||||||
code: TrapCode,
|
code: ir::TrapCode,
|
||||||
backtrace: Backtrace,
|
backtrace: Backtrace,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
use wasmtime_environ::ir::TrapCode::*;
|
let code = TrapCode::from_non_user(code);
|
||||||
let desc = match code {
|
Trap::new_with_trace(info, trap_pc, TrapReason::InstructionTrap(code), backtrace)
|
||||||
StackOverflow => "call stack exhausted",
|
|
||||||
HeapOutOfBounds => "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 => "unreachable",
|
|
||||||
Interrupt => "interrupt",
|
|
||||||
User(_) => unreachable!(),
|
|
||||||
};
|
|
||||||
let msg = TrapReason::Message(format!("wasm trap: {}", desc));
|
|
||||||
Trap::new_with_trace(info, trap_pc, msg, backtrace)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_with_trace(
|
fn new_with_trace(
|
||||||
@@ -174,6 +243,15 @@ impl Trap {
|
|||||||
pub fn trace(&self) -> &[FrameInfo] {
|
pub fn trace(&self) -> &[FrameInfo] {
|
||||||
&self.inner.wasm_trace
|
&self.inner.wasm_trace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Trap {
|
impl fmt::Debug for Trap {
|
||||||
@@ -214,7 +292,9 @@ impl std::error::Error for Trap {
|
|||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match &self.inner.reason {
|
match &self.inner.reason {
|
||||||
TrapReason::Error(e) => e.source(),
|
TrapReason::Error(e) => e.source(),
|
||||||
TrapReason::I32Exit(_) | TrapReason::Message(_) => None,
|
TrapReason::I32Exit(_) | TrapReason::Message(_) | TrapReason::InstructionTrap(_) => {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -452,3 +452,40 @@ fn present_after_module_drop() -> Result<()> {
|
|||||||
assert_eq!(t.trace()[0].func_index(), 0);
|
assert_eq!(t.trace()[0].func_index(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_trap_code(wat: &str, code: wasmtime::TrapCode) {
|
||||||
|
let store = Store::default();
|
||||||
|
let module = Module::new(store.engine(), wat).unwrap();
|
||||||
|
|
||||||
|
let err = match Instance::new(&store, &module, &[]) {
|
||||||
|
Ok(_) => unreachable!(),
|
||||||
|
Err(e) => e,
|
||||||
|
};
|
||||||
|
let trap = err.downcast_ref::<Trap>().unwrap();
|
||||||
|
assert_eq!(trap.trap_code(), Some(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn heap_out_of_bounds_trap() {
|
||||||
|
assert_trap_code(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(memory 0)
|
||||||
|
(func $start (drop (i32.load (i32.const 1000000))))
|
||||||
|
(start $start)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
TrapCode::MemoryOutOfBounds,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_trap_code(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(memory 0)
|
||||||
|
(func $start (drop (i32.load memory.size)))
|
||||||
|
(start $start)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
TrapCode::MemoryOutOfBounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user