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}