Skip to content

Commit cf7131e

Browse files
committed
Rework handling of recursive panics
1 parent 8e8116c commit cf7131e

File tree

3 files changed

+95
-54
lines changed

3 files changed

+95
-54
lines changed

library/std/src/panicking.rs

+67-53
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,18 @@ pub mod panic_count {
298298

299299
pub const ALWAYS_ABORT_FLAG: usize = 1 << (usize::BITS - 1);
300300

301-
// Panic count for the current thread.
302-
thread_local! { static LOCAL_PANIC_COUNT: Cell<usize> = const { Cell::new(0) } }
301+
/// A reason for forcing an immediate abort on panic.
302+
#[derive(Debug)]
303+
pub enum MustAbort {
304+
AlwaysAbort,
305+
PanicInHook,
306+
}
307+
308+
// Panic count for the current thread and whether a panic hook is currently
309+
// being executed..
310+
thread_local! {
311+
static LOCAL_PANIC_COUNT: Cell<(usize, bool)> = const { Cell::new((0, false)) }
312+
}
303313

304314
// Sum of panic counts from all threads. The purpose of this is to have
305315
// a fast path in `count_is_zero` (which is used by `panicking`). In any particular
@@ -328,34 +338,39 @@ pub mod panic_count {
328338
// panicking thread consumes at least 2 bytes of address space.
329339
static GLOBAL_PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
330340

331-
// Return the state of the ALWAYS_ABORT_FLAG and number of panics.
341+
// Increases the global and local panic count, and returns whether an
342+
// immediate abort is required.
332343
//
333-
// If ALWAYS_ABORT_FLAG is not set, the number is determined on a per-thread
334-
// base (stored in LOCAL_PANIC_COUNT), i.e. it is the amount of recursive calls
335-
// of the calling thread.
336-
// If ALWAYS_ABORT_FLAG is set, the number equals the *global* number of panic
337-
// calls. See above why LOCAL_PANIC_COUNT is not used.
338-
pub fn increase() -> (bool, usize) {
344+
// This also updates thread-local state to keep track of whether a panic
345+
// hook is currently executing.
346+
pub fn increase(run_panic_hook: bool) -> Option<MustAbort> {
339347
let global_count = GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed);
340-
let must_abort = global_count & ALWAYS_ABORT_FLAG != 0;
341-
let panics = if must_abort {
342-
global_count & !ALWAYS_ABORT_FLAG
343-
} else {
344-
LOCAL_PANIC_COUNT.with(|c| {
345-
let next = c.get() + 1;
346-
c.set(next);
347-
next
348-
})
349-
};
350-
(must_abort, panics)
348+
if global_count & ALWAYS_ABORT_FLAG != 0 {
349+
return Some(MustAbort::AlwaysAbort);
350+
}
351+
352+
LOCAL_PANIC_COUNT.with(|c| {
353+
let (count, in_panic_hook) = c.get();
354+
if in_panic_hook {
355+
return Some(MustAbort::PanicInHook);
356+
}
357+
c.set((count + 1, run_panic_hook));
358+
None
359+
})
360+
}
361+
362+
pub fn finished_panic_hook() {
363+
LOCAL_PANIC_COUNT.with(|c| {
364+
let (count, _) = c.get();
365+
c.set((count, false));
366+
});
351367
}
352368

353369
pub fn decrease() {
354370
GLOBAL_PANIC_COUNT.fetch_sub(1, Ordering::Relaxed);
355371
LOCAL_PANIC_COUNT.with(|c| {
356-
let next = c.get() - 1;
357-
c.set(next);
358-
next
372+
let (count, _) = c.get();
373+
c.set((count - 1, false));
359374
});
360375
}
361376

@@ -366,7 +381,7 @@ pub mod panic_count {
366381
// Disregards ALWAYS_ABORT_FLAG
367382
#[must_use]
368383
pub fn get_count() -> usize {
369-
LOCAL_PANIC_COUNT.with(|c| c.get())
384+
LOCAL_PANIC_COUNT.with(|c| c.get().0)
370385
}
371386

372387
// Disregards ALWAYS_ABORT_FLAG
@@ -394,7 +409,7 @@ pub mod panic_count {
394409
#[inline(never)]
395410
#[cold]
396411
fn is_zero_slow_path() -> bool {
397-
LOCAL_PANIC_COUNT.with(|c| c.get() == 0)
412+
LOCAL_PANIC_COUNT.with(|c| c.get().0 == 0)
398413
}
399414
}
400415

@@ -655,23 +670,22 @@ fn rust_panic_with_hook(
655670
location: &Location<'_>,
656671
can_unwind: bool,
657672
) -> ! {
658-
let (must_abort, panics) = panic_count::increase();
659-
660-
// If this is the third nested call (e.g., panics == 2, this is 0-indexed),
661-
// the panic hook probably triggered the last panic, otherwise the
662-
// double-panic check would have aborted the process. In this case abort the
663-
// process real quickly as we don't want to try calling it again as it'll
664-
// probably just panic again.
665-
if must_abort || panics > 2 {
666-
if panics > 2 {
667-
// Don't try to print the message in this case
668-
// - perhaps that is causing the recursive panics.
669-
rtprintpanic!("thread panicked while processing panic. aborting.\n");
670-
} else {
671-
// Unfortunately, this does not print a backtrace, because creating
672-
// a `Backtrace` will allocate, which we must to avoid here.
673-
let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind);
674-
rtprintpanic!("{panicinfo}\npanicked after panic::always_abort(), aborting.\n");
673+
let must_abort = panic_count::increase(true);
674+
675+
// Check if we need to abort immediately.
676+
if let Some(must_abort) = must_abort {
677+
match must_abort {
678+
panic_count::MustAbort::PanicInHook => {
679+
// Don't try to print the message in this case
680+
// - perhaps that is causing the recursive panics.
681+
rtprintpanic!("thread panicked while processing panic. aborting.\n");
682+
}
683+
panic_count::MustAbort::AlwaysAbort => {
684+
// Unfortunately, this does not print a backtrace, because creating
685+
// a `Backtrace` will allocate, which we must to avoid here.
686+
let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind);
687+
rtprintpanic!("{panicinfo}\npanicked after panic::always_abort(), aborting.\n");
688+
}
675689
}
676690
crate::sys::abort_internal();
677691
}
@@ -697,16 +711,16 @@ fn rust_panic_with_hook(
697711
};
698712
drop(hook);
699713

700-
if panics > 1 || !can_unwind {
701-
// If a thread panics while it's already unwinding then we
702-
// have limited options. Currently our preference is to
703-
// just abort. In the future we may consider resuming
704-
// unwinding or otherwise exiting the thread cleanly.
705-
if !can_unwind {
706-
rtprintpanic!("thread caused non-unwinding panic. aborting.\n");
707-
} else {
708-
rtprintpanic!("thread panicked while panicking. aborting.\n");
709-
}
714+
// Indicate that we have finished executing the panic hook. After this point
715+
// it is fine if there is a panic while executing destructors, as long as it
716+
// it contained within a `catch_unwind`.
717+
panic_count::finished_panic_hook();
718+
719+
if !can_unwind {
720+
// If a thread panics while running destructors or tries to unwind
721+
// through a nounwind function (e.g. extern "C") then we cannot continue
722+
// unwinding and have to abort immediately.
723+
rtprintpanic!("thread caused non-unwinding panic. aborting.\n");
710724
crate::sys::abort_internal();
711725
}
712726

@@ -716,7 +730,7 @@ fn rust_panic_with_hook(
716730
/// This is the entry point for `resume_unwind`.
717731
/// It just forwards the payload to the panic runtime.
718732
pub fn rust_panic_without_hook(payload: Box<dyn Any + Send>) -> ! {
719-
panic_count::increase();
733+
panic_count::increase(false);
720734

721735
struct RewrapBox(Box<dyn Any + Send>);
722736

tests/ui/backtrace.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,17 @@ fn runtest(me: &str) {
104104
"bad output3: {}", s);
105105

106106
// Make sure a stack trace isn't printed too many times
107+
//
108+
// Currently it is printed 3 times ("once", "twice" and "panic in a
109+
// function that cannot unwind") but in the future the last one may be
110+
// removed.
107111
let p = template(me).arg("double-fail")
108112
.env("RUST_BACKTRACE", "1").spawn().unwrap();
109113
let out = p.wait_with_output().unwrap();
110114
assert!(!out.status.success());
111115
let s = str::from_utf8(&out.stderr).unwrap();
112116
let mut i = 0;
113-
for _ in 0..2 {
117+
for _ in 0..3 {
114118
i += s[i + 10..].find("stack backtrace").unwrap() + 10;
115119
}
116120
assert!(s[i + 10..].find("stack backtrace").is_none(),
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// run-pass
2+
3+
// Checks that nested panics work correctly.
4+
5+
use std::panic::catch_unwind;
6+
7+
fn double() {
8+
struct Double;
9+
10+
impl Drop for Double {
11+
fn drop(&mut self) {
12+
let _ = catch_unwind(|| panic!("twice"));
13+
}
14+
}
15+
16+
let _d = Double;
17+
18+
panic!("once");
19+
}
20+
21+
fn main() {
22+
assert!(catch_unwind(|| double()).is_err());
23+
}

0 commit comments

Comments
 (0)