]> git.lizzy.rs Git - rust.git/commitdiff
Implement custom panic handlers
authorSteven Fackler <sfackler@gmail.com>
Fri, 18 Dec 2015 07:51:55 +0000 (23:51 -0800)
committerSteven Fackler <sfackler@gmail.com>
Wed, 23 Dec 2015 18:19:20 +0000 (11:19 -0700)
src/libstd/panic.rs
src/libstd/panicking.rs
src/test/run-fail/panic-set-handler.rs [new file with mode: 0644]
src/test/run-fail/panic-set-unset-handler.rs [new file with mode: 0644]
src/test/run-fail/panic-take-handler-nop.rs [new file with mode: 0644]
src/test/run-pass/panic-handler-chain.rs [new file with mode: 0644]
src/test/run-pass/panic-handler-flail-wildly.rs [new file with mode: 0644]
src/test/run-pass/panic-handler-set-twice.rs [new file with mode: 0644]

index 6e4ba337b08ee4817eeb4af6a80680d9cf06f61a..1550d55d177a27051f0bf89892a8fa8e0df221f0 100644 (file)
@@ -21,6 +21,8 @@
 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
index 2b2af350c992cc874737ccfe547e65ef9f7ffc2b..3f9a1c30ef4936c6d4767fcc27c1d767aa91af0d 100644 (file)
 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) }
 
     }
 }
 
-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.
+    ///
+    /// 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::<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
diff --git a/src/test/run-fail/panic-set-handler.rs b/src/test/run-fail/panic-set-handler.rs
new file mode 100644 (file)
index 0000000..bfeb407
--- /dev/null
@@ -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");
+}
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 (file)
index 0000000..6999aa7
--- /dev/null
@@ -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");
+}
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 (file)
index 0000000..fec1db2
--- /dev/null
@@ -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");
+}
diff --git a/src/test/run-pass/panic-handler-chain.rs b/src/test/run-pass/panic-handler-chain.rs
new file mode 100644 (file)
index 0000000..1ed592d
--- /dev/null
@@ -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));
+}
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 (file)
index 0000000..783a44b
--- /dev/null
@@ -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();
+        }
+    }
+}
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 (file)
index 0000000..edf65e8
--- /dev/null
@@ -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));
+}