diff --git a/lib/js-api/src/native.rs b/lib/js-api/src/native.rs index 1a3fac537..b30fac36f 100644 --- a/lib/js-api/src/native.rs +++ b/lib/js-api/src/native.rs @@ -80,7 +80,7 @@ macro_rules! impl_native_traits { let results = self.exported.function.apply( &JsValue::UNDEFINED, &Array::from_iter(params_list.iter()) - ).unwrap(); + )?; let mut rets_list_array = Rets::empty_array(); let mut_rets = rets_list_array.as_mut() as *mut [i128] as *mut i128; match Rets::size() { diff --git a/lib/js-api/src/trap.rs b/lib/js-api/src/trap.rs index 1b9fce49a..403769642 100644 --- a/lib/js-api/src/trap.rs +++ b/lib/js-api/src/trap.rs @@ -2,8 +2,10 @@ use std::error::Error; use std::fmt; use std::sync::Arc; +use std::convert::TryInto; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; +use wasm_bindgen::JsCast; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -57,8 +59,13 @@ impl RuntimeError { /// Raises a custom user Error pub fn raise(error: Box) -> ! { - let error = RuntimeError { - inner: Arc::new(RuntimeErrorSource::User(error)), + let error = if error.is::() { + *error.downcast::().unwrap() + } + else { + RuntimeError { + inner: Arc::new(RuntimeErrorSource::User(error)), + } }; let js_error: JsValue = error.into(); wasm_bindgen::throw_val(js_error) @@ -119,11 +126,18 @@ impl From for RuntimeError { RuntimeError { inner: Arc::new(RuntimeErrorSource::Js(original)), } + // let into_runtime: Result = original.clone().try_into(); + // match into_runtime { + // Ok(rt) => rt, + // Err(_) => RuntimeError { + // inner: Arc::new(RuntimeErrorSource::Js(original)), + // } + // } + // match original.dyn_into::() { + // Ok(rt) => rt, + // Err(original) => RuntimeError { + // inner: Arc::new(RuntimeErrorSource::Js(original)), + // } + // } } } - -// impl Into for RuntimeError { -// fn into(self) -> JsValue { - -// } -// } diff --git a/lib/js-api/tests/instance.rs b/lib/js-api/tests/instance.rs index eb7816b8f..02723fedb 100644 --- a/lib/js-api/tests/instance.rs +++ b/lib/js-api/tests/instance.rs @@ -585,3 +585,69 @@ fn test_native_function() { let add_one: NativeFunc = instance.exports.get_native_function("add_one").unwrap(); assert_eq!(add_one.call(1), Ok(2)); } + +#[wasm_bindgen_test] +fn test_custom_error() { + let store = Store::default(); + let module = Module::new( + &store, + br#" +(module + (type $run_t (func (param i32 i32) (result i32))) + (type $early_exit_t (func (param) (result))) + (import "env" "early_exit" (func $early_exit (type $early_exit_t))) + (func $run (type $run_t) (param $x i32) (param $y i32) (result i32) + (call $early_exit) + (i32.add + local.get $x + local.get $y)) + (export "run" (func $run))) +"#, + ).unwrap(); + + use std::fmt; + + #[derive(Debug, Clone, Copy)] + struct ExitCode(u32); + + impl fmt::Display for ExitCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl std::error::Error for ExitCode {} + + fn early_exit() { + RuntimeError::raise(Box::new(ExitCode(1))); + } + + let import_object = imports! { + "env" => { + "early_exit" => Function::new_native(&store, early_exit), + } + }; + let instance = Instance::new(&module, &import_object).unwrap(); + + let run_func: NativeFunc<(i32, i32), i32> = instance.exports.get_native_function("run").unwrap(); + + match run_func.call(1, 7) { + Ok(result) => { + assert!(false, + "Expected early termination with `ExitCode`, found: {}", + result + ); + } + Err(e) => { + assert!(false, "Unknown error `{:?}`", e); + match e.downcast::() { + // We found the exit code used to terminate execution. + Ok(exit_code) => { + assert_eq!(exit_code.0, 1); + } + Err(e) => { + assert!(false, "Unknown error `{:?}` found. expected `ErrorCode`", e); + } + }}, + } +} \ No newline at end of file