diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e299f1f6ebe..99a6b0204d59 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1144,6 +1144,8 @@ jobs: - crate: "wasmtime-cli" - crate: "wasmtime-environ --all-features" - crate: "pulley-interpreter --all-features" + - crate: "wasmtime-internal-error" + - crate: "wasmtime-internal-error --all-features" - script: ./ci/miri-provenance-test.sh - script: ./ci/miri-wast.sh ./tests/spec_testsuite/table.wast needs: determine diff --git a/Cargo.lock b/Cargo.lock index edd8d3237821..c4f75c076d07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4747,6 +4747,7 @@ dependencies = [ "wasmprinter", "wasmtime", "wasmtime-cli-flags", + "wasmtime-internal-error", "wasmtime-test-util", "wasmtime-wasi", "wasmtime-wast", @@ -4832,6 +4833,13 @@ dependencies = [ "wasmtime-internal-versioned-export-macros", ] +[[package]] +name = "wasmtime-internal-error" +version = "41.0.0" +dependencies = [ + "anyhow", +] + [[package]] name = "wasmtime-internal-explorer" version = "41.0.0" diff --git a/Cargo.toml b/Cargo.toml index c06d8eddaea7..7e3b86a98968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,6 +158,7 @@ members = [ "crates/bench-api", "crates/c-api/artifact", "crates/environ/fuzz", + "crates/error", "crates/misc/component-async-tests", "crates/test-programs", "crates/wasi-preview1-component-adapter", @@ -258,6 +259,7 @@ wasmtime-wast = { path = "crates/wast", version = "=41.0.0" } # that these are internal unsupported crates for external use. These exist as # part of the project organization of other public crates in Wasmtime and are # otherwise not supported in terms of CVEs for example. +wasmtime-error = { path = "crates/error", version = "=41.0.0", package = 'wasmtime-internal-error' } wasmtime-wmemcheck = { path = "crates/wmemcheck", version = "=41.0.0", package = 'wasmtime-internal-wmemcheck' } wasmtime-c-api-macros = { path = "crates/c-api-macros", version = "=41.0.0", package = 'wasmtime-internal-c-api-macros' } wasmtime-cache = { path = "crates/cache", version = "=41.0.0", package = 'wasmtime-internal-cache' } diff --git a/crates/error/Cargo.toml b/crates/error/Cargo.toml new file mode 100644 index 000000000000..b900cd25420a --- /dev/null +++ b/crates/error/Cargo.toml @@ -0,0 +1,23 @@ +[package] +authors.workspace = true +description = "INTERNAL: Wasmtime's universal error type's implementation" +edition.workspace = true +license = "Apache-2.0 WITH LLVM-exception" +name = "wasmtime-internal-error" +rust-version.workspace = true +version.workspace = true + +[dependencies] +anyhow = { workspace = true, optional = true } + +[lints] +workspace = true + +[features] +# Enable the use of the `std` crate. +std = [] +# Enable backtraces. +backtrace = ["std"] +# Enable the `From for anyhow::Error` implementation and +# `Error::from_anyhow` constructor. +anyhow = ["dep:anyhow"] diff --git a/crates/error/src/backtrace.rs b/crates/error/src/backtrace.rs new file mode 100644 index 000000000000..4301d3d360c7 --- /dev/null +++ b/crates/error/src/backtrace.rs @@ -0,0 +1,28 @@ +use std::backtrace::Backtrace; +use std::sync::atomic::{AtomicBool, Ordering}; + +static ENABLED: AtomicBool = AtomicBool::new(true); + +fn enabled() -> bool { + ENABLED.load(Ordering::Acquire) +} + +/// Forcibly disable capturing backtraces dynamically. +/// +/// XXX: This is only exposed for internal testing, to work around cargo +/// workspaces and feature resolution. This method may disappear or change +/// at any time. Instead of using this method, you should disable the +/// `backtrace` cargo feature. +#[doc(hidden)] +pub fn disable_backtrace() { + ENABLED.store(false, Ordering::Release) +} + +#[track_caller] +pub fn capture() -> Backtrace { + if enabled() { + Backtrace::capture() + } else { + Backtrace::disabled() + } +} diff --git a/crates/error/src/boxed.rs b/crates/error/src/boxed.rs new file mode 100644 index 000000000000..cba4d90f3dfe --- /dev/null +++ b/crates/error/src/boxed.rs @@ -0,0 +1,50 @@ +use super::{OutOfMemory, Result}; +use alloc::boxed::Box; +use core::alloc::Layout; +use core::ptr::NonNull; + +/// Try to allocate a block of memory that fits the given layout, or return an +/// `OutOfMemory` error. +/// +/// # Safety +/// +/// Same as `alloc::alloc::alloc`: layout must have non-zero size. +#[inline] +pub(crate) unsafe fn try_alloc(layout: Layout) -> Result, OutOfMemory> { + // Safety: same as our safety conditions. + debug_assert!(layout.size() > 0); + let ptr = unsafe { alloc::alloc::alloc(layout) }; + + if let Some(ptr) = NonNull::new(ptr) { + Ok(ptr) + } else { + out_of_line_slow_path!(Err(OutOfMemory::new())) + } +} + +/// Create a `Box`, or return an `OutOfMemory` error. +#[inline] +pub(crate) fn try_box(value: T) -> Result, OutOfMemory> { + let layout = alloc::alloc::Layout::new::(); + + if layout.size() == 0 { + // Safety: `Box` explicitly allows construction from dangling pointers + // (which are guaranteed non-null and aligned) for zero-sized types. + return Ok(unsafe { Box::from_raw(core::ptr::dangling::().cast_mut()) }); + } + + // Safety: layout size is non-zero. + let ptr = unsafe { try_alloc(layout)? }; + + let ptr = ptr.cast::(); + + // Safety: The allocation succeeded, and it has `T`'s layout, so the pointer + // is valid for writing a `T`. + unsafe { + ptr.write(value); + } + + // Safety: The pointer's memory block was allocated by the global allocator, + // is valid for `T`, and is initialized. + Ok(unsafe { Box::from_raw(ptr.as_ptr()) }) +} diff --git a/crates/error/src/context.rs b/crates/error/src/context.rs new file mode 100644 index 000000000000..b1890b5ee368 --- /dev/null +++ b/crates/error/src/context.rs @@ -0,0 +1,294 @@ +use crate::{ + Error, ErrorExt, Result, + error::{OomOrDynError, OomOrDynErrorMut, OomOrDynErrorRef}, +}; +use core::any::TypeId; +use core::fmt; +use core::ptr::NonNull; + +mod sealed { + use super::*; + pub trait Sealed {} + impl Sealed for Result {} + impl Sealed for Option {} +} + +/// Extension trait to add error context to results. +/// +/// This extension trait, and its methods, are the primary way to create error +/// chains. An error's debug output will include the full chain of +/// errors. Errors in these chains are accessible via the +/// [`Error::chain`][crate::Error::chain] and +/// [`Error::root_cause`][crate::Error::root_cause] methods. +/// +/// After applying error context of type `C`, calling +/// [`error.is::()`][crate::Error::is] will return `true` for the new error +/// (unless there was a memory allocation failure) in addition to any other +/// types `T` for which it was already the case that `error.is::()`. +/// +/// This boxes the inner `C` type, but if that box allocation fails, then this +/// trait's functions return an `Error` where +/// [`error.is::()`][crate::OutOfMemory] is true. +/// +/// # Example +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{Context as _, Result}; +/// # #[cfg(feature = "backtrace")] +/// # wasmtime_internal_error::disable_backtrace(); +/// +/// fn u32_to_u8(x: u32) -> Result { +/// let y = u8::try_from(x).with_context(|| { +/// format!("failed to convert `{x}` into a `u8` (max = `{}`)", u8::MAX) +/// })?; +/// Ok(y) +/// } +/// +/// let x = u32_to_u8(42).unwrap(); +/// assert_eq!(x, 42); +/// +/// let error = u32_to_u8(999).unwrap_err(); +/// +/// // The error is a `String` because of our added context. +/// assert!(error.is::()); +/// assert_eq!( +/// error.to_string(), +/// "failed to convert `999` into a `u8` (max = `255`)", +/// ); +/// +/// // But it is also a `TryFromIntError` because of the inner error. +/// assert!(error.is::()); +/// assert_eq!( +/// error.root_cause().to_string(), +/// "out of range integral type conversion attempted", +/// ); +/// +/// // The debug output of the error contains the full error chain. +/// assert_eq!( +/// format!("{error:?}").trim(), +/// r#" +/// failed to convert `999` into a `u8` (max = `255`) +/// +/// Caused by: +/// 0: out of range integral type conversion attempted +/// "#.trim(), +/// ); +/// ``` +/// +/// # Example with `Option` +/// +/// You can also use this trait to create the initial, root-cause `Error` when a +/// fallible function returns an `Option`: +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{Context as _, Result}; +/// +/// fn try_get(slice: &[T], i: usize) -> Result<&T> { +/// let elem: Option<&T> = slice.get(i); +/// elem.with_context(|| { +/// format!("out of bounds access: index is {i} but length is {}", slice.len()) +/// }) +/// } +/// +/// let arr = [921, 36, 123, 42, 785]; +/// +/// let x = try_get(&arr, 2).unwrap(); +/// assert_eq!(*x, 123); +/// +/// let error = try_get(&arr, 9999).unwrap_err(); +/// assert_eq!( +/// error.to_string(), +/// "out of bounds access: index is 9999 but length is 5", +/// ); +/// ``` +pub trait Context: sealed::Sealed { + /// Add additional, already-computed error context to this result. + /// + /// Because this method requires that the error context is already computed, + /// it should only be used when the `context` is already available or is + /// effectively a constant. Otherwise, it effectively forces computation of + /// the context, even when we aren't on an error path. The + /// [`Context::with_context`][crate::Context::with_context] method is + /// preferred in these scenarios, as it lazily computes the error context, + /// only doing so when we are actually on an error path. + fn context(self, context: C) -> Result + where + C: fmt::Display + Send + Sync + 'static; + + /// Add additional, lazily-computed error context to this result. + /// + /// Only invokes `f` to compute the error context when we are actually on an + /// error path. Does not invoke `f` if we are not on an error path. + fn with_context(self, f: F) -> Result + where + C: fmt::Display + Send + Sync + 'static, + F: FnOnce() -> C; +} + +impl Context for Result +where + E: core::error::Error + Send + Sync + 'static, +{ + #[inline] + fn context(self, context: C) -> Result + where + C: fmt::Display + Send + Sync + 'static, + { + match self { + Ok(x) => Ok(x), + Err(e) => out_of_line_slow_path!(Err(Error::new(e).context(context))), + } + } + + #[inline] + fn with_context(self, f: F) -> Result + where + C: fmt::Display + Send + Sync + 'static, + F: FnOnce() -> C, + { + match self { + Ok(x) => Ok(x), + Err(e) => out_of_line_slow_path!(Err(Error::new(e).context(f()))), + } + } +} + +impl Context for Result { + fn context(self, context: C) -> Result + where + C: fmt::Display + Send + Sync + 'static, + { + match self { + Ok(x) => Ok(x), + Err(e) => out_of_line_slow_path!(Err(e.context(context))), + } + } + + fn with_context(self, f: F) -> Result + where + C: fmt::Display + Send + Sync + 'static, + F: FnOnce() -> C, + { + match self { + Ok(x) => Ok(x), + Err(e) => out_of_line_slow_path!(Err(e.context(f()))), + } + } +} + +impl Context for Option { + fn context(self, context: C) -> Result + where + C: fmt::Display + Send + Sync + 'static, + { + match self { + Some(x) => Ok(x), + None => out_of_line_slow_path!(Err(Error::from_error_ext(ContextError { + context, + error: None + }))), + } + } + + fn with_context(self, f: F) -> Result + where + C: fmt::Display + Send + Sync + 'static, + F: FnOnce() -> C, + { + match self { + Some(x) => Ok(x), + None => out_of_line_slow_path!(Err(Error::from_error_ext(ContextError { + context: f(), + error: None + }))), + } + } +} + +// NB: The `repr(C)` is required for safety of the `ErrorExt::ext_is` +// implementation and the casts that are performed using that method's +// return value. +#[repr(C)] +pub(crate) struct ContextError { + // NB: This must be the first field for safety of the `ErrorExt::ext_is` + // implementation and the casts that are performed using that method's + // return value. + pub(crate) context: C, + + pub(crate) error: Option, +} + +impl fmt::Debug for ContextError +where + C: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for ContextError +where + C: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.context.fmt(f) + } +} + +impl core::error::Error for ContextError +where + C: fmt::Display + Send + Sync + 'static, +{ + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + let source = self.ext_source()?; + Some(source.as_dyn_core_error()) + } +} + +unsafe impl ErrorExt for ContextError +where + C: fmt::Display + Send + Sync + 'static, +{ + fn ext_source(&self) -> Option> { + let error = self.error.as_ref()?; + Some(error.inner.unpack()) + } + + fn ext_source_mut(&mut self) -> Option> { + let error = self.error.as_mut()?; + Some(error.inner.unpack_mut()) + } + + fn ext_take_source(&mut self) -> Option { + let error = self.error.take()?; + Some(error.inner) + } + + fn ext_is(&self, type_id: TypeId) -> bool { + // NB: need to check type id of `C`, not `Self` aka + // `ContextError`. + type_id == TypeId::of::() + } + + unsafe fn ext_move(self, to: NonNull) { + // Safety: implied by this trait method's contract. + unsafe { + to.cast::().write(self.context); + } + } + + #[cfg(feature = "backtrace")] + fn take_backtrace(&mut self) -> Option { + let error = self.error.as_mut()?; + match error.inner.unpack_mut() { + OomOrDynErrorMut::Oom(_) => None, + OomOrDynErrorMut::DynError(mut e) => { + let r = unsafe { e.as_mut() }; + r.backtrace.take() + } + } + } +} diff --git a/crates/error/src/error.rs b/crates/error/src/error.rs new file mode 100644 index 000000000000..021e7e63eb24 --- /dev/null +++ b/crates/error/src/error.rs @@ -0,0 +1,1812 @@ +use super::boxed::try_box; +use super::context::ContextError; +use super::ptr::{MutPtr, OwnedPtr, SharedPtr}; +use super::vtable::Vtable; +use crate::{OutOfMemory, Result}; +use alloc::boxed::Box; +use core::{ + any::TypeId, + fmt::{self, Debug}, + iter::FusedIterator, + mem, + ptr::NonNull, +}; +#[cfg(feature = "backtrace")] +use std::backtrace::{Backtrace, BacktraceStatus}; + +/// Internal extension trait for errors. +/// +/// # Safety +/// +/// Implementations must correctly report their type (or a type `T` where `*mut +/// Self` can be cast to `*mut T` and safely accessed) in `ext_is`. +pub(crate) unsafe trait ErrorExt: core::error::Error + Send + Sync + 'static { + /// Get a shared borrow of the next error in the chain. + fn ext_source(&self) -> Option>; + + /// Get an exclusive borrow of the next error in the chain. + fn ext_source_mut(&mut self) -> Option>; + + /// Take ownership of the next error in the chain. + fn ext_take_source(&mut self) -> Option; + + /// Is this error an instance of `T`, where `type_id == TypeId::of::()`? + /// + /// # Safety + /// + /// Implementations must return `true` only when they are actually a `T`, a + /// `#[repr(transparent)]` newtype wrapper around a `T`, or a `#[repr(C)]` + /// struct with a `T` as their first field. Safety relies on this invariant. + fn ext_is(&self, type_id: TypeId) -> bool; + + /// Move the inner `T` error into the storage referenced by `dest`. + /// + /// # Safety + /// + /// Callers must ensure that `dest` is valid for writing a `T` to. + /// + /// Implementations must ensure that the memory block pointed to by `dest` + /// contains a valid, initialized `T` upon successful return. + unsafe fn ext_move(self, dest: NonNull); + + /// Take the backtrace from this error, if any. + #[cfg(feature = "backtrace")] + fn take_backtrace(&mut self) -> Option; +} + +/// Morally a `dyn ErrorExt` trait object that holds its own vtable. +/// +/// Must only ever be used via some kind of indirection (pointer, reference, +/// `Box`, etc...) that is punning a `ConcreteError` and never directly as an +/// on-stack value, for example. +/// +/// See the docs for `Vtable` for details about why we make our own trait +/// objects. +/// +/// XXX: Must have a compatible layout with `ConcreteError`. See the +/// assertions in `BoxedDynError::new` and the +/// `dyn_error_and_concrete_error_layouts_are_compatible` test below. +#[repr(C)] +pub(crate) struct DynError { + // Safety: this vtable must always be associated with the `E` for the + // `ConcreteError` that this `DynError` is punning. + pub(crate) vtable: &'static Vtable, + #[cfg(feature = "backtrace")] + pub(crate) backtrace: Option, + // error: +} + +/// A `dyn ErrorExt` trait object that we know the concrete type of. +/// +/// XXX: Must have a compatible layout with `DynError`. See the +/// assertions in `BoxedDynError::new` and the +/// `dyn_error_and_concrete_error_layouts_are_compatible` test below. +#[repr(C)] +pub(crate) struct ConcreteError { + // Safety: this vtable must always be `E`'s vtable. This is ensured in + // `BoxDynError::new`. + pub(crate) vtable: &'static Vtable, + #[cfg(feature = "backtrace")] + pub(crate) backtrace: Option, + pub(crate) error: E, +} + +pub(crate) struct BoxedDynError { + inner: OwnedPtr, +} + +// Safety: `BoxedDynError::new` ensures that every concrete error type we make a +// `BoxedDynError` from is `Send`. +unsafe impl Send for BoxedDynError {} + +// Safety: `BoxedDynError::new` ensures that every concrete error type we make a +// `BoxedDynError` from is `Sync`. +unsafe impl Sync for BoxedDynError {} + +impl Drop for BoxedDynError { + fn drop(&mut self) { + let ptr = self.inner.raw_copy(); + // Safety: We own the pointer and it is valid for reading/writing + // `DynError`s. + let inner = unsafe { ptr.as_ref() }; + let vtable = inner.vtable; + // Safety: The vtable is for this pointer's concrete type and the + // pointer is valid to deallocate because we are passing ownership in. + unsafe { + (vtable.drop_and_deallocate)(ptr); + } + } +} + +impl BoxedDynError { + #[inline] + fn new(mut error: E) -> Result + where + // NB: This implies `Send + Sync`, which is necessary for safety. + E: ErrorExt, + { + #[cfg(not(feature = "backtrace"))] + let _ = &mut error; + + // Note: do not use `Option::or_else` here to avoid an extra frame + // showing up in the backtrace, which would create extra noise for users + // to mentally filter out. + #[cfg(feature = "backtrace")] + let backtrace = match error.take_backtrace() { + Some(bt) => bt, + None => crate::backtrace::capture(), + }; + + let error = try_box(ConcreteError { + vtable: Vtable::of::(), + #[cfg(feature = "backtrace")] + backtrace: Some(backtrace), + error, + })?; + + // We are going to pun the `ConcreteError` pointer into a `DynError` + // pointer. Debug assert that their layouts are compatible first. + #[cfg(debug_assertions)] + { + let dyn_size = mem::size_of::(); + let concrete_size = mem::size_of::>(); + assert!( + dyn_size <= concrete_size, + "assertion failed: {dyn_size} <= {concrete_size}" + ); + + let dyn_align = mem::align_of::(); + let concrete_align = mem::align_of::>(); + assert!( + dyn_align <= concrete_align, + "assertion failed: {dyn_align} <= {concrete_align}" + ); + + let dyn_offset = mem::offset_of!(DynError, vtable); + let concrete_offset = mem::offset_of!(ConcreteError, vtable); + assert_eq!(dyn_offset, concrete_offset); + + #[cfg(feature = "backtrace")] + { + let dyn_offset = mem::offset_of!(DynError, backtrace); + let concrete_offset = mem::offset_of!(ConcreteError, backtrace); + assert_eq!(dyn_offset, concrete_offset); + } + } + + let ptr = Box::into_raw(error); + let ptr = ptr.cast::(); + // Safety: `Box::into_raw` always returns a non-null pointer. + let ptr = unsafe { NonNull::new_unchecked(ptr) }; + let ptr = OwnedPtr::new(ptr); + Ok(Self::from_owned_ptr(ptr)) + } + + fn into_owned_ptr(self) -> OwnedPtr { + let ptr = self.inner.raw_copy(); + mem::forget(self); + ptr + } + + fn from_owned_ptr(inner: OwnedPtr) -> Self { + BoxedDynError { inner } + } +} + +/// Wasmtime's universal error type. +/// +/// 99% API-compatible with `anyhow::Error` but additionally allows recovery +/// from memory exhaustion (see the [`OutOfMemory`] error). +/// +/// `Error` is similar to `Box` +/// but fits in one word instead of two. Additionally, `Result<(), Error>` also +/// fits in a single word. +/// +/// When the `"backtrace"` cargo feature is enabled, `Error` contains a +/// backtrace. +/// +/// # Creating an `Error` +/// +/// Because `Error` implements `From` for all types `E` that implement +/// `core::error::Error + Send + Sync + 'static`, you don't usually need to +/// explicitly construct an `Error`. When you use `?`-style error propagation, +/// it will automatically get constructed from the root cause error for you. +/// +/// Most often when creating an `Error`, you just want to early-exit from the +/// function, returning `Err(...)`. The [`ensure!`][crate::ensure] macro +/// early-returns an error when a condition is not met (similar to how `assert!` +/// panics when a condition is not met) and the [`bail!`][crate::bail] macro +/// early-returns an error unconditionally. +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{bail, ensure, Result}; +/// +/// fn my_fallible_function(x: u32) -> Result<()> { +/// // This `ensure!` macro invocation is equivalent to +/// // +/// // if x % 2 != 0 { +/// // return Err(...); +/// // } +/// ensure!(x % 2 == 0, "{x} is not even!"); +/// +/// // This `bail!` macro invocation is equivalent to +/// // +/// // return Err(...); +/// bail!("oops, another error! {x}") +/// } +/// ``` +/// +/// If you do not want to early-return, just to create the `Error`, then the +/// [`anyhow!`][crate::anyhow] macro is preferred: +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{anyhow, Error}; +/// +/// let x = 42; +/// let my_error: Error = anyhow!("whoops! {x}"); +/// ``` +/// +/// If, however, you happen to require a constructor function instead of a +/// macro, you can use either [`Error::new`] or [`Error::msg`]: +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::Error; +/// +/// let messages = ["yikes", "uh oh", "ouch"]; +/// let errors = messages +/// .into_iter() +/// .map(Error::msg) +/// .collect::>(); +/// ``` +/// +/// # Printing an `Error` +/// +/// Different format strings will print an `Error` differently: +/// +/// * `{}`: Prints the `Display` of just the first error, without any of the +/// other errors in the chain or the root cause. +/// +/// * `{:#}`: Prints the `Display` of the first error, then (if there are more +/// errors in the chain) a colon, then the display of the second error in the +/// chain, etc... +/// +/// * `{:?}`: Prints the `Display` of the first error, then (if there are more +/// errors in the chain) a newline-separated list of the rest of the errors in +/// the chain, and finally (if the `"backtrace"` cargo feature is enabled, the +/// `RUST_BACKTRACE` environment variable is set and non-zero, and the +/// platform is supported by Rust's standard library's `Backtrace` type) the +/// captured backtrace is printed. +/// +/// This is the default formatting used when `fn main() -> +/// wasmtime::Result<()>` returns an error. +/// +/// * `{:#?}`: Prints an internal, debugging representation of the `Error`. We +/// make no guarantees about its stability. +/// +/// Here is an example showing the different formats for the same error: +/// +/// ``` +/// # fn _foo() { +/// #![cfg(all(feature = "backtrace", not(miri)))] +/// # let _ = unsafe { std::env::set_var("RUST_BACKTRACE", "1") }; +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{bail, Context as _, Result}; +/// +/// fn uno() -> Result<()> { +/// bail!("ouch") +/// } +/// +/// fn dos() -> Result<()> { +/// uno().context("whoops") +/// } +/// +/// fn tres() -> Result<()> { +/// dos().context("uh oh") +/// } +/// +/// let error = tres().unwrap_err(); +/// +/// println!("{error}"); +/// // Prints: +/// // +/// // uh oh +/// +/// println!("{error:#}"); +/// // Prints: +/// // +/// // uh oh: whoops: ouch +/// +/// println!("{error:?}"); +/// // Prints +/// // +/// // uh oh +/// // +/// // Caused by: +/// // 0: whoops +/// // 1: ouch +/// // +/// // Stack backtrace: +/// // <...> +/// // 7: example::uno +/// // 8: example::dos +/// // 9: example::tres +/// // 10: example::main +/// // <...> +/// +/// println!("{error:#?}"); +/// // Prints +/// // +/// // Error { +/// // <...> +/// // } +/// # } +/// ``` +/// +/// # Converting a `wasmtime::Error` into an `anyhow::Error` +/// +/// When the `"anyhow"` feature is enabled, there is a `From +/// for anyhow::Error` implementation. You can always call that implementation +/// explicitly if needed, but `?`-propagation allows the conversion to happen +/// seamlessly from functions that return a `Result` to +/// those that return a `Result`. +/// +/// ``` +/// # fn _foo() { +/// #![cfg(feature = "anyhow")] +/// # use wasmtime_internal_error as wasmtime; +/// +/// fn foo() -> Result<(), wasmtime::Error> { +/// wasmtime::bail!("decontamination failure") +/// } +/// +/// fn bar() -> Result<(), anyhow::Error> { +/// foo()?; // `?` is auto-converting here! +/// Ok(()) +/// } +/// +/// let error = bar().unwrap_err(); +/// assert_eq!(error.to_string(), "decontamination failure"); +/// # } +/// ``` +/// +/// # Converting an `anyhow::Error` into a `wasmtime::Error` +/// +/// When the `"anyhow"` feature is enabled, there is an `Error::from_anyhow` +/// constructor that you may use to convert an `anyhow::Error` into a +/// `wasmtime::Error`. (Unfortunately trait coherence does not allow us a +/// `From for wasmtime::Error` implementation.) This will +/// most-often be used in combination with `Result::map_err`: +/// +/// ``` +/// # fn _foo() { +/// #![cfg(feature = "anyhow")] +/// # use wasmtime_internal_error as wasmtime; +/// +/// fn baz() -> Result<(), anyhow::Error> { +/// anyhow::bail!("oops I ate worms") +/// } +/// +/// fn qux() -> Result<(), wasmtime::Error> { +/// baz().map_err(wasmtime::Error::from_anyhow)?; +/// Ok(()) +/// } +/// +/// let error = qux().unwrap_err(); +/// assert_eq!(error.to_string(), "oops I ate worms"); +/// # } +/// ``` +pub struct Error { + pub(crate) inner: OomOrDynError, +} + +/// For performance, it is important that `Error` and `Result<()>` fit in a +/// single word so that they can be passed in registers by rustc/llvm, rather +/// than on the stack, when used as a function's return type. +const _ERROR_IS_ONE_WORD_LARGE: () = assert!(mem::size_of::() == mem::size_of::()); +const _RESULT_OF_UNIT_IS_ONE_WORD_LARGE: () = + assert!(mem::size_of::>() == mem::size_of::()); + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + return f + .debug_struct("Error") + .field("inner", &self.inner.unpack()) + .finish(); + } + + let inner = self.inner.unpack(); + inner.display(f)?; + + if let Some(source) = inner.source() { + f.write_str("\n\nCaused by:\n")?; + for (i, e) in Chain::new(source).enumerate() { + writeln!(f, "\t{i}: {e}")?; + } + } + + #[cfg(feature = "backtrace")] + { + let backtrace = inner.backtrace(); + if let BacktraceStatus::Captured = backtrace.status() { + f.write_str("\nStack backtrace:\n")?; + fmt::Display::fmt(backtrace, f)?; + } + } + + Ok(()) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let inner = self.inner.unpack(); + inner.display(f)?; + + if f.alternate() { + if let Some(e) = inner.source() { + for e in Chain::new(e) { + write!(f, ": {e}")?; + } + } + } + + Ok(()) + } +} + +impl From for Error +where + E: core::error::Error + Send + Sync + 'static, +{ + fn from(error: E) -> Self { + Self::new(error) + } +} + +impl From for Box { + #[inline] + fn from(error: Error) -> Self { + error.into_boxed_dyn_error() + } +} + +/// Convert a [`Error`] into an [`anyhow::Error`]. +/// +/// # Example +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// let wasmtime_error = wasmtime::Error::msg("whoops"); +/// let anyhow_error = anyhow::Error::from(wasmtime_error); +/// ``` +// +// Unfortunately, we can't also implement `From for Error` +// because of trait coherence. From Rust's trait system's point of view, +// `anyhow` could theoretically add an `core::error::Error for anyhow::Error` +// implementation, which would make our desired `From` +// implementation conflict with our existing `From` +// implementation. They cannot in fact add that implementation, however, because +// they already have a `From for anyhow::Error` +// implementation and so adding `core::error::Error for anyhow::Error` would +// cause that impl to conflict with `From for T` (which is the same reason we +// cannot implement `core::error::Error for Error`). Nonetheless, our hands are +// tied here. +#[cfg(feature = "anyhow")] +impl From for anyhow::Error { + #[inline] + fn from(e: Error) -> Self { + anyhow::Error::from_boxed(e.into_boxed_dyn_error()) + } +} + +impl Error { + /// Construct a new `Error` from a type that implements + /// `core::error::Error`. + /// + /// Calling [`error.is::()`][Error::is] will return `true` for the new + /// error (unless there was a memory allocation failure). + /// + /// This boxes the inner error, but if that box allocation fails, then this + /// function returns an `Error` where + /// [`error.is::()`][crate::OutOfMemory] is true. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::Error; + /// + /// let error = Error::new(std::fmt::Error); + /// ``` + pub fn new(error: E) -> Self + where + E: core::error::Error + Send + Sync + 'static, + { + if TypeId::of::() == TypeId::of::() { + return Error { + inner: OutOfMemory::new().into(), + }; + } + + Self::from_error_ext(ForeignError(error)) + } + + /// Construct a new `Error` from any type that implements `Debug` and + /// `Display`. + /// + /// Calling [`error.is::()`][Error::is] will return `true` for the new + /// error (unless there was a memory allocation failure). + /// + /// This boxes the inner `M` type, but if that box allocation fails, then + /// this function returns an `Error` where + /// [`error.is::()`][crate::OutOfMemory] is true. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::Error; + /// + /// let error = Error::msg("hello"); + /// ``` + pub fn msg(message: M) -> Self + where + M: fmt::Debug + fmt::Display + Send + Sync + 'static, + { + Self::from_error_ext(MessageError(message)) + } + + /// Create an `Error` from a `Box`. + /// + /// This is useful when converting errors from other universal-error + /// libraries into this crate's `Error` type. Prefer [`Error::from_anyhow`] + /// for converting `anyhow::Error`s into `Error`s, as that preserves + /// `error.is::()`. + /// + /// Calling [`error.is::>()`][Error::is] will return `true` for the new error (unless + /// there was a memory allocation failure). + /// + /// This reboxes the inner error, but if that box allocation fails, then + /// this function returns an `Error` where + /// [`error.is::()`][crate::OutOfMemory] is true. + /// + /// # Example + /// + /// ``` + /// # fn _foo() { + /// #![cfg(all(feature = "std", feature = "anyhow"))] + /// # use wasmtime_internal_error as wasmtime; + /// use std::error::Error; + /// + /// let anyhow_error = anyhow::Error::msg("whoops"); + /// let boxed_error: Box = anyhow_error.into_boxed_dyn_error(); + /// let wasmtime_error = wasmtime::Error::from_boxed(boxed_error); + /// # } + /// ``` + pub fn from_boxed(error: Box) -> Self { + Self::from_error_ext(BoxedError(error)) + } + + /// Convert an `anyhow::Error` into an `Error`. + /// + /// Calling [`error.is::()`][Error::is] will return `true` + /// for the new error (unless there was a memory allocation failure). + /// + /// This reboxes the `anyhow::Error`, but if that box allocation fails, then + /// this function returns an `Error` where + /// [`error.is::()`][crate::OutOfMemory] is true. + /// + /// # Example + /// + /// ``` + /// # fn _foo() { + /// #![cfg(all(feature = "std", feature = "anyhow"))] + /// # use wasmtime_internal_error as wasmtime; + /// let anyhow_error = anyhow::Error::msg("failed to flim the flam"); + /// let wasmtime_error = wasmtime::Error::from_anyhow(anyhow_error); + /// assert_eq!( + /// wasmtime_error.to_string(), + /// "failed to flim the flam", + /// ); + /// # } + /// ``` + #[cfg(feature = "anyhow")] + #[inline] + pub fn from_anyhow(error: anyhow::Error) -> Self { + Self::from_error_ext(AnyhowError(error)) + } + + /// Add additional context to this error. + /// + /// The new context will show up first in the error chain, and the original + /// error will come next. + /// + /// This is similar to the [`Context::context`] trait method, but because it + /// is a method directly on [`Error`], there is no need for lazily-computing + /// the error context (like `with_context` does). + /// + /// Calling [`error.is::()`][Error::is] will return `true` for the new + /// error (unless there was a memory allocation failure) in addition to any + /// other types `T` for which it was already the case that + /// `error.is::()`. + /// + /// This boxes the inner `C` type, but if that box allocation fails, then + /// this function returns an `Error` where + /// [`error.is::()`][crate::OutOfMemory] is true. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::Error; + /// + /// let error = Error::msg("root cause"); + /// let error = error.context("failed to bonkinate"); + /// let error = error.context("cannot frob the blobbins"); + /// + /// assert!( + /// format!("{error:?}").contains( + /// r#" + /// cannot frob the blobbins + /// + /// Caused by: + /// 0: failed to bonkinate + /// 1: root cause + /// "#.trim(), + /// ), + /// ); + /// ``` + pub fn context(self, context: C) -> Self + where + C: fmt::Display + Send + Sync + 'static, + { + if self.inner.is_oom() { + self + } else { + Self::from_error_ext(ContextError { + context, + error: Some(self), + }) + } + } + + #[inline] + pub(crate) fn from_error_ext(error: impl ErrorExt) -> Self { + match BoxedDynError::new(error) { + Ok(boxed) => Error { + inner: boxed.into(), + }, + Err(oom) => out_of_line_slow_path!(Error { inner: oom.into() }), + } + } + + /// Get this error's backtrace. + /// + /// Backtraces will be automatically captured on initial `Error` creation + /// when all of the following conditions are met: + /// + /// * This crate's `"backtrace"` cargo feature is enabled + /// * Rust's `std::backtrace::Backtrace` supports the platform + /// * The `RUST_BACKTRACE` or `RUST_LIB_BACKTRACE` environment variables + /// are set and non-zero + /// + /// See [the `std::backtrace::Backtrace` + /// documentation](https://doc.rust-lang.org/stable/std/backtrace/struct.Backtrace.html) + /// for more details on backtraces. + /// + /// Note that `std::backtrace::Backtrace` does not provide a + /// fallible-capture mechanism that returns an error, rather than aborting + /// the process, when it encounters memory exhaustion. If you require + /// out-of-memory error handling, do not enable this crate's `"backtrace"` + /// cargo feature. + /// + /// # Example + /// + /// ``` + /// # fn _foo() { + /// #![cfg(feature = "backtrace")] + /// # use wasmtime_internal_error as wasmtime; + /// use std::backtrace::BacktraceStatus; + /// use wasmtime::Error; + /// + /// let error = Error::msg("whoops"); + /// + /// let backtrace = error.backtrace(); + /// if let BacktraceStatus::Captured = backtrace.status() { + /// println!("error backtrace is:\n{backtrace}"); + /// } + /// # } + /// ``` + #[inline] + #[cfg(feature = "backtrace")] + pub fn backtrace(&self) -> &Backtrace { + self.inner.unpack().backtrace() + } + + /// Iterate over this error's context chain. + /// + /// The iterator yields `&(dyn core::error::Error + 'static)` items. + /// + /// Iterates from the most recently added error context towards the root + /// cause. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::Error; + /// + /// let error = Error::msg("root cause"); + /// let error = error.context("failed to reticulate splines"); + /// let error = error.context("aborting launch"); + /// + /// let messages: Vec<_> = error.chain().map(|e| e.to_string()).collect(); + /// assert_eq!( + /// messages, + /// ["aborting launch", "failed to reticulate splines", "root cause"], + /// ); + /// ``` + #[inline] + pub fn chain(&self) -> Chain<'_> { + Chain::new(self.inner.unpack()) + } + + /// Get the last error in the context chain. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::Error; + /// + /// let error = Error::msg("ghosts"); + /// let error = error.context("failed to reticulate splines"); + /// let error = error.context("aborting launch"); + /// + /// assert_eq!( + /// error.root_cause().to_string(), + /// "ghosts", + /// ); + /// ``` + #[inline] + pub fn root_cause(&self) -> &(dyn core::error::Error + 'static) { + self.chain().last().expect("chain is always non-empty") + } + + /// Is this an `E` error? + /// + /// Returns true if any error in the context chain is an `E`. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::{Error, OutOfMemory}; + /// + /// let oom = Error::from(OutOfMemory::new()); + /// assert!(oom.is::()); + /// assert!(!oom.is::()); + /// + /// // Here is an example with additional error context. + /// let error = Error::from(u8::try_from(u32::MAX).unwrap_err()); + /// let error = error.context(format!("cannot convert {} into a u8", u32::MAX)); + /// assert!( + /// error.is::(), + /// "root cause is an int conversion failure", + /// ); + /// assert!( + /// error.is::(), + /// "additional context is a `String`", + /// ); + /// assert!( + /// !error.is::(), + /// "no error in the chain is an out-of-memory error", + /// ); + /// ``` + pub fn is(&self) -> bool + where + E: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + let mut error = Some(self.inner.unpack()); + while let Some(e) = error { + if e.is::() { + return true; + } else { + error = e.source(); + } + } + false + } + + /// Downcast this error into an `E`, taking ownership. + /// + /// If this error is an `E`, then `Ok(E)` is returned. Otherwise, + /// `Err(self)` is returned. + /// + /// If there are multiple instances of `E` in this error's chain, then the + /// first (as encountered by [`Error::chain`]'s iteration order) is + /// returned. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::{Error, OutOfMemory}; + /// + /// let error = Error::msg("whoops"); + /// + /// // `error` is not an `OutOfMemory`. + /// let downcasted = error.downcast::(); + /// assert!(downcasted.is_err()); + /// + /// // Get the original `error` back. + /// let error = downcasted.unwrap_err(); + /// + /// // `error` is an `&str`. + /// let downcasted = error.downcast::<&str>(); + /// assert!(downcasted.is_ok()); + /// assert_eq!(downcasted.unwrap(), "whoops"); + /// + /// // If there are multiple `E`s in the chain, the first in the chain is + /// // returned. + /// let error = Error::msg("root cause"); + /// let error = error.context("failed to recombobulate"); + /// assert_eq!( + /// error.downcast::<&str>().unwrap(), + /// "failed to recombobulate", + /// ); + /// ``` + pub fn downcast(self) -> Result + where + E: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + if !self.is::() { + return Err(self); + } + + let mut value = mem::MaybeUninit::::uninit(); + + // Safety: this error is an `E` and the given pointer is valid to write + // an `E` to. + unsafe { + self.inner + .downcast(TypeId::of::(), NonNull::from(&mut value).cast::()); + } + + // Safety: `OomOrDynError::downcast` guarantees that the given pointer's + // data is initialized upon successful return. + Ok(unsafe { value.assume_init() }) + } + + /// Downcast this error into a shared `&E` borrow. + /// + /// If this error is an `E`, then `Some(&E)` is returned. Otherwise, `None` + /// is returned. + /// + /// If there are multiple instances of `E` in this error's chain, then the + /// first (as encountered by [`Error::chain`]'s iteration order) is + /// returned. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::{Error, OutOfMemory}; + /// + /// let error = Error::msg("whoops"); + /// + /// // `error` is not an `OutOfMemory`. + /// assert!(error.downcast_ref::().is_none()); + /// + /// // `error` is an `&str`. + /// assert!(error.downcast_ref::<&str>().is_some()); + /// assert_eq!(*error.downcast_ref::<&str>().unwrap(), "whoops"); + /// + /// // If there are multiple `E`s in the chain, the first in the chain is + /// // returned. + /// let error = Error::msg("root cause"); + /// let error = error.context("failed to recombobulate"); + /// assert_eq!( + /// *error.downcast_ref::<&str>().unwrap(), + /// "failed to recombobulate", + /// ); + /// ``` + pub fn downcast_ref(&self) -> Option<&E> + where + E: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + let mut error = Some(self.inner.unpack()); + while let Some(e) = error { + if e.is::() { + return Some(match e { + OomOrDynErrorRef::DynError(ptr) => { + let ptr = ptr.cast::>(); + // Safety: we own the pointer, it is valid for reading, + // and we checked that it is an `E`. + let r = unsafe { ptr.as_ref() }; + &r.error + } + OomOrDynErrorRef::Oom(oom) => { + // Note: Even though we know that `E == OutOfMemory` + // here, we still have to do this dance to satisfy the + // type system. + debug_assert_eq!(TypeId::of::(), TypeId::of::()); + let ptr = NonNull::from(oom); + let ptr = ptr.cast::(); + // Safety: the pointer points to `oom`, which is valid + // for creating a shared reference to. + unsafe { ptr.as_ref() } + } + }); + } else { + error = e.source(); + } + } + None + } + + /// Downcast this error into an exclusive `&mut E` borrow. + /// + /// If this error is an `E`, then `Some(&mut E)` is returned. Otherwise, + /// `None` is returned. + /// + /// If there are multiple instances of `E` in this error's chain, then the + /// first (as encountered by [`Error::chain`]'s iteration order) is + /// returned. + /// + /// # Example + /// + /// ``` + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::{Error, OutOfMemory}; + /// + /// let mut error = Error::msg("whoops"); + /// + /// // `error` is not an `OutOfMemory`. + /// assert!(error.downcast_mut::().is_none()); + /// + /// // `error` is an `&str`. + /// assert!(error.downcast_mut::<&str>().is_some()); + /// assert_eq!(*error.downcast_mut::<&str>().unwrap(), "whoops"); + /// *error.downcast_mut::<&str>().unwrap() = "yikes"; + /// assert_eq!(*error.downcast_mut::<&str>().unwrap(), "yikes"); + /// + /// // If there are multiple `E`s in the chain, the first in the chain is + /// // returned. + /// let error = Error::msg("root cause"); + /// let mut error = error.context("failed to recombobulate"); + /// assert_eq!( + /// *error.downcast_mut::<&str>().unwrap(), + /// "failed to recombobulate", + /// ); + /// ``` + pub fn downcast_mut(&mut self) -> Option<&mut E> + where + E: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + let mut error = Some(self.inner.unpack_mut()); + while let Some(mut e) = error.take() { + if e.as_ref().is::() { + return Some(match e { + OomOrDynErrorMut::DynError(ptr) => { + let mut ptr = ptr.cast::>(); + // Safety: we own the pointer, it is valid for reading + // and writing, and we checked that it is an `E`. + let r = unsafe { ptr.as_mut() }; + &mut r.error + } + OomOrDynErrorMut::Oom(oom) => { + // Note: Even though we know that `E == OutOfMemory` + // here, we still have to do this dance to satisfy the + // type system. + debug_assert_eq!(TypeId::of::(), TypeId::of::()); + let ptr = NonNull::from(oom); + let mut ptr = ptr.cast::(); + // Safety: the pointer points to `oom`, which is valid + // for creating an exclusive reference to. + unsafe { ptr.as_mut() } + } + }); + } else { + error = e.source_mut(); + } + } + None + } + + /// Convert this error into a `Box`. + /// + /// This is useful for integrating this crate's `Error`s into other + /// universal-error libraries. + /// + /// This functionality is also available via a `From for Box>` implementation. + /// + /// # Example + /// + /// ``` + /// # fn _foo() { + /// #![cfg(feature = "std")] + /// use std::fmt; + /// + /// /// A stub representing some other error library. + /// #[derive(Debug)] + /// pub struct OtherError { + /// inner: Box, + /// } + /// + /// impl fmt::Display for OtherError { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// fmt::Display::fmt(&self.inner, f) + /// } + /// } + /// + /// impl std::error::Error for OtherError { + /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// self.inner.source() + /// } + /// } + /// + /// impl OtherError { + /// /// Create an `OtherError` from another error. + /// pub fn new(error: E) -> Self + /// where + /// E: std::error::Error + Send + Sync + 'static, + /// { + /// OtherError { inner: Box::new(error) } + /// } + /// + /// /// Create an `OtherError` from another, already-boxed error. + /// pub fn from_boxed(error: Box) -> Self { + /// OtherError { inner: error } + /// } + /// } + /// + /// # use wasmtime_internal_error as wasmtime; + /// use wasmtime::Error; + /// + /// // Create an `Error`. + /// let error = Error::msg("whoopsies"); + /// + /// // Convert it into an `OtherError`. + /// let error = OtherError::from_boxed(error.into_boxed_dyn_error()); + /// # } + /// ``` + #[inline] + pub fn into_boxed_dyn_error(self) -> Box { + self.inner.into_boxed_dyn_core_error() + } +} + +/// `ErrorExt` wrapper for foreign `core::error::Error` implementations. +/// +/// For `Error::new`'s use only. +/// +/// NB: The `repr(transparent)` is required for safety of the `ErrorExt::ext_is` +/// implementation and the casts that are performed using that method's return +/// value. +#[repr(transparent)] +struct ForeignError(E); + +impl fmt::Debug for ForeignError +where + E: core::error::Error + Send + Sync + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +impl fmt::Display for ForeignError +where + E: core::error::Error + Send + Sync + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl core::error::Error for ForeignError +where + E: core::error::Error + Send + Sync + 'static, +{ + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + self.0.source() + } +} + +// Safety: `ext_is` is correct, `ext_move` always writes to `dest`. +unsafe impl ErrorExt for ForeignError +where + E: core::error::Error + Send + Sync + 'static, +{ + fn ext_source(&self) -> Option> { + None + } + + fn ext_source_mut(&mut self) -> Option> { + None + } + + fn ext_take_source(&mut self) -> Option { + None + } + + unsafe fn ext_move(self, dest: NonNull) { + // Safety: implied by this trait method's safety contract. + unsafe { + dest.cast::().write(self.0); + } + } + + fn ext_is(&self, type_id: TypeId) -> bool { + // NB: need to check type id of `E`, not `Self` aka + // `ForeignError`. + type_id == TypeId::of::() + } + + #[cfg(feature = "backtrace")] + fn take_backtrace(&mut self) -> Option { + None + } +} + +/// `ErrorExt` wrapper for types given to `Error::msg`. +/// +/// For `Error::msg`'s use only. +/// +/// NB: The `repr(transparent)` is required for safety of the `ErrorExt::ext_is` +/// implementation and the casts that are performed using that method's return +/// value. +#[repr(transparent)] +struct MessageError(M); + +impl fmt::Debug for MessageError +where + M: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for MessageError +where + M: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl core::error::Error for MessageError where M: fmt::Debug + fmt::Display {} + +// Safety: `ext_is` is implemented correctly and `ext_move` always +// writes to its pointer. +unsafe impl ErrorExt for MessageError +where + M: fmt::Debug + fmt::Display + Send + Sync + 'static, +{ + fn ext_source(&self) -> Option> { + None + } + + fn ext_source_mut(&mut self) -> Option> { + None + } + + fn ext_take_source(&mut self) -> Option { + None + } + + fn ext_is(&self, type_id: TypeId) -> bool { + // NB: need to check type id of `M`, not `Self` aka + // `MessageError`. + type_id == TypeId::of::() + } + + unsafe fn ext_move(self, dest: NonNull) { + // Safety: implied by this trait method's contract. + unsafe { + dest.cast::().write(self.0); + } + } + + #[cfg(feature = "backtrace")] + fn take_backtrace(&mut self) -> Option { + None + } +} + +/// `ErrorExt` wrapper for `Box`. +/// +/// For `Error::from_boxed`'s use only. +/// +/// NB: The `repr(transparent)` is required for safety of the `ErrorExt::ext_is` +/// implementation and the casts that are performed using that method's return +/// value. +#[repr(transparent)] +struct BoxedError(Box); + +impl fmt::Debug for BoxedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +impl fmt::Display for BoxedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl core::error::Error for BoxedError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + self.0.source() + } +} + +// Safety: `ext_is` is implemented correctly and `ext_move` always +// writes to its pointer. +unsafe impl ErrorExt for BoxedError { + fn ext_source(&self) -> Option> { + None + } + + fn ext_source_mut(&mut self) -> Option> { + None + } + + fn ext_take_source(&mut self) -> Option { + None + } + + fn ext_is(&self, type_id: TypeId) -> bool { + // NB: need to check type id of `BoxDynSendSyncError`, not + // `BoxedError`. + type_id == TypeId::of::>() + } + + unsafe fn ext_move(self, dest: NonNull) { + // Safety: implied by this trait method's contract. + unsafe { + dest.cast::>() + .write(self.0); + } + } + + #[cfg(feature = "backtrace")] + fn take_backtrace(&mut self) -> Option { + None + } +} + +/// `ErrorExt` wrapper for `anyhow::Error`. +/// +/// For `Error::from_anyhow`'s use only. +/// +/// NB: The `repr(transparent)` is required for safety of the `ErrorExt::ext_is` +/// implementation and the casts that are performed using that method's return +/// value. +#[repr(transparent)] +#[cfg(feature = "anyhow")] +struct AnyhowError(anyhow::Error); + +#[cfg(feature = "anyhow")] +impl fmt::Debug for AnyhowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +#[cfg(feature = "anyhow")] +impl fmt::Display for AnyhowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +#[cfg(feature = "anyhow")] +impl core::error::Error for AnyhowError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + self.0.source() + } +} + +// Safety: `ext_is` is implemented correctly and `ext_move` always +// writes to its pointer. +#[cfg(feature = "anyhow")] +unsafe impl ErrorExt for AnyhowError { + fn ext_source(&self) -> Option> { + None + } + + fn ext_source_mut(&mut self) -> Option> { + None + } + + fn ext_take_source(&mut self) -> Option { + None + } + + fn ext_is(&self, type_id: TypeId) -> bool { + // NB: need to check type id of `BoxDynSendSyncError`, not + // `AnyhowError`. + type_id == TypeId::of::() + } + + unsafe fn ext_move(self, dest: NonNull) { + // Safety: implied by this trait method's contract. + unsafe { + dest.cast::().write(self.0); + } + } + + #[cfg(feature = "backtrace")] + fn take_backtrace(&mut self) -> Option { + None + } +} + +pub(crate) enum OomOrDynErrorRef<'a> { + // Safety: this must always be a valid pointer to read a `DynError` from for + // the `'a` lifetime. + DynError(SharedPtr<'a, DynError>), + + Oom(&'a OutOfMemory), +} + +impl<'a> Debug for OomOrDynErrorRef<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug(f) + } +} + +impl<'a> OomOrDynErrorRef<'a> { + fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OomOrDynErrorRef::DynError(e) => { + // Safety: invariant of this type. + let vtable = unsafe { e.as_ref().vtable }; + // Safety: using the vtable associated with this pointer's + // concrete type and the pointer is valid. + unsafe { (vtable.display)(*e, f) } + } + OomOrDynErrorRef::Oom(oom) => fmt::Display::fmt(oom, f), + } + } + + fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + OomOrDynErrorRef::Oom(oom) => f.debug_tuple("Oom").field(oom).finish(), + OomOrDynErrorRef::DynError(error) => { + struct DebugError<'a>(SharedPtr<'a, DynError>); + impl fmt::Debug for DebugError<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Safety: invariant of `OomOrDynError` that the pointer + // is valid. + let vtable = unsafe { self.0.as_ref().vtable }; + // Safety: the pointer is valid and the vtable is + // associated with the pointer's concrete error type. + unsafe { (vtable.debug)(self.0, f) } + } + } + + let mut f = f.debug_struct("DynError"); + f.field("error", &DebugError(error)); + if let Some(source) = self.source() { + f.field("source", &source); + } + f.finish() + } + } + } + + fn source(&self) -> Option> { + match self { + OomOrDynErrorRef::DynError(e) => { + // Safety: invariant of this type. + let vtable = unsafe { e.as_ref().vtable }; + // Safety: using the vtable associated with this pointer's + // concrete type and the pointer is valid. + unsafe { (vtable.source)(*e) } + } + OomOrDynErrorRef::Oom(_) => None, + } + } + + fn is(&self) -> bool + where + E: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + match self { + OomOrDynErrorRef::DynError(e) => { + // Safety: invariant of this type. + let vtable = unsafe { e.as_ref().vtable }; + // Safety: using the vtable associated with this pointer's + // concrete type and the pointer is valid. + unsafe { (vtable.is)(*e, TypeId::of::()) } + } + OomOrDynErrorRef::Oom(_) => TypeId::of::() == TypeId::of::(), + } + } + + pub(crate) fn as_dyn_core_error(&self) -> &'a (dyn core::error::Error + Send + Sync + 'static) { + match *self { + OomOrDynErrorRef::DynError(e) => { + // Safety: invariant of this type. + let vtable = unsafe { e.as_ref().vtable }; + // Safety: using the vtable associated with this pointer's + // concrete type and the pointer is valid. + unsafe { (vtable.as_dyn_core_error)(e) } + } + OomOrDynErrorRef::Oom(oom) => oom as _, + } + } + + #[cfg(feature = "backtrace")] + fn backtrace(&self) -> &'a Backtrace { + match self { + OomOrDynErrorRef::DynError(e) => { + // Safety: invariant of this type. + let r = unsafe { e.as_ref() }; + r.backtrace + .as_ref() + .expect("the first error in the chain always has the backtrace") + } + + OomOrDynErrorRef::Oom(_) => { + static DISABLED: Backtrace = Backtrace::disabled(); + &DISABLED + } + } + } +} + +pub(crate) enum OomOrDynErrorMut<'a> { + // Safety: this must always be a valid pointer to read and write a + // `DynError` from for the `'a` lifetime. + DynError(MutPtr<'a, DynError>), + + Oom(&'a mut OutOfMemory), +} + +impl<'a> OomOrDynErrorMut<'a> { + fn as_ref(&self) -> OomOrDynErrorRef<'_> { + match self { + OomOrDynErrorMut::DynError(e) => OomOrDynErrorRef::DynError(e.as_shared_ptr()), + OomOrDynErrorMut::Oom(oom) => OomOrDynErrorRef::Oom(oom), + } + } + + fn source_mut(&mut self) -> Option> { + match self { + OomOrDynErrorMut::DynError(e) => { + // Safety: invariant of this type. + let vtable = unsafe { e.as_ref().vtable }; + // Safety: using the vtable associated with this pointer's + // concrete type and the pointer is valid. + unsafe { (vtable.source_mut)(e.raw_copy()) } + } + OomOrDynErrorMut::Oom(_) => None, + } + } +} + +/// Bit packed version of `enum { BoxedDynError, OutOfMemory }` that relies on +/// implicit pointer tagging and `OutOfMemory` being zero-sized. +pub(crate) struct OomOrDynError { + // Safety: this must always be the casted-to-`u8` version of either (a) + // `0x1`, or (b) a valid, owned `DynError` pointer. (Note that these cases + // cannot overlap because `DynError`'s alignment is greater than `0x1`.) + inner: NonNull, +} + +// Safety: `OomOrDynError` is either an `OutOfMemory` or a `BoxedDynError` and +// both are `Send`. +unsafe impl Send for OomOrDynError {} + +// Safety: `OomOrDynError` is either an `OutOfMemory` or a `BoxedDynError` and +// both are `Sync`. +unsafe impl Sync for OomOrDynError {} + +const _OOM_OR_DYN_ERROR_SEND_SYNC_SAFETY: () = { + const fn assert_send_sync() {} + assert_send_sync::(); + assert_send_sync::(); +}; + +impl Drop for OomOrDynError { + fn drop(&mut self) { + if self.is_boxed_dyn_error() { + let inner = self.inner.cast::(); + let inner = OwnedPtr::new(inner); + let _ = BoxedDynError::from_owned_ptr(inner); + } else { + debug_assert!(self.is_oom()); + } + } +} + +impl From for OomOrDynError { + fn from(boxed: BoxedDynError) -> Self { + let inner = boxed.into_owned_ptr().into_non_null().cast::(); + debug_assert_ne!(inner, Self::OOM.inner); + OomOrDynError { inner } + } +} + +impl OomOrDynError { + const _SIZE: () = assert!(mem::size_of::() == mem::size_of::()); + + // Our pointer tagging relies on this property. + const _DYN_ERROR_HAS_GREATER_ALIGN_THAN_OOM: () = assert!(mem::align_of::() > 1); + + const OOM_REPR: NonNull = unsafe { + // Safety: `0x1` is not null. + NonNull::::new_unchecked(0x1 as *mut u8) + }; + + pub(crate) const OOM: Self = OomOrDynError { + inner: Self::OOM_REPR, + }; + + fn is_oom(&self) -> bool { + self.inner == Self::OOM_REPR + } + + fn is_boxed_dyn_error(&self) -> bool { + !self.is_oom() + } + + /// # Safety + /// + /// `self.is_oom()` must be true. + unsafe fn unchecked_oom(&self) -> &OutOfMemory { + debug_assert!(self.is_oom()); + let dangling = NonNull::::dangling(); + debug_assert_eq!(mem::size_of::(), 0); + // Safety: it is always valid to turn `T`'s dangling pointer into an + // `&T` reference for unit types. + unsafe { dangling.as_ref() } + } + + /// # Safety + /// + /// `self.is_oom()` must be true. + unsafe fn unchecked_oom_mut(&mut self) -> &mut OutOfMemory { + debug_assert!(self.is_oom()); + let mut dangling = NonNull::::dangling(); + debug_assert_eq!(mem::size_of::(), 0); + // Safety: it is always valid to turn `T`'s dangling pointer into an + // `&mut T` reference for unit types. + unsafe { dangling.as_mut() } + } + + /// # Safety + /// + /// `self.is_boxed_dyn_error()` must be true. + unsafe fn unchecked_into_dyn_error(self) -> OwnedPtr { + debug_assert!(self.is_boxed_dyn_error()); + let inner = self.inner.cast::(); + mem::forget(self); + OwnedPtr::new(inner) + } + + /// # Safety + /// + /// `self.is_boxed_dyn_error()` must be true. + unsafe fn unchecked_dyn_error_ref(&self) -> SharedPtr<'_, DynError> { + debug_assert!(self.is_boxed_dyn_error()); + SharedPtr::new(self.inner.cast::()) + } + + /// # Safety + /// + /// `self.is_boxed_dyn_error()` must be true. + unsafe fn unchecked_dyn_error_mut(&mut self) -> MutPtr<'_, DynError> { + debug_assert!(self.is_boxed_dyn_error()); + MutPtr::new(self.inner.cast::()) + } + + pub(crate) fn unpack(&self) -> OomOrDynErrorRef<'_> { + if self.is_oom() { + // Safety: is_oom() is true. + OomOrDynErrorRef::Oom(unsafe { self.unchecked_oom() }) + } else { + debug_assert!(self.is_boxed_dyn_error()); + // Safety: self.is_boxed_dyn_error() is true. + OomOrDynErrorRef::DynError(unsafe { self.unchecked_dyn_error_ref() }) + } + } + + pub(crate) fn unpack_mut(&mut self) -> OomOrDynErrorMut<'_> { + if self.is_oom() { + // Safety: self.is_oom() is true + OomOrDynErrorMut::Oom(unsafe { self.unchecked_oom_mut() }) + } else { + debug_assert!(self.is_boxed_dyn_error()); + // Safety: self.is_boxed_dyn_error() is true. + OomOrDynErrorMut::DynError(unsafe { self.unchecked_dyn_error_mut() }) + } + } + + pub(crate) fn into_boxed_dyn_core_error( + self, + ) -> Box { + let box_dyn_error_of_oom = || { + let ptr = NonNull::::dangling().as_ptr(); + // Safety: it is always safe to call `Box::::from_raw` on `T`'s + // dangling pointer if `T` is a unit type. + let boxed = unsafe { Box::from_raw(ptr) }; + boxed as _ + }; + + if self.is_oom() { + box_dyn_error_of_oom() + } else { + debug_assert!(self.is_boxed_dyn_error()); + // Safety: this is a boxed dyn error. + let ptr = unsafe { self.unchecked_into_dyn_error() }; + // Safety: invariant of the type that the pointer is valid. + let vtable = unsafe { ptr.as_ref().vtable }; + // Safety: the pointer is valid and the vtable is associated with + // this pointer's concrete error type. + match unsafe { (vtable.into_boxed_dyn_core_error)(ptr) } { + Ok(e) => e, + Err(_oom) => box_dyn_error_of_oom(), + } + } + } + + /// Given that this is known to be an instance of the type associated with + /// the given `TypeId`, do an owning-downcast to that type, writing the + /// result through the given `ret_ptr`, and deallocating `self` along the + /// way. + /// + /// The `ret_ptr`'s storage will contain an initialized instance of the + /// associated type upon this method's successful return. + /// + /// # Safety + /// + /// This error (or another in its chain) must be of the type associated with + /// `TypeId`. + /// + /// The given `ret_ptr` must point to a valid-but-uninitialized storage + /// location for an instance of the type associated with the given `TypeId`. + pub(crate) unsafe fn downcast(self, type_id: TypeId, ret_ptr: NonNull) { + if self.is_oom() { + debug_assert_eq!(type_id, TypeId::of::()); + // Safety: this is an OOM error. + let oom = unsafe { self.unchecked_oom() }; + // Safety: implied by this method's safety contract. + unsafe { + ret_ptr.cast::().write(*oom); + } + } else { + debug_assert!(self.is_boxed_dyn_error()); + // Safety: this is a boxed dyn error. + let ptr = unsafe { self.unchecked_into_dyn_error() }; + // Safety: invariant of this type that the pointer is valid. + let vtable = unsafe { ptr.as_ref().vtable }; + // Safety: the pointer is valid and the vtable is associated with + // this pointer's concrete type. + unsafe { (vtable.downcast)(ptr, type_id, ret_ptr) } + } + } +} + +/// An iterator over each error in an [`Error`]'s context chain. +/// +/// The iterator yields `&'a (dyn core::error::Error + 'static)` items. +/// +/// Iterates from the most recently added error context towards the root cause. +/// +/// Created by the [`Error::chain`] method. See that method's documentation for +/// more details. +pub struct Chain<'a> { + state: ChainState<'a>, +} + +enum ChainState<'a> { + Ours(OomOrDynErrorRef<'a>), + Core(Option<&'a (dyn core::error::Error + 'static)>), +} + +impl<'a> Chain<'a> { + fn new(error: OomOrDynErrorRef<'a>) -> Self { + Self { + state: ChainState::Ours(error), + } + } +} + +impl<'a> Iterator for Chain<'a> { + type Item = &'a (dyn core::error::Error + 'static); + + #[inline] + fn next(&mut self) -> Option { + match &mut self.state { + ChainState::Ours(e) => { + let core = e.as_dyn_core_error(); + self.state = if let Some(e) = e.source() { + ChainState::Ours(e) + } else { + ChainState::Core(core.source()) + }; + Some(core) + } + ChainState::Core(error) => { + let e = error.take()?; + self.state = ChainState::Core(e.source()); + Some(e) + } + } + } +} + +impl FusedIterator for Chain<'_> {} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug)] + struct TestError; + + impl fmt::Display for TestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } + } + + impl core::error::Error for TestError {} + + #[test] + fn from_oom() { + let mut error = Error::from(OutOfMemory::new()); + assert!(error.is::()); + assert!(error.downcast_ref::().is_some()); + assert!(error.downcast_mut::().is_some()); + + // NB: use this module's scope to check that the inner representation is + // `OomOrDynError::Oom` and not a `Box as Box`. This is why this test cannot be in `tests/tests.rs`. + assert!(error.inner.is_oom()); + } + + #[test] + fn dyn_error_and_concrete_error_layouts_are_compatible() { + type Concrete = ConcreteError; + + let dyn_size = mem::size_of::(); + let concrete_size = mem::size_of::(); + assert!( + dyn_size <= concrete_size, + "assertion failed: {dyn_size} <= {concrete_size}" + ); + + let dyn_align = mem::align_of::(); + let concrete_align = mem::align_of::(); + assert!( + dyn_align <= concrete_align, + "assertion failed: {dyn_align} <= {concrete_align}" + ); + + let dyn_offset = mem::offset_of!(DynError, vtable); + let concrete_offset = mem::offset_of!(Concrete, vtable); + assert_eq!(dyn_offset, concrete_offset); + + #[cfg(feature = "backtrace")] + { + let dyn_offset = mem::offset_of!(DynError, backtrace); + let concrete_offset = mem::offset_of!(Concrete, backtrace); + assert_eq!(dyn_offset, concrete_offset); + } + } +} diff --git a/crates/error/src/lib.rs b/crates/error/src/lib.rs new file mode 100644 index 000000000000..c6b5ff1d8186 --- /dev/null +++ b/crates/error/src/lib.rs @@ -0,0 +1,84 @@ +//! Wasmtime's universal error handling crate. +//! +//! 99% API-compatible with `anyhow`, but additionally handles out-of-memory +//! errors, instead of aborting the process. +//! +//! See the [`Error`] documentation for more details. + +#![no_std] +#![deny(missing_docs)] +#![doc(test(attr(deny(warnings))))] +#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +/// Internal macro to mark a block as a slow path, pulling it out into its own +/// cold function that is never inlined. +/// +/// This should be applied to the whole consequent/alternative block for a +/// conditional, never to a single expression within a larger block. +/// +/// # Example +/// +/// ```ignore +/// fn hot_function(x: u32) -> Result<()> { +/// if very_rare_condition(x) { +/// return out_of_line_slow_path!({ +/// // Handle the rare case... +/// // +/// // This pulls the handling of the rare condition out into +/// // its own, separate function, which keeps the generated code +/// // tight, handling only the common cases inline. +/// Ok(()) +/// }); +/// } +/// +/// // Handle the common case inline... +/// Ok(()) +/// } +/// ``` +macro_rules! out_of_line_slow_path { + ( $e:expr ) => {{ + #[cold] + #[inline(never)] + #[track_caller] + fn out_of_line_slow_path(f: impl FnOnce() -> T) -> T { + f() + } + + out_of_line_slow_path(|| $e) + }}; +} + +#[cfg(feature = "backtrace")] +mod backtrace; +mod boxed; +mod context; +mod error; +mod oom; +mod ptr; +mod vtable; + +#[doc(hidden)] +pub mod macros; + +#[cfg(feature = "backtrace")] +pub use backtrace::disable_backtrace; +pub use context::Context; +pub use error::*; +pub use oom::OutOfMemory; + +/// A result of either `Ok(T)` or an [`Err(Error)`][Error]. +pub type Result = core::result::Result; + +/// Return `core::result::Result::::Ok(value)`. +/// +/// Useful in situations where Rust's type inference cannot figure out that the +/// `Result`'s error type is [`Error`]. +#[allow(non_snake_case, reason = "matching anyhow API")] +pub fn Ok(value: T) -> Result { + core::result::Result::Ok(value) +} diff --git a/crates/error/src/macros.rs b/crates/error/src/macros.rs new file mode 100644 index 000000000000..d368e83568e8 --- /dev/null +++ b/crates/error/src/macros.rs @@ -0,0 +1,342 @@ +//! Macro definitions and the private runtime functions used in their generated +//! code. + +// Items used by macro-generated code. +pub use core::format_args; +pub use core::result::Result::Err; + +use super::{Error, OutOfMemory}; +use alloc::string::String; +use core::fmt::{self, write}; + +/// Construct an [`Error`](crate::Error) via string formatting or another error. +/// +/// Like `anyhow::anyhow!` but for [`wasmtime::Error`][crate::Error]. +/// +/// # String Formatting +/// +/// When a string literal is the first argument, it is interpreted as a format +/// string template and the rest of the arguments are format arguments: +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{anyhow, Error}; +/// +/// let x = 42; +/// let error: Error = anyhow!("x is {x}"); +/// assert_eq!(error.to_string(), "x is 42"); +/// +/// let error: Error = anyhow!("x / 2 is {}", x / 2); +/// assert_eq!(error.to_string(), "x / 2 is 21"); +/// +/// let error: Error = anyhow!("x + 1 is {y}", y = x + 1); +/// assert_eq!(error.to_string(), "x + 1 is 43"); +/// ``` +/// +/// # From Another Error +/// +/// When a string literal is not the first argument, then it is treated as a +/// foreign error and is converted into an [`Error`][crate::Error]. The argument +/// must be of a type that can be passed to either +/// [`Error::new`][crate::Error::new] or [`Error::msg`][crate::Error::msg]: +/// +/// ``` +/// # fn _foo() { +/// #![cfg(feature = "std")] +/// # use wasmtime_internal_error as wasmtime; +/// use std::fmt; +/// use wasmtime::{anyhow, Error}; +/// +/// #[derive(Debug)] +/// struct SomeOtherError(u32); +/// +/// impl fmt::Display for SomeOtherError { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "some other error (code {})", self.0) +/// } +/// } +/// +/// impl std::error::Error for SomeOtherError {} +/// +/// let error: Error = anyhow!(SomeOtherError(36)); +/// assert!(error.is::()); +/// assert_eq!(error.to_string(), "some other error (code 36)"); +/// # } +/// ``` +#[macro_export] +macro_rules! anyhow { + // Format-style invocation without explicit format arguments. + ( $message:literal $(,)? ) => { + $crate::Error::from_format_args($crate::macros::format_args!($message)) + }; + + // Format-style invocation with explicit format arguments. + ( $message:literal , $( $args:tt )* ) => { + $crate::Error::from_format_args($crate::macros::format_args!($message , $( $args )* )) + }; + + // Do either `Error::new($error)` or `Error::msg($error)` depending on + // whether `$error` implements `core::error::Error` or not. + ( $error:expr $(,)? ) => {{ + use $crate::macros::ctor_specialization::*; + let error = $error; + (&&error).wasmtime_error_choose_ctor().construct(error) + }}; +} + +/// Identical to the [`anyhow!`][crate::anyhow] macro. +/// +/// Provided for compatibility. +#[macro_export] +macro_rules! format_err { + ( $( $args:tt )* ) => { + anyhow!( $( $args )* ) + }; +} + +/// Early exit from the current function with an error. +/// +/// This helper is equivalent to `return Err(anyhow!(...))`. +/// +/// See the docs for the [`anyhow!`][crate::anyhow] macro for details on the +/// kinds of errors that can be constructed. +/// +/// Like `anyhow::bail!` but for [`wasmtime::Error`][crate::Error]. +/// +/// # Example +/// +/// ``` +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{bail, Result}; +/// +/// fn error_on_none(option: Option) -> Result { +/// match option { +/// None => bail!("`error_on_none` got `None`!"), +/// Some(x) => Ok(x), +/// } +/// } +/// +/// let x = error_on_none(Some(42)).unwrap(); +/// assert_eq!(x, 42); +/// +/// let error = error_on_none(None).unwrap_err(); +/// assert_eq!( +/// error.to_string(), +/// "`error_on_none` got `None`!", +/// ); +/// ``` +#[macro_export] +macro_rules! bail { + ( $($args:tt)* ) => {{ + return $crate::macros::Err($crate::anyhow!( $( $args )* )); + }}; +} + +/// Ensure that a condition holds true, or else early exit from the current +/// function with an error. +/// +/// `ensure!(condition, ...)` is equivalent to the following: +/// +/// ```ignore +/// if !condition { +/// return Err(anyhow!(...)); +/// } +/// ``` +/// +/// Like `anyhow::ensure!` but for [`wasmtime::Error`][crate::Error]. +/// +/// # Example +/// +/// ```rust +/// # use wasmtime_internal_error as wasmtime; +/// use wasmtime::{ensure, Result}; +/// +/// fn checked_div(a: u32, b: u32) -> Result { +/// ensure!(b != 0, "cannot divide by zero: {a} / {b}"); +/// Ok(a / b) +/// } +/// +/// let x = checked_div(6, 2).unwrap(); +/// assert_eq!(x, 3); +/// +/// let error = checked_div(9, 0).unwrap_err(); +/// assert_eq!( +/// error.to_string(), +/// "cannot divide by zero: 9 / 0", +/// ); +/// ``` +#[macro_export] +macro_rules! ensure { + ( $condition:expr , $( $args:tt )* ) => {{ + if $crate::macros::ensure::not($condition) { + $crate::bail!( $( $args )* ); + } + }}; +} + +/// We don't have specialization in stable Rust, so do a poor-person's +/// equivalent by hacking Rust's method name resolution and auto-deref. Given +/// that we have `n` versions of the "same" method, we do the following: +/// +/// * We define `n` different traits, which each define the same trait method +/// name. The method need not have the same type across traits, but each must +/// type-check when chosen by method resolution at a particular call site. +/// +/// * We implement each trait for an `i`-deep borrow of the type(s) we want to +/// specialize the `i`th implementation on, for example: +/// +/// ```ignore +/// impl Specialization1 for &MyType { ... } +/// impl Specialization2 for &&OtherType { ... } +/// impl Specialization3 for &&&AnotherType { ... } +/// ``` +/// +/// * Call sites must have all specialization traits in scope and must borrow +/// the receiver `n` times before calling the method. Rust's method name +/// resolution will choose the method with the least number of references that +/// is well-typed. Therefore, specialization implementations for lower numbers +/// of borrows are preferred over those with higher numbers of borrows when +/// specializations overlap. For example, if both `<&&&T as +/// Specialization3>::method` and `<&T as Specialization1>::method` are +/// well-typed at the trait method call site `(&&&&&t).method()`, then +/// `Specialization1` will be prioritized over `Specialization3`. +/// +/// In our specific case here of choosing an `Error` constructor, we only have +/// two specializations: +/// +/// 1. When the type implements `core::error::Error`, we want to use the +/// `Error::new` constructor, which will preserve +/// `core::error::Error::source` chains. +/// +/// 2. Otherwise, we want to use the `Error::msg` constructor. +/// +/// The `*CtorTrait`s are our `n` specialization traits. Their +/// `wasmtime_error_choose_ctor` methods will return different types, each of +/// which is a dispatcher to their associated constructor. Those dispatchers +/// each have a constructor signature that is syntactically identical, but only +/// guaranteed to be well-typed based on the specialization that we did by +/// getting the dispatcher in the first place. +pub mod ctor_specialization { + use super::*; + + pub trait NewCtorTrait { + #[inline] + fn wasmtime_error_choose_ctor(&self) -> NewCtor { + NewCtor + } + } + + impl NewCtorTrait for &E {} + + pub struct NewCtor; + + impl NewCtor { + #[inline] + pub fn construct(&self, error: E) -> Error + where + E: core::error::Error + Send + Sync + 'static, + { + Error::new(error) + } + } + + pub trait MsgCtorTrait { + #[inline] + fn wasmtime_error_choose_ctor(&self) -> MsgCtor { + MsgCtor + } + } + + impl MsgCtorTrait for &&M {} + + pub struct MsgCtor; + + impl MsgCtor { + #[inline] + pub fn construct(&self, message: M) -> Error + where + M: fmt::Debug + fmt::Display + Send + Sync + 'static, + { + Error::msg(message) + } + } +} + +/// Runtime code for creating an `Error` from format arguments, handling OOM in +/// the process. +pub mod formatting { + use super::*; + + #[derive(Default)] + struct Formatter { + message: String, + oom: Option, + } + + impl fmt::Write for Formatter { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.message.try_reserve(s.len()) { + Ok(()) => { + self.message.push_str(s); + Ok(()) + } + Err(_) => { + self.oom = Some(OutOfMemory::new()); + Err(fmt::Error) + } + } + } + } + + impl Error { + /// Construct an `Error` from format arguments. + /// + /// Only for use by the `anyhow!` macro. + #[doc(hidden)] + pub fn from_format_args(args: fmt::Arguments<'_>) -> Self { + if let Some(s) = args.as_str() { + return Self::msg(s); + } + + let mut f = Formatter::default(); + match write(&mut f, args) { + Ok(()) => { + debug_assert!(f.oom.is_none()); + Error::msg(f.message) + } + Err(fmt_error) => match f.oom { + Some(oom) => Error::new(oom), + None => Error::new(fmt_error), + }, + } + } + } +} + +pub mod ensure { + /// Convenience trait to enable `ensure!(cond, ...)` to work when `cond` is of + /// type `&bool`, not just `bool`. Saves useless rewrite-to-`*cond` busywork and + /// matches `anyhow`'s behavior. + pub trait ToBool { + fn to_bool(self) -> bool; + } + + impl ToBool for bool { + #[inline] + fn to_bool(self) -> bool { + self + } + } + + impl ToBool for &bool { + #[inline] + fn to_bool(self) -> bool { + *self + } + } + + #[inline] + pub fn not(b: impl ToBool) -> bool { + !b.to_bool() + } +} diff --git a/crates/error/src/oom.rs b/crates/error/src/oom.rs new file mode 100644 index 000000000000..27364b8427b7 --- /dev/null +++ b/crates/error/src/oom.rs @@ -0,0 +1,52 @@ +use super::OomOrDynError; +use core::fmt; + +/// Out-of-memory error. +/// +/// This error is the sentinel for allocation failure due to memory exhaustion. +/// +/// Constructing an [`Error`][crate::Error] from an `OutOfMemory` does not +/// allocate. +/// +/// Allocation failure inside any `Error` method that must allocate +/// (e.g. [`Error::context`][crate::Error::context]) will propagate an +/// `OutOfMemory` error. +#[derive(Clone, Copy, Default)] +pub struct OutOfMemory { + _private: (), +} + +impl fmt::Debug for OutOfMemory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("OutOfMemory") + } +} + +impl fmt::Display for OutOfMemory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("out of memory") + } +} + +impl core::error::Error for OutOfMemory { + #[inline] + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + None + } +} + +impl OutOfMemory { + /// Construct a new `OutOfMemory` error. + /// + /// This operation does not allocate. + #[inline] + pub const fn new() -> Self { + Self { _private: () } + } +} + +impl From for OomOrDynError { + fn from(OutOfMemory { _private: () }: OutOfMemory) -> Self { + OomOrDynError::OOM + } +} diff --git a/crates/error/src/ptr.rs b/crates/error/src/ptr.rs new file mode 100644 index 000000000000..a68901a59dc1 --- /dev/null +++ b/crates/error/src/ptr.rs @@ -0,0 +1,155 @@ +//! Newtype wrappers over raw pointers that document ownership. We use these in +//! various places instead of safe references because our type-punning would +//! trigger UB otherwise. + +use alloc::boxed::Box; +use core::{marker::PhantomData, ptr::NonNull}; + +/// A raw, owned pointer. +/// +/// You are required to call `T`'s `Drop` and deallocate the pointer, this won't +/// automatically do it for you like `Box`. +#[repr(transparent)] +pub(crate) struct OwnedPtr +where + T: ?Sized, +{ + ptr: NonNull, +} + +impl OwnedPtr { + pub(crate) fn new(ptr: NonNull) -> Self { + OwnedPtr { ptr } + } + + pub(crate) fn cast(self) -> OwnedPtr { + OwnedPtr::new(self.ptr.cast()) + } + + /// Make a raw copy of this pointer. + pub(crate) fn raw_copy(&self) -> Self { + Self::new(self.ptr) + } + + pub(crate) fn into_non_null(self) -> NonNull { + self.ptr + } + + /// # Safety + /// + /// It must be valid to call `NonNull::::as_ref` on our underlying pointer. + pub(crate) unsafe fn as_ref(&self) -> &T { + unsafe { self.ptr.as_ref() } + } + + /// # Safety + /// + /// It must be valid to call `Box::::from_raw` on our underlying pointer. + pub(crate) unsafe fn into_box(self) -> Box { + // Safety: same as our safety contract. + unsafe { Box::from_raw(self.ptr.as_ptr()) } + } +} + +/// A raw pointer that is semantically a shared borrow. +#[repr(transparent)] +pub(crate) struct SharedPtr<'a, T> +where + T: ?Sized, +{ + ptr: NonNull, + _lifetime: PhantomData<&'a T>, +} + +impl<'a, T> core::fmt::Debug for SharedPtr<'a, T> +where + T: ?Sized, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SharedPtr").field("ptr", &self.ptr).finish() + } +} + +impl Clone for SharedPtr<'_, T> +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> SharedPtr<'a, T> +where + T: ?Sized, +{ + pub(crate) fn new(ptr: NonNull) -> Self { + SharedPtr { + ptr, + _lifetime: PhantomData, + } + } + + pub(crate) fn cast(self) -> SharedPtr<'a, U> { + SharedPtr::new(self.ptr.cast()) + } + + /// # Safety + /// + /// It must be valid to call `NonNull::::as_ref` on the underlying + /// pointer. + pub(crate) unsafe fn as_ref(self) -> &'a T { + unsafe { self.ptr.as_ref() } + } +} + +impl Copy for SharedPtr<'_, T> where T: ?Sized {} + +/// A raw pointer that is semantically an exclusive borrow. +#[repr(transparent)] +pub(crate) struct MutPtr<'a, T> +where + T: ?Sized, +{ + ptr: NonNull, + _lifetime: PhantomData<&'a mut T>, +} + +impl<'a, T> MutPtr<'a, T> +where + T: ?Sized, +{ + pub(crate) fn new(ptr: NonNull) -> Self { + MutPtr { + ptr, + _lifetime: PhantomData, + } + } + + /// Make a raw copy of this pointer. + pub(crate) fn raw_copy(&self) -> Self { + Self::new(self.ptr) + } + + pub(crate) fn cast(self) -> MutPtr<'a, U> { + MutPtr::new(self.ptr.cast()) + } + + pub(crate) fn as_shared_ptr(&self) -> SharedPtr<'_, T> { + SharedPtr::new(self.ptr) + } + + /// # Safety + /// + /// It must be valid to call `NonNull::::as_ref` on the underlying pointer. + pub(crate) unsafe fn as_ref(&self) -> &'a T { + unsafe { self.ptr.as_ref() } + } + + /// # Safety + /// + /// It must be valid to call `NonNull::::as_mut` on the underlying pointer. + pub(crate) unsafe fn as_mut(&mut self) -> &'a mut T { + unsafe { self.ptr.as_mut() } + } +} diff --git a/crates/error/src/vtable.rs b/crates/error/src/vtable.rs new file mode 100644 index 000000000000..08a511da2d60 --- /dev/null +++ b/crates/error/src/vtable.rs @@ -0,0 +1,176 @@ +use super::{ConcreteError, DynError, ErrorExt, OomOrDynErrorMut, OomOrDynErrorRef, OutOfMemory}; +use crate::boxed::try_box; +use crate::ptr::{MutPtr, OwnedPtr, SharedPtr}; +use alloc::boxed::Box; +use core::{any::TypeId, fmt, ptr::NonNull}; + +/// A vtable containing the `ErrorExt` methods for some type `T`. +/// +/// This is used to create thin-pointer equivalents of `Box`, +/// `&dyn ErrorExt`, and `&mut ErrorExt`, which would all otherwise be two words +/// in size. +/// +/// # Safety +/// +/// The safety contract for all vtable functions is the same: +/// +/// * `SharedPtr<'_, DynError>`s must be valid for reading a `ConcreteError`, +/// `MutPtr<'_, DynError>`s must additionally be valid for writing a +/// `ConcreteError`, and `OwnedPtr`s must additionally be valid +/// to deallocate with `ConcreteError`'s layout. +/// +/// * If a `OomOrDynError{Ref,Mut}` return value contains a `{Shared,Mut}Ptr<'_, +/// DynError>`, it must be valid for reading (and, in the case of `MutPtr`, +/// writing) `DynError`s. +#[repr(C)] +pub(crate) struct Vtable { + pub(crate) display: unsafe fn(SharedPtr<'_, DynError>, &mut fmt::Formatter<'_>) -> fmt::Result, + pub(crate) debug: unsafe fn(SharedPtr<'_, DynError>, &mut fmt::Formatter<'_>) -> fmt::Result, + pub(crate) source: unsafe fn(SharedPtr<'_, DynError>) -> Option>, + pub(crate) source_mut: unsafe fn(MutPtr<'_, DynError>) -> Option>, + pub(crate) is: unsafe fn(SharedPtr<'_, DynError>, TypeId) -> bool, + pub(crate) as_dyn_core_error: + unsafe fn(SharedPtr<'_, DynError>) -> &(dyn core::error::Error + Send + Sync + 'static), + pub(crate) into_boxed_dyn_core_error: + unsafe fn( + OwnedPtr, + ) + -> Result, OutOfMemory>, + pub(crate) drop_and_deallocate: unsafe fn(OwnedPtr), + + /// Additional safety requirement: the `NonNull` pointer must be valid + /// for writing a `T`. + /// + /// Upon successful return, a `T` will have been written to that memory + /// block. + pub(crate) downcast: unsafe fn(OwnedPtr, TypeId, NonNull), +} + +impl Vtable { + /// Get the `Vtable` of the `E: ErrorExt` type parameter. + pub(crate) fn of() -> &'static Self + where + E: ErrorExt, + { + &Vtable { + display: display::, + debug: debug::, + source: source::, + source_mut: source_mut::, + is: is::, + as_dyn_core_error: as_dyn_core_error::, + into_boxed_dyn_core_error: into_boxed_dyn_core_error::, + drop_and_deallocate: drop_and_deallocate::, + downcast: downcast::, + } + } +} + +unsafe fn display(error: SharedPtr<'_, DynError>, f: &mut fmt::Formatter<'_>) -> fmt::Result +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let error = unsafe { error.as_ref() }; + fmt::Display::fmt(&error.error, f) +} + +unsafe fn debug(error: SharedPtr<'_, DynError>, f: &mut fmt::Formatter<'_>) -> fmt::Result +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let error = unsafe { error.as_ref() }; + fmt::Debug::fmt(&error.error, f) +} + +unsafe fn source(error: SharedPtr<'_, DynError>) -> Option> +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let error = unsafe { error.as_ref() }; + error.error.ext_source() +} + +unsafe fn source_mut(error: MutPtr<'_, DynError>) -> Option> +where + E: ErrorExt, +{ + let mut error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let error = unsafe { error.as_mut() }; + error.error.ext_source_mut() +} + +unsafe fn is(error: SharedPtr<'_, DynError>, type_id: TypeId) -> bool +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let error = unsafe { error.as_ref() }; + error.error.ext_is(type_id) +} + +unsafe fn as_dyn_core_error( + error: SharedPtr<'_, DynError>, +) -> &(dyn core::error::Error + Send + Sync + 'static) +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let error = unsafe { error.as_ref() }; + &error.error as _ +} + +unsafe fn into_boxed_dyn_core_error( + error: OwnedPtr, +) -> Result, OutOfMemory> +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let error = unsafe { error.into_box() }; + let error = try_box(error.error)?; + Ok(error as _) +} + +unsafe fn drop_and_deallocate(error: OwnedPtr) +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let _ = unsafe { error.into_box() }; +} + +unsafe fn downcast(error: OwnedPtr, type_id: TypeId, ret_ptr: NonNull) +where + E: ErrorExt, +{ + let error = error.cast::>(); + // Safety: implied by all vtable functions' safety contract. + let mut error = unsafe { error.into_box() }; + + if error.error.ext_is(type_id) { + // Safety: Implied by `downcast`'s additional safety safety requirement. + unsafe { + error.error.ext_move(ret_ptr); + } + } else { + let source = error + .error + .ext_take_source() + .expect("must have a source up the chain if `E` is not our target type"); + // Safety: implied by downcast's additional safety requirement. + unsafe { + source.downcast(type_id, ret_ptr); + } + } +} diff --git a/crates/error/tests/tests.rs b/crates/error/tests/tests.rs new file mode 100644 index 000000000000..aeabde5d5b1d --- /dev/null +++ b/crates/error/tests/tests.rs @@ -0,0 +1,825 @@ +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +use alloc::{ + format, + string::{String, ToString}, + sync::Arc, +}; +use core::{ + fmt, + sync::atomic::{AtomicU32, Ordering::SeqCst}, +}; +#[cfg(feature = "std")] +use std::backtrace::BacktraceStatus; +use wasmtime_internal_error::{Context, Error, OutOfMemory, Result, anyhow, bail, ensure}; + +#[derive(Debug)] +struct TestError(u32); + +impl fmt::Display for TestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl core::error::Error for TestError {} + +#[derive(Debug)] +struct CountDrops(Arc); + +impl CountDrops { + fn new(drops: &Arc) -> Self { + CountDrops(drops.clone()) + } +} + +impl Drop for CountDrops { + fn drop(&mut self) { + self.0.fetch_add(1, SeqCst); + } +} + +impl fmt::Display for CountDrops { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl core::error::Error for CountDrops {} + +#[derive(Debug)] +struct ChainError { + message: String, + source: Option>, +} + +impl fmt::Display for ChainError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.message) + } +} + +impl core::error::Error for ChainError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + let source = self.source.as_ref()?; + Some(&**source) + } +} + +impl ChainError { + fn new( + message: impl Into, + source: Option>, + ) -> Self { + let message = message.into(); + Self { message, source } + } +} + +#[test] +fn new() { + let mut error = Error::new(TestError(42)); + assert!(error.is::()); + assert_eq!(error.downcast_ref::().unwrap().0, 42); + error.downcast_mut::().unwrap().0 += 1; + assert_eq!(error.downcast_ref::().unwrap().0, 43); +} + +#[test] +fn new_drops() { + let drops = Arc::new(AtomicU32::new(0)); + let error = Error::new(CountDrops::new(&drops)); + assert_eq!(drops.load(SeqCst), 0); + drop(error); + assert_eq!(drops.load(SeqCst), 1); +} + +#[test] +fn from_error_with_large_align() { + // The `{ConcreteError,DynError}::error` fields are not at the same + // offset when the concrete error's type requires greater-than-pointer + // alignment. Exercise our various conversions and accesses to make sure + // that we do the right thing in this case (that is morally cast `*mut + // DynError` to `*mut ConcreteError` rather than casting `*mut + // TypeErasedError` to `*mut E`). + #[derive(Debug)] + #[repr(align(16))] + struct LargeAlign { + value: u128, + } + + impl fmt::Display for LargeAlign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } + } + + impl core::error::Error for LargeAlign {} + + let value = 0x1234_6578_1234_5678_1234_6578_1234_5678; + let error = Error::from(LargeAlign { value }); + assert!(error.is::()); + assert_eq!(error.downcast_ref::().unwrap().value, value); +} + +#[test] +fn msg() { + let error = Error::msg("uh oh!"); + assert!(error.is::<&str>()); + let msg = error.to_string(); + assert_eq!(msg, "uh oh!"); +} + +#[test] +fn msg_drops() { + let drops = Arc::new(AtomicU32::new(0)); + let error = Error::msg(CountDrops::new(&drops)); + assert_eq!(drops.load(SeqCst), 0); + drop(error); + assert_eq!(drops.load(SeqCst), 1); +} + +#[test] +fn from_error() { + let error = Error::from(TestError(42)); + assert!(error.is::()); + assert_eq!(error.downcast_ref::().unwrap().0, 42); +} + +#[test] +fn into_boxed_dyn_error() { + let error = ChainError::new("ouch", None); + let error = ChainError::new("whoops", Some(Box::new(error))); + let error = Error::new(error).context("yikes"); + + let error = error.into_boxed_dyn_error(); + assert_eq!(error.to_string(), "yikes"); + + let error = error.source().unwrap(); + assert_eq!(error.to_string(), "whoops"); + + let error = error.source().unwrap(); + assert_eq!(error.to_string(), "ouch"); + + assert!(error.source().is_none()); +} + +#[test] +fn is() { + // `is` is true for `Error::msg(T)` + let e = Error::msg("str"); + assert!(e.is::<&str>()); + assert!(!e.is::()); + assert!(!e.is::()); + + // `is` is true for `T` in the context chain. + let e = e.context(TestError(42)); + assert!(e.is::<&str>()); + assert!(e.is::()); + assert!(!e.is::()); + + // `is` is still true when there are multiple `T`s and `U`s in the + // chain. + let e = e.context(TestError(36)).context("another str"); + assert!(e.is::<&str>()); + assert!(e.is::()); + assert!(!e.is::()); + + // `is` is true for `Error::from(T)`. + let e = Error::from(TestError(36)); + assert!(e.is::()); + assert!(!e.is::<&str>()); + assert!(!e.is::()); + + // `is` is true for `Error::from(OutOfMemory)`. + let e = Error::from(OutOfMemory::new()); + assert!(e.is::()); + assert!(!e.is::()); + assert!(!e.is::<&str>()); +} + +#[test] +#[cfg(feature = "backtrace")] +fn backtrace() { + // Backtrace on OOM. + let e = Error::from(OutOfMemory::new()); + assert_eq!(e.backtrace().status(), BacktraceStatus::Disabled); + + let backtraces_enabled = + match std::env::var("RUST_LIB_BACKTRACE").or_else(|_| std::env::var("RUST_BACKTRACE")) { + Err(_) => false, + Ok(s) if s == "0" => false, + Ok(_) => true, + }; + + let assert_backtrace = |e: Error| { + if backtraces_enabled { + assert!(matches!( + e.backtrace().status(), + BacktraceStatus::Unsupported | BacktraceStatus::Captured + )); + } else { + assert_eq!(e.backtrace().status(), BacktraceStatus::Disabled); + } + }; + + // Backtrace on `Error::msg`. + assert_backtrace(Error::msg("whoops")); + + // Backtrace on `Error::new`. + assert_backtrace(Error::new(TestError(42))); + + // Backtrace on context chain. + assert_backtrace(Error::new(TestError(42)).context("yikes")); +} + +#[test] +fn anyhow_macro_string_literal() { + let error = anyhow!("literal"); + assert_eq!(error.to_string(), "literal"); +} + +#[test] +fn anyhow_macro_format_implicit_args() { + let x = 42; + let y = 36; + let error = anyhow!("implicit args {x} {y}"); + assert_eq!(error.to_string(), "implicit args 42 36"); +} + +#[test] +fn anyhow_macro_format_explicit_args() { + let a = 84; + let b = 72; + let error = anyhow!("explicit args {x} {y}", x = a / 2, y = b / 2); + assert_eq!(error.to_string(), "explicit args 42 36"); +} + +#[test] +fn anyhow_macro_core_error() { + let error = TestError(42); + let error = anyhow!(error); + assert!(error.is::()); + assert_eq!(error.to_string(), "TestError(42)"); +} + +#[test] +fn anyhow_macro_core_error_chain() { + let error = ChainError::new("ouch", None); + let error = ChainError::new("yikes", Some(Box::new(error))); + let error = ChainError::new("whoops", Some(Box::new(error))); + let error = anyhow!(error); + + let mut chain = error.chain(); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "whoops"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "yikes"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "ouch"); + + assert!(chain.next().is_none()); +} + +#[test] +fn anyhow_macro_msg() { + let error = 42; + let error = anyhow!(error); + assert_eq!(error.to_string(), "42"); +} + +#[test] +fn bail_macro() { + fn bail_string_literal() -> Result<()> { + bail!("whoops") + } + assert_eq!(bail_string_literal().unwrap_err().to_string(), "whoops"); + + fn bail_format_implicit(x: u32) -> Result<()> { + bail!("yikes {x}") + } + assert_eq!( + bail_format_implicit(42).unwrap_err().to_string(), + "yikes 42" + ); + + fn bail_format_explicit(y: u32) -> Result<()> { + bail!("ouch {}", y + 1) + } + assert_eq!(bail_format_explicit(35).unwrap_err().to_string(), "ouch 36"); + + fn bail_core_error() -> Result<()> { + bail!(TestError(13)) + } + assert_eq!(bail_core_error().unwrap_err().to_string(), "TestError(13)"); + + fn bail_display() -> Result<()> { + let x = 1337; + bail!(x) + } + assert_eq!(bail_display().unwrap_err().to_string(), "1337"); +} + +#[test] +fn ensure_macro() { + fn ensure_string_literal(c: bool) -> Result<()> { + ensure!(c, "whoops"); + Ok(()) + } + assert!(ensure_string_literal(true).is_ok()); + assert_eq!( + ensure_string_literal(false).unwrap_err().to_string(), + "whoops" + ); + + fn ensure_format_implicit(c: bool, x: u32) -> Result<()> { + ensure!(c, "yikes {x}"); + Ok(()) + } + assert!(ensure_format_implicit(true, 42).is_ok()); + assert_eq!( + ensure_format_implicit(false, 42).unwrap_err().to_string(), + "yikes 42" + ); + + fn ensure_format_explicit(c: bool, y: u32) -> Result<()> { + ensure!(c, "ouch {}", y + 1); + Ok(()) + } + assert!(ensure_format_explicit(true, 35).is_ok()); + assert_eq!( + ensure_format_explicit(false, 35).unwrap_err().to_string(), + "ouch 36" + ); + + fn ensure_core_error(c: bool) -> Result<()> { + ensure!(c, TestError(13)); + Ok(()) + } + assert!(ensure_core_error(true).is_ok()); + assert_eq!( + ensure_core_error(false).unwrap_err().to_string(), + "TestError(13)" + ); + + fn ensure_display(c: bool) -> Result<()> { + let x = 1337; + ensure!(c, x); + Ok(()) + } + assert!(ensure_display(true).is_ok()); + assert_eq!(ensure_display(false).unwrap_err().to_string(), "1337"); + + fn ensure_bool_ref(c: &bool) -> Result<()> { + ensure!(c, "whoops"); + Ok(()) + } + assert!(ensure_bool_ref(&true).is_ok()); + assert_eq!(ensure_bool_ref(&false).unwrap_err().to_string(), "whoops"); +} + +#[test] +fn downcast() { + // Error::msg(T) + let error = Error::msg("uh oh"); + let error = error.downcast::().unwrap_err(); + let error = error.downcast::().unwrap_err(); + assert_eq!(error.downcast::<&str>().unwrap(), "uh oh"); + + // Error::new() + let error = Error::new(TestError(42)); + let error = error.downcast::<&str>().unwrap_err(); + let error = error.downcast::().unwrap_err(); + assert_eq!(error.downcast::().unwrap().0, 42); + + // Error::from(oom) + let error = Error::from(OutOfMemory::new()); + let error = error.downcast::<&str>().unwrap_err(); + let error = error.downcast::().unwrap_err(); + assert!(error.downcast::().is_ok()); + + // First in context chain. + let error = Error::new(TestError(42)) + .context("yikes") + .context(OutOfMemory::new()); + let error = error.downcast::().unwrap_err(); + assert!(error.downcast::().is_ok()); + + // Middle in context chain. + let error = Error::new(TestError(42)) + .context("yikes") + .context(OutOfMemory::new()); + let error = error.downcast::().unwrap_err(); + assert_eq!(error.downcast::<&str>().unwrap(), "yikes"); + + // Last in context chain. + let error = Error::new(TestError(42)) + .context("yikes") + .context(OutOfMemory::new()); + let error = error.downcast::().unwrap_err(); + assert_eq!(error.downcast::().unwrap().0, 42); + + // Multiple `T`s in the context chain gives the first one. + let error = Error::new(TestError(42)).context(TestError(36)); + assert_eq!(error.downcast::().unwrap().0, 36); +} + +#[test] +fn downcast_drops_everything() { + // Error::new + let drops = Arc::new(AtomicU32::new(0)); + let error = Error::new(CountDrops::new(&drops)) + .context(CountDrops::new(&drops)) + .context(CountDrops::new(&drops)); + assert_eq!(drops.load(SeqCst), 0); + let c = error.downcast::().unwrap(); + assert_eq!(drops.load(SeqCst), 2); + drop(c); + assert_eq!(drops.load(SeqCst), 3); + + // Error::msg + let drops = Arc::new(AtomicU32::new(0)); + let error = Error::msg(CountDrops(drops.clone())) + .context(CountDrops(drops.clone())) + .context(CountDrops(drops.clone())); + assert_eq!(drops.load(SeqCst), 0); + let c = error.downcast::().unwrap(); + assert_eq!(drops.load(SeqCst), 2); + drop(c); + assert_eq!(drops.load(SeqCst), 3); +} + +#[test] +fn downcast_ref() { + // `Error::msg(T)` + let e = Error::msg("str"); + assert_eq!(e.downcast_ref::<&str>().copied().unwrap(), "str"); + assert!(e.downcast_ref::().is_none()); + assert!(e.downcast_ref::().is_none()); + + // Context chain. + let e = e.context(TestError(42)); + assert_eq!(e.downcast_ref::<&str>().copied().unwrap(), "str"); + assert_eq!(e.downcast_ref::().unwrap().0, 42); + assert!(e.downcast_ref::().is_none()); + + // Multiple `T`s in the context chain gives you the first one. + let e = e.context("another str"); + assert_eq!(e.downcast_ref::<&str>().copied().unwrap(), "another str"); + assert_eq!(e.downcast_ref::().unwrap().0, 42); + assert!(e.downcast_ref::().is_none()); + + // `Error::from(T)` + let e = Error::from(TestError(36)); + assert_eq!(e.downcast_ref::().unwrap().0, 36); + assert!(e.downcast_ref::<&str>().is_none()); + assert!(e.downcast_ref::().is_none()); + + // `Error::from(OutOfMemory)` + let e = Error::from(OutOfMemory::new()); + assert!(e.downcast_ref::().is_some()); + assert!(e.downcast_ref::().is_none()); + assert!(e.downcast_ref::<&str>().is_none()); +} + +#[test] +fn downcast_mut() { + // `Error::msg(T)` + let mut e = Error::msg("str"); + assert!(e.downcast_mut::().is_none()); + assert!(e.downcast_mut::().is_none()); + *e.downcast_mut::<&str>().unwrap() = "whoops"; + assert_eq!(*e.downcast_ref::<&str>().unwrap(), "whoops"); + + // Context chain. + let mut e = e.context(TestError(42)); + assert!(e.downcast_mut::().is_none()); + *e.downcast_mut::<&str>().unwrap() = "uh oh"; + assert_eq!(*e.downcast_ref::<&str>().unwrap(), "uh oh"); + e.downcast_mut::().unwrap().0 += 1; + assert_eq!(e.downcast_ref::().unwrap().0, 43); + + // Multiple `T`s in the context chain gives you the first one. + let mut e = e.context("another str"); + *e.downcast_mut::<&str>().unwrap() = "yikes"; + assert_eq!(*e.downcast_ref::<&str>().unwrap(), "yikes"); + assert_eq!(format!("{e:#}"), "yikes: TestError(43): uh oh"); + + // `Error::from(T)` + let mut e = Error::from(TestError(36)); + assert!(e.downcast_mut::<&str>().is_none()); + assert!(e.downcast_mut::().is_none()); + e.downcast_mut::().unwrap().0 += 1; + assert_eq!(e.downcast_ref::().unwrap().0, 37); + + // `Error::from(OutOfMemory)` + let mut e = Error::from(OutOfMemory::new()); + assert!(e.downcast_mut::().is_some()); + assert!(e.downcast_mut::().is_none()); + assert!(e.downcast_mut::<&str>().is_none()); +} + +#[test] +fn context_on_oom() { + let error = Error::new(OutOfMemory::new()); + let error = error.context("yikes"); + assert!(error.is::()); + assert!( + !error.is::<&str>(), + "shouldn't attempt to box up more context when we've already exhausted memory" + ); +} + +#[test] +fn context_on_ok_result() { + let result: Result = Ok(42); + let result = result.context("uh oh").context(TestError(1337)); + assert_eq!(result.unwrap(), 42); +} + +#[test] +fn context_on_err_result() { + let result: Result = Err(Error::new(TestError(42))).context("uh oh"); + let error = result.unwrap_err(); + + assert!(error.is::()); + assert_eq!(error.downcast_ref::().unwrap().0, 42); + + assert!(error.is::<&str>()); + assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); +} + +#[test] +fn context_on_some_option() { + let option = Some(42); + let result = option.context("uh oh").context(TestError(1337)); + assert_eq!(result.unwrap(), 42); +} + +#[test] +fn context_on_none_option() { + let option: Option = None; + let result = option.context(TestError(42)).context("uh oh"); + let error = result.unwrap_err(); + + assert!(error.is::()); + assert_eq!(error.downcast_ref::().unwrap().0, 42); + + assert!(error.is::<&str>()); + assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); +} + +#[test] +fn with_context_on_ok_result() { + let result: Result = Ok(42); + let result = result + .with_context(|| "uh oh") + .with_context(|| TestError(36)); + assert_eq!(result.unwrap(), 42); +} + +#[test] +fn with_context_on_err_result() { + let result: Result = Err(Error::new(TestError(36))); + let result = result.with_context(|| "uh oh"); + let error = result.unwrap_err(); + + assert!(error.is::()); + assert!(error.is::<&str>()); + assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); +} + +#[test] +fn with_context_on_some_option() { + let option = Some(36); + let result = option + .with_context(|| "uh oh") + .with_context(|| TestError(42)); + assert_eq!(result.unwrap(), 36); +} + +#[test] +fn with_context_on_none_option() { + let option: Option = None; + let result = option + .with_context(|| "uh oh") + .with_context(|| TestError(42)); + let error = result.unwrap_err(); + + assert!(error.is::<&str>()); + assert_eq!(error.downcast_ref::<&str>().copied().unwrap(), "uh oh"); + + assert!(error.is::()); + assert_eq!(error.downcast_ref::().unwrap().0, 42); +} + +#[test] +fn fmt_debug() { + let error = Error::msg("whoops").context("uh oh").context("yikes"); + let actual = format!("{error:?}"); + + let expected = "yikes\n\ + \n\ + Caused by:\n\ + \t0: uh oh\n\ + \t1: whoops\n"; + + #[cfg(feature = "backtrace")] + { + assert!(actual.starts_with(expected)); + if let BacktraceStatus::Captured = error.backtrace().status() { + assert!(actual.contains("Stack backtrace:")); + } + } + + #[cfg(not(feature = "backtrace"))] + { + assert_eq!(actual, expected); + } +} + +#[test] +fn fmt_debug_alternate() { + let error = ChainError::new("root cause", None); + let error = ChainError::new("whoops", Some(Box::new(error))); + let error = Error::new(error) + .context(TestError(42)) + .context("yikes") + .context("ouch"); + + let actual = format!("{error:#?}"); + let actual = actual.trim(); + println!("actual `{{:#?}}` output:\n{actual}"); + + let expected = r#" +Error { + inner: DynError { + error: ouch, + source: DynError { + error: yikes, + source: DynError { + error: TestError( + 42, + ), + source: DynError { + error: ChainError { + message: "whoops", + source: Some( + ChainError { + message: "root cause", + source: None, + }, + ), + }, + }, + }, + }, + }, +} + "# + .trim(); + println!("expected `{{:#?}}` output:\n{expected}"); + + assert_eq!(actual, expected); +} + +#[test] +fn fmt_debug_alternate_with_oom() { + let error = Error::new(OutOfMemory::new()); + + let actual = format!("{error:#?}"); + let actual = actual.trim(); + println!("actual `{{:#?}}` output:\n{actual}"); + + let expected = r#" +Error { + inner: Oom( + OutOfMemory, + ), +} + "# + .trim(); + println!("expected `{{:#?}}` output:\n{expected}"); + + assert_eq!(actual, expected); +} + +#[test] +fn fmt_display() { + let error = Error::msg("whoops").context("uh oh").context("yikes"); + assert_eq!(format!("{error}"), "yikes"); +} + +#[test] +fn fmt_display_alternate() { + let error = Error::msg("ouch") + .context("whoops") + .context("uh oh") + .context("yikes"); + assert_eq!(format!("{error:#}"), "yikes: uh oh: whoops: ouch"); +} + +#[test] +fn chain() { + let error = Error::msg("failure") + .context("uh oh") + .context(TestError(42)); + + let mut chain = error.chain(); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "TestError(42)"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "uh oh"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "failure"); + + assert!(chain.next().is_none()); + + for _ in 0..100 { + assert!(chain.next().is_none(), "`Chain` is a fused iterator"); + } +} + +#[test] +fn chain_on_error_with_source() { + let error = ChainError::new("yikes", None); + let error = ChainError::new("whoops", Some(Box::new(error))); + let error = ChainError::new("uh oh", Some(Box::new(error))); + let error = Error::new(error).context("ouch").context("oof"); + + let mut chain = error.chain(); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "oof"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "ouch"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "uh oh"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "whoops"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "yikes"); + + assert!(chain.next().is_none()); +} + +#[test] +fn root_cause() { + let error = ChainError::new("yikes", None); + let error = ChainError::new("whoops", Some(Box::new(error))); + let error = ChainError::new("uh oh", Some(Box::new(error))); + let error = Error::new(error).context("ouch").context("oof"); + let root = error.root_cause(); + assert_eq!(root.to_string(), "yikes"); + assert!(root.source().is_none()); +} + +#[test] +fn chain_with_leaf_sources() { + #[derive(Debug)] + struct ErrorWithSource(String, Box); + + impl fmt::Display for ErrorWithSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } + } + + impl core::error::Error for ErrorWithSource { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + Some(&*self.1) + } + } + + let error = Error::new(ErrorWithSource("leaf".to_string(), Box::new(TestError(42)))) + .context("oof") + .context("wow"); + + let mut chain = error.chain(); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "wow"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "oof"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "leaf"); + + let e = chain.next().unwrap(); + assert_eq!(e.to_string(), "TestError(42)"); + + assert!(chain.next().is_none()); +} diff --git a/crates/fuzzing/Cargo.toml b/crates/fuzzing/Cargo.toml index f60ec3aa219d..913d0a098073 100644 --- a/crates/fuzzing/Cargo.toml +++ b/crates/fuzzing/Cargo.toml @@ -29,6 +29,7 @@ wasmparser = { workspace = true } wasmprinter = { workspace = true } wasmtime-wast = { workspace = true, features = ['component-model'] } wasmtime = { workspace = true, features = ['default', 'winch'] } +wasmtime-error = { workspace = true, features = ['backtrace'] } wasm-encoder = { workspace = true } wasm-smith = { workspace = true, features = ['serde'] } wasm-mutate = { workspace = true } diff --git a/crates/fuzzing/src/oom.rs b/crates/fuzzing/src/oom.rs index 98277a8506be..45b635f82f43 100644 --- a/crates/fuzzing/src/oom.rs +++ b/crates/fuzzing/src/oom.rs @@ -3,10 +3,9 @@ //! Inspired by SpiderMonkey's `oomTest()` helper: //! https://firefox-source-docs.mozilla.org/js/hacking_tips.html#how-to-debug-oomtest-failures -use anyhow::bail; use backtrace::Backtrace; use std::{alloc::GlobalAlloc, cell::Cell, mem, ptr, time}; -use wasmtime::{Error, Result}; +use wasmtime_error::{Error, OutOfMemory, Result, bail}; /// An allocator for use with `OomTest`. #[non_exhaustive] @@ -154,6 +153,12 @@ impl OomTest { /// until the pass (or fail). pub fn new() -> Self { let _ = env_logger::try_init(); + + // NB: `std::backtrace::Backtrace` doesn't have ways to handle + // OOM. Ideally we would just disable the `"backtrace"` cargo feature, + // but workspace feature resolution doesn't play nice with that. + wasmtime_error::disable_backtrace(); + OomTest { max_iters: None, max_duration: None, @@ -236,9 +241,7 @@ impl OomTest { Ok(()) } - fn is_oom_error(&self, _: &Error) -> bool { - // TODO: We don't have an OOM error yet. Will likely need to make it so - // that `wasmtime::Error != anyhow::Error` as a first step here. - false + fn is_oom_error(&self, e: &Error) -> bool { + e.is::() } } diff --git a/crates/fuzzing/tests/oom.rs b/crates/fuzzing/tests/oom.rs index dc8ac8dfd920..3c919c807bef 100644 --- a/crates/fuzzing/tests/oom.rs +++ b/crates/fuzzing/tests/oom.rs @@ -1,5 +1,10 @@ -use std::alloc::{Layout, alloc}; -use wasmtime::{Config, Result}; +use std::{ + alloc::{Layout, alloc}, + fmt::{self, Write}, + sync::atomic::{AtomicU32, Ordering::SeqCst}, +}; +use wasmtime::Config; +use wasmtime_error::{Error, OutOfMemory, Result, anyhow}; use wasmtime_fuzzing::oom::{OomTest, OomTestAllocator}; #[global_allocator] @@ -34,3 +39,106 @@ fn config_new() -> Result<()> { Ok(()) }) } + +fn ok_if_not_oom(error: Error) -> Result<()> { + if error.is::() { + Err(error) + } else { + Ok(()) + } +} + +#[test] +fn error_new() -> Result<()> { + OomTest::new().test(|| { + let error = Error::new(u8::try_from(u32::MAX).unwrap_err()); + ok_if_not_oom(error) + }) +} + +#[test] +fn error_msg() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("ouch"); + ok_if_not_oom(error) + }) +} + +static X: AtomicU32 = AtomicU32::new(42); + +#[test] +fn error_fmt() -> Result<()> { + OomTest::new().test(|| { + let x = X.load(SeqCst); + let error = anyhow!("ouch: {x}"); + ok_if_not_oom(error) + }) +} + +#[test] +fn error_context() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + ok_if_not_oom(error) + }) +} + +#[test] +fn error_chain() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + for _ in error.chain() { + // Nothing to do here, just exercising the iteration. + } + ok_if_not_oom(error) + }) +} + +struct Null; +impl Write for Null { + fn write_str(&mut self, _s: &str) -> fmt::Result { + Ok(()) + } +} + +#[test] +fn display_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error}").unwrap(); + ok_if_not_oom(error) + }) +} + +#[test] +fn alternate_display_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error:?}").unwrap(); + ok_if_not_oom(error) + }) +} + +#[test] +fn debug_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error:?}").unwrap(); + ok_if_not_oom(error) + }) +} + +#[test] +fn alternate_debug_fmt_error() -> Result<()> { + OomTest::new().test(|| { + let error = Error::msg("hello"); + let error = error.context("goodbye"); + write!(&mut Null, "{error:#?}").unwrap(); + ok_if_not_oom(error) + }) +} diff --git a/scripts/publish.rs b/scripts/publish.rs index a93a616dc552..7106e05cc6bc 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -53,6 +53,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ // winch "winch", // wasmtime + "wasmtime-internal-error", "wasmtime-internal-versioned-export-macros", "wasmtime-internal-slab", "wasmtime-internal-component-util", diff --git a/supply-chain/config.toml b/supply-chain/config.toml index fc32af58f888..92090369c8bb 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -142,6 +142,9 @@ audit-as-crates-io = true [policy.wasmtime-internal-cranelift] audit-as-crates-io = true +[policy.wasmtime-internal-error] +audit-as-crates-io = true + [policy.wasmtime-internal-explorer] audit-as-crates-io = true