diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index 25c9201f2ed3a..c01f1289e15f4 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -366,6 +366,35 @@ pub mod panic_count { }); } + // Decrease the panic count only if the thread is already panicking. + // Allows running code nested within a wind that may itself unwind. + #[inline] + #[must_use] + pub fn try_decrease() -> bool { + if GLOBAL_PANIC_COUNT.load(Ordering::Relaxed) & !ALWAYS_ABORT_FLAG == 0 { + // Fast path: see count_is_zero. + false + } else { + try_decrease_slow_path() + } + } + + // We consider unwinding to be rare, so mark this function as cold. + // However, leave the inlining decision entirely to the optimizer. + #[cold] + fn try_decrease_slow_path() -> bool { + LOCAL_PANIC_COUNT.with(|c| { + let panic_count = c.get(); + if panic_count > 0 { + GLOBAL_PANIC_COUNT.fetch_sub(1, Ordering::Relaxed); + c.set(panic_count - 1); + true + } else { + false + } + }) + } + pub fn set_always_abort() { GLOBAL_PANIC_COUNT.fetch_or(ALWAYS_ABORT_FLAG, Ordering::Relaxed); } @@ -453,7 +482,12 @@ pub unsafe fn r#try R>(f: F) -> Result> // - `do_catch`, the second argument, can be called with the `data_ptr` as well. // See their safety preconditions for more information unsafe { - return if intrinsics::r#try(do_call::, data_ptr, do_catch::) == 0 { + let is_panicking = panic_count::try_decrease(); + let success = intrinsics::r#try(do_call::, data_ptr, do_catch::) == 0; + if is_panicking { + panic_count::increase(); + } + return if success { Ok(ManuallyDrop::into_inner(data.r)) } else { Err(ManuallyDrop::into_inner(data.p)) @@ -705,10 +739,10 @@ fn rust_panic_with_hook( } if panics > 1 || !can_unwind { - // 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. + // If the thread was already panicking, then the closest catch_unwind + // has already been claimed by the existing panic. If a new catch_unwind + // had been registered, it would have cleared the panicking flag. Since + // this panic is not well-nested, we just abort the process. rtprintpanic!("thread panicked while panicking. aborting.\n"); crate::sys::abort_internal(); } diff --git a/src/test/ui/panics/well-nested-panic.rs b/src/test/ui/panics/well-nested-panic.rs new file mode 100644 index 0000000000000..502019448da68 --- /dev/null +++ b/src/test/ui/panics/well-nested-panic.rs @@ -0,0 +1,18 @@ +// run-pass +// needs-unwind + +use std::panic::catch_unwind; + +struct Bomb; +impl Drop for Bomb { + fn drop(&mut self) { + let _ = catch_unwind(|| panic!("bomb")); + } +} + +fn main() { + let _ = catch_unwind(|| { + let _bomb = Bomb; + panic!("main"); + }); +}