From 706ddabf61ea23b66c461ea40068d56011847e1e Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 22 Apr 2019 15:06:40 -0700 Subject: [PATCH 1/4] Hook up error propagation --- lib/clif-backend/src/signal/mod.rs | 26 +++++++-- lib/clif-backend/src/signal/unix.rs | 47 +++++---------- lib/clif-backend/src/signal/windows.rs | 56 +++++++----------- lib/llvm-backend/build.rs | 2 + lib/llvm-backend/cpp/object_loader.hh | 15 ++++- lib/llvm-backend/src/backend.rs | 1 + lib/runtime-core/src/error.rs | 12 ++-- lib/runtime-core/src/instance.rs | 12 +++- lib/runtime-core/src/typed_func.rs | 67 ++++++++++++++++------ lib/runtime/examples/call.rs | 19 +++++- lib/singlepass-backend/src/codegen_x64.rs | 11 +++- lib/singlepass-backend/src/protect_unix.rs | 42 ++++++++------ lib/wasi/src/syscalls/mod.rs | 3 +- 13 files changed, 187 insertions(+), 126 deletions(-) diff --git a/lib/clif-backend/src/signal/mod.rs b/lib/clif-backend/src/signal/mod.rs index 8081f7cf849..660a8ed1513 100644 --- a/lib/clif-backend/src/signal/mod.rs +++ b/lib/clif-backend/src/signal/mod.rs @@ -27,6 +27,11 @@ thread_local! { pub static TRAP_EARLY_DATA: Cell>> = Cell::new(None); } +pub enum RunErr { + Trap(WasmTrapInfo), + Error(Box), +} + pub struct Caller { handler_data: HandlerData, trampolines: Arc, @@ -59,7 +64,8 @@ impl RunnableModule for Caller { func: NonNull, args: *const u64, rets: *mut u64, - _trap_info: *mut WasmTrapInfo, + trap_info: *mut WasmTrapInfo, + user_error: *mut Option>, invoke_env: Option>, ) -> bool { let handler_data = &*invoke_env.unwrap().cast().as_ptr(); @@ -68,14 +74,22 @@ impl RunnableModule for Caller { let res = call_protected(handler_data, || { // Leap of faith. trampoline(ctx, func, args, rets); - }) - .is_ok(); + }); // the trampoline is called from C on windows #[cfg(target_os = "windows")] - let res = call_protected(handler_data, trampoline, ctx, func, args, rets).is_ok(); - - res + let res = call_protected(handler_data, trampoline, ctx, func, args, rets); + + match res { + Err(err) => { + match err { + RunErr::Trap(info) => *trap_info = info, + RunErr::Error(data) => *user_error = Some(data), + } + false + } + Ok(()) => true, + } } let trampoline = self diff --git a/lib/clif-backend/src/signal/unix.rs b/lib/clif-backend/src/signal/unix.rs index 3e57a1588c3..aa587b7578f 100644 --- a/lib/clif-backend/src/signal/unix.rs +++ b/lib/clif-backend/src/signal/unix.rs @@ -10,7 +10,7 @@ //! unless you have memory unsafety elsewhere in your code. //! use crate::relocation::{TrapCode, TrapData}; -use crate::signal::HandlerData; +use crate::signal::{HandlerData, RunErr}; use libc::{c_int, c_void, siginfo_t}; use nix::sys::signal::{ sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV, @@ -18,7 +18,7 @@ use nix::sys::signal::{ use std::cell::{Cell, UnsafeCell}; use std::ptr; use std::sync::Once; -use wasmer_runtime_core::error::{RuntimeError, RuntimeResult}; +use wasmer_runtime_core::typed_func::WasmTrapInfo; extern "C" fn signal_trap_handler( signum: ::nix::libc::c_int, @@ -62,7 +62,7 @@ pub unsafe fn trigger_trap() -> ! { longjmp(jmp_buf as *mut c_void, 0) } -pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult { +pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> Result { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -76,7 +76,7 @@ pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R *jmp_buf = prev_jmp_buf; if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(RuntimeError::Error { data }) + Err(RunErr::Error(data)) } else { let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); @@ -85,33 +85,18 @@ pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R srcloc: _, }) = handler_data.lookup(inst_ptr) { - Err(match Signal::from_c_int(signum) { + Err(RunErr::Trap(match Signal::from_c_int(signum) { Ok(SIGILL) => match trapcode { - TrapCode::BadSignature => RuntimeError::Trap { - msg: "incorrect call_indirect signature".into(), - }, - TrapCode::IndirectCallToNull => RuntimeError::Trap { - msg: "indirect call to null".into(), - }, - TrapCode::HeapOutOfBounds => RuntimeError::Trap { - msg: "memory out-of-bounds access".into(), - }, - TrapCode::TableOutOfBounds => RuntimeError::Trap { - msg: "table out-of-bounds access".into(), - }, - _ => RuntimeError::Trap { - msg: "unknown trap".into(), - }, - }, - Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::Trap { - msg: "memory out-of-bounds access".into(), - }, - Ok(SIGFPE) => RuntimeError::Trap { - msg: "illegal arithmetic operation".into(), + TrapCode::BadSignature => WasmTrapInfo::IncorrectCallIndirectSignature, + TrapCode::IndirectCallToNull => WasmTrapInfo::CallIndirectOOB, + TrapCode::HeapOutOfBounds => WasmTrapInfo::MemoryOutOfBounds, + TrapCode::TableOutOfBounds => WasmTrapInfo::CallIndirectOOB, + _ => WasmTrapInfo::Unknown, }, + Ok(SIGSEGV) | Ok(SIGBUS) => WasmTrapInfo::MemoryOutOfBounds, + Ok(SIGFPE) => WasmTrapInfo::IllegalArithmetic, _ => unimplemented!(), - } - .into()) + })) } else { let signal = match Signal::from_c_int(signum) { Ok(SIGFPE) => "floating-point exception", @@ -122,10 +107,8 @@ pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R _ => "unkown trapped signal", }; // When the trap-handler is fully implemented, this will return more information. - Err(RuntimeError::Trap { - msg: format!("unknown trap at {:p} - {}", faulting_addr, signal).into(), - } - .into()) + let s = format!("unknown trap at {:p} - {}", faulting_addr, signal); + Err(RunErr::Error(Box::new(s))) } } } else { diff --git a/lib/clif-backend/src/signal/windows.rs b/lib/clif-backend/src/signal/windows.rs index 358b7323663..ec117e35646 100644 --- a/lib/clif-backend/src/signal/windows.rs +++ b/lib/clif-backend/src/signal/windows.rs @@ -1,10 +1,11 @@ use crate::relocation::{TrapCode, TrapData}; -use crate::signal::HandlerData; +use crate::signal::{HandlerData, RunErr}; use crate::trampoline::Trampoline; use std::cell::Cell; use std::ffi::c_void; use std::ptr::{self, NonNull}; use wasmer_runtime_core::error::{RuntimeError, RuntimeResult}; +use wasmer_runtime_core::typed_func::WasmTrapInfo; use wasmer_runtime_core::vm::Ctx; use wasmer_runtime_core::vm::Func; use wasmer_win_exception_handler::CallProtectedData; @@ -28,7 +29,7 @@ pub fn call_protected( func: NonNull, param_vec: *const u64, return_vec: *mut u64, -) -> RuntimeResult<()> { +) -> Result<(), RunErr> { // TODO: trap early // user code error // if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { @@ -52,38 +53,22 @@ pub fn call_protected( srcloc: _, }) = handler_data.lookup(instruction_pointer as _) { - Err(match signum as DWORD { - EXCEPTION_ACCESS_VIOLATION => RuntimeError::Trap { - msg: "memory out-of-bounds access".into(), - }, + Err(RunErr::Trap(match signum as DWORD { + EXCEPTION_ACCESS_VIOLATION => WasmTrapInfo::MemoryOutOfBounds, EXCEPTION_ILLEGAL_INSTRUCTION => match trapcode { - TrapCode::BadSignature => RuntimeError::Trap { - msg: "incorrect call_indirect signature".into(), - }, - TrapCode::IndirectCallToNull => RuntimeError::Trap { - msg: "indirect call to null".into(), - }, - TrapCode::HeapOutOfBounds => RuntimeError::Trap { - msg: "memory out-of-bounds access".into(), - }, - TrapCode::TableOutOfBounds => RuntimeError::Trap { - msg: "table out-of-bounds access".into(), - }, - _ => RuntimeError::Trap { - msg: "unknown trap".into(), - }, - }, - EXCEPTION_STACK_OVERFLOW => RuntimeError::Trap { - msg: "stack overflow trap".into(), - }, - EXCEPTION_INT_DIVIDE_BY_ZERO | EXCEPTION_INT_OVERFLOW => RuntimeError::Trap { - msg: "illegal arithmetic operation".into(), + TrapCode::BadSignature => WasmTrapInfo::IncorrectCallIndirectSignature, + TrapCode::IndirectCallToNull => WasmTrapInfo::CallIndirectOOB, + TrapCode::HeapOutOfBounds => WasmTrapInfo::MemoryOutOfBounds, + TrapCode::TableOutOfBounds => WasmTrapInfo::CallIndirectOOB, + TrapCode::UnreachableCodeReached => WasmTrapInfo::Unreachable, + _ => WasmTrapInfo::Unknown, }, - _ => RuntimeError::Trap { - msg: "unknown trap".into(), - }, - } - .into()) + EXCEPTION_STACK_OVERFLOW => WasmTrapInfo::Unknown, + EXCEPTION_INT_DIVIDE_BY_ZERO | EXCEPTION_INT_OVERFLOW => { + WasmTrapInfo::IllegalArithmetic + } + _ => WasmTrapInfo::Unknown, + })) } else { let signal = match signum as DWORD { EXCEPTION_FLT_DENORMAL_OPERAND @@ -98,10 +83,9 @@ pub fn call_protected( _ => "unkown trapped signal", }; - Err(RuntimeError::Trap { - msg: format!("unknown trap at {} - {}", exception_address, signal).into(), - } - .into()) + let s = format!("unknown trap at {} - {}", exception_address, signal); + + Err(RunErr::Error(Box::new(s))) } } diff --git a/lib/llvm-backend/build.rs b/lib/llvm-backend/build.rs index ed96cae9d4d..ab59748b147 100644 --- a/lib/llvm-backend/build.rs +++ b/lib/llvm-backend/build.rs @@ -213,6 +213,8 @@ fn main() { println!("cargo:rustc-link-lib=static=llvm-backend"); println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=cpp/object_loader.cpp"); + println!("cargo:rerun-if-changed=cpp/object_loader.hh"); // Enable "nightly" cfg if the current compiler is nightly. if rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly { diff --git a/lib/llvm-backend/cpp/object_loader.hh b/lib/llvm-backend/cpp/object_loader.hh index 134396b7892..63630a479b1 100644 --- a/lib/llvm-backend/cpp/object_loader.hh +++ b/lib/llvm-backend/cpp/object_loader.hh @@ -43,6 +43,11 @@ typedef struct visit_fde_t visit_fde; } callbacks_t; +typedef struct +{ + size_t data, vtable; +} box_any_t; + struct WasmException { public: @@ -61,7 +66,7 @@ struct UncatchableException : WasmException struct UserException : UncatchableException { public: - UserException(size_t data, size_t vtable) : data(data), vtable(vtable) {} + UserException(size_t data, size_t vtable) : error_data({ data, vtable }) {} virtual std::string description() const noexcept override { @@ -69,7 +74,7 @@ struct UserException : UncatchableException } // The parts of a `Box`. - size_t data, vtable; + box_any_t error_data; }; struct WasmTrap : UncatchableException @@ -194,6 +199,7 @@ extern "C" void *params, void *results, WasmTrap::Type *trap_out, + box_any_t *user_error, void *invoke_env) throw() { try @@ -206,6 +212,11 @@ extern "C" *trap_out = e.type; return false; } + catch (const UserException &e) + { + *user_error = e.error_data; + return false; + } catch (const WasmException &e) { *trap_out = WasmTrap::Type::Unknown; diff --git a/lib/llvm-backend/src/backend.rs b/lib/llvm-backend/src/backend.rs index 783a432ff23..8d908509e9a 100644 --- a/lib/llvm-backend/src/backend.rs +++ b/lib/llvm-backend/src/backend.rs @@ -93,6 +93,7 @@ extern "C" { params: *const u64, results: *mut u64, trap_out: *mut WasmTrapInfo, + user_error: *mut Option>, invoke_env: Option>, ) -> bool; } diff --git a/lib/runtime-core/src/error.rs b/lib/runtime-core/src/error.rs index 84ea04bc210..fb25acdb875 100644 --- a/lib/runtime-core/src/error.rs +++ b/lib/runtime-core/src/error.rs @@ -137,15 +137,13 @@ impl std::fmt::Display for RuntimeError { write!(f, "WebAssembly trap occured during runtime: {}", msg) } RuntimeError::Error { data } => { - let msg = if let Some(s) = data.downcast_ref::() { - s + if let Some(s) = data.downcast_ref::() { + write!(f, "\"{}\"", s) } else if let Some(s) = data.downcast_ref::<&str>() { - s + write!(f, "\"{}\"", s) } else { - "user-defined, opaque" - }; - - write!(f, "{}", msg) + write!(f, "unknown error") + } } } } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index db9a2847f10..78d0f7d912e 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -528,6 +528,7 @@ fn call_func_with_index( let run_wasm = |result_space: *mut u64| unsafe { let mut trap_info = WasmTrapInfo::Unknown; + let mut user_error = None; let success = invoke( trampoline, @@ -536,15 +537,20 @@ fn call_func_with_index( raw_args.as_ptr(), result_space, &mut trap_info, + &mut user_error, invoke_env, ); if success { Ok(()) } else { - Err(RuntimeError::Trap { - msg: trap_info.to_string().into(), - }) + if let Some(data) = user_error { + Err(RuntimeError::Error { data }) + } else { + Err(RuntimeError::Trap { + msg: trap_info.to_string().into(), + }) + } } }; diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index ef1b3c07786..c5ece859431 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -58,6 +58,7 @@ pub type Invoke = unsafe extern "C" fn( *const u64, *mut u64, *mut WasmTrapInfo, + *mut Option>, Option>, ) -> bool; @@ -104,7 +105,7 @@ pub trait WasmTypeList { f: NonNull, wasm: Wasm, ctx: *mut Ctx, - ) -> Result + ) -> Result where Rets: WasmTypeList; } @@ -213,6 +214,35 @@ where } } +impl WasmTypeList for Infallible { + type CStruct = Infallible; + type RetArray = [u64; 0]; + fn from_ret_array(_: Self::RetArray) -> Self { + unreachable!() + } + fn empty_ret_array() -> Self::RetArray { + unreachable!() + } + fn from_c_struct(_: Self::CStruct) -> Self { + unreachable!() + } + fn into_c_struct(self) -> Self::CStruct { + unreachable!() + } + fn types() -> &'static [Type] { + unreachable!() + } + #[allow(non_snake_case)] + unsafe fn call( + self, + _: NonNull, + _: Wasm, + _: *mut Ctx, + ) -> Result { + unreachable!() + } +} + impl WasmTypeList for (A,) { type CStruct = S1; type RetArray = [u64; 1]; @@ -242,11 +272,12 @@ impl WasmTypeList for (A,) { f: NonNull, wasm: Wasm, ctx: *mut Ctx, - ) -> Result { + ) -> Result { let (a,) = self; let args = [a.to_native().to_bits()]; let mut rets = Rets::empty_ret_array(); let mut trap = WasmTrapInfo::Unknown; + let mut user_error = None; if (wasm.invoke)( wasm.trampoline, @@ -255,11 +286,18 @@ impl WasmTypeList for (A,) { args.as_ptr(), rets.as_mut().as_mut_ptr(), &mut trap, + &mut user_error, wasm.invoke_env, ) { Ok(Rets::from_ret_array(rets)) } else { - Err(trap) + if let Some(data) = user_error { + Err(RuntimeError::Error { data }) + } else { + Err(RuntimeError::Trap { + msg: trap.to_string().into(), + }) + } } } } @@ -269,11 +307,7 @@ where Rets: WasmTypeList, { pub fn call(&self, a: A) -> Result { - unsafe { ::call(a, self.f, self.inner, self.ctx) }.map_err(|e| { - RuntimeError::Trap { - msg: e.to_string().into(), - } - }) + unsafe { ::call(a, self.f, self.inner, self.ctx) } } } @@ -307,17 +341,22 @@ macro_rules! impl_traits { &[$( $x::Native::TYPE, )*] } #[allow(non_snake_case)] - unsafe fn call(self, f: NonNull, wasm: Wasm, ctx: *mut Ctx) -> Result { + unsafe fn call(self, f: NonNull, wasm: Wasm, ctx: *mut Ctx) -> Result { #[allow(unused_parens)] let ( $( $x ),* ) = self; let args = [ $( $x.to_native().to_bits() ),* ]; let mut rets = Rets::empty_ret_array(); let mut trap = WasmTrapInfo::Unknown; + let mut user_error = None; - if (wasm.invoke)(wasm.trampoline, ctx, f, args.as_ptr(), rets.as_mut().as_mut_ptr(), &mut trap, wasm.invoke_env) { + if (wasm.invoke)(wasm.trampoline, ctx, f, args.as_ptr(), rets.as_mut().as_mut_ptr(), &mut trap, &mut user_error, wasm.invoke_env) { Ok(Rets::from_ret_array(rets)) } else { - Err(trap) + if let Some(data) = user_error { + Err(RuntimeError::Error { data }) + } else { + Err(RuntimeError::Trap { msg: trap.to_string().into() }) + } } } } @@ -359,11 +398,7 @@ macro_rules! impl_traits { #[allow(non_snake_case)] pub fn call(&self, $( $x: $x, )* ) -> Result { #[allow(unused_parens)] - unsafe { <( $( $x ),* ) as WasmTypeList>::call(( $($x),* ), self.f, self.inner, self.ctx) }.map_err(|e| { - RuntimeError::Trap { - msg: e.to_string().into(), - } - }) + unsafe { <( $( $x ),* ) as WasmTypeList>::call(( $($x),* ), self.f, self.inner, self.ctx) } } } }; diff --git a/lib/runtime/examples/call.rs b/lib/runtime/examples/call.rs index a8f27b4c00e..cbc632d2ed9 100644 --- a/lib/runtime/examples/call.rs +++ b/lib/runtime/examples/call.rs @@ -1,4 +1,4 @@ -use wasmer_runtime::{compile, error, imports, Ctx, Func, Value}; +use wasmer_runtime::{compile, error, error::RuntimeError, imports, Ctx, Func, Value}; use wabt::wat2wasm; @@ -7,6 +7,8 @@ static WAT: &'static str = r#" (type (;0;) (func (result i32))) (import "env" "do_panic" (func $do_panic (type 0))) (func $dbz (result i32) + call $do_panic + drop i32.const 42 i32.const 0 i32.div_u @@ -34,8 +36,13 @@ fn foobar(_ctx: &mut Ctx) -> i32 { 42 } -fn do_panic(_ctx: &mut Ctx) -> Result { - Err("error".to_string()) +#[derive(Debug)] +struct ExitCode { + code: i32, +} + +fn do_panic(_ctx: &mut Ctx) -> Result { + Err(ExitCode { code: 42 }) } fn main() -> Result<(), error::Error> { @@ -63,5 +70,11 @@ fn main() -> Result<(), error::Error> { println!("result: {:?}", result); + if let Err(RuntimeError::Error { data }) = result { + if let Ok(exit_code) = data.downcast::() { + println!("exit code: {:?}", exit_code); + } + } + Ok(()) } diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index 10f9f16af93..acf74e451cf 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -205,7 +205,8 @@ impl RunnableModule for X64ExecutionContext { func: NonNull, args: *const u64, rets: *mut u64, - _trap_info: *mut WasmTrapInfo, + trap_info: *mut WasmTrapInfo, + user_error: *mut Option>, num_params_plus_one: Option>, ) -> bool { let args = ::std::slice::from_raw_parts( @@ -227,7 +228,13 @@ impl RunnableModule for X64ExecutionContext { } true } - Err(_) => false, + Err(err) => { + match err { + protect_unix::RunErr::Trap(info) => *trap_info = info, + protect_unix::RunErr::Error(data) => *user_error = Some(data), + } + false + } } } diff --git a/lib/singlepass-backend/src/protect_unix.rs b/lib/singlepass-backend/src/protect_unix.rs index 30019db134d..3db54824ac2 100644 --- a/lib/singlepass-backend/src/protect_unix.rs +++ b/lib/singlepass-backend/src/protect_unix.rs @@ -17,7 +17,7 @@ use std::any::Any; use std::cell::{Cell, UnsafeCell}; use std::ptr; use std::sync::Once; -use wasmer_runtime_core::error::{RuntimeError, RuntimeResult}; +use wasmer_runtime_core::typed_func::WasmTrapInfo; extern "C" fn signal_trap_handler( signum: ::nix::libc::c_int, @@ -62,7 +62,12 @@ pub unsafe fn trigger_trap() -> ! { longjmp(jmp_buf as *mut c_void, 0) } -pub fn call_protected(f: impl FnOnce() -> T) -> RuntimeResult { +pub enum RunErr { + Trap(WasmTrapInfo), + Error(Box), +} + +pub fn call_protected(f: impl FnOnce() -> T) -> Result { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -76,23 +81,24 @@ pub fn call_protected(f: impl FnOnce() -> T) -> RuntimeResult { *jmp_buf = prev_jmp_buf; if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(RuntimeError::Error { data }) + Err(RunErr::Error(data)) } else { - let (faulting_addr, _inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); - - let signal = match Signal::from_c_int(signum) { - Ok(SIGFPE) => "floating-point exception", - Ok(SIGILL) => "illegal instruction", - Ok(SIGSEGV) => "segmentation violation", - Ok(SIGBUS) => "bus error", - Err(_) => "error while getting the Signal", - _ => "unkown trapped signal", - }; - // When the trap-handler is fully implemented, this will return more information. - Err(RuntimeError::Trap { - msg: format!("unknown trap at {:p} - {}", faulting_addr, signal).into(), - } - .into()) + // let (faulting_addr, _inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); + + // let signal = match Signal::from_c_int(signum) { + // Ok(SIGFPE) => "floating-point exception", + // Ok(SIGILL) => "illegal instruction", + // Ok(SIGSEGV) => "segmentation violation", + // Ok(SIGBUS) => "bus error", + // Err(_) => "error while getting the Signal", + // _ => "unkown trapped signal", + // }; + // // When the trap-handler is fully implemented, this will return more information. + // Err(RuntimeError::Trap { + // msg: format!("unknown trap at {:p} - {}", faulting_addr, signal).into(), + // } + // .into()) + Err(RunErr::Trap(WasmTrapInfo::Unknown)) } } else { let ret = f(); // TODO: Switch stack? diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index ddbb0e517db..ad2835d776e 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -13,6 +13,7 @@ use crate::{ }; use rand::{thread_rng, Rng}; use std::cell::Cell; +use std::convert::Infallible; use std::io::{self, Read, Seek, Write}; use wasmer_runtime_core::{debug, memory::Memory, vm::Ctx}; @@ -1431,7 +1432,7 @@ pub fn poll_oneoff( debug!("wasi::poll_oneoff"); unimplemented!() } -pub fn proc_exit(ctx: &mut Ctx, code: __wasi_exitcode_t) -> Result<(), ExitCode> { +pub fn proc_exit(ctx: &mut Ctx, code: __wasi_exitcode_t) -> Result { debug!("wasi::proc_exit, {}", code); Err(ExitCode { code }) } From aa9cd840857301a200caa7f628b18a34d9e2221e Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 22 Apr 2019 15:17:47 -0700 Subject: [PATCH 2/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a96a09994e..c65439f0d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All PRs to the Wasmer repository must add to this file. Blocks of changes will separated by version increments. ## **[Unreleased]** +- [#381](https://github.com/wasmerio/wasmer/pull/381) Allow retrieving propagated user errors. - [#379](https://github.com/wasmerio/wasmer/pull/379) Fix small return types from imported functions. - [#371](https://github.com/wasmerio/wasmer/pull/371) Add more Debug impl for WASI types - [#368](https://github.com/wasmerio/wasmer/pull/368) Fix issue with write buffering From 14325c975e6611edd11163e9cbdf766336acc9a3 Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 22 Apr 2019 15:36:47 -0700 Subject: [PATCH 3/4] Rename internal enum to make less confusing --- lib/clif-backend/src/signal/mod.rs | 6 +++--- lib/clif-backend/src/signal/unix.rs | 13 ++++++++----- lib/clif-backend/src/signal/windows.rs | 8 ++++---- lib/singlepass-backend/src/codegen_x64.rs | 4 ++-- lib/singlepass-backend/src/protect_unix.rs | 8 ++++---- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/clif-backend/src/signal/mod.rs b/lib/clif-backend/src/signal/mod.rs index 660a8ed1513..3facce2ad68 100644 --- a/lib/clif-backend/src/signal/mod.rs +++ b/lib/clif-backend/src/signal/mod.rs @@ -27,7 +27,7 @@ thread_local! { pub static TRAP_EARLY_DATA: Cell>> = Cell::new(None); } -pub enum RunErr { +pub enum CallProtError { Trap(WasmTrapInfo), Error(Box), } @@ -83,8 +83,8 @@ impl RunnableModule for Caller { match res { Err(err) => { match err { - RunErr::Trap(info) => *trap_info = info, - RunErr::Error(data) => *user_error = Some(data), + CallProtError::Trap(info) => *trap_info = info, + CallProtError::Error(data) => *user_error = Some(data), } false } diff --git a/lib/clif-backend/src/signal/unix.rs b/lib/clif-backend/src/signal/unix.rs index aa587b7578f..343203b9c5c 100644 --- a/lib/clif-backend/src/signal/unix.rs +++ b/lib/clif-backend/src/signal/unix.rs @@ -10,7 +10,7 @@ //! unless you have memory unsafety elsewhere in your code. //! use crate::relocation::{TrapCode, TrapData}; -use crate::signal::{HandlerData, RunErr}; +use crate::signal::{CallProtError, HandlerData}; use libc::{c_int, c_void, siginfo_t}; use nix::sys::signal::{ sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV, @@ -62,7 +62,10 @@ pub unsafe fn trigger_trap() -> ! { longjmp(jmp_buf as *mut c_void, 0) } -pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> Result { +pub fn call_protected( + handler_data: &HandlerData, + f: impl FnOnce() -> T, +) -> Result { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -76,7 +79,7 @@ pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R *jmp_buf = prev_jmp_buf; if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(RunErr::Error(data)) + Err(CallProtError::Error(data)) } else { let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); @@ -85,7 +88,7 @@ pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R srcloc: _, }) = handler_data.lookup(inst_ptr) { - Err(RunErr::Trap(match Signal::from_c_int(signum) { + Err(CallProtError::Trap(match Signal::from_c_int(signum) { Ok(SIGILL) => match trapcode { TrapCode::BadSignature => WasmTrapInfo::IncorrectCallIndirectSignature, TrapCode::IndirectCallToNull => WasmTrapInfo::CallIndirectOOB, @@ -108,7 +111,7 @@ pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R }; // When the trap-handler is fully implemented, this will return more information. let s = format!("unknown trap at {:p} - {}", faulting_addr, signal); - Err(RunErr::Error(Box::new(s))) + Err(CallProtError::Error(Box::new(s))) } } } else { diff --git a/lib/clif-backend/src/signal/windows.rs b/lib/clif-backend/src/signal/windows.rs index ec117e35646..30cb44ce6e2 100644 --- a/lib/clif-backend/src/signal/windows.rs +++ b/lib/clif-backend/src/signal/windows.rs @@ -1,5 +1,5 @@ use crate::relocation::{TrapCode, TrapData}; -use crate::signal::{HandlerData, RunErr}; +use crate::signal::{CallProtError, HandlerData}; use crate::trampoline::Trampoline; use std::cell::Cell; use std::ffi::c_void; @@ -29,7 +29,7 @@ pub fn call_protected( func: NonNull, param_vec: *const u64, return_vec: *mut u64, -) -> Result<(), RunErr> { +) -> Result<(), CallProtError> { // TODO: trap early // user code error // if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { @@ -53,7 +53,7 @@ pub fn call_protected( srcloc: _, }) = handler_data.lookup(instruction_pointer as _) { - Err(RunErr::Trap(match signum as DWORD { + Err(CallProtError::Trap(match signum as DWORD { EXCEPTION_ACCESS_VIOLATION => WasmTrapInfo::MemoryOutOfBounds, EXCEPTION_ILLEGAL_INSTRUCTION => match trapcode { TrapCode::BadSignature => WasmTrapInfo::IncorrectCallIndirectSignature, @@ -85,7 +85,7 @@ pub fn call_protected( let s = format!("unknown trap at {} - {}", exception_address, signal); - Err(RunErr::Error(Box::new(s))) + Err(CallProtError::Error(Box::new(s))) } } diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index acf74e451cf..ad496321453 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -230,8 +230,8 @@ impl RunnableModule for X64ExecutionContext { } Err(err) => { match err { - protect_unix::RunErr::Trap(info) => *trap_info = info, - protect_unix::RunErr::Error(data) => *user_error = Some(data), + protect_unix::CallProtError::Trap(info) => *trap_info = info, + protect_unix::CallProtError::Error(data) => *user_error = Some(data), } false } diff --git a/lib/singlepass-backend/src/protect_unix.rs b/lib/singlepass-backend/src/protect_unix.rs index 3db54824ac2..213db5f6a2a 100644 --- a/lib/singlepass-backend/src/protect_unix.rs +++ b/lib/singlepass-backend/src/protect_unix.rs @@ -62,12 +62,12 @@ pub unsafe fn trigger_trap() -> ! { longjmp(jmp_buf as *mut c_void, 0) } -pub enum RunErr { +pub enum CallProtError { Trap(WasmTrapInfo), Error(Box), } -pub fn call_protected(f: impl FnOnce() -> T) -> Result { +pub fn call_protected(f: impl FnOnce() -> T) -> Result { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -81,7 +81,7 @@ pub fn call_protected(f: impl FnOnce() -> T) -> Result { *jmp_buf = prev_jmp_buf; if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(RunErr::Error(data)) + Err(CallProtError::Error(data)) } else { // let (faulting_addr, _inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); @@ -98,7 +98,7 @@ pub fn call_protected(f: impl FnOnce() -> T) -> Result { // msg: format!("unknown trap at {:p} - {}", faulting_addr, signal).into(), // } // .into()) - Err(RunErr::Trap(WasmTrapInfo::Unknown)) + Err(CallProtError::Trap(WasmTrapInfo::Unknown)) } } else { let ret = f(); // TODO: Switch stack? From ff9de181f1aefcf1a1f751dd57573e0cd9b0381f Mon Sep 17 00:00:00 2001 From: Lachlan Sneff Date: Mon, 22 Apr 2019 15:53:21 -0700 Subject: [PATCH 4/4] Add error propagation test --- lib/runtime-core/src/typed_func.rs | 2 +- lib/runtime/tests/error_propagation.rs | 49 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 lib/runtime/tests/error_propagation.rs diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index c5ece859431..23a84873822 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -230,7 +230,7 @@ impl WasmTypeList for Infallible { unreachable!() } fn types() -> &'static [Type] { - unreachable!() + &[] } #[allow(non_snake_case)] unsafe fn call( diff --git a/lib/runtime/tests/error_propagation.rs b/lib/runtime/tests/error_propagation.rs new file mode 100644 index 00000000000..1b9f9fccfbf --- /dev/null +++ b/lib/runtime/tests/error_propagation.rs @@ -0,0 +1,49 @@ +#[test] +fn error_propagation() { + use std::convert::Infallible; + use wabt::wat2wasm; + use wasmer_runtime::{compile, error::RuntimeError, imports, Ctx, Func}; + + static WAT: &'static str = r#" + (module + (type (;0;) (func)) + (import "env" "ret_err" (func $ret_err (type 0))) + (func $call_panic + call $ret_err + ) + (export "call_err" (func $call_panic)) + ) + "#; + + #[derive(Debug)] + struct ExitCode { + code: i32, + } + + fn ret_err(_ctx: &mut Ctx) -> Result { + Err(ExitCode { code: 42 }) + } + + let wasm = wat2wasm(WAT).unwrap(); + + let module = compile(&wasm).unwrap(); + + let instance = module + .instantiate(&imports! { + "env" => { + "ret_err" => Func::new(ret_err), + }, + }) + .unwrap(); + + let foo: Func<(), ()> = instance.func("call_err").unwrap(); + + let result = foo.call(); + + if let Err(RuntimeError::Error { data }) = result { + let exit_code = data.downcast::().unwrap(); + assert_eq!(exit_code.code, 42); + } else { + panic!("didn't return RuntimeError::Error") + } +}