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