1use backtrace::Backtrace;
28use std::io::{self, Write};
29use std::marker::PhantomData;
30use std::panic::{self, PanicInfo};
31use std::cell::Cell;
32use std::thread;
33
34thread_local! {
35 static ON_PANIC: Cell<OnPanic> = Cell::new(OnPanic::Abort);
36}
37
38#[derive(Debug, Clone, Copy, PartialEq)]
40enum OnPanic {
41 Abort,
43 Unwind,
45 NeverAbort,
47}
48
49pub fn set(bug_url: &str, version: &str) {
56 panic::set_hook(Box::new({
57 let version = version.to_string();
58 let bug_url = bug_url.to_string();
59 move |c| {
60 panic_hook(c, &bug_url, &version)
61 }
62 }));
63}
64
65macro_rules! ABOUT_PANIC {
66 () => ("
67This is a bug. Please report it at:
68
69 {}
70")}
71
72fn set_abort(on_panic: OnPanic) -> OnPanic {
74 ON_PANIC.with(|val| {
75 let prev = val.get();
76 match prev {
77 OnPanic::Abort | OnPanic::Unwind => val.set(on_panic),
78 OnPanic::NeverAbort => (),
79 }
80 prev
81 })
82}
83
84pub struct AbortGuard {
92 previous_val: OnPanic,
94 _not_send: PhantomData<std::rc::Rc<()>>
96}
97
98impl AbortGuard {
99 pub fn force_unwind() -> AbortGuard {
102 AbortGuard {
103 previous_val: set_abort(OnPanic::Unwind),
104 _not_send: PhantomData
105 }
106 }
107
108 pub fn force_abort() -> AbortGuard {
111 AbortGuard {
112 previous_val: set_abort(OnPanic::Abort),
113 _not_send: PhantomData
114 }
115 }
116
117 pub fn never_abort() -> AbortGuard {
120 AbortGuard {
121 previous_val: set_abort(OnPanic::NeverAbort),
122 _not_send: PhantomData
123 }
124 }
125}
126
127impl Drop for AbortGuard {
128 fn drop(&mut self) {
129 set_abort(self.previous_val);
130 }
131}
132
133fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) {
135 let location = info.location();
136 let file = location.as_ref().map(|l| l.file()).unwrap_or("<unknown>");
137 let line = location.as_ref().map(|l| l.line()).unwrap_or(0);
138
139 let msg = match info.payload().downcast_ref::<&'static str>() {
140 Some(s) => *s,
141 None => match info.payload().downcast_ref::<String>() {
142 Some(s) => &s[..],
143 None => "Box<Any>",
144 }
145 };
146
147 let thread = thread::current();
148 let name = thread.name().unwrap_or("<unnamed>");
149
150 let backtrace = Backtrace::new();
151
152 let mut stderr = io::stderr();
153
154 let _ = writeln!(stderr, "");
155 let _ = writeln!(stderr, "====================");
156 let _ = writeln!(stderr, "");
157 let _ = writeln!(stderr, "Version: {}", version);
158 let _ = writeln!(stderr, "");
159 let _ = writeln!(stderr, "{:?}", backtrace);
160 let _ = writeln!(stderr, "");
161 let _ = writeln!(
162 stderr,
163 "Thread '{}' panicked at '{}', {}:{}",
164 name, msg, file, line
165 );
166
167 let _ = writeln!(stderr, ABOUT_PANIC!(), report_url);
168 ON_PANIC.with(|val| {
169 if val.get() == OnPanic::Abort {
170 ::std::process::exit(1);
171 }
172 })
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn does_not_abort() {
181 set("test", "1.2.3");
182 let _guard = AbortGuard::force_unwind();
183 ::std::panic::catch_unwind(|| panic!()).ok();
184 }
185
186 #[test]
187 fn does_not_abort_after_never_abort() {
188 set("test", "1.2.3");
189 let _guard = AbortGuard::never_abort();
190 let _guard = AbortGuard::force_abort();
191 std::panic::catch_unwind(|| panic!()).ok();
192 }
193}