Make runtime and trap errors well defined (WIP)

This commit is contained in:
Mark McCaskey 2020-04-23 12:40:35 -07:00
parent ab106af422
commit bfb6814f23
6 changed files with 125 additions and 35 deletions

View File

@ -1,12 +1,13 @@
use crate::{ use crate::{
relocation::{TrapData, TrapSink}, relocation::{TrapData, TrapSink, TrapCode},
resolver::FuncResolver, resolver::FuncResolver,
trampoline::Trampolines, trampoline::Trampolines,
}; };
use libc::c_void; use libc::c_void;
use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc}; use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc};
use wasmer_runtime_core::{ use wasmer_runtime_core::{
backend::RunnableModule, backend::{RunnableModule, ExceptionCode},
error::{InvokeError, RuntimeError},
module::ModuleInfo, module::ModuleInfo,
typed_func::{Trampoline, Wasm}, typed_func::{Trampoline, Wasm},
types::{LocalFuncIndex, SigIndex}, types::{LocalFuncIndex, SigIndex},
@ -26,10 +27,25 @@ pub use self::unix::*;
pub use self::windows::*; pub use self::windows::*;
thread_local! { thread_local! {
pub static TRAP_EARLY_DATA: Cell<Option<Box<dyn Any + Send>>> = Cell::new(None); pub static TRAP_EARLY_DATA: Cell<Option<RuntimeError>> = Cell::new(None);
} }
pub struct CallProtError(pub Box<dyn Any + Send>); pub enum CallProtError {
UnknownTrap {
address: usize,
signal: &'static str,
},
TrapCode {
code: ExceptionCode,
srcloc: u32,
},
UnknownTrapCode {
trap_code: TrapCode,
srcloc: u32,
},
EarlyTrap(RuntimeError),
Misc(Box<dyn Any + Send>),
}
pub struct Caller { pub struct Caller {
handler_data: HandlerData, handler_data: HandlerData,
@ -63,7 +79,7 @@ impl RunnableModule for Caller {
func: NonNull<vm::Func>, func: NonNull<vm::Func>,
args: *const u64, args: *const u64,
rets: *mut u64, rets: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>, error_out: *mut Option<InvokeError>,
invoke_env: Option<NonNull<c_void>>, invoke_env: Option<NonNull<c_void>>,
) -> bool { ) -> bool {
let handler_data = &*invoke_env.unwrap().cast().as_ptr(); let handler_data = &*invoke_env.unwrap().cast().as_ptr();
@ -80,6 +96,9 @@ impl RunnableModule for Caller {
match res { match res {
Err(err) => { 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); *error_out = Some(err.0);
false false
} }
@ -101,7 +120,7 @@ impl RunnableModule for Caller {
}) })
} }
unsafe fn do_early_trap(&self, data: Box<dyn Any + Send>) -> ! { unsafe fn do_early_trap(&self, data: RuntimeError) -> ! {
TRAP_EARLY_DATA.with(|cell| cell.set(Some(data))); TRAP_EARLY_DATA.with(|cell| cell.set(Some(data)));
trigger_trap() trigger_trap()
} }

View File

@ -79,16 +79,16 @@ pub fn call_protected<T>(
*jmp_buf = prev_jmp_buf; *jmp_buf = prev_jmp_buf;
if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(CallProtError(data)) Err(CallProtError::EarlyTrap(data))
} else { } else {
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
if let Some(TrapData { if let Some(TrapData {
trapcode, trapcode,
srcloc: _, srcloc,
}) = handler_data.lookup(inst_ptr) }) = 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 { Ok(SIGILL) => match trapcode {
TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds, TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds,
TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds, TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds,
@ -101,9 +101,10 @@ pub fn call_protected<T>(
TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic, TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic,
TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable, TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable,
_ => { _ => {
return Err(CallProtError(Box::new( return Err(CallProtError::UnknownTrapCode {
"unknown clif trap code".to_string(), trap_code: trapcode,
))) srcloc,
})
} }
}, },
Ok(SIGSEGV) | Ok(SIGBUS) => ExceptionCode::MemoryOutOfBounds, Ok(SIGSEGV) | Ok(SIGBUS) => ExceptionCode::MemoryOutOfBounds,
@ -112,7 +113,11 @@ pub fn call_protected<T>(
"ExceptionCode::Unknown signal:{:?}", "ExceptionCode::Unknown signal:{:?}",
Signal::from_c_int(signum) Signal::from_c_int(signum)
), ),
}))) };
Err(CallProtError::TrapCode {
srcloc,
code,
})
} else { } else {
let signal = match Signal::from_c_int(signum) { let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception", Ok(SIGFPE) => "floating-point exception",
@ -123,8 +128,10 @@ pub fn call_protected<T>(
_ => "unknown trapped signal", _ => "unknown trapped signal",
}; };
// When the trap-handler is fully implemented, this will return more information. // When the trap-handler is fully implemented, this will return more information.
let s = format!("unknown trap at {:p} - {}", faulting_addr, signal); Err(CallProtError::UnknownTrap {
Err(CallProtError(Box::new(s))) address: faulting_addr as usize,
signal,
})
} }
} }
} else { } else {

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
error::CompileResult, error::{CompileResult, RuntimeError},
module::ModuleInner, module::ModuleInner,
state::ModuleStateMap, state::ModuleStateMap,
typed_func::Wasm, typed_func::Wasm,
@ -282,7 +282,7 @@ pub trait RunnableModule: Send + Sync {
fn get_trampoline(&self, info: &ModuleInfo, sig_index: SigIndex) -> Option<Wasm>; fn get_trampoline(&self, info: &ModuleInfo, sig_index: SigIndex) -> Option<Wasm>;
/// Trap an error. /// Trap an error.
unsafe fn do_early_trap(&self, data: Box<dyn Any + Send>) -> !; unsafe fn do_early_trap(&self, data: RuntimeError) -> !;
/// Returns the machine code associated with this module. /// Returns the machine code associated with this module.
fn get_code(&self) -> Option<&[u8]> { fn get_code(&self) -> Option<&[u8]> {

View File

@ -1,6 +1,6 @@
//! The error module contains the data structures and helper functions used to implement errors that //! The error module contains the data structures and helper functions used to implement errors that
//! are produced and returned from the wasmer runtime core. //! 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 crate::types::{FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type};
use core::borrow::Borrow; use core::borrow::Borrow;
use std::any::Any; use std::any::Any;
@ -173,6 +173,7 @@ impl std::fmt::Display for LinkError {
impl std::error::Error for LinkError {} impl std::error::Error for LinkError {}
/*
/// This is the error type returned when calling /// This is the error type returned when calling
/// a WebAssembly function. /// a WebAssembly function.
/// ///
@ -210,6 +211,68 @@ impl std::fmt::Debug for RuntimeError {
impl std::error::Error 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(<string>))`.
///
/// 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<dyn Any + Send>)
}
/// TODO:
#[derive(Debug)]
pub enum InvokeError {
/// not yet handled error cases, ideally we should be able to handle them all
Misc(Box<dyn Any + Send>),
/// 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::<String>() {
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::<i32>() {
write!(f, "{}", n)
} else {
write!(f, "unknown error type")
}
},
}
}
}
/*
impl From<InternalError> for RuntimeError {
fn from(other: InternalError) -> Self {
RuntimeError(Box::new(other))
}
}
*/
/// This error type is produced by resolving a wasm function /// This error type is produced by resolving a wasm function
/// given its name. /// given its name.
/// ///

View File

@ -5,7 +5,7 @@
use crate::{ use crate::{
backend::RunnableModule, backend::RunnableModule,
backing::{ImportBacking, LocalBacking}, backing::{ImportBacking, LocalBacking},
error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError}, error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError, InvokeError},
export::{Context, Export, ExportIter, Exportable, FuncPointer}, export::{Context, Export, ExportIter, Exportable, FuncPointer},
global::Global, global::Global,
import::{ImportObject, LikeNamespace}, import::{ImportObject, LikeNamespace},
@ -584,10 +584,10 @@ pub(crate) fn call_func_with_index_inner(
invoke_env, invoke_env,
} = wasm; } = wasm;
let run_wasm = |result_space: *mut u64| unsafe { let run_wasm = |result_space: *mut u64| -> CallResult<()> {
let mut error_out = None; let mut error_out = None;
let success = invoke( let success = unsafe { invoke(
trampoline, trampoline,
ctx_ptr, ctx_ptr,
func_ptr, func_ptr,
@ -595,14 +595,15 @@ pub(crate) fn call_func_with_index_inner(
result_space, result_space,
&mut error_out, &mut error_out,
invoke_env, invoke_env,
); )};
if success { if success {
Ok(()) Ok(())
} else { } else {
Err(error_out let error: RuntimeError = error_out
.map(RuntimeError) .map(RuntimeError::InvokeError)
.unwrap_or_else(|| RuntimeError(Box::new("invoke(): Unknown error".to_string())))) .unwrap_or_else(|| RuntimeError::InvokeError(InvokeError::FailedWithNoError));
Err(error.into())
} }
}; };

View File

@ -1,7 +1,7 @@
//! The typed func module implements a way of representing a wasm function //! 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. //! with the correct types from rust. Function calls using a typed func have a low overhead.
use crate::{ use crate::{
error::RuntimeError, error::{RuntimeError, InvokeError},
export::{Context, Export, FuncPointer}, export::{Context, Export, FuncPointer},
import::IsExport, import::IsExport,
types::{FuncSig, NativeWasmType, Type, WasmExternType}, types::{FuncSig, NativeWasmType, Type, WasmExternType},
@ -37,7 +37,7 @@ pub type Invoke = unsafe extern "C" fn(
func: NonNull<vm::Func>, func: NonNull<vm::Func>,
args: *const u64, args: *const u64,
rets: *mut u64, rets: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>, error_out: *mut Option<InvokeError>,
extra: Option<NonNull<c_void>>, extra: Option<NonNull<c_void>>,
) -> bool; ) -> bool;
@ -340,7 +340,7 @@ impl<'a> DynamicFunc<'a> {
Err(e) => { Err(e) => {
// At this point, there is an error that needs to be trapped. // At this point, there is an error that needs to be trapped.
drop(args); // Release the Vec which will leak otherwise. 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)) Ok(Rets::from_ret_array(rets))
} else { } else {
Err(error_out.map(RuntimeError).unwrap_or_else(|| { Err(error_out.map_or_else(|| RuntimeError::InvokeError(InvokeError::FailedWithNoError), RuntimeError::InvokeError))
RuntimeError(Box::new("invoke(): Unknown error".to_string()))
}))
} }
} }
} }
@ -678,9 +676,10 @@ macro_rules! impl_traits {
Ok(Ok(returns)) => return returns.into_c_struct(), Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => { Ok(Err(err)) => {
let b: Box<_> = err.into(); let b: Box<_> = err.into();
b as Box<dyn Any + Send> RuntimeError::User(b as Box<dyn Any + Send>)
}, },
Err(err) => err, // TODO(blocking): this line is wrong!
Err(err) => RuntimeError::User(err),
}; };
// At this point, there is an error that needs to // 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(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => { Ok(Err(err)) => {
let b: Box<_> = err.into(); let b: Box<_> = err.into();
b as Box<dyn Any + Send> RuntimeError::User(b as Box<dyn Any + Send>)
}, },
Err(err) => err, // TODO(blocking): this line is wrong!
Err(err) => RuntimeError::User(err),
}; };
// At this point, there is an error that needs to // At this point, there is an error that needs to