panic_handler/
lib.rs

1// This file is part of Tetcore.
2
3// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Custom panic hook with bug report link
19//!
20//! This crate provides the [`set`] function, which wraps around [`std::panic::set_hook`] and
21//! sets up a panic hook that prints a backtrace and invites the user to open an issue to the
22//! given URL.
23//!
24//! By default, the panic handler aborts the process by calling [`std::process::exit`]. This can
25//! temporarily be disabled by using an [`AbortGuard`].
26
27use 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/// Panic action.
39#[derive(Debug, Clone, Copy, PartialEq)]
40enum OnPanic {
41	/// Abort when panic occurs.
42	Abort,
43	/// Unwind when panic occurs.
44	Unwind,
45	/// Always unwind even if someone changes strategy to Abort afterwards.
46	NeverAbort,
47}
48
49/// Set the panic hook.
50///
51/// Calls [`std::panic::set_hook`] to set up the panic hook.
52///
53/// The `bug_url` parameter is an invitation for users to visit that URL to submit a bug report
54/// in the case where a panic happens.
55pub 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
72/// Set aborting flag. Returns previous value of the flag.
73fn 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
84/// RAII guard for whether panics in the current thread should unwind or abort.
85///
86/// Sets a thread-local abort flag on construction and reverts to the previous setting when dropped.
87/// Does not implement `Send` on purpose.
88///
89/// > **Note**: Because we restore the previous value when dropped, you are encouraged to leave
90/// > the `AbortGuard` on the stack and let it destroy itself naturally.
91pub struct AbortGuard {
92	/// Value that was in `ABORT` before we created this guard.
93	previous_val: OnPanic,
94	/// Marker so that `AbortGuard` doesn't implement `Send`.
95	_not_send: PhantomData<std::rc::Rc<()>>
96}
97
98impl AbortGuard {
99	/// Create a new guard. While the guard is alive, panics that happen in the current thread will
100	/// unwind the stack (unless another guard is created afterwards).
101	pub fn force_unwind() -> AbortGuard {
102		AbortGuard {
103			previous_val: set_abort(OnPanic::Unwind),
104			_not_send: PhantomData
105		}
106	}
107
108	/// Create a new guard. While the guard is alive, panics that happen in the current thread will
109	/// abort the process (unless another guard is created afterwards).
110	pub fn force_abort() -> AbortGuard {
111		AbortGuard {
112			previous_val: set_abort(OnPanic::Abort),
113			_not_send: PhantomData
114		}
115	}
116
117	/// Create a new guard. While the guard is alive, panics that happen in the current thread will
118	/// **never** abort the process (even if `AbortGuard::force_abort()` guard will be created afterwards).
119	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
133/// Function being called when a panic happens.
134fn 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}