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:
Leonardo Yvens
2020-10-27 18:30:45 -03:00
committed by GitHub
parent f6d5b8772c
commit bde9555793
4 changed files with 141 additions and 27 deletions

View File

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

View File

@@ -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::*;

View File

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

View File

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