From 84cf7827b17a96c6699fb471ff2927b0f314dd17 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Thu, 22 Oct 2020 12:47:33 -0300 Subject: [PATCH 1/4] add Trap::trap_code --- crates/wasmtime/src/trap.rs | 54 ++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 02f65774632a..ee306b4a3ec5 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -23,14 +23,36 @@ enum TrapReason { /// A structured error describing a trap. Error(Box), + + /// A specific code for a trap triggered while executing WASM. + /// This is never `TrapCode::User`. + InstructionTrap(TrapCode), } impl fmt::Display for TrapReason { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use wasmtime_environ::ir::TrapCode::*; match self { TrapReason::Message(s) => write!(f, "{}", s), TrapReason::I32Exit(status) => write!(f, "Exited with i32 exit status {}", status), TrapReason::Error(e) => write!(f, "{}", e), + TrapReason::InstructionTrap(code) => { + let desc = match code { + 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!(), + }; + write!(f, "wasm trap: {}", desc) + } } } } @@ -105,23 +127,8 @@ impl Trap { code: TrapCode, backtrace: Backtrace, ) -> Self { - use wasmtime_environ::ir::TrapCode::*; - let desc = match code { - 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) + assert!(!matches!(code, TrapCode::User(_))); + Trap::new_with_trace(info, trap_pc, TrapReason::InstructionTrap(code), backtrace) } fn new_with_trace( @@ -174,6 +181,15 @@ impl Trap { pub fn trace(&self) -> &[FrameInfo] { &self.inner.wasm_trace } + + /// Code of a trap that happened while executing a WASM instruction. + /// This is never `TrapCode::User`. + pub fn trap_code(&self) -> Option { + match self.inner.reason { + TrapReason::InstructionTrap(code) => Some(code), + _ => None, + } + } } impl fmt::Debug for Trap { @@ -214,7 +230,9 @@ impl std::error::Error for Trap { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self.inner.reason { TrapReason::Error(e) => e.source(), - TrapReason::I32Exit(_) | TrapReason::Message(_) => None, + TrapReason::I32Exit(_) | TrapReason::Message(_) | TrapReason::InstructionTrap(_) => { + None + } } } } From 9dcb93bca2c8022ee872081e50435ebaedd5bcbd Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Fri, 23 Oct 2020 18:24:43 -0300 Subject: [PATCH 2/4] Add non-exhaustive wasmtime::TrapCode --- crates/wasmtime/src/trap.rs | 121 ++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 26 deletions(-) diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index ee306b4a3ec5..59372d4bdbb0 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -3,7 +3,7 @@ use crate::FrameInfo; use backtrace::Backtrace; use std::fmt; use std::sync::Arc; -use wasmtime_environ::ir::TrapCode; +use wasmtime_environ::ir; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -25,38 +25,108 @@ enum TrapReason { Error(Box), /// A specific code for a trap triggered while executing WASM. - /// This is never `TrapCode::User`. InstructionTrap(TrapCode), } impl fmt::Display for TrapReason { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use wasmtime_environ::ir::TrapCode::*; match self { TrapReason::Message(s) => write!(f, "{}", s), TrapReason::I32Exit(status) => write!(f, "Exited with i32 exit status {}", status), TrapReason::Error(e) => write!(f, "{}", e), - TrapReason::InstructionTrap(code) => { - let desc = match code { - 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!(), - }; - write!(f, "wasm trap: {}", desc) - } + 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. + /// + /// On some platforms, a stack overflow may also be indicated by a segmentation fault from the + /// stack guard page. + 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, + + /// 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. + 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. + /// This trap is resumable. + 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::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(_) => 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", + 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", + }; + write!(f, "{}", desc) + } +} + struct TrapInner { reason: TrapReason, wasm_trace: Vec, @@ -104,9 +174,9 @@ impl Trap { let mut code = info .lookup_trap_info(pc) .map(|info| info.trap_code) - .unwrap_or(TrapCode::StackOverflow); - if maybe_interrupted && code == TrapCode::StackOverflow { - code = TrapCode::Interrupt; + .unwrap_or(ir::TrapCode::StackOverflow); + if maybe_interrupted && code == ir::TrapCode::StackOverflow { + code = ir::TrapCode::Interrupt; } Trap::new_wasm(&info, Some(pc), code, backtrace) } @@ -124,10 +194,10 @@ impl Trap { fn new_wasm( info: &GlobalFrameInfo, trap_pc: Option, - code: TrapCode, + code: ir::TrapCode, backtrace: Backtrace, ) -> Self { - assert!(!matches!(code, TrapCode::User(_))); + let code = TrapCode::from_non_user(code); Trap::new_with_trace(info, trap_pc, TrapReason::InstructionTrap(code), backtrace) } @@ -183,7 +253,6 @@ impl Trap { } /// Code of a trap that happened while executing a WASM instruction. - /// This is never `TrapCode::User`. pub fn trap_code(&self) -> Option { match self.inner.reason { TrapReason::InstructionTrap(code) => Some(code), From 5c718f295e7bbd18c070a20fd625dcfc32df0240 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Tue, 27 Oct 2020 14:00:47 -0300 Subject: [PATCH 3/4] wasmtime: Better document TrapCode --- cranelift/codegen/src/ir/trapcode.rs | 3 -- crates/wasmtime/src/lib.rs | 2 +- crates/wasmtime/src/trap.rs | 60 ++++++++++++++++++++++------ 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/cranelift/codegen/src/ir/trapcode.rs b/cranelift/codegen/src/ir/trapcode.rs index 612c979a0abf..3114114f6dc6 100644 --- a/cranelift/codegen/src/ir/trapcode.rs +++ b/cranelift/codegen/src/ir/trapcode.rs @@ -12,9 +12,6 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum TrapCode { /// 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, /// A `heap_addr` instruction detected an out-of-bounds error. diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index e914cb724c56..d59d21d65aca 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -255,7 +255,7 @@ pub use crate::linker::*; pub use crate::module::Module; pub use crate::r#ref::ExternRef; pub use crate::runtime::*; -pub use crate::trap::Trap; +pub use crate::trap::*; pub use crate::types::*; pub use crate::values::*; diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 59372d4bdbb0..18ba262a38bf 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -46,22 +46,15 @@ impl fmt::Display for TrapReason { #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum TrapCode { /// 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, - /// 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. @@ -83,7 +76,6 @@ pub enum TrapCode { UnreachableCodeReached, /// Execution has potentially run too long and may be interrupted. - /// This trap is resumable. Interrupt, } @@ -92,7 +84,7 @@ impl TrapCode { fn from_non_user(code: ir::TrapCode) -> Self { match code { ir::TrapCode::StackOverflow => TrapCode::StackOverflow, - ir::TrapCode::HeapOutOfBounds => TrapCode::HeapOutOfBounds, + ir::TrapCode::HeapOutOfBounds => TrapCode::MemoryOutOfBounds, ir::TrapCode::HeapMisaligned => TrapCode::HeapMisaligned, ir::TrapCode::TableOutOfBounds => TrapCode::TableOutOfBounds, ir::TrapCode::IndirectCallToNull => TrapCode::IndirectCallToNull, @@ -112,7 +104,7 @@ impl fmt::Display for TrapCode { use TrapCode::*; let desc = match self { StackOverflow => "call stack exhausted", - HeapOutOfBounds => "out of bounds memory access", + MemoryOutOfBounds => "out of bounds memory access", HeapMisaligned => "misaligned memory access", TableOutOfBounds => "undefined element: out of bounds table access", IndirectCallToNull => "uninitialized element", @@ -253,6 +245,7 @@ impl Trap { } /// 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 { match self.inner.reason { TrapReason::InstructionTrap(code) => Some(code), @@ -324,3 +317,44 @@ impl From> for Trap { } } } + +#[test] +fn heap_out_of_bounds_trap() { + let store = crate::Store::default(); + let module = crate::Module::new( + store.engine(), + r#" + (module + (memory 0) + (func $start (drop (i32.load (i32.const 1000000)))) + (start $start) + ) + "#, + ) + .unwrap(); + + let err = match crate::Instance::new(&store, &module, &[]) { + Ok(_) => unreachable!(), + Err(e) => e, + }; + let trap = err.downcast_ref::().unwrap(); + assert_eq!(trap.trap_code(), Some(TrapCode::MemoryOutOfBounds)); + + let module = crate::Module::new( + store.engine(), + r#" + (module + (memory 0) + (func $start (drop (i32.load memory.size))) + (start $start) + ) + "#, + ) + .unwrap(); + let err = match crate::Instance::new(&store, &module, &[]) { + Ok(_) => unreachable!(), + Err(e) => e, + }; + let trap = err.downcast_ref::().unwrap(); + assert_eq!(trap.trap_code(), Some(TrapCode::MemoryOutOfBounds)); +} From 123d69fdeed3344c51bbcd876c0efca5ddd9847a Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Tue, 27 Oct 2020 17:43:53 -0300 Subject: [PATCH 4/4] move and refactor test --- crates/wasmtime/src/trap.rs | 41 ------------------------------------- tests/all/traps.rs | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 18ba262a38bf..a33d22679a93 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -317,44 +317,3 @@ impl From> for Trap { } } } - -#[test] -fn heap_out_of_bounds_trap() { - let store = crate::Store::default(); - let module = crate::Module::new( - store.engine(), - r#" - (module - (memory 0) - (func $start (drop (i32.load (i32.const 1000000)))) - (start $start) - ) - "#, - ) - .unwrap(); - - let err = match crate::Instance::new(&store, &module, &[]) { - Ok(_) => unreachable!(), - Err(e) => e, - }; - let trap = err.downcast_ref::().unwrap(); - assert_eq!(trap.trap_code(), Some(TrapCode::MemoryOutOfBounds)); - - let module = crate::Module::new( - store.engine(), - r#" - (module - (memory 0) - (func $start (drop (i32.load memory.size))) - (start $start) - ) - "#, - ) - .unwrap(); - let err = match crate::Instance::new(&store, &module, &[]) { - Ok(_) => unreachable!(), - Err(e) => e, - }; - let trap = err.downcast_ref::().unwrap(); - assert_eq!(trap.trap_code(), Some(TrapCode::MemoryOutOfBounds)); -} diff --git a/tests/all/traps.rs b/tests/all/traps.rs index c85cd9864643..51e83af5e50d 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -452,3 +452,40 @@ fn present_after_module_drop() -> Result<()> { 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::().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, + ); +}