// Copyright 2014 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. //! Implementation of various bits and pieces of the `panic!` macro and //! associated runtime pieces. //! //! Specifically, this module contains the implementation of: //! //! * Panic hooks //! * Executing a panic up to doing the actual implementation //! * Shims around "try" use prelude::v1::*; use io::prelude::*; use any::Any; use cell::Cell; use cell::RefCell; use fmt; use intrinsics; use mem; use raw; use sys_common::rwlock::RWLock; #[cfg(feature = "backtrace")] use sync::atomic::{AtomicBool, Ordering}; use sys::stdio::Stderr; #[cfg(feature = "backtrace")] use sys_common::backtrace; use sys_common::thread_info; use sys_common::util; use thread; thread_local! { pub static LOCAL_STDERR: RefCell>> = { RefCell::new(None) } } thread_local! { pub static PANIC_COUNT: Cell = Cell::new(0) } // Binary interface to the panic runtime that the standard library depends on. // // The standard library is tagged with `#![needs_panic_runtime]` (introduced in // RFC 1513) to indicate that it requires some other crate tagged with // `#![panic_runtime]` to exist somewhere. Each panic runtime is intended to // implement these symbols (with the same signatures) so we can get matched up // to them. // // One day this may look a little less ad-hoc with the compiler helping out to // hook up these functions, but it is not this day! #[allow(improper_ctypes)] extern { fn __rust_maybe_catch_panic(f: fn(*mut u8), data: *mut u8, data_ptr: *mut usize, vtable_ptr: *mut usize) -> u32; #[unwind] fn __rust_start_panic(data: usize, vtable: usize) -> u32; } #[derive(Copy, Clone)] enum Hook { Default, Custom(*mut (Fn(&PanicInfo) + 'static + Sync + Send)), } static HOOK_LOCK: RWLock = RWLock::new(); static mut HOOK: Hook = Hook::Default; #[cfg(feature = "backtrace")] static FIRST_PANIC: AtomicBool = AtomicBool::new(true); /// Registers a custom panic hook, replacing any that was previously registered. /// /// The panic hook is invoked when a thread panics, but before the panic runtime /// is invoked. As such, the hook will run with both the aborting and unwinding /// runtimes. The default hook prints a message to standard error and generates /// a backtrace if requested, but this behavior can be customized with the /// `set_hook` and `take_hook` functions. /// /// The hook 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 hook is a global resource. /// /// # Panics /// /// Panics if called from a panicking thread. #[stable(feature = "panic_hooks", since = "1.10.0")] pub fn set_hook(hook: Box) { if thread::panicking() { panic!("cannot modify the panic hook from a panicking thread"); } unsafe { HOOK_LOCK.write(); let old_hook = HOOK; HOOK = Hook::Custom(Box::into_raw(hook)); HOOK_LOCK.write_unlock(); if let Hook::Custom(ptr) = old_hook { Box::from_raw(ptr); } } } /// Unregisters the current panic hook, returning it. /// /// If no custom hook is registered, the default hook will be returned. /// /// # Panics /// /// Panics if called from a panicking thread. #[stable(feature = "panic_hooks", since = "1.10.0")] pub fn take_hook() -> Box { if thread::panicking() { panic!("cannot modify the panic hook from a panicking thread"); } unsafe { HOOK_LOCK.write(); let hook = HOOK; HOOK = Hook::Default; HOOK_LOCK.write_unlock(); match hook { Hook::Default => Box::new(default_hook), Hook::Custom(ptr) => {Box::from_raw(ptr)} // FIXME #30530 } } } /// A struct providing information about a panic. #[stable(feature = "panic_hooks", since = "1.10.0")] 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`. #[stable(feature = "panic_hooks", since = "1.10.0")] 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. #[stable(feature = "panic_hooks", since = "1.10.0")] pub fn location(&self) -> Option<&Location> { Some(&self.location) } } /// A struct containing information about the location of a panic. #[stable(feature = "panic_hooks", since = "1.10.0")] 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. #[stable(feature = "panic_hooks", since = "1.10.0")] pub fn file(&self) -> &str { self.file } /// Returns the line number from which the panic originated. #[stable(feature = "panic_hooks", since = "1.10.0")] pub fn line(&self) -> u32 { self.line } } fn default_hook(info: &PanicInfo) { #[cfg(feature = "backtrace")] let panics = PANIC_COUNT.with(|c| c.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. #[cfg(feature = "backtrace")] 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 info.payload.downcast_ref::() { Some(s) => &s[..], None => "Box", } }; let mut err = Stderr::new().ok(); let thread = thread_info::current_thread(); let name = thread.as_ref().and_then(|t| t.name()).unwrap_or(""); let write = |err: &mut ::io::Write| { let _ = writeln!(err, "thread '{}' panicked at '{}', {}:{}", name, msg, file, line); #[cfg(feature = "backtrace")] { if log_backtrace { let _ = backtrace::write(err); } else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) { let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace."); } } }; let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take()); match (prev, err.as_mut()) { (Some(mut stderr), _) => { write(&mut *stderr); let mut s = Some(stderr); LOCAL_STDERR.with(|slot| { *slot.borrow_mut() = s.take(); }); } (None, Some(ref mut err)) => { write(err) } _ => {} } } /// Invoke a closure, capturing the cause of an unwinding panic if one occurs. pub unsafe fn try R>(f: F) -> Result> { let mut slot = None; let mut f = Some(f); let ret = PANIC_COUNT.with(|s| { let prev = s.get(); s.set(0); let mut to_run = || { slot = Some(f.take().unwrap()()); }; let fnptr = get_call(&mut to_run); let dataptr = &mut to_run as *mut _ as *mut u8; let mut any_data = 0; let mut any_vtable = 0; let fnptr = mem::transmute::(fnptr); let r = __rust_maybe_catch_panic(fnptr, dataptr, &mut any_data, &mut any_vtable); s.set(prev); if r == 0 { Ok(()) } else { Err(mem::transmute(raw::TraitObject { data: any_data as *mut _, vtable: any_vtable as *mut _, })) } }); return ret.map(|()| { slot.take().unwrap() }); fn get_call(_: &mut F) -> fn(&mut F) { call } fn call(f: &mut F) { f() } } /// Determines whether the current thread is unwinding because of panic. pub fn panicking() -> bool { PANIC_COUNT.with(|c| c.get() != 0) } /// Entry point of panic from the libcore crate. #[cfg(not(test))] #[lang = "panic_fmt"] #[unwind] pub extern fn rust_begin_panic(msg: fmt::Arguments, file: &'static str, line: u32) -> ! { begin_panic_fmt(&msg, &(file, line)) } /// The entry point for panicking with a formatted message. /// /// This is designed to reduce the amount of code required at the call /// site as much as possible (so that `panic!()` has as low an impact /// on (e.g.) the inlining of other functions as possible), by moving /// the actual formatting into this shared place. #[unstable(feature = "libstd_sys_internals", reason = "used by the panic! macro", issue = "0")] #[inline(never)] #[cold] pub fn begin_panic_fmt(msg: &fmt::Arguments, file_line: &(&'static str, u32)) -> ! { use fmt::Write; // We do two allocations here, unfortunately. But (a) they're // required with the current scheme, and (b) we don't handle // panic + OOM properly anyway (see comment in begin_panic // below). let mut s = String::new(); let _ = s.write_fmt(*msg); begin_panic(s, file_line) } /// This is the entry point of panicking for panic!() and assert!(). #[unstable(feature = "libstd_sys_internals", reason = "used by the panic! macro", issue = "0")] #[inline(never)] #[cold] // avoid code bloat at the call sites as much as possible pub fn begin_panic(msg: M, file_line: &(&'static str, u32)) -> ! { // Note that this should be the only allocation performed in this code path. // Currently this means that panic!() on OOM will invoke this code path, // but then again we're not really ready for panic on OOM anyway. If // we do start doing this, then we should propagate this allocation to // be performed in the parent of this thread instead of the thread that's // panicking. rust_panic_with_hook(Box::new(msg), file_line) } /// Executes the primary logic for a panic, including checking for recursive /// panics and panic hooks. /// /// This is the entry point or panics from libcore, formatted panics, and /// `Box` panics. Here we'll verify that we're not panicking recursively, /// run panic hooks, and then delegate to the actual implementation of panics. #[inline(never)] #[cold] fn rust_panic_with_hook(msg: Box, file_line: &(&'static str, u32)) -> ! { let (file, line) = *file_line; let panics = PANIC_COUNT.with(|c| { let prev = c.get(); c.set(prev + 1); prev }); // If this is the third nested call (e.g. panics == 2, this is 0-indexed), // the panic hook probably triggered the last panic, otherwise the // double-panic check would have aborted the process. In this case abort the // process real quickly as we don't want to try calling it again as it'll // probably just panic again. if panics > 1 { util::dumb_print(format_args!("thread panicked while processing \ panic. aborting.\n")); unsafe { intrinsics::abort() } } unsafe { let info = PanicInfo { payload: &*msg, location: Location { file: file, line: line, }, }; HOOK_LOCK.read(); match HOOK { Hook::Default => default_hook(&info), Hook::Custom(ptr) => (*ptr)(&info), } HOOK_LOCK.read_unlock(); } if panics > 0 { // If a thread panics while it's already unwinding then we // have limited options. Currently our preference is to // just abort. In the future we may consider resuming // unwinding or otherwise exiting the thread cleanly. util::dumb_print(format_args!("thread panicked while panicking. \ aborting.\n")); unsafe { intrinsics::abort() } } rust_panic(msg) } /// A private no-mangle function on which to slap yer breakpoints. #[no_mangle] #[allow(private_no_mangle_fns)] // yes we get it, but we like breakpoints pub fn rust_panic(msg: Box) -> ! { let code = unsafe { let obj = mem::transmute::<_, raw::TraitObject>(msg); __rust_start_panic(obj.data as usize, obj.vtable as usize) }; rtabort!("failed to initiate panic, error {}", code) }