-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement RFC 1328 Custom Panic Handlers #30485
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,10 +15,12 @@ use any::Any; | |
use cell::Cell; | ||
use cell::RefCell; | ||
use intrinsics; | ||
use sync::StaticRwLock; | ||
use sys::stdio::Stderr; | ||
use sys_common::backtrace; | ||
use sys_common::thread_info; | ||
use sys_common::util; | ||
use thread; | ||
|
||
thread_local! { pub static PANIC_COUNT: Cell<usize> = Cell::new(0) } | ||
|
||
|
@@ -28,11 +30,138 @@ thread_local! { | |
} | ||
} | ||
|
||
fn log_panic(obj: &(Any+Send), file: &'static str, line: u32, | ||
log_backtrace: bool) { | ||
let msg = match obj.downcast_ref::<&'static str>() { | ||
#[derive(Copy, Clone)] | ||
enum Handler { | ||
Default, | ||
Custom(*mut (Fn(&PanicInfo) + 'static + Sync + Send)), | ||
} | ||
|
||
static HANDLER_LOCK: StaticRwLock = StaticRwLock::new(); | ||
static mut HANDLER: Handler = Handler::Default; | ||
|
||
/// Registers a custom panic handler, replacing any that was previously | ||
/// registered. | ||
/// | ||
/// The panic handler is invoked when a thread panics, but before it begins | ||
/// unwinding the stack. The default handler prints a message to standard error | ||
/// and generates a backtrace if requested, but this behavior can be customized | ||
/// with the `set_handler` and `take_handler` functions. | ||
/// | ||
/// The handler is provided with a `PanicInfo` struct which contains information | ||
/// about the origin of the panic, including the payload passed to `panic!` and | ||
/// the source code location from which the panic originated. | ||
/// | ||
/// The panic handler is a global resource. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if called from a panicking thread. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub fn set_handler<F>(handler: F) where F: Fn(&PanicInfo) + 'static + Sync + Send { | ||
if thread::panicking() { | ||
panic!("cannot modify the panic handler from a panicking thread"); | ||
} | ||
|
||
let handler = Box::new(handler); | ||
unsafe { | ||
let lock = HANDLER_LOCK.write(); | ||
let old_handler = HANDLER; | ||
HANDLER = Handler::Custom(Box::into_raw(handler)); | ||
drop(lock); | ||
|
||
if let Handler::Custom(ptr) = old_handler { | ||
Box::from_raw(ptr); | ||
} | ||
} | ||
} | ||
|
||
/// Unregisters the current panic handler, returning it. | ||
/// | ||
/// If no custom handler is registered, the default handler will be returned. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if called from a panicking thread. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub fn take_handler() -> Box<Fn(&PanicInfo) + 'static + Sync + Send> { | ||
if thread::panicking() { | ||
panic!("cannot modify the panic handler from a panicking thread"); | ||
} | ||
|
||
unsafe { | ||
let lock = HANDLER_LOCK.write(); | ||
let handler = HANDLER; | ||
HANDLER = Handler::Default; | ||
drop(lock); | ||
|
||
match handler { | ||
Handler::Default => Box::new(default_handler), | ||
Handler::Custom(ptr) => {Box::from_raw(ptr)} // FIXME #30530 | ||
} | ||
} | ||
} | ||
|
||
/// A struct providing information about a panic. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub struct PanicInfo<'a> { | ||
payload: &'a (Any + Send), | ||
location: Location<'a>, | ||
} | ||
|
||
impl<'a> PanicInfo<'a> { | ||
/// Returns the payload associated with the panic. | ||
/// | ||
/// This will commonly, but not always, be a `&'static str` or `String`. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub fn payload(&self) -> &(Any + Send) { | ||
self.payload | ||
} | ||
|
||
/// Returns information about the location from which the panic originated, | ||
/// if available. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this explain a bit more that although There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
/// | ||
/// This method will currently always return `Some`, but this may change | ||
/// in future versions. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub fn location(&self) -> Option<&Location> { | ||
Some(&self.location) | ||
} | ||
} | ||
|
||
/// A struct containing information about the location of a panic. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub struct Location<'a> { | ||
file: &'a str, | ||
line: u32, | ||
} | ||
|
||
impl<'a> Location<'a> { | ||
/// Returns the name of the source file from which the panic originated. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub fn file(&self) -> &str { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In theory this lifetime could be lifted up to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I'm trying to be as conservative as possible for some theoretical future where the location info is pulled out of debuginfo or something like that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense |
||
self.file | ||
} | ||
|
||
/// Returns the line number from which the panic originated. | ||
#[unstable(feature = "panic_handler", reason = "awaiting feedback", issue = "30449")] | ||
pub fn line(&self) -> u32 { | ||
self.line | ||
} | ||
} | ||
|
||
fn default_handler(info: &PanicInfo) { | ||
let panics = PANIC_COUNT.with(|s| s.get()); | ||
|
||
// If this is a double panic, make sure that we print a backtrace | ||
// for this panic. Otherwise only print it if logging is enabled. | ||
let log_backtrace = panics >= 2 || backtrace::log_enabled(); | ||
|
||
let file = info.location.file; | ||
let line = info.location.line; | ||
|
||
let msg = match info.payload.downcast_ref::<&'static str>() { | ||
Some(s) => *s, | ||
None => match obj.downcast_ref::<String>() { | ||
None => match info.payload.downcast_ref::<String>() { | ||
Some(s) => &s[..], | ||
None => "Box<Any>", | ||
} | ||
|
@@ -81,10 +210,21 @@ pub fn on_panic(obj: &(Any+Send), file: &'static str, line: u32) { | |
unsafe { intrinsics::abort() } | ||
} | ||
|
||
// If this is a double panic, make sure that we print a backtrace | ||
// for this panic. Otherwise only print it if logging is enabled. | ||
let log_backtrace = panics >= 2 || backtrace::log_enabled(); | ||
log_panic(obj, file, line, log_backtrace); | ||
let info = PanicInfo { | ||
payload: obj, | ||
location: Location { | ||
file: file, | ||
line: line, | ||
}, | ||
}; | ||
|
||
unsafe { | ||
let _lock = HANDLER_LOCK.read(); | ||
match HANDLER { | ||
Handler::Default => default_handler(&info), | ||
Handler::Custom(ptr) => (*ptr)(&info), | ||
} | ||
} | ||
|
||
if panics >= 2 { | ||
// If a thread panics while it's already unwinding then we | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// error-pattern:greetings from the panic handler | ||
|
||
#![feature(std_panic, panic_handler)] | ||
use std::panic; | ||
use std::io::{self, Write}; | ||
|
||
fn main() { | ||
panic::set_handler(|i| { | ||
write!(io::stderr(), "greetings from the panic handler"); | ||
}); | ||
panic!("foobar"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// error-pattern:thread '<main>' panicked at 'foobar' | ||
|
||
#![feature(std_panic, panic_handler)] | ||
use std::panic; | ||
use std::io::{self, Write}; | ||
|
||
fn main() { | ||
panic::set_handler(|i| { | ||
write!(io::stderr(), "greetings from the panic handler"); | ||
}); | ||
panic::take_handler(); | ||
panic!("foobar"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// error-pattern:thread '<main>' panicked at 'foobar' | ||
|
||
#![feature(std_panic, panic_handler)] | ||
use std::panic; | ||
|
||
fn main() { | ||
panic::take_handler(); | ||
panic!("foobar"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
#![feature(panic_handler, const_fn, std_panic)] | ||
|
||
use std::sync::atomic::{AtomicUsize, Ordering}; | ||
use std::panic; | ||
use std::thread; | ||
|
||
static A: AtomicUsize = AtomicUsize::new(0); | ||
static B: AtomicUsize = AtomicUsize::new(0); | ||
|
||
fn main() { | ||
panic::set_handler(|_| { A.fetch_add(1, Ordering::SeqCst); }); | ||
let handler = panic::take_handler(); | ||
panic::set_handler(move |info| { | ||
B.fetch_add(1, Ordering::SeqCst); | ||
handler(info); | ||
}); | ||
|
||
let _ = thread::spawn(|| { | ||
panic!(); | ||
}).join(); | ||
|
||
assert_eq!(1, A.load(Ordering::SeqCst)); | ||
assert_eq!(1, B.load(Ordering::SeqCst)); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
#![feature(panic_handler, std_panic)] | ||
|
||
use std::panic; | ||
use std::thread; | ||
|
||
fn a() { | ||
panic::set_handler(|_| println!("hello yes this is a")); | ||
panic::take_handler(); | ||
panic::set_handler(|_| println!("hello yes this is a part 2")); | ||
panic::take_handler(); | ||
} | ||
|
||
fn b() { | ||
panic::take_handler(); | ||
panic::take_handler(); | ||
panic::take_handler(); | ||
panic::take_handler(); | ||
panic::take_handler(); | ||
panic!(); | ||
} | ||
|
||
fn c() { | ||
panic::set_handler(|_| ()); | ||
panic::set_handler(|_| ()); | ||
panic::set_handler(|_| ()); | ||
panic::set_handler(|_| ()); | ||
panic::set_handler(|_| ()); | ||
panic::set_handler(|_| ()); | ||
panic!(); | ||
} | ||
|
||
fn main() { | ||
for _ in 0..10 { | ||
let mut handles = vec![]; | ||
for _ in 0..10 { | ||
handles.push(thread::spawn(a)); | ||
} | ||
for _ in 0..10 { | ||
handles.push(thread::spawn(b)); | ||
} | ||
for _ in 0..10 { | ||
handles.push(thread::spawn(c)); | ||
} | ||
for handle in handles { | ||
let _ = handle.join(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
#![feature(panic_handler, const_fn, std_panic)] | ||
|
||
use std::sync::atomic::{AtomicUsize, Ordering}; | ||
use std::panic; | ||
use std::thread; | ||
|
||
static A: AtomicUsize = AtomicUsize::new(0); | ||
|
||
fn main() { | ||
panic::set_handler(|_| ()); | ||
panic::set_handler(|info| { A.fetch_add(1, Ordering::SeqCst); }); | ||
|
||
let _ = thread::spawn(|| { | ||
panic!(); | ||
}).join(); | ||
|
||
assert_eq!(1, A.load(Ordering::SeqCst)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to define these in this module? They seems closely related to it at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to, but the panicking module needs access to all of the internals and the privacy doesn't work in a reasonable way. They could be moved in a world with rust-lang/rfcs#1422.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, that's fine for now