1
Fork 0

Auto merge of #64154 - alexcrichton:std-backtrace, r=sfackler

std: Add a `backtrace` module

This commit adds a `backtrace` module to the standard library, as
designed in [RFC 2504]. The `Backtrace` type is intentionally very
conservative, effectively only allowing capturing it and printing it.

Additionally this commit also adds a `backtrace` method to the `Error`
trait which defaults to returning `None`, as specified in [RFC 2504].
More information about the design here can be found in [RFC 2504] and in
the [tracking issue].

Implementation-wise this is all based on the `backtrace` crate and very
closely mirrors the `backtrace::Backtrace` type on crates.io. Otherwise
it's pretty standard in how it handles everything internally.

[RFC 2504]: https://github.com/rust-lang/rfcs/blob/master/text/2504-fix-error.md
[tracking issue]: https://github.com/rust-lang/rust/issues/53487

cc #53487
This commit is contained in:
bors 2019-09-11 14:46:08 +00:00
commit fe6d05a8b3
5 changed files with 508 additions and 54 deletions

View file

@ -4,8 +4,9 @@
use crate::env;
use crate::fmt;
use crate::io;
use crate::borrow::Cow;
use crate::io::prelude::*;
use crate::path::{self, Path};
use crate::path::{self, Path, PathBuf};
use crate::sync::atomic::{self, Ordering};
use crate::sys::mutex::Mutex;
@ -14,10 +15,26 @@ use backtrace::{BacktraceFmt, BytesOrWideString, PrintFmt};
/// Max number of frames to print.
const MAX_NB_FRAMES: usize = 100;
/// Prints the current backtrace.
pub fn print(w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
pub fn lock() -> impl Drop {
struct Guard;
static LOCK: Mutex = Mutex::new();
impl Drop for Guard {
fn drop(&mut self) {
unsafe {
LOCK.unlock();
}
}
}
unsafe {
LOCK.lock();
return Guard;
}
}
/// Prints the current backtrace.
pub fn print(w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
// There are issues currently linking libbacktrace into tests, and in
// general during libstd's own unit tests we're not testing this path. In
// test mode immediately return here to optimize away any references to the
@ -29,71 +46,67 @@ pub fn print(w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
// Use a lock to prevent mixed output in multithreading context.
// Some platforms also requires it, like `SymFromAddr` on Windows.
unsafe {
LOCK.lock();
let res = _print(w, format);
LOCK.unlock();
res
let _lock = lock();
_print(w, format)
}
}
fn _print(w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
unsafe fn _print(w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
struct DisplayBacktrace {
format: PrintFmt,
}
impl fmt::Display for DisplayBacktrace {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
_print_fmt(fmt, self.format)
unsafe {
_print_fmt(fmt, self.format)
}
}
}
write!(w, "{}", DisplayBacktrace { format })
}
fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::Result {
unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::Result {
let cwd = env::current_dir().ok();
let mut print_path = move |fmt: &mut fmt::Formatter<'_>, bows: BytesOrWideString<'_>| {
output_filename(fmt, bows, print_fmt)
output_filename(fmt, bows, print_fmt, cwd.as_ref())
};
let mut bt_fmt = BacktraceFmt::new(fmt, print_fmt, &mut print_path);
bt_fmt.add_context()?;
let mut skipped = false;
unsafe {
let mut idx = 0;
let mut res = Ok(());
backtrace::trace_unsynchronized(|frame| {
if print_fmt == PrintFmt::Short && idx > MAX_NB_FRAMES {
skipped = true;
return false;
}
let mut idx = 0;
let mut res = Ok(());
backtrace::trace_unsynchronized(|frame| {
if print_fmt == PrintFmt::Short && idx > MAX_NB_FRAMES {
return false;
}
let mut hit = false;
let mut stop = false;
backtrace::resolve_frame_unsynchronized(frame, |symbol| {
hit = true;
if print_fmt == PrintFmt::Short {
if let Some(sym) = symbol.name().and_then(|s| s.as_str()) {
if sym.contains("__rust_begin_short_backtrace") {
skipped = true;
stop = true;
return;
}
let mut hit = false;
let mut stop = false;
backtrace::resolve_frame_unsynchronized(frame, |symbol| {
hit = true;
if print_fmt == PrintFmt::Short {
if let Some(sym) = symbol.name().and_then(|s| s.as_str()) {
if sym.contains("__rust_begin_short_backtrace") {
stop = true;
return;
}
}
res = bt_fmt.frame().symbol(frame, symbol);
});
if stop {
return false;
}
if !hit {
res = bt_fmt.frame().print_raw(frame.ip(), None, None, None);
}
idx += 1;
res.is_ok()
res = bt_fmt.frame().symbol(frame, symbol);
});
res?;
}
if stop {
return false;
}
if !hit {
res = bt_fmt.frame().print_raw(frame.ip(), None, None, None);
}
idx += 1;
res.is_ok()
});
res?;
bt_fmt.finish()?;
if skipped {
if print_fmt == PrintFmt::Short {
writeln!(
fmt,
"note: Some details are omitted, \
@ -153,36 +166,34 @@ pub fn log_enabled() -> Option<PrintFmt> {
/// Prints the filename of the backtrace frame.
///
/// See also `output`.
fn output_filename(
pub fn output_filename(
fmt: &mut fmt::Formatter<'_>,
bows: BytesOrWideString<'_>,
print_fmt: PrintFmt,
cwd: Option<&PathBuf>,
) -> fmt::Result {
#[cfg(windows)]
let path_buf;
let file = match bows {
let file: Cow<'_, Path> = match bows {
#[cfg(unix)]
BytesOrWideString::Bytes(bytes) => {
use crate::os::unix::prelude::*;
Path::new(crate::ffi::OsStr::from_bytes(bytes))
Path::new(crate::ffi::OsStr::from_bytes(bytes)).into()
}
#[cfg(not(unix))]
BytesOrWideString::Bytes(bytes) => {
Path::new(crate::str::from_utf8(bytes).unwrap_or("<unknown>"))
Path::new(crate::str::from_utf8(bytes).unwrap_or("<unknown>")).into()
}
#[cfg(windows)]
BytesOrWideString::Wide(wide) => {
use crate::os::windows::prelude::*;
path_buf = crate::ffi::OsString::from_wide(wide);
Path::new(&path_buf)
Cow::Owned(crate::ffi::OsString::from_wide(wide).into())
}
#[cfg(not(windows))]
BytesOrWideString::Wide(_wide) => {
Path::new("<unknown>")
Path::new("<unknown>").into()
}
};
if print_fmt == PrintFmt::Short && file.is_absolute() {
if let Ok(cwd) = env::current_dir() {
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);