1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use std::cell::UnsafeCell;
use std::env;
use std::ffi::OsString;
use std::fmt;
#[allow(deprecated)] // to allow for older Rust versions (<1.24)
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use std::sync::Mutex;

pub use super::backtrace::Backtrace;

const GENERAL_BACKTRACE: &str = "RUST_BACKTRACE";
const FAILURE_BACKTRACE: &str = "RUST_FAILURE_BACKTRACE";

pub(super) struct InternalBacktrace {
    backtrace: Option<MaybeResolved>,
}

struct MaybeResolved {
    resolved: Mutex<bool>,
    backtrace: UnsafeCell<Backtrace>,
}

unsafe impl Send for MaybeResolved {}
unsafe impl Sync for MaybeResolved {}

impl InternalBacktrace {
    pub(super) fn new() -> InternalBacktrace {
        #[allow(deprecated)] // to allow for older Rust versions (<1.24)
        static ENABLED: AtomicUsize = ATOMIC_USIZE_INIT;

        match ENABLED.load(Ordering::SeqCst) {
            0 => {
                let enabled = is_backtrace_enabled(|var| env::var_os(var));
                ENABLED.store(enabled as usize + 1, Ordering::SeqCst);
                if !enabled {
                    return InternalBacktrace { backtrace: None }
                }
            }
            1 => return InternalBacktrace { backtrace: None },
            _ => {}
        }

        InternalBacktrace {
            backtrace: Some(MaybeResolved {
                resolved: Mutex::new(false),
                backtrace: UnsafeCell::new(Backtrace::new_unresolved()),
            }),
        }
    }

    pub(super) fn none() -> InternalBacktrace {
        InternalBacktrace { backtrace: None }
    }

    pub(super) fn as_backtrace(&self) -> Option<&Backtrace> {
        let bt = match self.backtrace {
            Some(ref bt) => bt,
            None => return None,
        };
        let mut resolved = bt.resolved.lock().unwrap();
        unsafe {
            if !*resolved {
                (*bt.backtrace.get()).resolve();
                *resolved = true;
            }
            Some(&*bt.backtrace.get())
        }
    }

    pub(super) fn is_none(&self) -> bool {
        self.backtrace.is_none()
    }
}

impl fmt::Debug for InternalBacktrace {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("InternalBacktrace")
            .field("backtrace", &self.as_backtrace())
            .finish()
    }
}

fn is_backtrace_enabled<F: Fn(&str) -> Option<OsString>>(get_var: F) -> bool {
    match get_var(FAILURE_BACKTRACE) {
        Some(ref val) if val != "0" => true,
        Some(ref val) if val == "0" => false,
        _ => match get_var(GENERAL_BACKTRACE) {
            Some(ref val) if val != "0" => true,
            _                           => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const YEA: Option<&str> = Some("1");
    const NAY: Option<&str> = Some("0");
    const NOT_SET: Option<&str> = None;

    macro_rules! test_enabled {
        (failure: $failure:ident, general: $general:ident => $result:expr) => {{
            assert_eq!(is_backtrace_enabled(|var| match var {
                FAILURE_BACKTRACE   => $failure.map(OsString::from),
                GENERAL_BACKTRACE   => $general.map(OsString::from),
                _                   => panic!()
            }), $result);
        }}
    }

    #[test]
    fn always_enabled_if_failure_is_set_to_yes() {
        test_enabled!(failure: YEA, general: YEA => true);
        test_enabled!(failure: YEA, general: NOT_SET => true);
        test_enabled!(failure: YEA, general: NAY => true);
    }

    #[test]
    fn never_enabled_if_failure_is_set_to_no() {
        test_enabled!(failure: NAY, general: YEA => false);
        test_enabled!(failure: NAY, general: NOT_SET => false);
        test_enabled!(failure: NAY, general: NAY => false);
    }

    #[test]
    fn follows_general_if_failure_is_not_set() {
        test_enabled!(failure: NOT_SET, general: YEA => true);
        test_enabled!(failure: NOT_SET, general: NOT_SET => false);
        test_enabled!(failure: NOT_SET, general: NAY => false);
    }
}