Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better errors #3813

Merged
merged 13 commits into from
Apr 25, 2023
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