Skip to content

Commit

Permalink
Merge pull request #190 from wasmerio/error-refactor
Browse files Browse the repository at this point in the history
Error refactor
  • Loading branch information
syrusakbary authored Jul 25, 2020
2 parents be415df + ffd3617 commit 936ebe6
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 51 deletions.
22 changes: 17 additions & 5 deletions lib/cli/src/commands/run/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::utils::{parse_envvar, parse_mapdir};
use anyhow::{Context, Result};
use std::path::PathBuf;
use wasmer::{Instance, Module};
use wasmer_wasi::{get_wasi_version, WasiState, WasiVersion};
use wasmer_wasi::{get_wasi_version, WasiError, WasiState, WasiVersion};

use structopt::StructOpt;

Expand Down Expand Up @@ -61,10 +61,22 @@ impl Wasi {
wasi_env.set_memory(instance.exports.get_memory("memory")?.clone());

let start = instance.exports.get_function("_start")?;
start
.call(&[])
.with_context(|| "failed to run WASI `_start` function")?;
let result = start.call(&[]);

Ok(())
match result {
Ok(_) => Ok(()),
Err(err) => {
let err: anyhow::Error = match err.downcast::<WasiError>() {
Ok(WasiError::Exit(exit_code)) => {
// We should exit with the provided exit code
std::process::exit(exit_code as _);
}
Ok(err) => err.into(),
Err(err) => err.into(),
};
Err(err)
}
}
.with_context(|| "failed to run WASI `_start` function")
}
}
110 changes: 81 additions & 29 deletions lib/engine/src/trap/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,30 @@ pub struct RuntimeError {
inner: Arc<RuntimeErrorInner>,
}

/// The source of the `RuntimeError`.
#[derive(Debug)]
enum RuntimeErrorSource {
Generic(String),
User(Box<dyn Error + Send + Sync>),
Trap(TrapCode),
}

impl fmt::Display for RuntimeErrorSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Generic(s) => write!(f, "{}", s),
Self::User(s) => write!(f, "{}", s),
Self::Trap(s) => write!(f, "{}", s.message()),
}
}
}

struct RuntimeErrorInner {
message: String,
/// The source error (this can be a custom user `Error` or a [`TrapCode`])
source: RuntimeErrorSource,
/// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`).
wasm_trace: Vec<FrameInfo>,
/// The native backtrace
native_trace: Backtrace,
}

Expand All @@ -24,29 +45,39 @@ fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
}

impl RuntimeError {
/// Creates a new `Trap` with `message`.
/// Creates a new generic `RuntimeError` with the given `message`.
///
/// # Example
/// ```
/// let trap = wasmer_engine::RuntimeError::new("unexpected error");
/// assert_eq!("unexpected error", trap.message());
/// ```
pub fn new<I: Into<String>>(message: I) -> Self {
let info = FRAME_INFO.read().unwrap();
Self::new_with_trace(info, None, message.into(), Backtrace::new_unresolved())
let msg = message.into();
Self::new_with_trace(
info,
None,
RuntimeErrorSource::Generic(msg),
Backtrace::new_unresolved(),
)
}

/// Create a new RuntimeError from a Trap.
pub fn from_trap(trap: Trap) -> Self {
let info = FRAME_INFO.read().unwrap();
match trap {
Trap::User(error) => {
// Since we're the only one using the internals (in
// theory) we should only see user errors which were originally
// created from our own `Trap` type (see the trampoline module
// with functions).
*error
.downcast()
.expect("only `RuntimeError` errors are supported")
match error.downcast::<RuntimeError>() {
// The error is already a RuntimeError, we return it directly
Ok(runtime_error) => *runtime_error,
Err(e) => Self::new_with_trace(
info,
None,
RuntimeErrorSource::User(e),
Backtrace::new_unresolved(),
),
}
}
// A trap caused by an error on the generated machine code for a Wasm function
Trap::Wasm {
Expand All @@ -68,13 +99,13 @@ impl RuntimeError {
.map_or(signal_trap.unwrap_or(TrapCode::StackOverflow), |info| {
info.trap_code
});
Self::new_wasm(info, Some(pc), code, backtrace)
Self::new_with_trace(info, Some(pc), RuntimeErrorSource::Trap(code), backtrace)
}
// A trap triggered manually from the Wasmer runtime
Trap::Runtime {
trap_code,
backtrace,
} => Self::new_wasm(info, None, trap_code, backtrace),
} => Self::new_with_trace(info, None, RuntimeErrorSource::Trap(trap_code), backtrace),
}
}

Expand All @@ -83,20 +114,10 @@ impl RuntimeError {
unsafe { raise_user_trap(error) }
}

fn new_wasm(
info: RwLockReadGuard<GlobalFrameInfo>,
trap_pc: Option<usize>,
code: TrapCode,
backtrace: Backtrace,
) -> Self {
let msg = code.message().to_string();
Self::new_with_trace(info, trap_pc, msg, backtrace)
}

fn new_with_trace(
info: RwLockReadGuard<GlobalFrameInfo>,
trap_pc: Option<usize>,
message: String,
source: RuntimeErrorSource,
native_trace: Backtrace,
) -> Self {
let frames: Vec<usize> = native_trace
Expand Down Expand Up @@ -153,29 +174,52 @@ impl RuntimeError {

Self {
inner: Arc::new(RuntimeErrorInner {
message,
source,
wasm_trace,
native_trace,
}),
}
}

/// Returns a reference the `message` stored in `Trap`.
pub fn message(&self) -> &str {
&self.inner.message
pub fn message(&self) -> String {
format!("{}", self.inner.source)
}

/// Returns a list of function frames in WebAssembly code that led to this
/// trap happening.
pub fn trace(&self) -> &[FrameInfo] {
&self.inner.wasm_trace
}

/// Attempts to downcast the `RuntimeError` to a concrete type.
pub fn downcast<T: Error + 'static>(self) -> Result<T, Self> {
match Arc::try_unwrap(self.inner) {
// We only try to downcast user errors
Ok(RuntimeErrorInner {
source: RuntimeErrorSource::User(err),
..
}) if err.is::<T>() => Ok(*err.downcast::<T>().unwrap()),
Ok(inner) => Err(Self {
inner: Arc::new(inner),
}),
Err(inner) => Err(Self { inner }),
}
}

/// Returns true if the `RuntimeError` is the same as T
pub fn is<T: Error + 'static>(&self) -> bool {
match &self.inner.source {
RuntimeErrorSource::User(err) => err.is::<T>(),
_ => false,
}
}
}

impl fmt::Debug for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeError")
.field("message", &self.inner.message)
.field("source", &self.inner.source)
.field("wasm_trace", &self.inner.wasm_trace)
.field("native_trace", &self.inner.native_trace)
.finish()
Expand All @@ -184,7 +228,7 @@ impl fmt::Debug for RuntimeError {

impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RuntimeError: {}", self.inner.message)?;
write!(f, "RuntimeError: {}", self.message())?;
let trace = self.trace();
if trace.is_empty() {
return Ok(());
Expand Down Expand Up @@ -213,7 +257,15 @@ impl fmt::Display for RuntimeError {
}
}

impl std::error::Error for RuntimeError {}
impl std::error::Error for RuntimeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.inner.source {
RuntimeErrorSource::User(err) => Some(&**err),
RuntimeErrorSource::Trap(err) => Some(err),
_ => None,
}
}
}

impl From<Trap> for RuntimeError {
fn from(trap: Trap) -> Self {
Expand Down
34 changes: 17 additions & 17 deletions lib/vm/src/trap/trapcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
use core::fmt::{self, Display, Formatter};
use core::str::FromStr;
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// A trap code describing the reason for a trap.
///
/// All trap instructions have an explicit trap code.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Error)]
#[repr(u32)]
pub enum TrapCode {
/// The current stack space was exhausted.
Expand Down Expand Up @@ -99,23 +100,22 @@ impl TrapCode {

impl Display for TrapCode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use self::TrapCode::*;
let identifier = match *self {
StackOverflow => "stk_ovf",
HeapSetterOutOfBounds => "heap_set_oob",
HeapAccessOutOfBounds => "heap_get_oob",
TableSetterOutOfBounds => "table_set_oob",
TableAccessOutOfBounds => "table_get_oob",
OutOfBounds => "oob",
IndirectCallToNull => "icall_null",
BadSignature => "bad_sig",
IntegerOverflow => "int_ovf",
IntegerDivisionByZero => "int_divz",
BadConversionToInteger => "bad_toint",
UnreachableCodeReached => "unreachable",
Interrupt => "interrupt",
UnalignedAtomic => "unalign_atom",
VMOutOfMemory => "oom",
Self::StackOverflow => "stk_ovf",
Self::HeapSetterOutOfBounds => "heap_set_oob",
Self::HeapAccessOutOfBounds => "heap_get_oob",
Self::TableSetterOutOfBounds => "table_set_oob",
Self::TableAccessOutOfBounds => "table_get_oob",
Self::OutOfBounds => "oob",
Self::IndirectCallToNull => "icall_null",
Self::BadSignature => "bad_sig",
Self::IntegerOverflow => "int_ovf",
Self::IntegerDivisionByZero => "int_divz",
Self::BadConversionToInteger => "bad_toint",
Self::UnreachableCodeReached => "unreachable",
Self::Interrupt => "interrupt",
Self::UnalignedAtomic => "unalign_atom",
Self::VMOutOfMemory => "oom",
// User(x) => return write!(f, "user{}", x),
};
f.write_str(identifier)
Expand Down

0 comments on commit 936ebe6

Please sign in to comment.