diff --git a/.github/workflows/test-js.yaml b/.github/workflows/test-js.yaml index 486eac08bc9..f83064f488c 100644 --- a/.github/workflows/test-js.yaml +++ b/.github/workflows/test-js.yaml @@ -46,3 +46,6 @@ jobs: - name: Compile Wasmer to WebAssembly and test with a JavaScript host run: make test-js + + - name: Compile Wasmer to WebAssembly and test with a JavaScript host (no-std) + run: make test-js-core \ No newline at end of file diff --git a/Makefile b/Makefile index 8355918c5a3..7ad96636e9b 100644 --- a/Makefile +++ b/Makefile @@ -486,6 +486,9 @@ test-packages: test-js: test-js-api test-js-wasi +test-js-core: + cd lib/api && wasm-pack test --node -- --no-default-features --features js,core,wasm-types-polyfill,wat + test-js-api: cd lib/api && wasm-pack test --node -- --no-default-features --features js-default,wat diff --git a/lib/api/src/js/error.rs b/lib/api/src/js/error.rs index 23df188dcc5..ffc9eeff8b3 100644 --- a/lib/api/src/js/error.rs +++ b/lib/api/src/js/error.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "core")] +use crate::alloc::borrow::Cow; use crate::js::lib::std::string::String; use crate::js::trap::RuntimeError; #[cfg(feature = "std")] @@ -116,7 +118,7 @@ impl From for WasmError { pub enum SerializeError { /// An IO error #[cfg_attr(feature = "std", error(transparent))] - Io(#[from] std::io::Error), + Io(#[cfg_attr(feature = "std", from)] std::io::Error), /// A generic serialization error #[cfg_attr(feature = "std", error("{0}"))] Generic(String), @@ -125,11 +127,12 @@ pub enum SerializeError { /// The Deserialize error can occur when loading a /// compiled Module from a binary. /// Copied from wasmer_compiler::DeSerializeError -#[derive(Error, Debug)] +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(Error))] pub enum DeserializeError { /// An IO error #[cfg_attr(feature = "std", error(transparent))] - Io(#[from] std::io::Error), + Io(#[cfg_attr(feature = "std", from)] std::io::Error), /// A generic deserialization error #[cfg_attr(feature = "std", error("{0}"))] Generic(String), @@ -151,19 +154,20 @@ pub enum DeserializeError { /// 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(Error, Debug)] -#[error("Link error: {0}")] +#[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. - #[error("Error while importing {0:?}.{1:?}: {2}")] + #[cfg_attr(feature = "std", error("Error while importing {0:?}.{1:?}: {2}"))] Import(String, String, ImportError), #[cfg(not(target_arch = "wasm32"))] /// A trap ocurred during linking. - #[error("RuntimeError occurred during linking: {0}")] + #[cfg_attr(feature = "std", error("RuntimeError occurred during linking: {0}"))] Trap(#[source] RuntimeError), /// Insufficient resources available for linking. - #[error("Insufficient resources: {0}")] + #[cfg_attr(feature = "std", error("Insufficient resources: {0}"))] Resource(String), } diff --git a/lib/api/src/js/externals/function.rs b/lib/api/src/js/externals/function.rs index 5e1d169eab4..770c462ae60 100644 --- a/lib/api/src/js/externals/function.rs +++ b/lib/api/src/js/externals/function.rs @@ -1148,6 +1148,10 @@ mod inner { match result { Ok(Ok(result)) => return result.into_c_struct(&mut store), #[allow(deprecated)] + #[cfg(feature = "std")] + Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), + #[cfg(feature = "core")] + #[allow(deprecated)] Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), Err(_panic) => unimplemented!(), } @@ -1191,6 +1195,10 @@ mod inner { match result { Ok(Ok(result)) => return result.into_c_struct(&mut store), + #[cfg(feature = "std")] + #[allow(deprecated)] + Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), + #[cfg(feature = "core")] #[allow(deprecated)] Ok(Err(trap)) => RuntimeError::raise(Box::new(trap)), Err(_panic) => unimplemented!(), diff --git a/lib/api/src/js/mod.rs b/lib/api/src/js/mod.rs index a3c55228557..e9e9e329530 100644 --- a/lib/api/src/js/mod.rs +++ b/lib/api/src/js/mod.rs @@ -7,12 +7,12 @@ compile_error!( compile_error!("Both the `std` and `core` features are disabled. Please enable one of them."); #[cfg(feature = "core")] -extern crate alloc; +pub(crate) extern crate alloc; mod lib { #[cfg(feature = "core")] pub mod std { - pub use alloc::{borrow, boxed, str, string, sync, vec}; + pub use crate::alloc::{borrow, boxed, str, string, sync, vec}; pub use core::fmt; pub use hashbrown as collections; } @@ -23,7 +23,7 @@ mod lib { } } -mod error; +pub(crate) mod error; mod export; mod exports; mod externals; diff --git a/lib/api/src/js/trap.rs b/lib/api/src/js/trap.rs index 0d5ca531860..c9d1ac3f389 100644 --- a/lib/api/src/js/trap.rs +++ b/lib/api/src/js/trap.rs @@ -6,6 +6,89 @@ use wasm_bindgen::convert::FromWasmAbi; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; +pub trait CoreError: fmt::Debug + fmt::Display { + fn source(&self) -> Option<&(dyn CoreError + 'static)> { + None + } + + fn type_id(&self) -> core::any::TypeId + where + Self: 'static, + { + core::any::TypeId::of::() + } + + fn description(&self) -> &str { + "description() is deprecated; use Display" + } + fn cause(&self) -> Option<&dyn CoreError> { + self.source() + } +} + +impl CoreError for T {} + +impl dyn CoreError + 'static { + /// Returns `true` if the inner type is the same as `T`. + pub fn core_is_equal(&self) -> bool { + let t = core::any::TypeId::of::(); + let concrete = self.type_id(); + t == concrete + } +} + +impl dyn CoreError + Send + Sync + 'static { + /// Returns `true` if the inner type is the same as `T`. + pub fn core_is_equal(&self) -> bool { + let t = core::any::TypeId::of::(); + let concrete = self.type_id(); + t == concrete + } +} + +impl dyn CoreError + Send { + #[inline] + /// Attempts to downcast the box to a concrete type. + pub fn downcast_core( + self: Box, + ) -> Result, Box> { + let err: Box = self; + ::downcast_core(err).map_err(|s| unsafe { + // Reapply the `Send` marker. + core::mem::transmute::, Box>(s) + }) + } +} + +impl dyn CoreError + Send + Sync { + #[inline] + /// Attempts to downcast the box to a concrete type. + pub fn downcast_core(self: Box) -> Result, Box> { + let err: Box = self; + ::downcast_core(err).map_err(|s| unsafe { + // Reapply the `Send + Sync` marker. + core::mem::transmute::, Box>(s) + }) + } +} + +impl dyn CoreError { + #[inline] + /// Attempts to downcast the box to a concrete type. + pub fn downcast_core( + self: Box, + ) -> Result, Box> { + if self.core_is_equal::() { + unsafe { + let raw: *mut dyn CoreError = Box::into_raw(self); + Ok(Box::from_raw(raw as *mut T)) + } + } else { + Err(self) + } + } +} + /// A struct representing an aborted instruction execution, with a message /// indicating the cause. #[wasm_bindgen] @@ -30,7 +113,10 @@ impl PartialEq for RuntimeError { #[derive(Debug)] enum RuntimeErrorSource { Generic(String), + #[cfg(feature = "std")] User(Box), + #[cfg(feature = "core")] + User(Box), Js(JsValue), } @@ -64,16 +150,27 @@ impl RuntimeError { /// Raises a custom user Error #[deprecated(since = "2.1.1", note = "return a Result from host functions instead")] + #[cfg(feature = "std")] pub(crate) fn raise(error: Box) -> ! { let error = Self::user(error); let js_error: JsValue = error.into(); wasm_bindgen::throw_val(js_error) } + /// Raises a custom user Error + #[deprecated(since = "2.1.1", note = "return a Result from host functions instead")] + #[cfg(feature = "core")] + pub(crate) fn raise(error: Box) -> ! { + let error = Self::user(error); + let js_error: JsValue = error.into(); + wasm_bindgen::throw_val(js_error) + } + /// Creates a custom user Error. /// /// This error object can be passed through Wasm frames and later retrieved /// using the `downcast` method. + #[cfg(feature = "std")] pub fn user(error: Box) -> Self { match error.downcast::() { // The error is already a RuntimeError, we return it directly @@ -84,6 +181,17 @@ impl RuntimeError { } } + #[cfg(feature = "core")] + pub fn user(error: Box) -> Self { + match error.downcast_core::() { + // The error is already a RuntimeError, we return it directly + Ok(runtime_error) => *runtime_error, + Err(error) => RuntimeError { + inner: Arc::new(RuntimeErrorSource::User(error)), + }, + } + } + /// Returns a reference the `message` stored in `Trap`. pub fn message(&self) -> String { format!("{}", self.inner) @@ -93,7 +201,12 @@ impl RuntimeError { pub fn downcast(self) -> Result { match Arc::try_unwrap(self.inner) { // We only try to downcast user errors + #[cfg(feature = "std")] Ok(RuntimeErrorSource::User(err)) if err.is::() => Ok(*err.downcast::().unwrap()), + #[cfg(feature = "core")] + Ok(RuntimeErrorSource::User(err)) if (*err).core_is_equal::() => { + Ok(*err.downcast_core::().unwrap()) + } Ok(inner) => Err(Self { inner: Arc::new(inner), }), @@ -104,7 +217,10 @@ impl RuntimeError { /// Returns true if the `RuntimeError` is the same as T pub fn is(&self) -> bool { match self.inner.as_ref() { + #[cfg(feature = "std")] RuntimeErrorSource::User(err) => err.is::(), + #[cfg(feature = "core")] + RuntimeErrorSource::User(err) => (*err).core_is_equal::(), _ => false, } } @@ -125,6 +241,7 @@ impl fmt::Display for RuntimeError { } } +#[cfg(feature = "std")] impl std::error::Error for RuntimeError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self.inner.as_ref() { diff --git a/lib/compiler/src/engine/trap/error.rs b/lib/compiler/src/engine/trap/error.rs index 76ae212fb41..e1c0dab5e62 100644 --- a/lib/compiler/src/engine/trap/error.rs +++ b/lib/compiler/src/engine/trap/error.rs @@ -12,12 +12,19 @@ pub struct RuntimeError { inner: Arc, } +pub trait CoreError: fmt::Debug + fmt::Display + core::any::Any {} + +impl CoreError for T {} + /// The source of the `RuntimeError`. #[derive(Debug)] enum RuntimeErrorSource { Generic(String), OutOfMemory, + #[cfg(feature = "std")] User(Box), + #[cfg(feature = "core")] + User(Box), Trap(TrapCode), } @@ -110,6 +117,7 @@ impl RuntimeError { /// /// This error object can be passed through Wasm frames and later retrieved /// using the `downcast` method. + #[cfg(feature = "std")] pub fn user(error: Box) -> Self { match error.downcast::() { // The error is already a RuntimeError, we return it directly @@ -126,6 +134,27 @@ impl RuntimeError { } } + /// Creates a custom user Error. + /// + /// This error object can be passed through Wasm frames and later retrieved + /// using the `downcast` method. + #[cfg(feature = "core")] + pub fn user(error: Box) -> Self { + match error.downcast::() { + // The error is already a RuntimeError, we return it directly + Ok(runtime_error) => *runtime_error, + Err(error) => { + let info = FRAME_INFO.read().unwrap(); + Self::new_with_trace( + &info, + None, + RuntimeErrorSource::User(error), + Backtrace::new_unresolved(), + ) + } + } + } + fn new_with_trace( info: &GlobalFrameInfo, trap_pc: Option,