use backtrace;
use std::fmt;
use std::fs::File;
use std::io::{BufRead, BufReader, ErrorKind, Write};
use std::panic::PanicInfo;
use std::path::PathBuf;
use std::sync::Mutex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[cfg(feature = "failure-bt")]
pub mod failure;
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 {
match std::env::var("RUST_BACKTRACE") {
Ok(ref x) if x == "full" => Verbosity::Full,
Ok(_) => Verbosity::Medium,
Err(_) => Verbosity::Minimal,
}
}
}
pub fn create_panic_handler(
settings: Settings,
) -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
let settings_mutex = Mutex::new(settings);
Box::new(move |pi| {
let mut settings_lock = settings_mutex.lock().unwrap();
if let Err(e) = print_panic_info(pi, &mut *settings_lock) {
eprintln!("Error while printing panic: {:?}", e);
}
})
}
pub fn install() {
std::panic::set_hook(create_panic_handler(Settings::new()))
}
pub fn install_with_settings(settings: Settings) {
std::panic::set_hook(create_panic_handler(settings))
}
struct Frame {
name: Option<String>,
lineno: Option<u32>,
filename: Option<PathBuf>,
}
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",
"core::result::unwrap_failed",
"core::panicking::panic_fmt",
"color_backtrace::create_panic_handler",
"std::panicking::begin_panic",
"begin_panic_fmt",
"failure::backtrace::Backtrace::new",
"backtrace::capture",
"failure::error_message::err_msg",
"<failure::error::Error as core::convert::From<F>>::from",
];
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::"];
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, s: &mut Settings) -> 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 {
s.out.set_color(&s.colors.selected_src_ln)?;
writeln!(s.out, "{:>8} > {}", cur_line_no, line?)?;
s.out.reset()?;
} else {
writeln!(s.out, "{:>8} │ {}", cur_line_no, line?)?;
}
}
Ok(())
}
fn print(&self, i: usize, s: &mut Settings) -> IOResult {
let is_dependency_code = self.is_dependency_code();
write!(s.out, "{:>2}: ", i)?;
let name = self
.name
.as_ref()
.map(String::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));
s.out.set_color(if is_dependency_code {
&s.colors.dependency_code
} else {
&s.colors.crate_code
})?;
if has_hash_suffix {
write!(s.out, "{}", &name[..name.len() - 19])?;
if s.strip_function_hash {
writeln!(s.out)?;
} else {
s.out.set_color(if is_dependency_code {
&s.colors.dependency_code_hash
} else {
&s.colors.crate_code_hash
})?;
writeln!(s.out, "{}", &name[name.len() - 19..])?;
}
} else {
writeln!(s.out, "{}", name)?;
}
s.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!(s.out, " at {}:{}", filestr, lineno)?;
} else {
writeln!(s.out, " at <unknown source file>")?;
}
if s.verbosity >= Verbosity::Full {
self.print_source_if_avail(s)?;
}
Ok(())
}
}
#[derive(Debug)]
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()
}
}
pub struct Settings {
message: String,
out: Box<dyn WriteColor + Send>,
verbosity: Verbosity,
strip_function_hash: bool,
colors: ColorScheme,
}
impl Default for Settings {
fn default() -> Self {
Self {
verbosity: Verbosity::from_env(),
message: "The application panicked (crashed).".to_owned(),
out: Box::new(StandardStream::stderr(if atty::is(atty::Stream::Stderr) {
ColorChoice::Always
} else {
ColorChoice::Never
})),
strip_function_hash: false,
colors: ColorScheme::classic(),
}
}
}
impl fmt::Debug for Settings {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Settings")
.field("message", &self.message)
.field("verbosity", &self.verbosity)
.field("strip_function_hash_part", &self.strip_function_hash)
.field("colors", &self.colors)
.finish()
}
}
impl Settings {
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 output_stream(mut self, out: Box<dyn WriteColor + Send>) -> Self {
self.out = out;
self
}
pub fn verbosity(mut self, v: Verbosity) -> Self {
self.verbosity = v;
self
}
pub fn strip_function_hash(mut self, strip: bool) -> Self {
self.strip_function_hash = strip;
self
}
}
pub fn print_backtrace(trace: &backtrace::Backtrace, settings: &mut Settings) -> IOResult {
let s = settings;
writeln!(s.out, "{:━^80}", " BACKTRACE ")?;
let frames: Vec<_> = trace
.frames()
.iter()
.flat_map(|frame| frame.symbols())
.map(|sym| Frame {
name: sym.name().map(|x| x.to_string()),
lineno: sym.lineno(),
filename: sym.filename().map(|x| x.into()),
})
.collect();
let top_cutoff = frames
.iter()
.rposition(Frame::is_post_panic_code)
.map(|x| x + 1)
.unwrap_or(0);
let bottom_cutoff = frames
.iter()
.position(Frame::is_runtime_init_code)
.unwrap_or_else(|| frames.len());
if top_cutoff != 0 {
let text = format!("({} post panic frames hidden)", top_cutoff);
s.out.set_color(&s.colors.frames_omitted_msg)?;
writeln!(s.out, "{:^80}", text)?;
s.out.reset()?;
}
let num_frames = frames.len();
let frames = frames
.into_iter()
.skip(top_cutoff)
.take(bottom_cutoff - top_cutoff)
.zip(top_cutoff..);
for (frame, i) in frames {
frame.print(i, s)?;
}
if bottom_cutoff != num_frames {
let text = format!(
"({} runtime init frames hidden)",
num_frames - bottom_cutoff
);
s.out.set_color(&s.colors.frames_omitted_msg)?;
writeln!(s.out, "{:^80}", text)?;
s.out.reset()?;
}
Ok(())
}
pub fn print_panic_info(pi: &PanicInfo, s: &mut Settings) -> IOResult {
s.out.set_color(&s.colors.header)?;
writeln!(s.out, "{}", s.message)?;
s.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!(s.out, "Message: ")?;
s.out.set_color(&s.colors.msg_loc_prefix)?;
writeln!(s.out, "{}", payload)?;
s.out.reset()?;
write!(s.out, "Location: ")?;
if let Some(loc) = pi.location() {
s.out.set_color(&s.colors.src_loc)?;
write!(s.out, "{}", loc.file())?;
s.out.set_color(&s.colors.src_loc_separator)?;
write!(s.out, ":")?;
s.out.set_color(&s.colors.src_loc)?;
writeln!(s.out, "{}", loc.line())?;
s.out.reset()?;
} else {
writeln!(s.out, "<unknown>")?;
}
if s.verbosity == Verbosity::Minimal {
write!(s.out, "\nBacktrace omitted. Run with ")?;
s.out.set_color(&s.colors.env_var)?;
write!(s.out, "RUST_BACKTRACE=1")?;
s.out.reset()?;
writeln!(s.out, " environment variable to display it.")?;
}
if s.verbosity <= Verbosity::Medium {
if s.verbosity == Verbosity::Medium {
writeln!(s.out)?;
}
write!(s.out, "Run with ")?;
s.out.set_color(&s.colors.env_var)?;
write!(s.out, "RUST_BACKTRACE=full")?;
s.out.reset()?;
writeln!(s.out, " to include source snippets.")?;
}
if s.verbosity >= Verbosity::Medium {
print_backtrace(&backtrace::Backtrace::new(), s)?;
}
Ok(())
}