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}