]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
1a4d82fc | 11 | use prelude::v1::*; |
c34b1796 | 12 | use io::prelude::*; |
1a4d82fc JJ |
13 | |
14 | use any::Any; | |
b039eaaf | 15 | use cell::Cell; |
1a4d82fc | 16 | use cell::RefCell; |
b039eaaf | 17 | use intrinsics; |
9cc50fc6 | 18 | use sync::StaticRwLock; |
7453a54e | 19 | use sync::atomic::{AtomicBool, Ordering}; |
c34b1796 | 20 | use sys::stdio::Stderr; |
e9174d1e | 21 | use sys_common::backtrace; |
d9579d0f | 22 | use sys_common::thread_info; |
b039eaaf | 23 | use sys_common::util; |
9cc50fc6 | 24 | use thread; |
b039eaaf SL |
25 | |
26 | thread_local! { pub static PANIC_COUNT: Cell<usize> = Cell::new(0) } | |
1a4d82fc | 27 | |
1a4d82fc | 28 | thread_local! { |
c34b1796 | 29 | pub static LOCAL_STDERR: RefCell<Option<Box<Write + Send>>> = { |
1a4d82fc JJ |
30 | RefCell::new(None) |
31 | } | |
32 | } | |
33 | ||
9cc50fc6 | 34 | #[derive(Copy, Clone)] |
54a0048b | 35 | enum Hook { |
9cc50fc6 SL |
36 | Default, |
37 | Custom(*mut (Fn(&PanicInfo) + 'static + Sync + Send)), | |
38 | } | |
39 | ||
54a0048b SL |
40 | static HOOK_LOCK: StaticRwLock = StaticRwLock::new(); |
41 | static mut HOOK: Hook = Hook::Default; | |
7453a54e | 42 | static FIRST_PANIC: AtomicBool = AtomicBool::new(true); |
9cc50fc6 | 43 | |
54a0048b | 44 | /// Registers a custom panic hook, replacing any that was previously registered. |
9cc50fc6 | 45 | /// |
54a0048b SL |
46 | /// The panic hook is invoked when a thread panics, but before it begins |
47 | /// unwinding the stack. The default hook prints a message to standard error | |
9cc50fc6 | 48 | /// and generates a backtrace if requested, but this behavior can be customized |
54a0048b | 49 | /// with the `set_hook` and `take_hook` functions. |
9cc50fc6 | 50 | /// |
54a0048b | 51 | /// The hook is provided with a `PanicInfo` struct which contains information |
9cc50fc6 SL |
52 | /// about the origin of the panic, including the payload passed to `panic!` and |
53 | /// the source code location from which the panic originated. | |
54 | /// | |
54a0048b | 55 | /// The panic hook is a global resource. |
9cc50fc6 SL |
56 | /// |
57 | /// # Panics | |
58 | /// | |
59 | /// Panics if called from a panicking thread. | |
60 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
54a0048b | 61 | pub fn set_hook(hook: Box<Fn(&PanicInfo) + 'static + Sync + Send>) { |
9cc50fc6 | 62 | if thread::panicking() { |
54a0048b | 63 | panic!("cannot modify the panic hook from a panicking thread"); |
9cc50fc6 SL |
64 | } |
65 | ||
9cc50fc6 | 66 | unsafe { |
54a0048b SL |
67 | let lock = HOOK_LOCK.write(); |
68 | let old_hook = HOOK; | |
69 | HOOK = Hook::Custom(Box::into_raw(hook)); | |
9cc50fc6 SL |
70 | drop(lock); |
71 | ||
54a0048b | 72 | if let Hook::Custom(ptr) = old_hook { |
9cc50fc6 SL |
73 | Box::from_raw(ptr); |
74 | } | |
75 | } | |
76 | } | |
77 | ||
54a0048b | 78 | /// Unregisters the current panic hook, returning it. |
9cc50fc6 | 79 | /// |
54a0048b | 80 | /// If no custom hook is registered, the default hook will be returned. |
9cc50fc6 SL |
81 | /// |
82 | /// # Panics | |
83 | /// | |
84 | /// Panics if called from a panicking thread. | |
85 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
54a0048b | 86 | pub fn take_hook() -> Box<Fn(&PanicInfo) + 'static + Sync + Send> { |
9cc50fc6 | 87 | if thread::panicking() { |
54a0048b | 88 | panic!("cannot modify the panic hook from a panicking thread"); |
9cc50fc6 SL |
89 | } |
90 | ||
91 | unsafe { | |
54a0048b SL |
92 | let lock = HOOK_LOCK.write(); |
93 | let hook = HOOK; | |
94 | HOOK = Hook::Default; | |
9cc50fc6 SL |
95 | drop(lock); |
96 | ||
54a0048b SL |
97 | match hook { |
98 | Hook::Default => Box::new(default_hook), | |
99 | Hook::Custom(ptr) => {Box::from_raw(ptr)} // FIXME #30530 | |
9cc50fc6 SL |
100 | } |
101 | } | |
102 | } | |
103 | ||
104 | /// A struct providing information about a panic. | |
105 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
106 | pub struct PanicInfo<'a> { | |
107 | payload: &'a (Any + Send), | |
108 | location: Location<'a>, | |
109 | } | |
110 | ||
111 | impl<'a> PanicInfo<'a> { | |
112 | /// Returns the payload associated with the panic. | |
113 | /// | |
114 | /// This will commonly, but not always, be a `&'static str` or `String`. | |
115 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
116 | pub fn payload(&self) -> &(Any + Send) { | |
117 | self.payload | |
118 | } | |
119 | ||
120 | /// Returns information about the location from which the panic originated, | |
121 | /// if available. | |
122 | /// | |
123 | /// This method will currently always return `Some`, but this may change | |
124 | /// in future versions. | |
125 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
126 | pub fn location(&self) -> Option<&Location> { | |
127 | Some(&self.location) | |
128 | } | |
129 | } | |
130 | ||
131 | /// A struct containing information about the location of a panic. | |
132 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
133 | pub struct Location<'a> { | |
134 | file: &'a str, | |
135 | line: u32, | |
136 | } | |
137 | ||
138 | impl<'a> Location<'a> { | |
139 | /// Returns the name of the source file from which the panic originated. | |
140 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
141 | pub fn file(&self) -> &str { | |
142 | self.file | |
143 | } | |
144 | ||
145 | /// Returns the line number from which the panic originated. | |
146 | #[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | |
147 | pub fn line(&self) -> u32 { | |
148 | self.line | |
149 | } | |
150 | } | |
151 | ||
54a0048b | 152 | fn default_hook(info: &PanicInfo) { |
9cc50fc6 SL |
153 | let panics = PANIC_COUNT.with(|s| s.get()); |
154 | ||
155 | // If this is a double panic, make sure that we print a backtrace | |
156 | // for this panic. Otherwise only print it if logging is enabled. | |
157 | let log_backtrace = panics >= 2 || backtrace::log_enabled(); | |
158 | ||
159 | let file = info.location.file; | |
160 | let line = info.location.line; | |
161 | ||
162 | let msg = match info.payload.downcast_ref::<&'static str>() { | |
1a4d82fc | 163 | Some(s) => *s, |
9cc50fc6 | 164 | None => match info.payload.downcast_ref::<String>() { |
85aaf69f | 165 | Some(s) => &s[..], |
1a4d82fc JJ |
166 | None => "Box<Any>", |
167 | } | |
168 | }; | |
62682a34 | 169 | let mut err = Stderr::new().ok(); |
d9579d0f AL |
170 | let thread = thread_info::current_thread(); |
171 | let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>"); | |
b039eaaf SL |
172 | |
173 | let write = |err: &mut ::io::Write| { | |
174 | let _ = writeln!(err, "thread '{}' panicked at '{}', {}:{}", | |
175 | name, msg, file, line); | |
7453a54e | 176 | |
b039eaaf SL |
177 | if log_backtrace { |
178 | let _ = backtrace::write(err); | |
7453a54e SL |
179 | } else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) { |
180 | let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace."); | |
b039eaaf SL |
181 | } |
182 | }; | |
183 | ||
1a4d82fc | 184 | let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take()); |
62682a34 SL |
185 | match (prev, err.as_mut()) { |
186 | (Some(mut stderr), _) => { | |
b039eaaf | 187 | write(&mut *stderr); |
1a4d82fc JJ |
188 | let mut s = Some(stderr); |
189 | LOCAL_STDERR.with(|slot| { | |
190 | *slot.borrow_mut() = s.take(); | |
191 | }); | |
192 | } | |
b039eaaf | 193 | (None, Some(ref mut err)) => { write(err) } |
62682a34 | 194 | _ => {} |
1a4d82fc | 195 | } |
b039eaaf | 196 | } |
1a4d82fc | 197 | |
b039eaaf SL |
198 | pub fn on_panic(obj: &(Any+Send), file: &'static str, line: u32) { |
199 | let panics = PANIC_COUNT.with(|s| { | |
200 | let count = s.get() + 1; | |
201 | s.set(count); | |
202 | count | |
203 | }); | |
204 | ||
205 | // If this is the third nested call, on_panic triggered the last panic, | |
206 | // otherwise the double-panic check would have aborted the process. | |
207 | // Even if it is likely that on_panic was unable to log the backtrace, | |
208 | // abort immediately to avoid infinite recursion, so that attaching a | |
209 | // debugger provides a useable stacktrace. | |
210 | if panics >= 3 { | |
211 | util::dumb_print(format_args!("thread panicked while processing \ | |
9cc50fc6 | 212 | panic. aborting.\n")); |
b039eaaf SL |
213 | unsafe { intrinsics::abort() } |
214 | } | |
215 | ||
9cc50fc6 SL |
216 | let info = PanicInfo { |
217 | payload: obj, | |
218 | location: Location { | |
219 | file: file, | |
220 | line: line, | |
221 | }, | |
222 | }; | |
223 | ||
224 | unsafe { | |
54a0048b SL |
225 | let _lock = HOOK_LOCK.read(); |
226 | match HOOK { | |
227 | Hook::Default => default_hook(&info), | |
228 | Hook::Custom(ptr) => (*ptr)(&info), | |
9cc50fc6 SL |
229 | } |
230 | } | |
b039eaaf SL |
231 | |
232 | if panics >= 2 { | |
233 | // If a thread panics while it's already unwinding then we | |
234 | // have limited options. Currently our preference is to | |
235 | // just abort. In the future we may consider resuming | |
236 | // unwinding or otherwise exiting the thread cleanly. | |
237 | util::dumb_print(format_args!("thread panicked while panicking. \ | |
9cc50fc6 | 238 | aborting.\n")); |
b039eaaf | 239 | unsafe { intrinsics::abort() } |
1a4d82fc JJ |
240 | } |
241 | } |