tor_error/internal.rs
1//! The InternalError type, macro for generating it, etc.
2
3use std::fmt::{self, Debug, Display};
4use std::panic;
5use std::sync::Arc;
6
7use super::*;
8
9#[cfg(all(feature = "backtrace", not(miri)))]
10/// Backtrace implementation for when the feature is enabled
11mod ie_backtrace {
12 use super::*;
13 use std::backtrace::Backtrace;
14
15 #[derive(Debug, Clone)]
16 /// Captured backtrace, if turned on
17 pub(crate) struct Captured(Arc<Backtrace>);
18
19 /// Capture a backtrace, if turned on
20 pub(crate) fn capture() -> Captured {
21 Captured(Arc::new(Backtrace::force_capture()))
22 }
23
24 impl Display for Captured {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 Display::fmt(&self.0, f)
27 }
28 }
29}
30
31#[cfg(any(not(feature = "backtrace"), miri))]
32/// Backtrace implementation for when the feature is disabled
33mod ie_backtrace {
34 use super::*;
35
36 #[derive(Debug, Clone)]
37 /// "Captured backtrace", but actually nothing
38 pub(crate) struct Captured;
39
40 /// "Capture a backtrace", but actually return nothing
41 pub(crate) fn capture() -> Captured {
42 Captured
43 }
44
45 impl Display for Captured {
46 fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
47 Ok(())
48 }
49 }
50}
51
52#[derive(Debug, Clone)]
53/// Programming error (a bug)
54//
55// Boxed because it is fairly large (>=12 words), and will be in a variant in many other errors.
56//
57// This is a single Bug type containing a kind in BugRepr, rather than separate InternalError and
58// BadApiUsage types, primarily because that means that one Bug(#[from] tor_error::Bug) suffices in
59// every crate's particular error type.
60pub struct Bug(Box<BugRepr>);
61
62/// The source of an Bug
63type SourceError = Arc<dyn std::error::Error + Send + Sync + 'static>;
64
65#[derive(Debug, Clone)]
66/// Internal error (a bug)
67struct BugRepr {
68 /// Message, usually from internal!() like format!
69 message: String,
70
71 /// File and line number
72 location: &'static panic::Location<'static>,
73
74 /// Backtrace, perhaps
75 backtrace: ie_backtrace::Captured,
76
77 /// Source, perhaps
78 source: Option<SourceError>,
79
80 /// Kind
81 ///
82 /// `Internal` or `BadApiUsage`
83 kind: ErrorKind,
84}
85
86impl Bug {
87 /// Create a bug error report capturing this call site and backtrace
88 ///
89 /// Prefer to use [`internal!`],
90 /// as that makes it easy to add additional information
91 /// via format parameters.
92 #[track_caller]
93 pub fn new<S: Into<String>>(kind: ErrorKind, message: S) -> Self {
94 Bug::new_inner(kind, message.into(), None)
95 }
96
97 /// Create an internal error
98 #[track_caller]
99 fn new_inner(kind: ErrorKind, message: String, source: Option<SourceError>) -> Self {
100 Bug(BugRepr {
101 kind,
102 message,
103 source,
104 location: panic::Location::caller(),
105 backtrace: ie_backtrace::capture(),
106 }
107 .into())
108 }
109
110 /// Create an bug error report from another error, capturing this call site and backtrace
111 ///
112 /// In `map_err`, and perhaps elsewhere, prefer to use [`into_internal!`],
113 /// as that makes it easy to add additional information
114 /// via format parameters.
115 #[track_caller]
116 pub fn from_error<E, S>(kind: ErrorKind, source: E, message: S) -> Self
117 where
118 S: Into<String>,
119 E: std::error::Error + Send + Sync + 'static,
120 {
121 Bug::new_inner(kind, message.into(), Some(Arc::new(source)))
122 }
123
124 /// Adds context to an internal error or bug
125 ///
126 /// Prepends `prepend + ": "` to the message.
127 //
128 // The name is by analogy with `anyhow`, and matches the `BugContext` trait
129 // (But distinguished to avoid mental and trait method clashes with `anyhow`
130 // and make things clear at the call site.)
131 pub fn bug_context(mut self, prepend: impl Display) -> Self {
132 self.0.message = format!("{prepend}: {}", self.0.message);
133 self
134 }
135}
136
137impl std::error::Error for Bug {
138 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
139 self.0
140 .source
141 .as_deref()
142 .map(|traitobj| traitobj as _ /* cast away Send and Sync */)
143 }
144}
145
146impl Display for Bug {
147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148 writeln!(
149 f,
150 "{} at {}: {}",
151 self.0.kind, &self.0.location, &self.0.message
152 )?;
153 Display::fmt(&self.0.backtrace, f)?;
154 Ok(())
155 }
156}
157
158/// Extension trait for `.bug_context()` on `Result`
159pub trait BugContext: Sealed {
160 /// Adds context to a internal error or bug
161 ///
162 /// Prepends `prepend + ": "` to the error message, if it's an error (`[Bug]`).
163 fn bug_context<D: Display>(self, prefix: D) -> Self;
164}
165impl<T> BugContext for Result<T, Bug> {
166 fn bug_context<D: Display>(self, prefix: D) -> Self {
167 self.map_err(move |e| e.bug_context(prefix))
168 }
169}
170impl<T> Sealed for Result<T, Bug> {}
171/// Sealed (for `BugContext`)
172// Separate from crate::sealed::Sealed because that has wide blanket impls which we don't want
173mod sealed {
174 /// Sealed
175 pub trait Sealed {}
176}
177use sealed::Sealed;
178
179/// Create an internal error, including a message like `format!`, and capturing this call site
180///
181/// The calling stack backtrace is also captured,
182/// when the `backtrace` cargo feature this is enabled.
183///
184/// # Examples
185///
186/// ```
187/// use tor_error::internal;
188///
189/// # fn main() -> Result<(), tor_error::Bug> {
190/// # let mut cells = [()].iter();
191/// let need_cell = cells.next().ok_or_else(|| internal!("no cells"))?;
192/// # Ok(())
193/// # }
194/// ```
195//
196// In principle this macro could perhaps support internal!(from=source, "format", ...)
197// but there are alternative ways of writing that:
198// Bug::new_from(source, format!(...)) or
199// into_internal!("format", ...)(source)
200// Those are not so bad for what we think will be the rare cases not
201// covered by internal!(...) or map_err(into_internal!(...))
202#[macro_export]
203macro_rules! internal {
204 { $( $arg:tt )* } => {
205 $crate::Bug::new($crate::ErrorKind::Internal, format!($($arg)*))
206 }
207}
208
209/// Create a bad API usage error, including a message like `format!`, and capturing this call site
210///
211/// The calling stack backtrace is also captured,
212/// when the `backtrace` cargo feature this is enabled.
213///
214/// # Examples
215///
216/// ```
217/// use tor_error::bad_api_usage;
218///
219/// # fn main() -> Result<(), tor_error::Bug> {
220/// # let mut targets = [()].iter();
221/// let need_target = targets.next().ok_or_else(|| bad_api_usage!("no targets"))?;
222/// # Ok(())
223/// # }
224#[macro_export]
225macro_rules! bad_api_usage {
226 { $( $arg:tt )* } => {
227 $crate::Bug::new($crate::ErrorKind::BadApiUsage, format!($($arg)*))
228 }
229}
230
231/// Helper for converting an error into an internal error
232///
233/// Returns a closure implementing `FnOnce(E) -> Bug`.
234/// The source error `E` must be `std::error::Error + Send + Sync + 'static`.
235///
236/// # Examples
237/// ```
238/// use tor_error::into_internal;
239///
240/// # fn main() -> Result<(), tor_error::Bug> {
241/// # let s = b"";
242/// let s = std::str::from_utf8(s).map_err(into_internal!("bad bytes: {:?}", s))?;
243/// # Ok(())
244/// # }
245/// ```
246#[macro_export]
247macro_rules! into_internal {
248 { $( $arg:tt )* } => {
249 std::convert::identity( // Hides the IEFI from clippy::redundant_closure_call
250 |source| $crate::Bug::from_error($crate::ErrorKind::Internal, source, format!($($arg)*))
251 )
252 }
253}
254
255/// Helper for converting an error into an bad API usage error
256///
257/// Returns a closure implementing `FnOnce(E) -> InternalError`.
258/// The source error `E` must be `std::error::Error + Send + Sync + 'static`.
259///
260/// # Examples
261/// ```
262/// use tor_error::into_bad_api_usage;
263///
264/// # fn main() -> Result<(), tor_error::Bug> {
265/// # let host = b"";
266/// let host = std::str::from_utf8(host).map_err(into_bad_api_usage!("hostname is bad UTF-8: {:?}", host))?;
267/// # Ok(())
268/// # }
269/// ```
270#[macro_export]
271macro_rules! into_bad_api_usage {
272 { $( $arg:tt )* } => {
273 std::convert::identity( // Hides the IEFI from clippy::redundant_closure_call
274 |source| $crate::Bug::from_error($crate::ErrorKind::BadApiUsage, source, format!($($arg)*))
275 )
276 }
277}
278
279impl HasKind for Bug {
280 fn kind(&self) -> ErrorKind {
281 self.0.kind
282 }
283}
284
285#[cfg(test)]
286mod test {
287 // @@ begin test lint list maintained by maint/add_warning @@
288 #![allow(clippy::bool_assert_comparison)]
289 #![allow(clippy::clone_on_copy)]
290 #![allow(clippy::dbg_macro)]
291 #![allow(clippy::mixed_attributes_style)]
292 #![allow(clippy::print_stderr)]
293 #![allow(clippy::print_stdout)]
294 #![allow(clippy::single_char_pattern)]
295 #![allow(clippy::unwrap_used)]
296 #![allow(clippy::unchecked_time_subtraction)]
297 #![allow(clippy::useless_vec)]
298 #![allow(clippy::needless_pass_by_value)]
299 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
300 use super::*;
301
302 // We test this on "important" and "reliable" platforms only.
303 //
304 // This test case mainly is to ensure that we are using the backtrace module correctly, etc.,
305 // which can be checked by doing it on one platform.
306 //
307 // Doing the test on on *all* platforms would simply expose us to the vagaries of platform
308 // backtrace support. Arti ought not to fail its tests just because someone is using a
309 // platform with poor backtrace support.
310 //
311 // On the other hand, we *do* want to know that things are correct on platforms where we think
312 // Rust backtraces work properly.
313 //
314 // So this list is a compromise. See
315 // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/509#note_2803085
316 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
317 #[test]
318 #[inline(never)]
319 fn internal_macro_test() {
320 let start_of_func = line!();
321
322 let e = internal!("Couldn't {} the {}.", "wobble", "wobbling device");
323 assert_eq!(e.0.message, "Couldn't wobble the wobbling device.");
324 assert!(e.0.location.file().ends_with("internal.rs"));
325 assert!(e.0.location.line() > start_of_func);
326 assert!(e.0.source.is_none());
327
328 let s = e.to_string();
329 dbg!(&s);
330
331 assert!(s.starts_with("internal error (bug) at "));
332 assert!(s.contains("Couldn't wobble the wobbling device."));
333 #[cfg(feature = "backtrace")]
334 assert!(s.contains("internal_macro_test"));
335
336 #[derive(thiserror::Error, Debug)]
337 enum Wrap {
338 #[error("Internal error")]
339 Internal(#[from] Bug),
340 }
341
342 let w: Wrap = e.into();
343 let s = format!("Got: {}", w.report());
344 dbg!(&s);
345 assert!(s.contains("Couldn't wobble the wobbling device."));
346 }
347
348 #[test]
349 fn source() {
350 use std::error::Error;
351 use std::str::FromStr;
352
353 let start_of_func = line!();
354 let s = "penguin";
355 let inner = u32::from_str(s).unwrap_err();
356 let outer = u32::from_str(s)
357 .map_err(into_internal!("{} is not a number", s))
358 .unwrap_err();
359
360 let afterwards = line!();
361
362 assert_eq!(outer.source().unwrap().to_string(), inner.to_string());
363 assert!(outer.0.location.line() > start_of_func);
364 assert!(outer.0.location.line() < afterwards);
365 }
366}