From bfb6814f23e4c03d37a4e2f903055df63ac2968d Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Thu, 23 Apr 2020 12:40:35 -0700 Subject: [PATCH 1/2] Make runtime and trap errors well defined (WIP) --- lib/clif-backend/src/signal/mod.rs | 31 +++++++++++--- lib/clif-backend/src/signal/unix.rs | 25 +++++++---- lib/runtime-core/src/backend.rs | 4 +- lib/runtime-core/src/error.rs | 65 ++++++++++++++++++++++++++++- lib/runtime-core/src/instance.rs | 15 +++---- lib/runtime-core/src/typed_func.rs | 20 ++++----- 6 files changed, 125 insertions(+), 35 deletions(-) diff --git a/lib/clif-backend/src/signal/mod.rs b/lib/clif-backend/src/signal/mod.rs index cb6e147b1a7..6ce156c9642 100644 --- a/lib/clif-backend/src/signal/mod.rs +++ b/lib/clif-backend/src/signal/mod.rs @@ -1,12 +1,13 @@ use crate::{ - relocation::{TrapData, TrapSink}, + relocation::{TrapData, TrapSink, TrapCode}, resolver::FuncResolver, trampoline::Trampolines, }; use libc::c_void; use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc}; use wasmer_runtime_core::{ - backend::RunnableModule, + backend::{RunnableModule, ExceptionCode}, + error::{InvokeError, RuntimeError}, module::ModuleInfo, typed_func::{Trampoline, Wasm}, types::{LocalFuncIndex, SigIndex}, @@ -26,10 +27,25 @@ pub use self::unix::*; pub use self::windows::*; thread_local! { - pub static TRAP_EARLY_DATA: Cell>> = Cell::new(None); + pub static TRAP_EARLY_DATA: Cell> = Cell::new(None); } -pub struct CallProtError(pub Box); +pub enum CallProtError { + UnknownTrap { + address: usize, + signal: &'static str, + }, + TrapCode { + code: ExceptionCode, + srcloc: u32, + }, + UnknownTrapCode { + trap_code: TrapCode, + srcloc: u32, + }, + EarlyTrap(RuntimeError), + Misc(Box), +} pub struct Caller { handler_data: HandlerData, @@ -63,7 +79,7 @@ impl RunnableModule for Caller { func: NonNull, args: *const u64, rets: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, invoke_env: Option>, ) -> bool { let handler_data = &*invoke_env.unwrap().cast().as_ptr(); @@ -80,6 +96,9 @@ impl RunnableModule for Caller { match res { Err(err) => { + // probably makes the most sense to actually do a translation here to a + // a generic type defined in runtime-core + // TODO: figure out _this_ error return story *error_out = Some(err.0); false } @@ -101,7 +120,7 @@ impl RunnableModule for Caller { }) } - unsafe fn do_early_trap(&self, data: Box) -> ! { + unsafe fn do_early_trap(&self, data: RuntimeError) -> ! { TRAP_EARLY_DATA.with(|cell| cell.set(Some(data))); trigger_trap() } diff --git a/lib/clif-backend/src/signal/unix.rs b/lib/clif-backend/src/signal/unix.rs index 98571cbbff3..8b517dac4e8 100644 --- a/lib/clif-backend/src/signal/unix.rs +++ b/lib/clif-backend/src/signal/unix.rs @@ -79,16 +79,16 @@ pub fn call_protected( *jmp_buf = prev_jmp_buf; if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(CallProtError(data)) + Err(CallProtError::EarlyTrap(data)) } else { let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); if let Some(TrapData { trapcode, - srcloc: _, + srcloc, }) = handler_data.lookup(inst_ptr) { - Err(CallProtError(Box::new(match Signal::from_c_int(signum) { + let code = match Signal::from_c_int(signum) { Ok(SIGILL) => match trapcode { TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds, TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds, @@ -101,9 +101,10 @@ pub fn call_protected( TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic, TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable, _ => { - return Err(CallProtError(Box::new( - "unknown clif trap code".to_string(), - ))) + return Err(CallProtError::UnknownTrapCode { + trap_code: trapcode, + srcloc, + }) } }, Ok(SIGSEGV) | Ok(SIGBUS) => ExceptionCode::MemoryOutOfBounds, @@ -112,7 +113,11 @@ pub fn call_protected( "ExceptionCode::Unknown signal:{:?}", Signal::from_c_int(signum) ), - }))) + }; + Err(CallProtError::TrapCode { + srcloc, + code, + }) } else { let signal = match Signal::from_c_int(signum) { Ok(SIGFPE) => "floating-point exception", @@ -123,8 +128,10 @@ pub fn call_protected( _ => "unknown trapped signal", }; // When the trap-handler is fully implemented, this will return more information. - let s = format!("unknown trap at {:p} - {}", faulting_addr, signal); - Err(CallProtError(Box::new(s))) + Err(CallProtError::UnknownTrap { + address: faulting_addr as usize, + signal, + }) } } } else { diff --git a/lib/runtime-core/src/backend.rs b/lib/runtime-core/src/backend.rs index c6235475d8d..cbef1db3bc0 100644 --- a/lib/runtime-core/src/backend.rs +++ b/lib/runtime-core/src/backend.rs @@ -1,5 +1,5 @@ use crate::{ - error::CompileResult, + error::{CompileResult, RuntimeError}, module::ModuleInner, state::ModuleStateMap, typed_func::Wasm, @@ -282,7 +282,7 @@ pub trait RunnableModule: Send + Sync { fn get_trampoline(&self, info: &ModuleInfo, sig_index: SigIndex) -> Option; /// Trap an error. - unsafe fn do_early_trap(&self, data: Box) -> !; + unsafe fn do_early_trap(&self, data: RuntimeError) -> !; /// Returns the machine code associated with this module. fn get_code(&self) -> Option<&[u8]> { diff --git a/lib/runtime-core/src/error.rs b/lib/runtime-core/src/error.rs index a685a8238f2..c705e5213d3 100644 --- a/lib/runtime-core/src/error.rs +++ b/lib/runtime-core/src/error.rs @@ -1,6 +1,6 @@ //! The error module contains the data structures and helper functions used to implement errors that //! are produced and returned from the wasmer runtime core. -use crate::backend::ExceptionCode; +//use crate::backend::ExceptionCode; use crate::types::{FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type}; use core::borrow::Borrow; use std::any::Any; @@ -173,6 +173,7 @@ impl std::fmt::Display for LinkError { impl std::error::Error for LinkError {} +/* /// This is the error type returned when calling /// a WebAssembly function. /// @@ -210,6 +211,68 @@ impl std::fmt::Debug for RuntimeError { impl std::error::Error for RuntimeError {} +*/ + +/// An `InternalError` is an error that happened inside of Wasmer and is a +/// catch-all for errors that would otherwise be returned as +/// `RuntimeError(Box::new())`. +/// +/// This type provides greater visibility into the kinds of things that may fail +/// and improves the ability of users to handle them, though these errors may be +/// extremely rare and impossible to handle. +#[derive(Debug)] +pub enum RuntimeError { + /// When an invoke returns an error (this is where exception codes come from?) + InvokeError(InvokeError), + /// A user triggered error value. + /// + /// An error returned from a host function. + User(Box) +} + +/// TODO: +#[derive(Debug)] +pub enum InvokeError { + /// not yet handled error cases, ideally we should be able to handle them all + Misc(Box), + /// Indicates an exceptional circumstance such as a bug that should be reported or + /// a hardware failure. + FailedWithNoError, +} + +impl std::error::Error for RuntimeError {} + +impl std::fmt::Display for RuntimeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + // TODO: ideally improve the error type of invoke + RuntimeError::InvokeError(_) => write!(f, "Error when calling invoke"), + RuntimeError::User(user_error) => { + write!(f, "User supplied error: ")?; + if let Some(s) = user_error.downcast_ref::() { + write!(f, "\"{}\"", s) + } else if let Some(s) = user_error.downcast_ref::<&str>() { + write!(f, "\"{}\"", s) + } else if let Some(n) = user_error.downcast_ref::() { + write!(f, "{}", n) + } else { + write!(f, "unknown error type") + } + }, + } + } +} + +/* +impl From for RuntimeError { + fn from(other: InternalError) -> Self { + RuntimeError(Box::new(other)) + } +} + */ + + + /// This error type is produced by resolving a wasm function /// given its name. /// diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 48e59d4ec65..4eb43d71f70 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -5,7 +5,7 @@ use crate::{ backend::RunnableModule, backing::{ImportBacking, LocalBacking}, - error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError}, + error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError, InvokeError}, export::{Context, Export, ExportIter, Exportable, FuncPointer}, global::Global, import::{ImportObject, LikeNamespace}, @@ -584,10 +584,10 @@ pub(crate) fn call_func_with_index_inner( invoke_env, } = wasm; - let run_wasm = |result_space: *mut u64| unsafe { + let run_wasm = |result_space: *mut u64| -> CallResult<()> { let mut error_out = None; - let success = invoke( + let success = unsafe { invoke( trampoline, ctx_ptr, func_ptr, @@ -595,14 +595,15 @@ pub(crate) fn call_func_with_index_inner( result_space, &mut error_out, invoke_env, - ); + )}; if success { Ok(()) } else { - Err(error_out - .map(RuntimeError) - .unwrap_or_else(|| RuntimeError(Box::new("invoke(): Unknown error".to_string())))) + let error: RuntimeError = error_out + .map(RuntimeError::InvokeError) + .unwrap_or_else(|| RuntimeError::InvokeError(InvokeError::FailedWithNoError)); + Err(error.into()) } }; diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index a6337bc5888..1daa5831971 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -1,7 +1,7 @@ //! The typed func module implements a way of representing a wasm function //! with the correct types from rust. Function calls using a typed func have a low overhead. use crate::{ - error::RuntimeError, + error::{RuntimeError, InvokeError}, export::{Context, Export, FuncPointer}, import::IsExport, types::{FuncSig, NativeWasmType, Type, WasmExternType}, @@ -37,7 +37,7 @@ pub type Invoke = unsafe extern "C" fn( func: NonNull, args: *const u64, rets: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, extra: Option>, ) -> bool; @@ -340,7 +340,7 @@ impl<'a> DynamicFunc<'a> { Err(e) => { // At this point, there is an error that needs to be trapped. drop(args); // Release the Vec which will leak otherwise. - (&*vmctx.module).runnable_module.do_early_trap(e) + (&*vmctx.module).runnable_module.do_early_trap(RuntimeError::User(e)) } } } @@ -588,9 +588,7 @@ macro_rules! impl_traits { ) { Ok(Rets::from_ret_array(rets)) } else { - Err(error_out.map(RuntimeError).unwrap_or_else(|| { - RuntimeError(Box::new("invoke(): Unknown error".to_string())) - })) + Err(error_out.map_or_else(|| RuntimeError::InvokeError(InvokeError::FailedWithNoError), RuntimeError::InvokeError)) } } } @@ -678,9 +676,10 @@ macro_rules! impl_traits { Ok(Ok(returns)) => return returns.into_c_struct(), Ok(Err(err)) => { let b: Box<_> = err.into(); - b as Box + RuntimeError::User(b as Box) }, - Err(err) => err, + // TODO(blocking): this line is wrong! + Err(err) => RuntimeError::User(err), }; // At this point, there is an error that needs to @@ -791,9 +790,10 @@ macro_rules! impl_traits { Ok(Ok(returns)) => return returns.into_c_struct(), Ok(Err(err)) => { let b: Box<_> = err.into(); - b as Box + RuntimeError::User(b as Box) }, - Err(err) => err, + // TODO(blocking): this line is wrong! + Err(err) => RuntimeError::User(err), }; // At this point, there is an error that needs to From b9ec8f98450e5891c8897bdbaf4835039f42134a Mon Sep 17 00:00:00 2001 From: Mark McCaskey Date: Fri, 24 Apr 2020 13:21:45 -0700 Subject: [PATCH 2/2] Define runtime error values explicitly in Singlepass --- lib/clif-backend/src/signal/mod.rs | 25 ++---------- lib/clif-backend/src/signal/unix.rs | 24 ++++------- lib/llvm-backend/src/backend.rs | 38 ++++++++++------- lib/middleware-common/src/metering.rs | 3 +- lib/runtime-core/src/codegen.rs | 5 +-- lib/runtime-core/src/error.rs | 50 ++++++++++++++++++----- lib/runtime-core/src/fault.rs | 36 ++++++++-------- lib/runtime-core/src/instance.rs | 22 +++++----- lib/runtime-core/src/state.rs | 4 +- lib/runtime-core/src/typed_func.rs | 6 ++- lib/singlepass-backend/src/codegen_x64.rs | 10 ++--- src/commands/run.rs | 8 +++- 12 files changed, 129 insertions(+), 102 deletions(-) diff --git a/lib/clif-backend/src/signal/mod.rs b/lib/clif-backend/src/signal/mod.rs index 6ce156c9642..cf3d1eb021f 100644 --- a/lib/clif-backend/src/signal/mod.rs +++ b/lib/clif-backend/src/signal/mod.rs @@ -1,12 +1,12 @@ use crate::{ - relocation::{TrapData, TrapSink, TrapCode}, + relocation::{TrapData, TrapSink}, resolver::FuncResolver, trampoline::Trampolines, }; use libc::c_void; -use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc}; +use std::{cell::Cell, ptr::NonNull, sync::Arc}; use wasmer_runtime_core::{ - backend::{RunnableModule, ExceptionCode}, + backend::RunnableModule, error::{InvokeError, RuntimeError}, module::ModuleInfo, typed_func::{Trampoline, Wasm}, @@ -30,23 +30,6 @@ thread_local! { pub static TRAP_EARLY_DATA: Cell> = Cell::new(None); } -pub enum CallProtError { - UnknownTrap { - address: usize, - signal: &'static str, - }, - TrapCode { - code: ExceptionCode, - srcloc: u32, - }, - UnknownTrapCode { - trap_code: TrapCode, - srcloc: u32, - }, - EarlyTrap(RuntimeError), - Misc(Box), -} - pub struct Caller { handler_data: HandlerData, trampolines: Arc, @@ -99,7 +82,7 @@ impl RunnableModule for Caller { // probably makes the most sense to actually do a translation here to a // a generic type defined in runtime-core // TODO: figure out _this_ error return story - *error_out = Some(err.0); + *error_out = Some(err); false } Ok(()) => true, diff --git a/lib/clif-backend/src/signal/unix.rs b/lib/clif-backend/src/signal/unix.rs index 8b517dac4e8..965359e354f 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::{CallProtError, HandlerData}; +use crate::signal::HandlerData; use libc::{c_int, c_void, siginfo_t}; use nix::sys::signal::{ sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV, @@ -19,6 +19,7 @@ use std::cell::{Cell, UnsafeCell}; use std::ptr; use std::sync::Once; use wasmer_runtime_core::backend::ExceptionCode; +use wasmer_runtime_core::error::InvokeError; extern "C" fn signal_trap_handler( signum: ::nix::libc::c_int, @@ -65,7 +66,7 @@ pub unsafe fn trigger_trap() -> ! { pub fn call_protected( handler_data: &HandlerData, f: impl FnOnce() -> T, -) -> Result { +) -> Result { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -79,15 +80,11 @@ pub fn call_protected( *jmp_buf = prev_jmp_buf; if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(CallProtError::EarlyTrap(data)) + Err(InvokeError::EarlyTrap(Box::new(data))) } else { let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); - if let Some(TrapData { - trapcode, - srcloc, - }) = handler_data.lookup(inst_ptr) - { + if let Some(TrapData { trapcode, srcloc }) = handler_data.lookup(inst_ptr) { let code = match Signal::from_c_int(signum) { Ok(SIGILL) => match trapcode { TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds, @@ -101,8 +98,8 @@ pub fn call_protected( TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic, TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable, _ => { - return Err(CallProtError::UnknownTrapCode { - trap_code: trapcode, + return Err(InvokeError::UnknownTrapCode { + trap_code: format!("{:?}", trapcode), srcloc, }) } @@ -114,10 +111,7 @@ pub fn call_protected( Signal::from_c_int(signum) ), }; - Err(CallProtError::TrapCode { - srcloc, - code, - }) + Err(InvokeError::TrapCode { srcloc, code }) } else { let signal = match Signal::from_c_int(signum) { Ok(SIGFPE) => "floating-point exception", @@ -128,7 +122,7 @@ pub fn call_protected( _ => "unknown trapped signal", }; // When the trap-handler is fully implemented, this will return more information. - Err(CallProtError::UnknownTrap { + Err(InvokeError::UnknownTrap { address: faulting_addr as usize, signal, }) diff --git a/lib/llvm-backend/src/backend.rs b/lib/llvm-backend/src/backend.rs index a2ac15a940c..c3922b6bed9 100644 --- a/lib/llvm-backend/src/backend.rs +++ b/lib/llvm-backend/src/backend.rs @@ -11,7 +11,6 @@ use inkwell::{ }; use libc::c_char; use std::{ - any::Any, cell::RefCell, ffi::{c_void, CString}, mem, @@ -27,6 +26,7 @@ use wasmer_runtime_core::{ CacheGen, ExceptionCode, RunnableModule, }, cache::Error as CacheError, + error::{InvokeError, RuntimeError}, module::ModuleInfo, state::ModuleStateMap, structures::TypedIndex, @@ -56,7 +56,7 @@ extern "C" { /// but this is cleaner, I think? #[cfg_attr(nightly, unwind(allowed))] #[allow(improper_ctypes)] - fn throw_any(data: *mut dyn Any) -> !; + fn throw_runtime_error(data: RuntimeError) -> !; #[allow(improper_ctypes)] fn cxx_invoke_trampoline( @@ -66,7 +66,7 @@ extern "C" { params: *const u64, results: *mut u64, trap_out: *mut i32, - error_out: *mut Option>, + error_out: *mut Option, invoke_env: Option>, ) -> bool; } @@ -79,7 +79,7 @@ unsafe extern "C" fn invoke_trampoline( func_ptr: NonNull, params: *const u64, results: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, invoke_env: Option>, ) -> bool { let mut trap_out: i32 = -1; @@ -95,15 +95,22 @@ unsafe extern "C" fn invoke_trampoline( ); // Translate trap code if an error occurred. if !ret && (*error_out).is_none() && trap_out != -1 { - *error_out = Some(Box::new(match trap_out { - 0 => ExceptionCode::Unreachable, - 1 => ExceptionCode::IncorrectCallIndirectSignature, - 2 => ExceptionCode::MemoryOutOfBounds, - 3 => ExceptionCode::CallIndirectOOB, - 4 => ExceptionCode::IllegalArithmetic, - 5 => ExceptionCode::MisalignedAtomicAccess, - _ => return ret, - })); + *error_out = { + let exception_code = match trap_out { + 0 => ExceptionCode::Unreachable, + 1 => ExceptionCode::IncorrectCallIndirectSignature, + 2 => ExceptionCode::MemoryOutOfBounds, + 3 => ExceptionCode::CallIndirectOOB, + 4 => ExceptionCode::IllegalArithmetic, + 5 => ExceptionCode::MisalignedAtomicAccess, + _ => return ret, + }; + Some(InvokeError::TrapCode { + code: exception_code, + // TODO: + srcloc: 0, + }) + }; } ret } @@ -467,8 +474,9 @@ impl RunnableModule for LLVMBackend { self.msm.clone() } - unsafe fn do_early_trap(&self, data: Box) -> ! { - throw_any(Box::leak(data)) + unsafe fn do_early_trap(&self, data: RuntimeError) -> ! { + // maybe need to box leak it? + throw_runtime_error(data) } } diff --git a/lib/middleware-common/src/metering.rs b/lib/middleware-common/src/metering.rs index 409035d4ba7..a201403d266 100644 --- a/lib/middleware-common/src/metering.rs +++ b/lib/middleware-common/src/metering.rs @@ -1,5 +1,6 @@ use wasmer_runtime_core::{ codegen::{Event, EventSink, FunctionMiddleware, InternalEvent}, + error::RuntimeError, module::ModuleInfo, vm::{Ctx, InternalField}, wasmparser::{Operator, Type as WpType, TypeOrFuncType as WpTypeOrFuncType}, @@ -96,7 +97,7 @@ impl FunctionMiddleware for Metering { ty: WpTypeOrFuncType::Type(WpType::EmptyBlockType), })); sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new(|_| { - Err(Box::new(ExecutionLimitExceededError)) + Err(RuntimeError::Metering(Box::new(ExecutionLimitExceededError))) })))); sink.push(Event::WasmOwned(Operator::End)); } diff --git a/lib/runtime-core/src/codegen.rs b/lib/runtime-core/src/codegen.rs index 4e2bf74a4a2..6ff5c596c03 100644 --- a/lib/runtime-core/src/codegen.rs +++ b/lib/runtime-core/src/codegen.rs @@ -6,13 +6,12 @@ use crate::{ backend::RunnableModule, backend::{CacheGen, Compiler, CompilerConfig, Features, Token}, cache::{Artifact, Error as CacheError}, - error::{CompileError, CompileResult}, + error::{CompileError, CompileResult, RuntimeError}, module::{ModuleInfo, ModuleInner}, structures::Map, types::{FuncIndex, FuncSig, SigIndex}, }; use smallvec::SmallVec; -use std::any::Any; use std::collections::HashMap; use std::fmt; use std::fmt::Debug; @@ -23,7 +22,7 @@ use wasmparser::{Operator, Type as WpType}; /// A type that defines a function pointer, which is called when breakpoints occur. pub type BreakpointHandler = - Box Result<(), Box> + Send + Sync + 'static>; + Box Result<(), RuntimeError> + Send + Sync + 'static>; /// Maps instruction pointers to their breakpoint handlers. pub type BreakpointMap = Arc>; diff --git a/lib/runtime-core/src/error.rs b/lib/runtime-core/src/error.rs index c705e5213d3..b501c6067ad 100644 --- a/lib/runtime-core/src/error.rs +++ b/lib/runtime-core/src/error.rs @@ -1,6 +1,6 @@ //! The error module contains the data structures and helper functions used to implement errors that //! are produced and returned from the wasmer runtime core. -//use crate::backend::ExceptionCode; +use crate::backend::ExceptionCode; use crate::types::{FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type}; use core::borrow::Borrow; use std::any::Any; @@ -222,31 +222,63 @@ impl std::error::Error for RuntimeError {} /// extremely rare and impossible to handle. #[derive(Debug)] pub enum RuntimeError { - /// When an invoke returns an error (this is where exception codes come from?) + /// When an invoke returns an error InvokeError(InvokeError), + /// A metering triggered error value. + /// + /// An error of this type indicates that it was returned by the metering system. + Metering(Box), /// A user triggered error value. /// /// An error returned from a host function. - User(Box) + User(Box), } /// TODO: #[derive(Debug)] pub enum InvokeError { - /// not yet handled error cases, ideally we should be able to handle them all - Misc(Box), /// Indicates an exceptional circumstance such as a bug that should be reported or /// a hardware failure. FailedWithNoError, + + /// TODO: + UnknownTrap { + /// TODO: + address: usize, + /// TODO: + signal: &'static str, + }, + /// TODO: + TrapCode { + /// TODO: + code: ExceptionCode, + /// TODO: + srcloc: u32, + }, + /// TODO: + UnknownTrapCode { + /// TODO: + trap_code: String, + /// TODO: + srcloc: u32, + }, + /// extra TODO: investigate if this can be a `Box` instead (looks like probably no) + /// TODO: + EarlyTrap(Box), + /// Indicates an error that ocurred related to breakpoints. (currently Singlepass only) + Breakpoint(Box), } +//impl std::error::Error for InvokeError {} + impl std::error::Error for RuntimeError {} impl std::fmt::Display for RuntimeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - // TODO: ideally improve the error type of invoke + // TODO: update invoke error formatting RuntimeError::InvokeError(_) => write!(f, "Error when calling invoke"), + RuntimeError::Metering(_) => write!(f, "unknown metering error type"), RuntimeError::User(user_error) => { write!(f, "User supplied error: ")?; if let Some(s) = user_error.downcast_ref::() { @@ -256,9 +288,9 @@ impl std::fmt::Display for RuntimeError { } else if let Some(n) = user_error.downcast_ref::() { write!(f, "{}", n) } else { - write!(f, "unknown error type") + write!(f, "unknown user error type") } - }, + } } } } @@ -270,8 +302,6 @@ impl From for RuntimeError { } } */ - - /// This error type is produced by resolving a wasm function /// given its name. diff --git a/lib/runtime-core/src/fault.rs b/lib/runtime-core/src/fault.rs index d8c985e09b9..cdd5078b501 100644 --- a/lib/runtime-core/src/fault.rs +++ b/lib/runtime-core/src/fault.rs @@ -29,14 +29,14 @@ pub mod raw { use crate::codegen::{BreakpointInfo, BreakpointMap}; use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR}; -use crate::state::{CodeVersion, ExecutionStateImage}; +use crate::state::{CodeVersion, ExecutionStateImage, InstanceImage}; +use crate::error::{RuntimeError, InvokeError}; use crate::vm; use libc::{mmap, mprotect, siginfo_t, MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE}; use nix::sys::signal::{ sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTRAP, }; -use std::any::Any; use std::cell::{Cell, RefCell, UnsafeCell}; use std::ffi::c_void; use std::process; @@ -61,7 +61,7 @@ type SetJmpBuffer = [i32; SETJMP_BUFFER_LEN]; struct UnwindInfo { jmpbuf: SetJmpBuffer, // in breakpoints: Option, - payload: Option>, // out + payload: Option, // out } /// A store for boundary register preservation. @@ -182,7 +182,7 @@ pub unsafe fn clear_wasm_interrupt() { pub unsafe fn catch_unsafe_unwind R>( f: F, breakpoints: Option, -) -> Result> { +) -> Result { let unwind = UNWIND.with(|x| x.get()); let old = (*unwind).take(); *unwind = Some(UnwindInfo { @@ -205,7 +205,7 @@ pub unsafe fn catch_unsafe_unwind R>( } /// Begins an unsafe unwind. -pub unsafe fn begin_unsafe_unwind(e: Box) -> ! { +pub unsafe fn begin_unsafe_unwind(e: RuntimeError) -> ! { let unwind = UNWIND.with(|x| x.get()); let inner = (*unwind) .as_mut() @@ -279,7 +279,7 @@ extern "C" fn signal_trap_handler( static ARCH: Architecture = Architecture::Aarch64; let mut should_unwind = false; - let mut unwind_result: Box = Box::new(()); + let mut unwind_result: Option> = None; unsafe { let fault = get_fault_info(siginfo as _, ucontext); @@ -302,7 +302,7 @@ extern "C" fn signal_trap_handler( ) { match ib.ty { InlineBreakpointType::Middleware => { - let out: Option>> = + let out: Option> = with_breakpoint_map(|bkpt_map| { bkpt_map.and_then(|x| x.get(&ip)).map(|x| { x(BreakpointInfo { @@ -313,7 +313,7 @@ extern "C" fn signal_trap_handler( if let Some(Ok(())) = out { } else if let Some(Err(e)) = out { should_unwind = true; - unwind_result = e; + unwind_result = Some(Err(e)); } } } @@ -328,7 +328,7 @@ extern "C" fn signal_trap_handler( }) }); if should_unwind { - begin_unsafe_unwind(unwind_result); + begin_unsafe_unwind(unwind_result.unwrap().unwrap_err()); } if early_return { return; @@ -342,9 +342,9 @@ extern "C" fn signal_trap_handler( match Signal::from_c_int(signum) { Ok(SIGTRAP) => { // breakpoint - let out: Option>> = - with_breakpoint_map(|bkpt_map| { - bkpt_map.and_then(|x| x.get(&(fault.ip.get()))).map(|x| { + let out: Option> = + with_breakpoint_map(|bkpt_map| -> Option> { + bkpt_map.and_then(|x| x.get(&(fault.ip.get()))).map(|x| -> Result<(), RuntimeError> { x(BreakpointInfo { fault: Some(&fault), }) @@ -355,7 +355,7 @@ extern "C" fn signal_trap_handler( return false; } Some(Err(e)) => { - unwind_result = e; + unwind_result = Some(Err(e)); return true; } None => {} @@ -387,7 +387,7 @@ extern "C" fn signal_trap_handler( if is_suspend_signal { // If this is a suspend signal, we parse the runtime state and return the resulting image. let image = build_instance_image(ctx, es_image); - unwind_result = Box::new(image); + unwind_result = Some(Ok(image)); } else { // Otherwise, this is a real exception and we just throw it to the caller. if !es_image.frames.is_empty() { @@ -415,7 +415,11 @@ extern "C" fn signal_trap_handler( None }); if let Some(code) = exc_code { - unwind_result = Box::new(code); + unwind_result = Some(Err(RuntimeError::InvokeError(InvokeError::TrapCode { + code, + // TODO: + srcloc: 0, + }))); } } @@ -423,7 +427,7 @@ extern "C" fn signal_trap_handler( }); if should_unwind { - begin_unsafe_unwind(unwind_result); + begin_unsafe_unwind(unwind_result.unwrap().unwrap_err()); } } } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 4eb43d71f70..7dd176b657c 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -5,7 +5,7 @@ use crate::{ backend::RunnableModule, backing::{ImportBacking, LocalBacking}, - error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError, InvokeError}, + error::{CallResult, InvokeError, ResolveError, ResolveResult, Result, RuntimeError}, export::{Context, Export, ExportIter, Exportable, FuncPointer}, global::Global, import::{ImportObject, LikeNamespace}, @@ -587,15 +587,17 @@ pub(crate) fn call_func_with_index_inner( let run_wasm = |result_space: *mut u64| -> CallResult<()> { let mut error_out = None; - let success = unsafe { invoke( - trampoline, - ctx_ptr, - func_ptr, - raw_args.as_ptr(), - result_space, - &mut error_out, - invoke_env, - )}; + let success = unsafe { + invoke( + trampoline, + ctx_ptr, + func_ptr, + raw_args.as_ptr(), + result_space, + &mut error_out, + invoke_env, + ) + }; if success { Ok(()) diff --git a/lib/runtime-core/src/state.rs b/lib/runtime-core/src/state.rs index 55809e965d7..fb3f5d2b769 100644 --- a/lib/runtime-core/src/state.rs +++ b/lib/runtime-core/src/state.rs @@ -705,7 +705,7 @@ pub mod x64 { use crate::structures::TypedIndex; use crate::types::LocalGlobalIndex; use crate::vm::Ctx; - use std::any::Any; + use crate::error::RuntimeError; #[allow(clippy::cast_ptr_alignment)] unsafe fn compute_vmctx_deref(vmctx: *const Ctx, seq: &[usize]) -> u64 { @@ -738,7 +738,7 @@ pub mod x64 { image: InstanceImage, vmctx: &mut Ctx, breakpoints: Option, - ) -> Result> { + ) -> Result { let mut stack: Vec = vec![0; 1048576 * 8 / 8]; // 8MB stack let mut stack_offset: usize = stack.len(); diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 1daa5831971..4594bcd4f80 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -1,7 +1,7 @@ //! The typed func module implements a way of representing a wasm function //! with the correct types from rust. Function calls using a typed func have a low overhead. use crate::{ - error::{RuntimeError, InvokeError}, + error::{InvokeError, RuntimeError}, export::{Context, Export, FuncPointer}, import::IsExport, types::{FuncSig, NativeWasmType, Type, WasmExternType}, @@ -340,7 +340,9 @@ impl<'a> DynamicFunc<'a> { Err(e) => { // At this point, there is an error that needs to be trapped. drop(args); // Release the Vec which will leak otherwise. - (&*vmctx.module).runnable_module.do_early_trap(RuntimeError::User(e)) + (&*vmctx.module) + .runnable_module + .do_early_trap(RuntimeError::User(e)) } } } diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index e51db5b7c5b..f51af7c5862 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -10,7 +10,6 @@ use dynasmrt::x64::Assembler; use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi}; use smallvec::SmallVec; use std::{ - any::Any, collections::{BTreeMap, HashMap}, ffi::c_void, iter, mem, @@ -29,6 +28,7 @@ use wasmer_runtime_core::{ codegen::*, fault::{self, raw::register_preservation_trampoline}, loader::CodeMemory, + error::{InvokeError, RuntimeError}, memory::MemoryType, module::{ModuleInfo, ModuleInner}, state::{ @@ -214,7 +214,7 @@ pub struct X64FunctionCode { breakpoints: Option< HashMap< AssemblyOffset, - Box Result<(), Box> + Send + Sync + 'static>, + Box Result<(), RuntimeError> + Send + Sync + 'static>, >, >, returns: SmallVec<[WpType; 1]>, @@ -507,7 +507,7 @@ impl RunnableModule for X64ExecutionContext { func: NonNull, args: *const u64, rets: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, num_params_plus_one: Option>, ) -> bool { let rm: &Box = &(&*(*ctx).module).runnable_module; @@ -655,7 +655,7 @@ impl RunnableModule for X64ExecutionContext { true } Err(err) => { - *error_out = Some(err); + *error_out = Some(InvokeError::Breakpoint(Box::new(err))); false } }; @@ -680,7 +680,7 @@ impl RunnableModule for X64ExecutionContext { }) } - unsafe fn do_early_trap(&self, data: Box) -> ! { + unsafe fn do_early_trap(&self, data: RuntimeError) -> ! { fault::begin_unsafe_unwind(data); } diff --git a/src/commands/run.rs b/src/commands/run.rs index b58075fd777..43223793762 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -20,6 +20,7 @@ use wasmer_runtime::{ }; use wasmer_runtime_core::{ self, + error::RuntimeError, backend::{Compiler, CompilerConfig, MemoryBoundCheckMode}, loader::{Instance as LoadedInstance, LocalLoader}, Module, @@ -437,9 +438,12 @@ fn execute_wasi( } if let Err(ref err) = result { - if let Some(error_code) = err.0.downcast_ref::() { - std::process::exit(error_code.code as i32) + if let RuntimeError::User(user_error) = err { + if let Some(error_code) = user_error.downcast_ref::() { + std::process::exit(error_code.code as i32) + } } + return Err(format!("error: {:?}", err)); } }