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}