Skip to content

Commit

Permalink
Merge pull request #3813 from wasmerio/better-errors
Browse files Browse the repository at this point in the history
Better errors
  • Loading branch information
syrusakbary authored Apr 25, 2023
2 parents bd75b2b + 88f712a commit d9d479e
Show file tree
Hide file tree
Showing 35 changed files with 697 additions and 786 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ bytes = "1"
# - Optional shared dependencies.
wat = { version = "1.0", optional = true }
tracing = { version = "0.1", optional = true }
rustc-demangle = "0.1"

# Dependencies and Development Dependencies for `sys`.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Expand Down
227 changes: 225 additions & 2 deletions lib/api/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
#[cfg(feature = "js")]
pub use crate::js::errors::{LinkError, RuntimeError};
use crate::js::trap::Trap;
use std::fmt;
use std::sync::Arc;
use thiserror::Error;
use wasmer_types::{FrameInfo, TrapCode};
#[cfg(feature = "sys")]
pub use wasmer_compiler::{LinkError, RuntimeError};
use wasmer_vm::Trap;

use wasmer_types::ImportError;

/// The WebAssembly.LinkError object indicates an error during
/// module instantiation (besides traps from the start function).
///
/// This is based on the [link error][link-error] API.
///
/// [link-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(Error))]
#[cfg_attr(feature = "std", error("Link error: {0}"))]
pub enum LinkError {
/// An error occurred when checking the import types.
#[cfg_attr(feature = "std", error("Error while importing {0:?}.{1:?}: {2}"))]
Import(String, String, ImportError),

/// A trap ocurred during linking.
#[cfg_attr(feature = "std", error("RuntimeError occurred during linking: {0}"))]
Trap(#[source] RuntimeError),
/// Insufficient resources available for linking.
#[cfg_attr(feature = "std", error("Insufficient resources: {0}"))]
Resource(String),
}

/// An error while instantiating a module.
///
Expand Down Expand Up @@ -38,3 +65,199 @@ pub enum InstantiationError {
#[cfg_attr(feature = "std", error("incorrect OS or architecture"))]
DifferentArchOS,
}

/// A struct representing an aborted instruction execution, with a message
/// indicating the cause.
#[derive(Clone)]
pub struct RuntimeError {
pub(crate) inner: Arc<RuntimeErrorInner>,
}

#[derive(Debug)]
struct RuntimeStringError {
details: String,
}

impl RuntimeStringError {
fn new(msg: String) -> RuntimeStringError {
RuntimeStringError { details: msg }
}
}

impl fmt::Display for RuntimeStringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.details)
}
}

impl std::error::Error for RuntimeStringError {
fn description(&self) -> &str {
&self.details
}
}

pub(crate) struct RuntimeErrorInner {
/// The source error
pub(crate) source: Trap,
/// The trap code (if any)
trap_code: Option<TrapCode>,
/// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`).
wasm_trace: Vec<FrameInfo>,
}

impl RuntimeError {
/// Creates a new generic `RuntimeError` with the given `message`.
///
/// # Example
/// ```
/// let trap = wasmer::RuntimeError::new("unexpected error");
/// assert_eq!("unexpected error", trap.message());
/// ```
pub fn new<I: Into<String>>(message: I) -> Self {
let msg = message.into();
let source = RuntimeStringError::new(msg);
Self::user(Box::new(source))
}

/// Creates `RuntimeError` from an error and a WasmTrace
///
/// # Example
/// ```ignore
/// let wasm_trace = vec![wasmer_types::FrameInfo::new(
/// "my_module".to_string(),
/// 0,
/// Some("my_function".to_string()),
/// 0.into(),
/// 2.into()
/// )];
/// let trap = wasmer::RuntimeError::new_from_source(my_error, wasm_trace, None);
/// assert_eq!("unexpected error", trap.message());
/// ```
pub fn new_from_source(
source: Trap,
wasm_trace: Vec<FrameInfo>,
trap_code: Option<TrapCode>,
) -> Self {
// println!("CREATING ERROR FROM TRAP {}", source);
Self {
inner: Arc::new(RuntimeErrorInner {
source,
wasm_trace,
trap_code,
}),
}
}

/// Creates a custom user Error.
///
/// This error object can be passed through Wasm frames and later retrieved
/// using the `downcast` method.
pub fn user(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
match error.downcast::<Self>() {
Ok(err) => *err,
Err(error) => error.into(),
}
}

/// Returns a reference the `message` stored in `Trap`.
pub fn message(&self) -> String {
if let Some(trap_code) = self.inner.trap_code {
trap_code.message().to_string()
} else {
self.inner.source.to_string()
}
}

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

/// Returns trap code, if it's a Trap
pub fn to_trap(self) -> Option<TrapCode> {
self.inner.trap_code
}

// /// Returns trap code, if it's a Trap
// pub fn to_source(self) -> &'static Trap {
// &self.inner.as_ref().source
// }

/// Attempts to downcast the `RuntimeError` to a concrete type.
pub fn downcast<T: std::error::Error + 'static>(self) -> Result<T, Self> {
match Arc::try_unwrap(self.inner) {
Ok(inner) if inner.source.is::<T>() => Ok(inner.source.downcast::<T>().unwrap()),
Ok(inner) => Err(Self {
inner: Arc::new(inner),
}),
Err(inner) => Err(Self { inner }),
}
}

/// Attempts to downcast the `RuntimeError` to a concrete type.
pub fn downcast_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
self.inner.as_ref().source.downcast_ref::<T>()
}

/// Returns true if the `RuntimeError` is the same as T
pub fn is<T: std::error::Error + 'static>(&self) -> bool {
self.inner.source.is::<T>()
}
}

impl fmt::Debug for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeError")
.field("source", &self.inner.source)
.field("wasm_trace", &self.inner.wasm_trace)
.finish()
}
}

impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RuntimeError: {}", self.message())?;
let trace = self.trace();
if trace.is_empty() {
return Ok(());
}
for frame in self.trace().iter() {
let name = frame.module_name();
let func_index = frame.func_index();
writeln!(f)?;
write!(f, " at ")?;
match frame.function_name() {
Some(name) => match rustc_demangle::try_demangle(name) {
Ok(name) => write!(f, "{}", name)?,
Err(_) => write!(f, "{}", name)?,
},
None => write!(f, "<unnamed>")?,
}
write!(
f,
" ({}[{}]:0x{:x})",
name,
func_index,
frame.module_offset()
)?;
}
Ok(())
}
}

impl std::error::Error for RuntimeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source.source()
}
}

impl From<Box<dyn std::error::Error + Send + Sync>> for RuntimeError {
fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
match error.downcast::<Self>() {
// The error is already a RuntimeError, we return it directly
Ok(runtime_error) => *runtime_error,
Err(error) => Trap::user(error).into(),
}
}
}
49 changes: 25 additions & 24 deletions lib/api/src/js/errors.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
use crate::js::lib::std::string::String;
pub use crate::js::trap::RuntimeError;
#[cfg(feature = "std")]
use thiserror::Error;
use wasmer_types::ImportError;
use crate::js::trap::Trap;
use crate::RuntimeError;
use wasm_bindgen::prelude::*;

/// The WebAssembly.LinkError object indicates an error during
/// module instantiation (besides traps from the start function).
///
/// This is based on the [link error][link-error] API.
///
/// [link-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/LinkError
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(Error))]
#[cfg_attr(feature = "std", error("Link error: {0}"))]
pub enum LinkError {
/// An error occurred when checking the import types.
#[cfg_attr(feature = "std", error("Error while importing {0:?}.{1:?}: {2}"))]
Import(String, String, ImportError),
impl From<Trap> for RuntimeError {
fn from(trap: Trap) -> Self {
if trap.is::<RuntimeError>() {
return trap.downcast::<RuntimeError>().unwrap();
}
let wasm_trace = vec![];
let trap_code = None;
// let (wasm_trace, trap_code) = wasmer_compiler::get_trace_and_trapcode(&trap);
RuntimeError::new_from_source(trap, wasm_trace, trap_code)
}
}

impl From<RuntimeError> for JsValue {
fn from(_err: RuntimeError) -> Self {
// err.inner.source.into()
unimplemented!();
}
}

/// A trap ocurred during linking.
#[cfg_attr(feature = "std", error("RuntimeError occurred during linking: {0}"))]
Trap(#[source] RuntimeError),
/// Insufficient resources available for linking.
#[cfg_attr(feature = "std", error("Insufficient resources: {0}"))]
Resource(String),
pub(crate) fn raise(error: Box<dyn std::error::Error + Send + Sync>) -> ! {
let error = Trap::user(error);
let js_error: JsValue = error.into();
wasm_bindgen::throw_val(js_error)
}
8 changes: 4 additions & 4 deletions lib/api/src/js/externals/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,10 @@ macro_rules! impl_host_function {
Ok(Ok(result)) => return result.into_c_struct(&mut store),
#[allow(deprecated)]
#[cfg(feature = "std")]
Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)),
Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)),
#[cfg(feature = "core")]
#[allow(deprecated)]
Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)),
Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)),
Err(_panic) => unimplemented!(),
}
}
Expand Down Expand Up @@ -442,10 +442,10 @@ macro_rules! impl_host_function {
Ok(Ok(result)) => return result.into_c_struct(&mut store),
#[cfg(feature = "std")]
#[allow(deprecated)]
Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)),
Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)),
#[cfg(feature = "core")]
#[allow(deprecated)]
Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)),
Ok(Err(trap)) => crate::js::errors::raise(Box::new(trap)),
Err(_panic) => unimplemented!(),
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/api/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) mod module;
#[cfg(feature = "wasm-types-polyfill")]
mod module_info_polyfill;
pub(crate) mod store;
mod trap;
pub(crate) mod trap;
pub(crate) mod typed_function;
pub(crate) mod vm;
mod wasm_bindgen_polyfill;
Expand Down
Loading

0 comments on commit d9d479e

Please sign in to comment.