Skip to content
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

Merged
merged 1 commit into from
Dec 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libstd/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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


/// A marker trait which represents "panic safe" types in Rust.
///
/// This trait is implemented by default for many types and behaves similarly in
Expand Down
156 changes: 148 additions & 8 deletions src/libstd/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }

Expand All @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this explain a bit more that although Some is always returned today it's not guaranteed to always be available?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory this lifetime could be lifted up to 'a, but it may be best to stick to this which is conservative

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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>",
}
Expand Down Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions src/test/run-fail/panic-set-handler.rs
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");
}
23 changes: 23 additions & 0 deletions src/test/run-fail/panic-set-unset-handler.rs
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");
}
19 changes: 19 additions & 0 deletions src/test/run-fail/panic-take-handler-nop.rs
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");
}
33 changes: 33 additions & 0 deletions src/test/run-pass/panic-handler-chain.rs
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));
}
57 changes: 57 additions & 0 deletions src/test/run-pass/panic-handler-flail-wildly.rs
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();
}
}
}
27 changes: 27 additions & 0 deletions src/test/run-pass/panic-handler-set-twice.rs
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));
}