402 lines
12 KiB
Rust
402 lines
12 KiB
Rust
#[cfg(backtrace)]
|
|
pub(crate) use std::backtrace::{Backtrace, BacktraceStatus};
|
|
|
|
#[cfg(all(not(backtrace), feature = "backtrace"))]
|
|
pub(crate) use self::capture::{Backtrace, BacktraceStatus};
|
|
|
|
#[cfg(not(any(backtrace, feature = "backtrace")))]
|
|
pub(crate) enum Backtrace {}
|
|
|
|
#[cfg(backtrace)]
|
|
macro_rules! impl_backtrace {
|
|
() => {
|
|
std::backtrace::Backtrace
|
|
};
|
|
}
|
|
|
|
#[cfg(all(not(backtrace), feature = "backtrace"))]
|
|
macro_rules! impl_backtrace {
|
|
() => {
|
|
impl core::fmt::Debug + core::fmt::Display
|
|
};
|
|
}
|
|
|
|
#[cfg(any(backtrace, feature = "backtrace"))]
|
|
macro_rules! backtrace {
|
|
() => {
|
|
Some(crate::backtrace::Backtrace::capture())
|
|
};
|
|
}
|
|
|
|
#[cfg(not(any(backtrace, feature = "backtrace")))]
|
|
macro_rules! backtrace {
|
|
() => {
|
|
None
|
|
};
|
|
}
|
|
|
|
#[cfg(backtrace)]
|
|
macro_rules! backtrace_if_absent {
|
|
($err:expr) => {
|
|
match $err.backtrace() {
|
|
Some(_) => None,
|
|
None => backtrace!(),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(all(feature = "std", not(backtrace), feature = "backtrace"))]
|
|
macro_rules! backtrace_if_absent {
|
|
($err:expr) => {
|
|
backtrace!()
|
|
};
|
|
}
|
|
|
|
#[cfg(all(feature = "std", not(backtrace), not(feature = "backtrace")))]
|
|
macro_rules! backtrace_if_absent {
|
|
($err:expr) => {
|
|
None
|
|
};
|
|
}
|
|
|
|
#[cfg(all(not(backtrace), feature = "backtrace"))]
|
|
mod capture {
|
|
use backtrace::{BacktraceFmt, BytesOrWideString, Frame, PrintFmt, SymbolName};
|
|
use core::cell::UnsafeCell;
|
|
use core::fmt::{self, Debug, Display};
|
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::borrow::Cow;
|
|
use std::env;
|
|
use std::path::{self, Path, PathBuf};
|
|
use std::sync::Once;
|
|
|
|
pub(crate) struct Backtrace {
|
|
inner: Inner,
|
|
}
|
|
|
|
pub(crate) enum BacktraceStatus {
|
|
Unsupported,
|
|
Disabled,
|
|
Captured,
|
|
}
|
|
|
|
enum Inner {
|
|
Unsupported,
|
|
Disabled,
|
|
Captured(LazilyResolvedCapture),
|
|
}
|
|
|
|
struct Capture {
|
|
actual_start: usize,
|
|
resolved: bool,
|
|
frames: Vec<BacktraceFrame>,
|
|
}
|
|
|
|
struct BacktraceFrame {
|
|
frame: Frame,
|
|
symbols: Vec<BacktraceSymbol>,
|
|
}
|
|
|
|
struct BacktraceSymbol {
|
|
name: Option<Vec<u8>>,
|
|
filename: Option<BytesOrWide>,
|
|
lineno: Option<u32>,
|
|
colno: Option<u32>,
|
|
}
|
|
|
|
enum BytesOrWide {
|
|
Bytes(Vec<u8>),
|
|
Wide(Vec<u16>),
|
|
}
|
|
|
|
impl Debug for Backtrace {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
let capture = match &self.inner {
|
|
Inner::Unsupported => return fmt.write_str("<unsupported>"),
|
|
Inner::Disabled => return fmt.write_str("<disabled>"),
|
|
Inner::Captured(c) => c.force(),
|
|
};
|
|
|
|
let frames = &capture.frames[capture.actual_start..];
|
|
|
|
write!(fmt, "Backtrace ")?;
|
|
|
|
let mut dbg = fmt.debug_list();
|
|
|
|
for frame in frames {
|
|
if frame.frame.ip().is_null() {
|
|
continue;
|
|
}
|
|
|
|
dbg.entries(&frame.symbols);
|
|
}
|
|
|
|
dbg.finish()
|
|
}
|
|
}
|
|
|
|
impl Debug for BacktraceFrame {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
let mut dbg = fmt.debug_list();
|
|
dbg.entries(&self.symbols);
|
|
dbg.finish()
|
|
}
|
|
}
|
|
|
|
impl Debug for BacktraceSymbol {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(fmt, "{{ ")?;
|
|
|
|
if let Some(fn_name) = self.name.as_ref().map(|b| SymbolName::new(b)) {
|
|
write!(fmt, "fn: \"{:#}\"", fn_name)?;
|
|
} else {
|
|
write!(fmt, "fn: <unknown>")?;
|
|
}
|
|
|
|
if let Some(fname) = self.filename.as_ref() {
|
|
write!(fmt, ", file: \"{:?}\"", fname)?;
|
|
}
|
|
|
|
if let Some(line) = self.lineno {
|
|
write!(fmt, ", line: {:?}", line)?;
|
|
}
|
|
|
|
write!(fmt, " }}")
|
|
}
|
|
}
|
|
|
|
impl Debug for BytesOrWide {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
output_filename(
|
|
fmt,
|
|
match self {
|
|
BytesOrWide::Bytes(w) => BytesOrWideString::Bytes(w),
|
|
BytesOrWide::Wide(w) => BytesOrWideString::Wide(w),
|
|
},
|
|
PrintFmt::Short,
|
|
env::current_dir().as_ref().ok(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Backtrace {
|
|
fn enabled() -> bool {
|
|
static ENABLED: AtomicUsize = AtomicUsize::new(0);
|
|
match ENABLED.load(Ordering::Relaxed) {
|
|
0 => {}
|
|
1 => return false,
|
|
_ => return true,
|
|
}
|
|
let enabled = match env::var_os("RUST_LIB_BACKTRACE") {
|
|
Some(s) => s != "0",
|
|
None => match env::var_os("RUST_BACKTRACE") {
|
|
Some(s) => s != "0",
|
|
None => false,
|
|
},
|
|
};
|
|
ENABLED.store(enabled as usize + 1, Ordering::Relaxed);
|
|
enabled
|
|
}
|
|
|
|
#[inline(never)] // want to make sure there's a frame here to remove
|
|
pub(crate) fn capture() -> Backtrace {
|
|
if Backtrace::enabled() {
|
|
Backtrace::create(Backtrace::capture as usize)
|
|
} else {
|
|
let inner = Inner::Disabled;
|
|
Backtrace { inner }
|
|
}
|
|
}
|
|
|
|
// Capture a backtrace which starts just before the function addressed
|
|
// by `ip`
|
|
fn create(ip: usize) -> Backtrace {
|
|
let mut frames = Vec::new();
|
|
let mut actual_start = None;
|
|
backtrace::trace(|frame| {
|
|
frames.push(BacktraceFrame {
|
|
frame: frame.clone(),
|
|
symbols: Vec::new(),
|
|
});
|
|
if frame.symbol_address() as usize == ip && actual_start.is_none() {
|
|
actual_start = Some(frames.len() + 1);
|
|
}
|
|
true
|
|
});
|
|
|
|
// If no frames came out assume that this is an unsupported platform
|
|
// since `backtrace` doesn't provide a way of learning this right
|
|
// now, and this should be a good enough approximation.
|
|
let inner = if frames.is_empty() {
|
|
Inner::Unsupported
|
|
} else {
|
|
Inner::Captured(LazilyResolvedCapture::new(Capture {
|
|
actual_start: actual_start.unwrap_or(0),
|
|
frames,
|
|
resolved: false,
|
|
}))
|
|
};
|
|
|
|
Backtrace { inner }
|
|
}
|
|
|
|
pub(crate) fn status(&self) -> BacktraceStatus {
|
|
match self.inner {
|
|
Inner::Unsupported => BacktraceStatus::Unsupported,
|
|
Inner::Disabled => BacktraceStatus::Disabled,
|
|
Inner::Captured(_) => BacktraceStatus::Captured,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for Backtrace {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
let capture = match &self.inner {
|
|
Inner::Unsupported => return fmt.write_str("unsupported backtrace"),
|
|
Inner::Disabled => return fmt.write_str("disabled backtrace"),
|
|
Inner::Captured(c) => c.force(),
|
|
};
|
|
|
|
let full = fmt.alternate();
|
|
let (frames, style) = if full {
|
|
(&capture.frames[..], PrintFmt::Full)
|
|
} else {
|
|
(&capture.frames[capture.actual_start..], PrintFmt::Short)
|
|
};
|
|
|
|
// When printing paths we try to strip the cwd if it exists,
|
|
// otherwise we just print the path as-is. Note that we also only do
|
|
// this for the short format, because if it's full we presumably
|
|
// want to print everything.
|
|
let cwd = env::current_dir();
|
|
let mut print_path = move |fmt: &mut fmt::Formatter, path: BytesOrWideString| {
|
|
output_filename(fmt, path, style, cwd.as_ref().ok())
|
|
};
|
|
|
|
let mut f = BacktraceFmt::new(fmt, style, &mut print_path);
|
|
f.add_context()?;
|
|
for frame in frames {
|
|
let mut f = f.frame();
|
|
if frame.symbols.is_empty() {
|
|
f.print_raw(frame.frame.ip(), None, None, None)?;
|
|
} else {
|
|
for symbol in frame.symbols.iter() {
|
|
f.print_raw_with_column(
|
|
frame.frame.ip(),
|
|
symbol.name.as_ref().map(|b| SymbolName::new(b)),
|
|
symbol.filename.as_ref().map(|b| match b {
|
|
BytesOrWide::Bytes(w) => BytesOrWideString::Bytes(w),
|
|
BytesOrWide::Wide(w) => BytesOrWideString::Wide(w),
|
|
}),
|
|
symbol.lineno,
|
|
symbol.colno,
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
f.finish()?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct LazilyResolvedCapture {
|
|
sync: Once,
|
|
capture: UnsafeCell<Capture>,
|
|
}
|
|
|
|
impl LazilyResolvedCapture {
|
|
fn new(capture: Capture) -> Self {
|
|
LazilyResolvedCapture {
|
|
sync: Once::new(),
|
|
capture: UnsafeCell::new(capture),
|
|
}
|
|
}
|
|
|
|
fn force(&self) -> &Capture {
|
|
self.sync.call_once(|| {
|
|
// Safety: This exclusive reference can't overlap with any
|
|
// others. `Once` guarantees callers will block until this
|
|
// closure returns. `Once` also guarantees only a single caller
|
|
// will enter this closure.
|
|
unsafe { &mut *self.capture.get() }.resolve();
|
|
});
|
|
|
|
// Safety: This shared reference can't overlap with the exclusive
|
|
// reference above.
|
|
unsafe { &*self.capture.get() }
|
|
}
|
|
}
|
|
|
|
// Safety: Access to the inner value is synchronized using a thread-safe
|
|
// `Once`. So long as `Capture` is `Sync`, `LazilyResolvedCapture` is too
|
|
unsafe impl Sync for LazilyResolvedCapture where Capture: Sync {}
|
|
|
|
impl Capture {
|
|
fn resolve(&mut self) {
|
|
// If we're already resolved, nothing to do!
|
|
if self.resolved {
|
|
return;
|
|
}
|
|
self.resolved = true;
|
|
|
|
for frame in self.frames.iter_mut() {
|
|
let symbols = &mut frame.symbols;
|
|
let frame = &frame.frame;
|
|
backtrace::resolve_frame(frame, |symbol| {
|
|
symbols.push(BacktraceSymbol {
|
|
name: symbol.name().map(|m| m.as_bytes().to_vec()),
|
|
filename: symbol.filename_raw().map(|b| match b {
|
|
BytesOrWideString::Bytes(b) => BytesOrWide::Bytes(b.to_owned()),
|
|
BytesOrWideString::Wide(b) => BytesOrWide::Wide(b.to_owned()),
|
|
}),
|
|
lineno: symbol.lineno(),
|
|
colno: symbol.colno(),
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prints the filename of the backtrace frame.
|
|
fn output_filename(
|
|
fmt: &mut fmt::Formatter,
|
|
bows: BytesOrWideString,
|
|
print_fmt: PrintFmt,
|
|
cwd: Option<&PathBuf>,
|
|
) -> fmt::Result {
|
|
let file: Cow<Path> = match bows {
|
|
#[cfg(unix)]
|
|
BytesOrWideString::Bytes(bytes) => {
|
|
use std::os::unix::ffi::OsStrExt;
|
|
Path::new(std::ffi::OsStr::from_bytes(bytes)).into()
|
|
}
|
|
#[cfg(not(unix))]
|
|
BytesOrWideString::Bytes(bytes) => {
|
|
Path::new(std::str::from_utf8(bytes).unwrap_or("<unknown>")).into()
|
|
}
|
|
#[cfg(windows)]
|
|
BytesOrWideString::Wide(wide) => {
|
|
use std::os::windows::ffi::OsStringExt;
|
|
Cow::Owned(std::ffi::OsString::from_wide(wide).into())
|
|
}
|
|
#[cfg(not(windows))]
|
|
BytesOrWideString::Wide(_wide) => Path::new("<unknown>").into(),
|
|
};
|
|
if print_fmt == PrintFmt::Short && file.is_absolute() {
|
|
if let Some(cwd) = cwd {
|
|
if let Ok(stripped) = file.strip_prefix(&cwd) {
|
|
if let Some(s) = stripped.to_str() {
|
|
return write!(fmt, ".{}{}", path::MAIN_SEPARATOR, s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Display::fmt(&file.display(), fmt)
|
|
}
|
|
}
|
|
|
|
fn _assert_send_sync() {
|
|
fn _assert<T: Send + Sync>() {}
|
|
_assert::<Backtrace>();
|
|
}
|