alt_failure/backtrace/
internal.rs

1use std::cell::UnsafeCell;
2use std::env;
3use std::ffi::OsString;
4use std::fmt;
5#[allow(deprecated)] // to allow for older Rust versions (<1.24)
6use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
7use std::sync::Mutex;
8
9pub use super::backtrace::Backtrace;
10
11const GENERAL_BACKTRACE: &str = "RUST_BACKTRACE";
12const FAILURE_BACKTRACE: &str = "RUST_FAILURE_BACKTRACE";
13
14pub(super) struct InternalBacktrace {
15    backtrace: Option<MaybeResolved>,
16}
17
18struct MaybeResolved {
19    resolved: Mutex<bool>,
20    backtrace: UnsafeCell<Backtrace>,
21}
22
23unsafe impl Send for MaybeResolved {}
24unsafe impl Sync for MaybeResolved {}
25
26impl InternalBacktrace {
27    pub(super) fn new() -> InternalBacktrace {
28        #[allow(deprecated)] // to allow for older Rust versions (<1.24)
29        static ENABLED: AtomicUsize = ATOMIC_USIZE_INIT;
30
31        match ENABLED.load(Ordering::SeqCst) {
32            0 => {
33                let enabled = is_backtrace_enabled(|var| env::var_os(var));
34                ENABLED.store(enabled as usize + 1, Ordering::SeqCst);
35                if !enabled {
36                    return InternalBacktrace { backtrace: None }
37                }
38            }
39            1 => return InternalBacktrace { backtrace: None },
40            _ => {}
41        }
42
43        InternalBacktrace {
44            backtrace: Some(MaybeResolved {
45                resolved: Mutex::new(false),
46                backtrace: UnsafeCell::new(Backtrace::new_unresolved()),
47            }),
48        }
49    }
50
51    pub(super) fn none() -> InternalBacktrace {
52        InternalBacktrace { backtrace: None }
53    }
54
55    pub(super) fn as_backtrace(&self) -> Option<&Backtrace> {
56        let bt = match self.backtrace {
57            Some(ref bt) => bt,
58            None => return None,
59        };
60        let mut resolved = bt.resolved.lock().unwrap();
61        unsafe {
62            if !*resolved {
63                (*bt.backtrace.get()).resolve();
64                *resolved = true;
65            }
66            Some(&*bt.backtrace.get())
67        }
68    }
69
70    pub(super) fn is_none(&self) -> bool {
71        self.backtrace.is_none()
72    }
73}
74
75impl fmt::Debug for InternalBacktrace {
76    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77        f.debug_struct("InternalBacktrace")
78            .field("backtrace", &self.as_backtrace())
79            .finish()
80    }
81}
82
83fn is_backtrace_enabled<F: Fn(&str) -> Option<OsString>>(get_var: F) -> bool {
84    match get_var(FAILURE_BACKTRACE) {
85        Some(ref val) if val != "0" => true,
86        Some(ref val) if val == "0" => false,
87        _ => match get_var(GENERAL_BACKTRACE) {
88            Some(ref val) if val != "0" => true,
89            _                           => false,
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    const YEA: Option<&str> = Some("1");
99    const NAY: Option<&str> = Some("0");
100    const NOT_SET: Option<&str> = None;
101
102    macro_rules! test_enabled {
103        (failure: $failure:ident, general: $general:ident => $result:expr) => {{
104            assert_eq!(is_backtrace_enabled(|var| match var {
105                FAILURE_BACKTRACE   => $failure.map(OsString::from),
106                GENERAL_BACKTRACE   => $general.map(OsString::from),
107                _                   => panic!()
108            }), $result);
109        }}
110    }
111
112    #[test]
113    fn always_enabled_if_failure_is_set_to_yes() {
114        test_enabled!(failure: YEA, general: YEA => true);
115        test_enabled!(failure: YEA, general: NOT_SET => true);
116        test_enabled!(failure: YEA, general: NAY => true);
117    }
118
119    #[test]
120    fn never_enabled_if_failure_is_set_to_no() {
121        test_enabled!(failure: NAY, general: YEA => false);
122        test_enabled!(failure: NAY, general: NOT_SET => false);
123        test_enabled!(failure: NAY, general: NAY => false);
124    }
125
126    #[test]
127    fn follows_general_if_failure_is_not_set() {
128        test_enabled!(failure: NOT_SET, general: YEA => true);
129        test_enabled!(failure: NOT_SET, general: NOT_SET => false);
130        test_enabled!(failure: NOT_SET, general: NAY => false);
131    }
132}