use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, ErrorKind};
use std::panic::PanicInfo;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use termcolor::{Ansi, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
pub use termcolor;
type IOResult<T = ()> = Result<T, std::io::Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Verbosity {
Minimal,
Medium,
Full,
}
impl Verbosity {
pub fn from_env() -> Self {
Self::convert_env(env::var("RUST_BACKTRACE").ok())
}
pub fn lib_from_env() -> Self {
Self::convert_env(
env::var("RUST_LIB_BACKTRACE")
.or_else(|_| env::var("RUST_BACKTRACE"))
.ok(),
)
}
fn convert_env(env: Option<String>) -> Self {
match env {
Some(ref x) if x == "full" => Verbosity::Full,
Some(_) => Verbosity::Medium,
None => Verbosity::Minimal,
}
}
}
pub fn install() {
BacktracePrinter::default().install(default_output_stream());
}
pub fn default_output_stream() -> Box<StandardStream> {
Box::new(StandardStream::stderr(if atty::is(atty::Stream::Stderr) {
ColorChoice::Always
} else {
ColorChoice::Never
}))
}
#[deprecated(
since = "0.4.0",
note = "Use `BacktracePrinter::into_panic_handler()` instead."
)]
pub fn create_panic_handler(
printer: BacktracePrinter,
) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
let out_stream_mutex = Mutex::new(default_output_stream());
Box::new(move |pi| {
let mut lock = out_stream_mutex.lock().unwrap();
if let Err(e) = printer.print_panic_info(pi, &mut *lock) {
eprintln!("Error while printing panic: {:?}", e);
}
})
}
#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::install()` instead.")]
pub fn install_with_settings(printer: BacktracePrinter) {
std::panic::set_hook(printer.into_panic_handler(default_output_stream()))
}
pub type FilterCallback = dyn Fn(&mut Vec<&Frame>) + Send + Sync + 'static;
#[derive(Debug)]
pub struct Frame {
pub n: usize,
pub name: Option<String>,
pub lineno: Option<u32>,
pub filename: Option<PathBuf>,
pub ip: usize,
_private_ctor: (),
}
impl Frame {
fn is_dependency_code(&self) -> bool {
const SYM_PREFIXES: &[&str] = &[
"std::",
"core::",
"backtrace::backtrace::",
"_rust_begin_unwind",
"color_traceback::",
"__rust_",
"___rust_",
"__pthread",
"_main",
"main",
"__scrt_common_main_seh",
"BaseThreadInitThunk",
"_start",
"__libc_start_main",
"start_thread",
];
if let Some(ref name) = self.name {
if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
return true;
}
}
const FILE_PREFIXES: &[&str] = &[
"/rustc/",
"src/libstd/",
"src/libpanic_unwind/",
"src/libtest/",
];
if let Some(ref filename) = self.filename {
let filename = filename.to_string_lossy();
if FILE_PREFIXES.iter().any(|x| filename.starts_with(x))
|| filename.contains("/.cargo/registry/src/")
{
return true;
}
}
false
}
fn is_post_panic_code(&self) -> bool {
const SYM_PREFIXES: &[&str] = &[
"_rust_begin_unwind",
"rust_begin_unwind",
"core::result::unwrap_failed",
"core::option::expect_none_failed",
"core::panicking::panic_fmt",
"color_backtrace::create_panic_handler",
"std::panicking::begin_panic",
"begin_panic_fmt",
"backtrace::capture",
];
match self.name.as_ref() {
Some(name) => SYM_PREFIXES.iter().any(|x| name.starts_with(x)),
None => false,
}
}
fn is_runtime_init_code(&self) -> bool {
const SYM_PREFIXES: &[&str] = &[
"std::rt::lang_start::",
"test::run_test::run_test_inner::",
"std::sys_common::backtrace::__rust_begin_short_backtrace",
];
let (name, file) = match (self.name.as_ref(), self.filename.as_ref()) {
(Some(name), Some(filename)) => (name, filename.to_string_lossy()),
_ => return false,
};
if SYM_PREFIXES.iter().any(|x| name.starts_with(x)) {
return true;
}
if name == "{{closure}}" && file == "src/libtest/lib.rs" {
return true;
}
false
}
fn print_source_if_avail(&self, mut out: impl WriteColor, s: &BacktracePrinter) -> IOResult {
let (lineno, filename) = match (self.lineno, self.filename.as_ref()) {
(Some(a), Some(b)) => (a, b),
_ => return Ok(()),
};
let file = match File::open(filename) {
Ok(file) => file,
Err(ref e) if e.kind() == ErrorKind::NotFound => return Ok(()),
e @ Err(_) => e?,
};
let reader = BufReader::new(file);
let start_line = lineno - 2.min(lineno - 1);
let surrounding_src = reader.lines().skip(start_line as usize - 1).take(5);
for (line, cur_line_no) in surrounding_src.zip(start_line..) {
if cur_line_no == lineno {
out.set_color(&s.colors.selected_src_ln)?;
writeln!(out, "{:>8} > {}", cur_line_no, line?)?;
out.reset()?;
} else {
writeln!(out, "{:>8} │ {}", cur_line_no, line?)?;
}
}
Ok(())
}
#[cfg(all(
feature = "resolve-modules",
unix,
not(any(target_os = "macos", target_os = "ios"))
))]
fn module_info(&self) -> Option<(String, usize)> {
use regex::Regex;
use std::path::Path;
let re = Regex::new(
r"(?x)
^
(?P<start>[0-9a-f]{8,16})
-
(?P<end>[0-9a-f]{8,16})
\s
(?P<perm>[-rwxp]{4})
\s
(?P<offset>[0-9a-f]{8})
\s
[0-9a-f]+:[0-9a-f]+
\s
[0-9]+
\s+
(?P<path>.*)
$
",
)
.unwrap();
let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
for line in BufReader::new(mapsfile).lines() {
let line = line.unwrap();
if let Some(caps) = re.captures(&line) {
let (start, end, path) = (
usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
caps.name("path").unwrap().as_str().to_string(),
);
if self.ip >= start && self.ip < end {
return if let Some(filename) = Path::new(&path).file_name() {
Some((filename.to_str().unwrap().to_string(), start))
} else {
None
};
}
}
}
None
}
#[cfg(not(all(
feature = "resolve-modules",
unix,
not(any(target_os = "macos", target_os = "ios"))
)))]
fn module_info(&self) -> Option<(String, usize)> {
None
}
fn print(&self, i: usize, out: &mut impl WriteColor, s: &BacktracePrinter) -> IOResult {
let is_dependency_code = self.is_dependency_code();
write!(out, "{:>2}: ", i)?;
if s.should_print_addresses() {
if let Some((module_name, module_base)) = self.module_info() {
write!(out, "{}:0x{:08x} - ", module_name, self.ip - module_base)?;
} else {
write!(out, "0x{:016x} - ", self.ip)?;
}
}
let name = self
.name
.as_ref()
.map(|s| s.as_str())
.unwrap_or("<unknown>");
let has_hash_suffix = name.len() > 19
&& &name[name.len() - 19..name.len() - 16] == "::h"
&& name[name.len() - 16..].chars().all(|x| x.is_digit(16));
out.set_color(if is_dependency_code {
&s.colors.dependency_code
} else {
&s.colors.crate_code
})?;
if has_hash_suffix {
write!(out, "{}", &name[..name.len() - 19])?;
if s.strip_function_hash {
writeln!(out)?;
} else {
out.set_color(if is_dependency_code {
&s.colors.dependency_code_hash
} else {
&s.colors.crate_code_hash
})?;
writeln!(out, "{}", &name[name.len() - 19..])?;
}
} else {
writeln!(out, "{}", name)?;
}
out.reset()?;
if let Some(ref file) = self.filename {
let filestr = file.to_str().unwrap_or("<bad utf8>");
let lineno = self
.lineno
.map_or("<unknown line>".to_owned(), |x| x.to_string());
writeln!(out, " at {}:{}", filestr, lineno)?;
} else {
writeln!(out, " at <unknown source file>")?;
}
if s.current_verbosity() >= Verbosity::Full {
self.print_source_if_avail(out, s)?;
}
Ok(())
}
}
pub fn default_frame_filter(frames: &mut Vec<&Frame>) {
let top_cutoff = frames
.iter()
.rposition(|x| x.is_post_panic_code())
.map(|x| x + 2)
.unwrap_or(0);
let bottom_cutoff = frames
.iter()
.position(|x| x.is_runtime_init_code())
.unwrap_or_else(|| frames.len());
let rng = top_cutoff..=bottom_cutoff;
frames.retain(|x| rng.contains(&x.n))
}
#[derive(Debug, Clone)]
pub struct ColorScheme {
pub frames_omitted_msg: ColorSpec,
pub header: ColorSpec,
pub msg_loc_prefix: ColorSpec,
pub src_loc: ColorSpec,
pub src_loc_separator: ColorSpec,
pub env_var: ColorSpec,
pub dependency_code: ColorSpec,
pub dependency_code_hash: ColorSpec,
pub crate_code: ColorSpec,
pub crate_code_hash: ColorSpec,
pub selected_src_ln: ColorSpec,
}
impl ColorScheme {
fn cs(fg: Option<Color>, intense: bool, bold: bool) -> ColorSpec {
let mut cs = ColorSpec::new();
cs.set_fg(fg);
cs.set_bold(bold);
cs.set_intense(intense);
cs
}
pub fn classic() -> Self {
Self {
frames_omitted_msg: Self::cs(Some(Color::Cyan), true, false),
header: Self::cs(Some(Color::Red), false, false),
msg_loc_prefix: Self::cs(Some(Color::Cyan), false, false),
src_loc: Self::cs(Some(Color::Magenta), false, false),
src_loc_separator: Self::cs(Some(Color::White), false, false),
env_var: Self::cs(None, false, true),
dependency_code: Self::cs(Some(Color::Green), false, false),
dependency_code_hash: Self::cs(Some(Color::Black), true, false),
crate_code: Self::cs(Some(Color::Red), true, false),
crate_code_hash: Self::cs(Some(Color::Black), true, false),
selected_src_ln: Self::cs(None, false, true),
}
}
}
impl Default for ColorScheme {
fn default() -> Self {
Self::classic()
}
}
#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter` instead.")]
pub type Settings = BacktracePrinter;
#[derive(Clone)]
pub struct BacktracePrinter {
message: String,
verbosity: Verbosity,
lib_verbosity: Verbosity,
strip_function_hash: bool,
is_panic_handler: bool,
colors: ColorScheme,
filters: Vec<Arc<FilterCallback>>,
should_print_addresses: bool,
}
impl Default for BacktracePrinter {
fn default() -> Self {
Self {
verbosity: Verbosity::from_env(),
lib_verbosity: Verbosity::lib_from_env(),
message: "The application panicked (crashed).".to_owned(),
strip_function_hash: false,
colors: ColorScheme::classic(),
is_panic_handler: false,
filters: vec![Arc::new(default_frame_filter)],
should_print_addresses: false,
}
}
}
impl std::fmt::Debug for BacktracePrinter {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Settings")
.field("message", &self.message)
.field("verbosity", &self.verbosity)
.field("lib_verbosity", &self.lib_verbosity)
.field("strip_function_hash", &self.strip_function_hash)
.field("is_panic_handler", &self.is_panic_handler)
.field("print_addresses", &self.should_print_addresses)
.field("colors", &self.colors)
.finish()
}
}
impl BacktracePrinter {
pub fn new() -> Self {
Self::default()
}
pub fn color_scheme(mut self, colors: ColorScheme) -> Self {
self.colors = colors;
self
}
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
pub fn verbosity(mut self, v: Verbosity) -> Self {
self.verbosity = v;
self
}
pub fn lib_verbosity(mut self, v: Verbosity) -> Self {
self.lib_verbosity = v;
self
}
pub fn strip_function_hash(mut self, strip: bool) -> Self {
self.strip_function_hash = strip;
self
}
pub fn print_addresses(mut self, val: bool) -> Self {
self.should_print_addresses = val;
self
}
pub fn add_frame_filter(mut self, filter: Box<FilterCallback>) -> Self {
self.filters.push(filter.into());
self
}
pub fn clear_frame_filters(mut self) -> Self {
self.filters.clear();
self
}
}
impl BacktracePrinter {
pub fn install(self, out: impl WriteColor + Sync + Send + 'static) {
std::panic::set_hook(self.into_panic_handler(out))
}
pub fn into_panic_handler(
mut self,
out: impl WriteColor + Sync + Send + 'static,
) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
self.is_panic_handler = true;
let out_stream_mutex = Mutex::new(out);
Box::new(move |pi| {
let mut lock = out_stream_mutex.lock().unwrap();
if let Err(e) = self.print_panic_info(pi, &mut *lock) {
eprintln!("Error while printing panic: {:?}", e);
}
})
}
pub fn print_trace(&self, trace: &backtrace::Backtrace, out: &mut impl WriteColor) -> IOResult {
writeln!(out, "{:━^80}", " BACKTRACE ")?;
let frames: Vec<_> = trace
.frames()
.iter()
.flat_map(|frame| frame.symbols().iter().map(move |sym| (frame.ip(), sym)))
.zip(1usize..)
.map(|((ip, sym), n)| Frame {
name: sym.name().map(|x| x.to_string()),
lineno: sym.lineno(),
filename: sym.filename().map(|x| x.into()),
n,
ip: ip as usize,
_private_ctor: (),
})
.collect();
let mut filtered_frames = frames.iter().collect();
match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
Some("1") | Some("on") | Some("y") => (),
_ => {
for filter in &self.filters {
filter(&mut filtered_frames);
}
}
}
if filtered_frames.is_empty() {
return writeln!(out, "<empty backtrace>");
}
filtered_frames.sort_by_key(|x| x.n);
macro_rules! print_hidden {
($n:expr) => {
out.set_color(&self.colors.frames_omitted_msg)?;
let n = $n;
let text = format!(
"{decorator} {n} frame{plural} hidden {decorator}",
n = n,
plural = if n == 1 { "" } else { "s" },
decorator = "⋮",
);
writeln!(out, "{:^80}", text)?;
out.reset()?;
};
}
let mut last_n = 0;
for frame in &filtered_frames {
let frame_delta = frame.n - last_n - 1;
if frame_delta != 0 {
print_hidden!(frame_delta);
}
frame.print(frame.n, out, self)?;
last_n = frame.n;
}
let last_filtered_n = filtered_frames.last().unwrap().n;
let last_unfiltered_n = frames.last().unwrap().n;
if last_filtered_n < last_unfiltered_n {
print_hidden!(last_unfiltered_n - last_filtered_n);
}
Ok(())
}
pub fn format_trace_to_string(&self, trace: &backtrace::Backtrace) -> IOResult<String> {
let mut ansi = Ansi::new(vec![]);
self.print_trace(trace, &mut ansi)?;
Ok(String::from_utf8(ansi.into_inner()).unwrap())
}
pub fn print_panic_info(&self, pi: &PanicInfo, out: &mut impl WriteColor) -> IOResult {
out.set_color(&self.colors.header)?;
writeln!(out, "{}", self.message)?;
out.reset()?;
let payload = pi
.payload()
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| pi.payload().downcast_ref::<&str>().cloned())
.unwrap_or("<non string panic payload>");
write!(out, "Message: ")?;
out.set_color(&self.colors.msg_loc_prefix)?;
writeln!(out, "{}", payload)?;
out.reset()?;
write!(out, "Location: ")?;
if let Some(loc) = pi.location() {
out.set_color(&self.colors.src_loc)?;
write!(out, "{}", loc.file())?;
out.set_color(&self.colors.src_loc_separator)?;
write!(out, ":")?;
out.set_color(&self.colors.src_loc)?;
writeln!(out, "{}", loc.line())?;
out.reset()?;
} else {
writeln!(out, "<unknown>")?;
}
if self.current_verbosity() == Verbosity::Minimal {
write!(out, "\nBacktrace omitted.\n\nRun with ")?;
out.set_color(&self.colors.env_var)?;
write!(out, "RUST_BACKTRACE=1")?;
out.reset()?;
writeln!(out, " environment variable to display it.")?;
} else {
write!(out, "\nRun with ")?;
out.set_color(&self.colors.env_var)?;
write!(out, "COLORBT_SHOW_HIDDEN=1")?;
out.reset()?;
writeln!(out, " environment variable to disable frame filtering.")?;
}
if self.current_verbosity() <= Verbosity::Medium {
write!(out, "Run with ")?;
out.set_color(&self.colors.env_var)?;
write!(out, "RUST_BACKTRACE=full")?;
out.reset()?;
writeln!(out, " to include source snippets.")?;
}
if self.current_verbosity() >= Verbosity::Medium {
self.print_trace(&backtrace::Backtrace::new(), out)?;
}
Ok(())
}
fn current_verbosity(&self) -> Verbosity {
if self.is_panic_handler {
self.verbosity
} else {
self.lib_verbosity
}
}
fn should_print_addresses(&self) -> bool {
self.should_print_addresses
}
}
#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
s.print_trace(trace, &mut default_output_stream())
}
#[deprecated(
since = "0.4.0",
note = "Use `BacktracePrinter::print_panic_info` instead`"
)]
pub fn print_panic_info(pi: &PanicInfo, s: &mut BacktracePrinter) -> IOResult {
s.print_panic_info(pi, &mut default_output_stream())
}