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::{
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<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 {
handler_data: HandlerData,
@ -63,7 +79,7 @@ impl RunnableModule for Caller {
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<InvokeError>,
invoke_env: Option<NonNull<c_void>>,
) -> 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<dyn Any + Send>) -> ! {
unsafe fn do_early_trap(&self, data: RuntimeError) -> ! {
TRAP_EARLY_DATA.with(|cell| cell.set(Some(data)));
trigger_trap()
}

View File

@ -79,16 +79,16 @@ pub fn call_protected<T>(
*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<T>(
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<T>(
"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<T>(
_ => "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 {

View File

@ -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<Wasm>;
/// 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.
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
//! 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(<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
/// given its name.
///

View File

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

View File

@ -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<vm::Func>,
args: *const u64,
rets: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<InvokeError>,
extra: Option<NonNull<c_void>>,
) -> 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<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
@ -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<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