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}