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}