Skip to main content

wasmtime_internal_core/error/
context.rs

1use crate::error::{Error, ErrorExt, OutOfMemory, Result, boxed::try_new_uninit_box};
2use core::any::TypeId;
3use core::fmt;
4use core::ptr::NonNull;
5use std_alloc::boxed::Box;
6
7mod sealed {
8    use super::*;
9    pub trait Sealed {}
10    impl<T, E> Sealed for Result<T, E> {}
11    impl<T> Sealed for Option<T> {}
12}
13
14/// Extension trait to add error context to results.
15///
16/// This extension trait, and its methods, are the primary way to create error
17/// chains. An error's debug output will include the full chain of
18/// errors. Errors in these chains are accessible via the
19/// [`Error::chain`] and [`Error::root_cause`] methods.
20///
21/// After applying error context of type `C`, calling
22/// [`error.is::<C>()`](Error::is) will return `true` for the new error
23/// (unless there was a memory allocation failure) in addition to any other
24/// types `T` for which it was already the case that `error.is::<T>()`.
25///
26/// This boxes the inner `C` type, but if that box allocation fails, then this
27/// trait's functions return an `Error` where
28/// [`error.is::<OutOfMemory>()`](OutOfMemory) is true.
29///
30/// # Example
31///
32/// ```
33/// # use wasmtime_internal_core::error as wasmtime;
34/// use wasmtime::{Context as _, Result};
35/// # #[cfg(feature = "backtrace")]
36/// # wasmtime_internal_core::error::disable_backtrace();
37///
38/// fn u32_to_u8(x: u32) -> Result<u8> {
39///     let y = u8::try_from(x).with_context(|| {
40///         format!("failed to convert `{x}` into a `u8` (max = `{}`)", u8::MAX)
41///     })?;
42///     Ok(y)
43/// }
44///
45/// let x = u32_to_u8(42).unwrap();
46/// assert_eq!(x, 42);
47///
48/// let error = u32_to_u8(999).unwrap_err();
49///
50/// // The error is a `String` because of our added context.
51/// assert!(error.is::<String>());
52/// assert_eq!(
53///     error.to_string(),
54///     "failed to convert `999` into a `u8` (max = `255`)",
55/// );
56///
57/// // But it is also a `TryFromIntError` because of the inner error.
58/// assert!(error.is::<std::num::TryFromIntError>());
59/// assert_eq!(
60///     error.root_cause().to_string(),
61///     "out of range integral type conversion attempted",
62/// );
63///
64/// // The debug output of the error contains the full error chain.
65/// assert_eq!(
66///     format!("{error:?}").trim(),
67///     r#"
68/// failed to convert `999` into a `u8` (max = `255`)
69///
70/// Caused by:
71///     out of range integral type conversion attempted
72///     "#.trim(),
73/// );
74/// ```
75///
76/// # Example with `Option<T>`
77///
78/// You can also use this trait to create the initial, root-cause `Error` when a
79/// fallible function returns an `Option`:
80///
81/// ```
82/// # use wasmtime_internal_core as wasmtime;
83/// use wasmtime::error::{Context as _, Result};
84///
85/// fn try_get<T>(slice: &[T], i: usize) -> Result<&T> {
86///     let elem: Option<&T> = slice.get(i);
87///     elem.with_context(|| {
88///         format!("out of bounds access: index is {i} but length is {}", slice.len())
89///     })
90/// }
91///
92/// let arr = [921, 36, 123, 42, 785];
93///
94/// let x = try_get(&arr, 2).unwrap();
95/// assert_eq!(*x, 123);
96///
97/// let error = try_get(&arr, 9999).unwrap_err();
98/// assert_eq!(
99///     error.to_string(),
100///     "out of bounds access: index is 9999 but length is 5",
101/// );
102/// ```
103pub trait Context<T, E>: sealed::Sealed {
104    /// Add additional, already-computed error context to this result.
105    ///
106    /// Because this method requires that the error context is already computed,
107    /// it should only be used when the `context` is already available or is
108    /// effectively a constant. Otherwise, it effectively forces computation of
109    /// the context, even when we aren't on an error path. The
110    /// [`Context::with_context`](Context::with_context) method is
111    /// preferred in these scenarios, as it lazily computes the error context,
112    /// only doing so when we are actually on an error path.
113    fn context<C>(self, context: C) -> Result<T, Error>
114    where
115        C: fmt::Display + Send + Sync + 'static;
116
117    /// Add additional, lazily-computed error context to this result.
118    ///
119    /// Only invokes `f` to compute the error context when we are actually on an
120    /// error path. Does not invoke `f` if we are not on an error path.
121    fn with_context<C, F>(self, f: F) -> Result<T, Error>
122    where
123        C: fmt::Display + Send + Sync + 'static,
124        F: FnOnce() -> C;
125}
126
127impl<T, E> Context<T, E> for Result<T, E>
128where
129    E: core::error::Error + Send + Sync + 'static,
130{
131    #[inline]
132    fn context<C>(self, context: C) -> Result<T>
133    where
134        C: fmt::Display + Send + Sync + 'static,
135    {
136        match self {
137            Ok(x) => Ok(x),
138            Err(e) => Err(Error::new(e).context(context)),
139        }
140    }
141
142    #[inline]
143    fn with_context<C, F>(self, f: F) -> Result<T>
144    where
145        C: fmt::Display + Send + Sync + 'static,
146        F: FnOnce() -> C,
147    {
148        match self {
149            Ok(x) => Ok(x),
150            Err(e) => Err(Error::new(e).context(f())),
151        }
152    }
153}
154
155impl<T> Context<T, Error> for Result<T> {
156    fn context<C>(self, context: C) -> Result<T, Error>
157    where
158        C: fmt::Display + Send + Sync + 'static,
159    {
160        match self {
161            Ok(x) => Ok(x),
162            Err(e) => Err(e.context(context)),
163        }
164    }
165
166    fn with_context<C, F>(self, f: F) -> Result<T, Error>
167    where
168        C: fmt::Display + Send + Sync + 'static,
169        F: FnOnce() -> C,
170    {
171        match self {
172            Ok(x) => Ok(x),
173            Err(e) => Err(e.context(f())),
174        }
175    }
176}
177
178impl<T> Context<T, core::convert::Infallible> for Option<T> {
179    fn context<C>(self, context: C) -> Result<T>
180    where
181        C: fmt::Display + Send + Sync + 'static,
182    {
183        match self {
184            Some(x) => Ok(x),
185            None => Err(Error::from_error_ext(ContextError {
186                context,
187                error: None,
188            })),
189        }
190    }
191
192    fn with_context<C, F>(self, f: F) -> Result<T>
193    where
194        C: fmt::Display + Send + Sync + 'static,
195        F: FnOnce() -> C,
196    {
197        match self {
198            Some(x) => Ok(x),
199            None => Err(Error::from_error_ext(ContextError {
200                context: f(),
201                error: None,
202            })),
203        }
204    }
205}
206
207// NB: The `repr(C)` is required for safety of the `ErrorExt::ext_is`
208// implementation and the casts that are performed using that method's
209// return value.
210#[repr(C)]
211pub(crate) struct ContextError<C> {
212    // NB: This must be the first field for safety of the `ErrorExt::ext_is`
213    // implementation and the casts that are performed using that method's
214    // return value.
215    pub(crate) context: C,
216
217    pub(crate) error: Option<Error>,
218}
219
220impl<C> fmt::Debug for ContextError<C>
221where
222    C: fmt::Display,
223{
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        fmt::Display::fmt(self, f)
226    }
227}
228
229impl<C> fmt::Display for ContextError<C>
230where
231    C: fmt::Display,
232{
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        self.context.fmt(f)
235    }
236}
237
238impl<C> core::error::Error for ContextError<C>
239where
240    C: fmt::Display + Send + Sync + 'static,
241{
242    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
243        let source = self.ext_source()?;
244        Some(source.inner.as_dyn_core_error())
245    }
246}
247
248unsafe impl<C> ErrorExt for ContextError<C>
249where
250    C: fmt::Display + Send + Sync + 'static,
251{
252    fn ext_as_dyn_core_error(&self) -> &(dyn core::error::Error + Send + Sync + 'static) {
253        self
254    }
255
256    fn ext_into_boxed_dyn_core_error(
257        self,
258    ) -> Result<Box<dyn core::error::Error + Send + Sync + 'static>, OutOfMemory> {
259        let boxed = try_new_uninit_box()?;
260        Ok(Box::write(boxed, self) as _)
261    }
262
263    fn ext_source(&self) -> Option<&Error> {
264        self.error.as_ref()
265    }
266
267    fn ext_source_mut(&mut self) -> Option<&mut Error> {
268        self.error.as_mut()
269    }
270
271    fn ext_is(&self, type_id: TypeId) -> bool {
272        // NB: need to check type id of `C`, not `Self` aka
273        // `ContextError<C>`.
274        type_id == TypeId::of::<C>()
275    }
276
277    unsafe fn ext_move(self, to: NonNull<u8>) {
278        // Safety: implied by this trait method's contract.
279        unsafe {
280            to.cast::<C>().write(self.context);
281        }
282    }
283
284    #[cfg(feature = "backtrace")]
285    fn take_backtrace(&mut self) -> Option<std::backtrace::Backtrace> {
286        self.error.as_mut()?.take_backtrace()
287    }
288
289    #[cfg(feature = "anyhow")]
290    fn ext_into_anyhow(mut self) -> anyhow::Error {
291        match self.error.take() {
292            Some(error) => anyhow::Error::from(error).context(self.context),
293            None => anyhow::Error::msg(self),
294        }
295    }
296}