Skip to content

Commit ec4360a

Browse files
authored
Rollup merge of #92828 - Amanieu:unwind-abort, r=dtolnay
Print a helpful message if unwinding aborts when it reaches a nounwind function This is implemented by routing `TerminatorKind::Abort` back through the panic handler, but with a special flag in the `PanicInfo` which indicates that the panic handler should *not* attempt to unwind the stack and should instead abort immediately. This is useful for the planned change in rust-lang/lang-team#97 which would make `Drop` impls `nounwind` by default. ### Code ```rust #![feature(c_unwind)] fn panic() { panic!() } extern "C" fn nounwind() { panic(); } fn main() { nounwind(); } ``` ### Before ``` $ ./test thread 'main' panicked at 'explicit panic', test.rs:4:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Illegal instruction (core dumped) ``` ### After ``` $ ./test thread 'main' panicked at 'explicit panic', test.rs:4:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' panicked at 'panic in a function that cannot unwind', test.rs:7:1 stack backtrace: 0: 0x556f8f86ec9b - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hdccefe11a6ac4396 1: 0x556f8f88ac6c - core::fmt::write::he152b28c41466ebb 2: 0x556f8f85d6e2 - std::io::Write::write_fmt::h0c261480ab86f3d3 3: 0x556f8f8654fa - std::panicking::default_hook::{{closure}}::h5d7346f3ff7f6c1b 4: 0x556f8f86512b - std::panicking::default_hook::hd85803a1376cac7f 5: 0x556f8f865a91 - std::panicking::rust_panic_with_hook::h4dc1c5a3036257ac 6: 0x556f8f86f079 - std::panicking::begin_panic_handler::{{closure}}::hdda1d83c7a9d34d2 7: 0x556f8f86edc4 - std::sys_common::backtrace::__rust_end_short_backtrace::h5b70ed0cce71e95f 8: 0x556f8f865592 - rust_begin_unwind 9: 0x556f8f85a764 - core::panicking::panic_no_unwind::h2606ab3d78c87899 10: 0x556f8f85b910 - test::nounwind::hade6c7ee65050347 11: 0x556f8f85b936 - test::main::hdc6e02cb36343525 12: 0x556f8f85b7e3 - core::ops::function::FnOnce::call_once::h4d02663acfc7597f 13: 0x556f8f85b739 - std::sys_common::backtrace::__rust_begin_short_backtrace::h071d40135adb0101 14: 0x556f8f85c149 - std::rt::lang_start::{{closure}}::h70dbfbf38b685e93 15: 0x556f8f85c791 - std::rt::lang_start_internal::h798f1c0268d525aa 16: 0x556f8f85c131 - std::rt::lang_start::h476a7ee0a0bb663f 17: 0x556f8f85b963 - main 18: 0x7f64c0822b25 - __libc_start_main 19: 0x556f8f85ae8e - _start 20: 0x0 - <unknown> thread panicked while panicking. aborting. Aborted (core dumped) ```
2 parents 7e05fda + d050e9c commit ec4360a

File tree

5 files changed

+62
-11
lines changed

5 files changed

+62
-11
lines changed

core/src/panic/panic_info.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub struct PanicInfo<'a> {
3131
payload: &'a (dyn Any + Send),
3232
message: Option<&'a fmt::Arguments<'a>>,
3333
location: &'a Location<'a>,
34+
can_unwind: bool,
3435
}
3536

3637
impl<'a> PanicInfo<'a> {
@@ -44,9 +45,10 @@ impl<'a> PanicInfo<'a> {
4445
pub fn internal_constructor(
4546
message: Option<&'a fmt::Arguments<'a>>,
4647
location: &'a Location<'a>,
48+
can_unwind: bool,
4749
) -> Self {
4850
struct NoPayload;
49-
PanicInfo { location, message, payload: &NoPayload }
51+
PanicInfo { location, message, payload: &NoPayload, can_unwind }
5052
}
5153

5254
#[unstable(
@@ -127,6 +129,18 @@ impl<'a> PanicInfo<'a> {
127129
// deal with that case in std::panicking::default_hook and core::panicking::panic_fmt.
128130
Some(&self.location)
129131
}
132+
133+
/// Returns whether the panic handler is allowed to unwind the stack from
134+
/// the point where the panic occurred.
135+
///
136+
/// This is true for most kinds of panics with the exception of panics
137+
/// caused by trying to unwind out of a `Drop` implementation or a function
138+
/// whose ABI does not support unwinding.
139+
#[must_use]
140+
#[unstable(feature = "panic_can_unwind", issue = "92988")]
141+
pub fn can_unwind(&self) -> bool {
142+
self.can_unwind
143+
}
130144
}
131145

132146
#[stable(feature = "panic_hook_display", since = "1.26.0")]

core/src/panicking.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,31 @@ fn panic_bounds_check(index: usize, len: usize) -> ! {
7777
panic!("index out of bounds: the len is {} but the index is {}", len, index)
7878
}
7979

80+
#[cfg(not(bootstrap))]
81+
#[cold]
82+
#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))]
83+
#[track_caller]
84+
#[lang = "panic_no_unwind"] // needed by codegen for panic in nounwind function
85+
fn panic_no_unwind() -> ! {
86+
if cfg!(feature = "panic_immediate_abort") {
87+
super::intrinsics::abort()
88+
}
89+
90+
// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call
91+
// that gets resolved to the `#[panic_handler]` function.
92+
extern "Rust" {
93+
#[lang = "panic_impl"]
94+
fn panic_impl(pi: &PanicInfo<'_>) -> !;
95+
}
96+
97+
// PanicInfo with the `can_unwind` flag set to false forces an abort.
98+
let fmt = format_args!("panic in a function that cannot unwind");
99+
let pi = PanicInfo::internal_constructor(Some(&fmt), Location::caller(), false);
100+
101+
// SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call.
102+
unsafe { panic_impl(&pi) }
103+
}
104+
80105
/// The entry point for panicking with a formatted message.
81106
///
82107
/// This is designed to reduce the amount of code required at the call
@@ -104,7 +129,7 @@ pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
104129
fn panic_impl(pi: &PanicInfo<'_>) -> !;
105130
}
106131

107-
let pi = PanicInfo::internal_constructor(Some(&fmt), Location::caller());
132+
let pi = PanicInfo::internal_constructor(Some(&fmt), Location::caller(), true);
108133

109134
// SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call.
110135
unsafe { panic_impl(&pi) }

std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@
311311
#![feature(once_cell)]
312312
#![feature(panic_info_message)]
313313
#![feature(panic_internals)]
314+
#![feature(panic_can_unwind)]
314315
#![feature(panic_unwind)]
315316
#![feature(pin_static_ref)]
316317
#![feature(portable_simd)]

std/src/panicking.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -576,9 +576,14 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
576576
let msg = info.message().unwrap(); // The current implementation always returns Some
577577
crate::sys_common::backtrace::__rust_end_short_backtrace(move || {
578578
if let Some(msg) = msg.as_str() {
579-
rust_panic_with_hook(&mut StrPanicPayload(msg), info.message(), loc);
579+
rust_panic_with_hook(&mut StrPanicPayload(msg), info.message(), loc, info.can_unwind());
580580
} else {
581-
rust_panic_with_hook(&mut PanicPayload::new(msg), info.message(), loc);
581+
rust_panic_with_hook(
582+
&mut PanicPayload::new(msg),
583+
info.message(),
584+
loc,
585+
info.can_unwind(),
586+
);
582587
}
583588
})
584589
}
@@ -602,7 +607,7 @@ pub const fn begin_panic<M: Any + Send>(msg: M) -> ! {
602607

603608
let loc = Location::caller();
604609
return crate::sys_common::backtrace::__rust_end_short_backtrace(move || {
605-
rust_panic_with_hook(&mut PanicPayload::new(msg), None, loc)
610+
rust_panic_with_hook(&mut PanicPayload::new(msg), None, loc, true)
606611
});
607612

608613
struct PanicPayload<A> {
@@ -647,6 +652,7 @@ fn rust_panic_with_hook(
647652
payload: &mut dyn BoxMeUp,
648653
message: Option<&fmt::Arguments<'_>>,
649654
location: &Location<'_>,
655+
can_unwind: bool,
650656
) -> ! {
651657
let (must_abort, panics) = panic_count::increase();
652658

@@ -663,14 +669,14 @@ fn rust_panic_with_hook(
663669
} else {
664670
// Unfortunately, this does not print a backtrace, because creating
665671
// a `Backtrace` will allocate, which we must to avoid here.
666-
let panicinfo = PanicInfo::internal_constructor(message, location);
672+
let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind);
667673
rtprintpanic!("{}\npanicked after panic::always_abort(), aborting.\n", panicinfo);
668674
}
669-
intrinsics::abort()
675+
crate::sys::abort_internal();
670676
}
671677

672678
unsafe {
673-
let mut info = PanicInfo::internal_constructor(message, location);
679+
let mut info = PanicInfo::internal_constructor(message, location, can_unwind);
674680
let _guard = HOOK_LOCK.read();
675681
match HOOK {
676682
// Some platforms (like wasm) know that printing to stderr won't ever actually
@@ -691,13 +697,13 @@ fn rust_panic_with_hook(
691697
};
692698
}
693699

694-
if panics > 1 {
700+
if panics > 1 || !can_unwind {
695701
// If a thread panics while it's already unwinding then we
696702
// have limited options. Currently our preference is to
697703
// just abort. In the future we may consider resuming
698704
// unwinding or otherwise exiting the thread cleanly.
699705
rtprintpanic!("thread panicked while panicking. aborting.\n");
700-
intrinsics::abort()
706+
crate::sys::abort_internal();
701707
}
702708

703709
rust_panic(payload)

std/src/sys/unix/process/process_unix/tests.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,10 @@ fn test_command_fork_no_unwind() {
5353
let status = got.expect("panic unexpectedly propagated");
5454
dbg!(status);
5555
let signal = status.signal().expect("expected child process to die of signal");
56-
assert!(signal == libc::SIGABRT || signal == libc::SIGILL || signal == libc::SIGTRAP);
56+
assert!(
57+
signal == libc::SIGABRT
58+
|| signal == libc::SIGILL
59+
|| signal == libc::SIGTRAP
60+
|| signal == libc::SIGSEGV
61+
);
5762
}

0 commit comments

Comments
 (0)