use crate::error::{Error, ErrorExt, OutOfMemory, Result, boxed::try_new_uninit_box}; use core::any::TypeId; use core::fmt; use core::ptr::NonNull; use std_alloc::boxed::Box; 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`] and [`Error::root_cause`] methods. /// /// After applying error context of type `C`, 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 /// trait's functions return an `Error` where /// [`error.is::()`](OutOfMemory) is true. /// /// # Example /// /// ``` /// # use wasmtime_internal_core::error as wasmtime; /// use wasmtime::{Context as _, Result}; /// # #[cfg(feature = "backtrace")] /// # wasmtime_internal_core::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: /// 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_core as wasmtime; /// use wasmtime::error::{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`](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) => 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) => 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) => 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) => 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 => 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 => 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.inner.as_dyn_core_error()) } } unsafe impl ErrorExt for ContextError where C: fmt::Display + Send + Sync + 'static, { fn ext_as_dyn_core_error(&self) -> &(dyn core::error::Error + Send + Sync + 'static) { self } fn ext_into_boxed_dyn_core_error( self, ) -> Result, OutOfMemory> { let boxed = try_new_uninit_box()?; Ok(Box::write(boxed, self) as _) } fn ext_source(&self) -> Option<&Error> { self.error.as_ref() } fn ext_source_mut(&mut self) -> Option<&mut Error> { self.error.as_mut() } 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 { self.error.as_mut()?.take_backtrace() } #[cfg(feature = "anyhow")] fn ext_into_anyhow(mut self) -> anyhow::Error { match self.error.take() { Some(error) => anyhow::Error::from(error).context(self.context), None => anyhow::Error::msg(self), } } }