#[cfg(feature = "backtrace")]
use alloc::sync::Arc;
#[cfg(feature = "backtrace")]
use std::{
backtrace::Backtrace,
env,
sync::atomic::{AtomicU8, Ordering as AtomicOrdering}
};
#[cfg(feature = "backtrace")]
const BACKTRACE_STATE_UNSET: u8 = 0;
#[cfg(feature = "backtrace")]
const BACKTRACE_STATE_ENABLED: u8 = 1;
#[cfg(feature = "backtrace")]
const BACKTRACE_STATE_DISABLED: u8 = 2;
#[cfg(feature = "backtrace")]
static BACKTRACE_STATE: AtomicU8 = AtomicU8::new(BACKTRACE_STATE_UNSET);
#[cfg(feature = "backtrace")]
pub(crate) fn capture_backtrace_snapshot() -> Option<Arc<Backtrace>> {
if should_capture_backtrace() {
Some(Arc::new(Backtrace::capture()))
} else {
None
}
}
#[cfg(feature = "backtrace")]
fn should_capture_backtrace() -> bool {
match BACKTRACE_STATE.load(AtomicOrdering::Acquire) {
BACKTRACE_STATE_ENABLED => true,
BACKTRACE_STATE_DISABLED => false,
_ => {
let enabled = detect_backtrace_preference();
BACKTRACE_STATE.store(
if enabled {
BACKTRACE_STATE_ENABLED
} else {
BACKTRACE_STATE_DISABLED
},
AtomicOrdering::Release
);
enabled
}
}
}
#[cfg(feature = "backtrace")]
fn detect_backtrace_preference() -> bool {
#[cfg(all(test, feature = "backtrace"))]
if let Some(value) = test_backtrace_override::get() {
return value;
}
match env::var_os("RUST_BACKTRACE") {
None => false,
Some(value) => {
let value = value.to_string_lossy();
let trimmed = value.trim();
if trimmed.is_empty() {
return false;
}
let lowered = trimmed.to_ascii_lowercase();
!(matches!(lowered.as_str(), "0" | "off" | "false"))
}
}
}
#[cfg(all(test, feature = "backtrace"))]
pub fn reset_backtrace_preference() {
BACKTRACE_STATE.store(BACKTRACE_STATE_UNSET, AtomicOrdering::Release);
test_backtrace_override::set(None);
}
#[cfg(all(test, feature = "backtrace"))]
pub fn set_backtrace_preference_override(value: Option<bool>) {
test_backtrace_override::set(value);
}
#[cfg(all(test, feature = "backtrace"))]
mod test_backtrace_override {
use std::sync::atomic::{AtomicI8, Ordering};
const OVERRIDE_UNSET: i8 = -1;
const OVERRIDE_DISABLED: i8 = 0;
const OVERRIDE_ENABLED: i8 = 1;
static OVERRIDE_STATE: AtomicI8 = AtomicI8::new(OVERRIDE_UNSET);
pub(super) fn set(value: Option<bool>) {
let state = match value {
Some(true) => OVERRIDE_ENABLED,
Some(false) => OVERRIDE_DISABLED,
None => OVERRIDE_UNSET
};
OVERRIDE_STATE.store(state, Ordering::Release);
}
pub(super) fn get() -> Option<bool> {
match OVERRIDE_STATE.load(Ordering::Acquire) {
OVERRIDE_ENABLED => Some(true),
OVERRIDE_DISABLED => Some(false),
_ => None
}
}
}
#[cfg(all(test, feature = "backtrace"))]
mod tests {
use super::*;
#[test]
fn capture_snapshot_returns_some_when_enabled() {
reset_backtrace_preference();
set_backtrace_preference_override(Some(true));
let result = capture_backtrace_snapshot();
assert!(result.is_some());
reset_backtrace_preference();
}
#[test]
fn capture_snapshot_returns_none_when_disabled() {
reset_backtrace_preference();
set_backtrace_preference_override(Some(false));
let result = capture_backtrace_snapshot();
assert!(result.is_none());
reset_backtrace_preference();
}
#[test]
fn should_capture_returns_true_when_enabled() {
reset_backtrace_preference();
set_backtrace_preference_override(Some(true));
assert!(should_capture_backtrace());
reset_backtrace_preference();
}
#[test]
fn should_capture_caches_enabled_state() {
reset_backtrace_preference();
set_backtrace_preference_override(Some(true));
should_capture_backtrace();
set_backtrace_preference_override(Some(false));
assert!(
should_capture_backtrace(),
"should use cached enabled state"
);
reset_backtrace_preference();
}
#[test]
fn detect_preference_respects_override() {
reset_backtrace_preference();
set_backtrace_preference_override(Some(true));
assert!(detect_backtrace_preference());
set_backtrace_preference_override(Some(false));
assert!(!detect_backtrace_preference());
reset_backtrace_preference();
}
#[test]
fn detect_preference_returns_false_by_default() {
reset_backtrace_preference();
set_backtrace_preference_override(None);
let result = detect_backtrace_preference();
reset_backtrace_preference();
let _ = result;
}
#[test]
fn reset_clears_state_and_override() {
set_backtrace_preference_override(Some(true));
BACKTRACE_STATE.store(BACKTRACE_STATE_ENABLED, AtomicOrdering::Release);
reset_backtrace_preference();
assert_eq!(
BACKTRACE_STATE.load(AtomicOrdering::Acquire),
BACKTRACE_STATE_UNSET
);
assert_eq!(test_backtrace_override::get(), None);
}
}