Skip to main content

wasmtime_internal_core/error/
context.rs

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