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
125impl std::error::Error for Bug {
126 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
127 self.0
128 .source
129 .as_deref()
130 .map(|traitobj| traitobj as _ /* cast away Send and Sync */)
131 }
132}
133
134impl Display for Bug {
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 writeln!(
137 f,
138 "{} at {}: {}",
139 self.0.kind, &self.0.location, &self.0.message
140 )?;
141 Display::fmt(&self.0.backtrace, f)?;
142 Ok(())
143 }
144}
145
146/// Create an internal error, including a message like `format!`, and capturing this call site
147///
148/// The calling stack backtrace is also captured,
149/// when the `backtrace` cargo feature this is enabled.
150///
151/// # Examples
152///
153/// ```
154/// use tor_error::internal;
155///
156/// # fn main() -> Result<(), tor_error::Bug> {
157/// # let mut cells = [()].iter();
158/// let need_cell = cells.next().ok_or_else(|| internal!("no cells"))?;
159/// # Ok(())
160/// # }
161/// ```
162//
163// In principle this macro could perhaps support internal!(from=source, "format", ...)
164// but there are alternative ways of writing that:
165// Bug::new_from(source, format!(...)) or
166// into_internal!("format", ...)(source)
167// Those are not so bad for what we think will be the rare cases not
168// covered by internal!(...) or map_err(into_internal!(...))
169#[macro_export]
170macro_rules! internal {
171 { $( $arg:tt )* } => {
172 $crate::Bug::new($crate::ErrorKind::Internal, format!($($arg)*))
173 }
174}
175
176/// Create a bad API usage error, including a message like `format!`, and capturing this call site
177///
178/// The calling stack backtrace is also captured,
179/// when the `backtrace` cargo feature this is enabled.
180///
181/// # Examples
182///
183/// ```
184/// use tor_error::bad_api_usage;
185///
186/// # fn main() -> Result<(), tor_error::Bug> {
187/// # let mut targets = [()].iter();
188/// let need_target = targets.next().ok_or_else(|| bad_api_usage!("no targets"))?;
189/// # Ok(())
190/// # }
191#[macro_export]
192macro_rules! bad_api_usage {
193 { $( $arg:tt )* } => {
194 $crate::Bug::new($crate::ErrorKind::BadApiUsage, format!($($arg)*))
195 }
196}
197
198/// Helper for converting an error into an internal error
199///
200/// Returns a closure implementing `FnOnce(E) -> Bug`.
201/// The source error `E` must be `std::error::Error + Send + Sync + 'static`.
202///
203/// # Examples
204/// ```
205/// use tor_error::into_internal;
206///
207/// # fn main() -> Result<(), tor_error::Bug> {
208/// # let s = b"";
209/// let s = std::str::from_utf8(s).map_err(into_internal!("bad bytes: {:?}", s))?;
210/// # Ok(())
211/// # }
212/// ```
213#[macro_export]
214macro_rules! into_internal {
215 { $( $arg:tt )* } => {
216 std::convert::identity( // Hides the IEFI from clippy::redundant_closure_call
217 |source| $crate::Bug::from_error($crate::ErrorKind::Internal, source, format!($($arg)*))
218 )
219 }
220}
221
222/// Helper for converting an error into an bad API usage error
223///
224/// Returns a closure implementing `FnOnce(E) -> InternalError`.
225/// The source error `E` must be `std::error::Error + Send + Sync + 'static`.
226///
227/// # Examples
228/// ```
229/// use tor_error::into_bad_api_usage;
230///
231/// # fn main() -> Result<(), tor_error::Bug> {
232/// # let host = b"";
233/// let host = std::str::from_utf8(host).map_err(into_bad_api_usage!("hostname is bad UTF-8: {:?}", host))?;
234/// # Ok(())
235/// # }
236/// ```
237#[macro_export]
238macro_rules! into_bad_api_usage {
239 { $( $arg:tt )* } => {
240 std::convert::identity( // Hides the IEFI from clippy::redundant_closure_call
241 |source| $crate::Bug::from_error($crate::ErrorKind::BadApiUsage, source, format!($($arg)*))
242 )
243 }
244}
245
246impl HasKind for Bug {
247 fn kind(&self) -> ErrorKind {
248 self.0.kind
249 }
250}
251
252#[cfg(test)]
253mod test {
254 // @@ begin test lint list maintained by maint/add_warning @@
255 #![allow(clippy::bool_assert_comparison)]
256 #![allow(clippy::clone_on_copy)]
257 #![allow(clippy::dbg_macro)]
258 #![allow(clippy::mixed_attributes_style)]
259 #![allow(clippy::print_stderr)]
260 #![allow(clippy::print_stdout)]
261 #![allow(clippy::single_char_pattern)]
262 #![allow(clippy::unwrap_used)]
263 #![allow(clippy::unchecked_duration_subtraction)]
264 #![allow(clippy::useless_vec)]
265 #![allow(clippy::needless_pass_by_value)]
266 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
267 use super::*;
268
269 // We test this on "important" and "reliable" platforms only.
270 //
271 // This test case mainly is to ensure that we are using the backtrace module correctly, etc.,
272 // which can be checked by doing it on one platform.
273 //
274 // Doing the test on on *all* platforms would simply expose us to the vagaries of platform
275 // backtrace support. Arti ought not to fail its tests just because someone is using a
276 // platform with poor backtrace support.
277 //
278 // On the other hand, we *do* want to know that things are correct on platforms where we think
279 // Rust backtraces work properly.
280 //
281 // So this list is a compromise. See
282 // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/509#note_2803085
283 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
284 #[test]
285 #[inline(never)]
286 fn internal_macro_test() {
287 let start_of_func = line!();
288
289 let e = internal!("Couldn't {} the {}.", "wobble", "wobbling device");
290 assert_eq!(e.0.message, "Couldn't wobble the wobbling device.");
291 assert!(e.0.location.file().ends_with("internal.rs"));
292 assert!(e.0.location.line() > start_of_func);
293 assert!(e.0.source.is_none());
294
295 let s = e.to_string();
296 dbg!(&s);
297
298 assert!(s.starts_with("internal error (bug) at "));
299 assert!(s.contains("Couldn't wobble the wobbling device."));
300 #[cfg(feature = "backtrace")]
301 assert!(s.contains("internal_macro_test"));
302
303 #[derive(thiserror::Error, Debug)]
304 enum Wrap {
305 #[error("Internal error")]
306 Internal(#[from] Bug),
307 }
308
309 let w: Wrap = e.into();
310 let s = format!("Got: {}", w.report());
311 dbg!(&s);
312 assert!(s.contains("Couldn't wobble the wobbling device."));
313 }
314
315 #[test]
316 fn source() {
317 use std::error::Error;
318 use std::str::FromStr;
319
320 let start_of_func = line!();
321 let s = "penguin";
322 let inner = u32::from_str(s).unwrap_err();
323 let outer = u32::from_str(s)
324 .map_err(into_internal!("{} is not a number", s))
325 .unwrap_err();
326
327 let afterwards = line!();
328
329 assert_eq!(outer.source().unwrap().to_string(), inner.to_string());
330 assert!(outer.0.location.line() > start_of_func);
331 assert!(outer.0.location.line() < afterwards);
332 }
333}