Files
addr2line
adler
aho_corasick
backtrace
bitflags
cfg_if
ctrlc
dirs
dirs_next
dirs_sys
dirs_sys_next
docopt
endian_type
failure
failure_derive
fd_lock
gimli
lalrpop_util
lazy_static
libc
log
memchr
memoffset
miniz_oxide
nibble_vec
nix
numtoa
object
oursh
proc_macro2
pwd
quote
radix_trie
regex
regex_syntax
retain_mut
rustc_demangle
rustyline
serde
serde_derive
smallvec
strsim
syn
synstructure
termion
unicode_segmentation
unicode_width
unicode_xid
utf8parse
  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);
    }
}