use std::backtrace::Backtrace;
use std::backtrace::BacktraceStatus;
use std::io;
use std::panic;
use std::panic::AssertUnwindSafe;
use std::panic::PanicInfo;
use std::panic::UnwindSafe;
use std::sync::Mutex;
use std::thread;
use crate::f;
use crate::file::Context;
use crate::report::Report;
#[allow(clippy::needless_doctest_main)]
pub fn handle<R, Cb>(
ctx: &mut Context,
report: &Report,
options: Options,
callback: Cb,
) -> R
where
Cb: FnOnce(&mut Context) -> R,
Cb: UnwindSafe,
{
static ICE: Mutex<Option<Ice>> = Mutex::new(None);
let options2 = options.clone();
panic::set_hook(Box::new(move |panic| {
*ICE.lock().unwrap() = Some(Ice::generate(panic, options2.clone()));
}));
panic::catch_unwind(AssertUnwindSafe(|| callback(ctx))).unwrap_or_else(|e| {
let ice = ICE
.lock()
.unwrap()
.take()
.unwrap_or_else(|| Ice::with_no_context(options));
ice.report(report);
let _ignored = report.write_out(io::stderr());
panic::resume_unwind(e)
})
}
#[derive(Default)]
pub struct Ice {
what: Option<String>,
where_: Option<(String, Option<String>)>,
why: Option<Backtrace>,
options: Options,
}
#[derive(Default, Clone)]
pub struct Options {
pub show_backtrace: Option<bool>,
pub what_panicked: Option<String>,
pub report_bugs_at: Option<String>,
pub extra_notes: Vec<String>,
}
impl Ice {
pub fn with_no_context(options: Options) -> Self {
Self {
what: None,
where_: None,
why: None,
options,
}
}
pub fn generate(panic: &PanicInfo, options: Options) -> Self {
let msg = panic.payload();
let msg = Option::or(
msg.downcast_ref::<&str>().copied().map(str::to_string),
msg.downcast_ref::<String>().cloned(),
);
let thread = thread::current();
let thread_name = match thread.name() {
Some(name) => name.into(),
_ => format!("{:?}", thread.id()),
};
let location = panic.location().map(ToString::to_string);
let backtrace = if options.show_backtrace.is_none() {
Some(Backtrace::capture())
.filter(|bt| bt.status() == BacktraceStatus::Captured)
} else if options.show_backtrace == Some(true) {
Some(Backtrace::force_capture())
} else {
None
};
Self {
what: msg,
where_: Some((thread_name, location)),
why: backtrace,
options,
}
}
pub fn report(self, report: &Report) {
report.error(f!(
"internal compiler error: {}",
self.what.as_deref().unwrap_or("unknown panic")
));
report.note(f!(
"{} unexpectedly panicked. this is a bug",
self
.options
.what_panicked
.as_deref()
.unwrap_or("the compiler"),
));
if let Some(at) = self.options.report_bugs_at {
report.note(f!("please file a bug at: {at}"));
}
for note in self.options.extra_notes {
report.note(f!("{note}"));
}
if let Some(bt) = self.why {
match self.where_ {
Some((thread, Some(loc))) => {
report.note(f!("thread \"{thread}\" panicked at {loc}\n{bt}"))
}
Some((thread, _)) => {
report.note(f!("thread \"{thread}\" panicked\n{bt}"))
}
None => report.note(f!("backtrace:\n{bt}")),
};
}
}
}