Skip to main content

wasmtime_internal_error/
context.rs

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