From f1148a540a16ba4ba2635403a9b014da9683851b Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Thu, 17 Dec 2015 23:51:55 -0800 Subject: [PATCH] Implement custom panic handlers --- src/libstd/panic.rs | 2 + src/libstd/panicking.rs | 156 +++++++++++++++++- src/test/run-fail/panic-set-handler.rs | 22 +++ src/test/run-fail/panic-set-unset-handler.rs | 23 +++ src/test/run-fail/panic-take-handler-nop.rs | 19 +++ src/test/run-pass/panic-handler-chain.rs | 33 ++++ .../run-pass/panic-handler-flail-wildly.rs | 57 +++++++ src/test/run-pass/panic-handler-set-twice.rs | 27 +++ 8 files changed, 331 insertions(+), 8 deletions(-) create mode 100644 src/test/run-fail/panic-set-handler.rs create mode 100644 src/test/run-fail/panic-set-unset-handler.rs create mode 100644 src/test/run-fail/panic-take-handler-nop.rs create mode 100644 src/test/run-pass/panic-handler-chain.rs create mode 100644 src/test/run-pass/panic-handler-flail-wildly.rs create mode 100644 src/test/run-pass/panic-handler-set-twice.rs diff --git a/src/libstd/panic.rs b/src/libstd/panic.rs index 6e4ba337b08ee..1550d55d177a2 100644 --- a/src/libstd/panic.rs +++ b/src/libstd/panic.rs @@ -21,6 +21,8 @@ use sync::{Arc, Mutex, RwLock}; use sys_common::unwind; use thread::Result; +pub use panicking::{take_handler, set_handler, PanicInfo, Location}; + /// A marker trait which represents "panic safe" types in Rust. /// /// This trait is implemented by default for many types and behaves similarly in diff --git a/src/libstd/panicking.rs b/src/libstd/panicking.rs index 2b2af350c992c..3f9a1c30ef493 100644 --- a/src/libstd/panicking.rs +++ b/src/libstd/panicking.rs @@ -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 = 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(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 { + 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. + /// + /// 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 { + 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::() { + None => match info.payload.downcast_ref::() { Some(s) => &s[..], None => "Box", } @@ -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 diff --git a/src/test/run-fail/panic-set-handler.rs b/src/test/run-fail/panic-set-handler.rs new file mode 100644 index 0000000000000..bfeb407dd25a4 --- /dev/null +++ b/src/test/run-fail/panic-set-handler.rs @@ -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 or the MIT license +// , 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"); +} diff --git a/src/test/run-fail/panic-set-unset-handler.rs b/src/test/run-fail/panic-set-unset-handler.rs new file mode 100644 index 0000000000000..6999aa715e791 --- /dev/null +++ b/src/test/run-fail/panic-set-unset-handler.rs @@ -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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' 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"); +} diff --git a/src/test/run-fail/panic-take-handler-nop.rs b/src/test/run-fail/panic-take-handler-nop.rs new file mode 100644 index 0000000000000..fec1db24adf09 --- /dev/null +++ b/src/test/run-fail/panic-take-handler-nop.rs @@ -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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// error-pattern:thread '
' panicked at 'foobar' + +#![feature(std_panic, panic_handler)] +use std::panic; + +fn main() { + panic::take_handler(); + panic!("foobar"); +} diff --git a/src/test/run-pass/panic-handler-chain.rs b/src/test/run-pass/panic-handler-chain.rs new file mode 100644 index 0000000000000..1ed592d3d6b92 --- /dev/null +++ b/src/test/run-pass/panic-handler-chain.rs @@ -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 or the MIT license +// , 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)); +} diff --git a/src/test/run-pass/panic-handler-flail-wildly.rs b/src/test/run-pass/panic-handler-flail-wildly.rs new file mode 100644 index 0000000000000..783a44beaf36a --- /dev/null +++ b/src/test/run-pass/panic-handler-flail-wildly.rs @@ -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 or the MIT license +// , 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(); + } + } +} diff --git a/src/test/run-pass/panic-handler-set-twice.rs b/src/test/run-pass/panic-handler-set-twice.rs new file mode 100644 index 0000000000000..edf65e8e2aa69 --- /dev/null +++ b/src/test/run-pass/panic-handler-set-twice.rs @@ -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 or the MIT license +// , 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)); +}