Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/core/src/error/context.rs
3073 views
1
use crate::error::{
2
Error, ErrorExt, OutOfMemory, Result,
3
boxed::try_new_uninit_box,
4
error::{OomOrDynError, OomOrDynErrorMut, OomOrDynErrorRef},
5
};
6
use core::any::TypeId;
7
use core::fmt;
8
use core::ptr::NonNull;
9
use std_alloc::boxed::Box;
10
11
mod 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
/// ```
107
pub 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
131
impl<T, E> Context<T, E> for Result<T, E>
132
where
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
159
impl<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
182
impl<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)]
215
pub(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
224
impl<C> fmt::Debug for ContextError<C>
225
where
226
C: fmt::Display,
227
{
228
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229
fmt::Display::fmt(self, f)
230
}
231
}
232
233
impl<C> fmt::Display for ContextError<C>
234
where
235
C: fmt::Display,
236
{
237
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238
self.context.fmt(f)
239
}
240
}
241
242
impl<C> core::error::Error for ContextError<C>
243
where
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
252
unsafe impl<C> ErrorExt for ContextError<C>
253
where
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
}
307
308