diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 9983b98b4f50c..63c765379382c 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -292,6 +292,7 @@ language_item_table! { // a weak lang item, but do not have it defined. Panic, sym::panic, panic_fn, Target::Fn, GenericRequirement::Exact(0); PanicFmt, sym::panic_fmt, panic_fmt, Target::Fn, GenericRequirement::None; + PanicError, sym::panic_error, panic_error, Target::Fn, GenericRequirement::None; PanicDisplay, sym::panic_display, panic_display, Target::Fn, GenericRequirement::None; PanicStr, sym::panic_str, panic_str, Target::Fn, GenericRequirement::None; ConstPanicFmt, sym::const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 186e81268f32b..49746bf14798a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -959,6 +959,7 @@ symbols! { panic_abort, panic_bounds_check, panic_display, + panic_error, panic_fmt, panic_handler, panic_impl, diff --git a/library/core/src/panic.rs b/library/core/src/panic.rs index 7a8b04d6f3c13..7a84dcf1b7e49 100644 --- a/library/core/src/panic.rs +++ b/library/core/src/panic.rs @@ -58,6 +58,19 @@ pub macro panic_2021 { ), } +/// Panic the current thread with the given error as the panic payload. +/// +/// The message can be of any (`Error + 'static`) type, not just strings. +/// +/// See the [`panic!`] macro for more information about panicking. +#[unstable(feature = "panic_error", issue = "none")] +#[inline] +#[track_caller] +#[cfg(not(bootstrap))] +pub fn panic_error(error: E) -> ! { + crate::panicking::panic_error(&error); +} + /// An internal trait used by libstd to pass data from libstd to `panic_unwind` /// and other panic runtimes. Not intended to be stabilized any time soon, do /// not use. diff --git a/library/core/src/panic/panic_info.rs b/library/core/src/panic/panic_info.rs index d8e421df5de5d..faf86771dae94 100644 --- a/library/core/src/panic/panic_info.rs +++ b/library/core/src/panic/panic_info.rs @@ -1,6 +1,8 @@ use crate::any::Any; use crate::fmt; use crate::panic::Location; +#[cfg(not(bootstrap))] +use crate::error::Error; /// A struct providing information about a panic. /// @@ -28,11 +30,29 @@ use crate::panic::Location; #[stable(feature = "panic_hooks", since = "1.10.0")] #[derive(Debug)] pub struct PanicInfo<'a> { - payload: &'a (dyn Any + Send), + payload: PayloadKind<'a>, message: Option<&'a fmt::Arguments<'a>>, location: &'a Location<'a>, } +#[derive(Debug)] +enum PayloadKind<'a> { + Any(&'a (dyn Any + Send)), + #[cfg(not(bootstrap))] + Error(&'a (dyn Error + 'static)), +} + +impl<'a> PayloadKind<'a> { + fn downcast_ref(&self) -> Option<&T> { + match self { + PayloadKind::Any(payload) => payload.downcast_ref(), + #[cfg(not(bootstrap))] + PayloadKind::Error(_) => None, + // PayloadKind::Error(error) => error.downcast_ref(), + } + } +} + impl<'a> PanicInfo<'a> { #[unstable( feature = "panic_internals", @@ -46,7 +66,23 @@ impl<'a> PanicInfo<'a> { location: &'a Location<'a>, ) -> Self { struct NoPayload; - PanicInfo { location, message, payload: &NoPayload } + PanicInfo { location, message, payload: PayloadKind::Any(&NoPayload) } + } + + #[unstable( + feature = "panic_internals", + reason = "internal details of the implementation of the `panic!` and related macros", + issue = "none" + )] + #[doc(hidden)] + #[inline] + #[cfg(not(bootstrap))] + pub fn error_constructor( + // message: Option<&'a fmt::Arguments<'a>>, + error: &'a (dyn crate::error::Error + 'static), + location: &'a Location<'a>, + ) -> Self { + PanicInfo { location, message: None, payload: PayloadKind::Error(error) } } #[unstable( @@ -57,7 +93,7 @@ impl<'a> PanicInfo<'a> { #[doc(hidden)] #[inline] pub fn set_payload(&mut self, info: &'a (dyn Any + Send)) { - self.payload = info; + self.payload = PayloadKind::Any(info); } /// Returns the payload associated with the panic. @@ -84,7 +120,13 @@ impl<'a> PanicInfo<'a> { #[must_use] #[stable(feature = "panic_hooks", since = "1.10.0")] pub fn payload(&self) -> &(dyn Any + Send) { - self.payload + #[cfg(not(bootstrap))] + struct HackerVoice; + match self.payload { + PayloadKind::Any(payload) => payload, + #[cfg(not(bootstrap))] + PayloadKind::Error(_) => &HackerVoice, // I'm in + } } /// If the `panic!` macro from the `core` crate (not from `std`) @@ -96,6 +138,17 @@ impl<'a> PanicInfo<'a> { self.message } + /// If the panic was created with `panic_error` returns the source error of the panic. + #[must_use] + #[unstable(feature = "panic_error", issue = "none")] + #[cfg(not(bootstrap))] + pub fn error(&self) -> Option<&(dyn core::error::Error + 'static)> { + match self.payload { + PayloadKind::Any(_) => None, + PayloadKind::Error(source) => Some(source), + } + } + /// Returns information about the location from which the panic originated, /// if available. /// diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index eedea6562bd4d..582a45464d032 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -107,6 +107,39 @@ pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! { unsafe { panic_impl(&pi) } } +/// 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. +#[cold] +// If panic_immediate_abort, inline the abort call, +// otherwise avoid inlining because of it is cold path. +#[cfg(not(bootstrap))] +#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))] +#[cfg_attr(feature = "panic_immediate_abort", inline)] +#[track_caller] +#[lang = "panic_error"] // needed for const-evaluated panics +#[rustc_do_not_const_check] // hooked by const-eval +pub const fn panic_error(error: &(dyn core::error::Error + 'static)) -> ! { + if cfg!(feature = "panic_immediate_abort") { + super::intrinsics::abort() + } + + // NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call + // that gets resolved to the `#[panic_handler]` function. + extern "Rust" { + #[lang = "panic_impl"] + fn panic_impl(pi: &PanicInfo<'_>) -> !; + } + + let pi = PanicInfo::error_constructor(error, Location::caller()); + + // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call. + unsafe { panic_impl(&pi) } +} + /// This function is used instead of panic_fmt in const eval. #[lang = "const_panic_fmt"] pub const fn const_panic_fmt(fmt: fmt::Arguments<'_>) -> ! { diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 22e721d79bfed..810d0f55e845c 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -311,6 +311,7 @@ #![feature(nll)] #![feature(nonnull_slice_from_raw_parts)] #![feature(once_cell)] +#![cfg_attr(not(bootstrap), feature(panic_error))] #![feature(panic_info_message)] #![feature(panic_internals)] #![feature(panic_unwind)] diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index 87854fe4f2970..deb070ae85052 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -196,6 +196,8 @@ fn default_hook(info: &PanicInfo<'_>) { Some(s) => *s, None => match info.payload().downcast_ref::() { Some(s) => &s[..], + #[cfg(not(bootstrap))] + None if info.error().is_some() => "non-recoverable runtime Error", None => "Box", }, }; @@ -442,6 +444,7 @@ pub fn panicking() -> bool { /// Entry point of panics from the libcore crate (`panic_impl` lang item). #[cfg(not(test))] +#[cfg(bootstrap)] #[panic_handler] pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { struct PanicPayload<'a> { @@ -504,6 +507,77 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { }) } +/// Entry point of panics from the libcore crate (`panic_impl` lang item). +#[cfg(not(test))] +#[cfg(not(bootstrap))] +#[panic_handler] +pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { + struct PanicPayload<'a> { + inner: &'a fmt::Arguments<'a>, + string: Option, + } + + impl<'a> PanicPayload<'a> { + fn new(inner: &'a fmt::Arguments<'a>) -> PanicPayload<'a> { + PanicPayload { inner, string: None } + } + + fn fill(&mut self) -> &mut String { + use crate::fmt::Write; + + let inner = self.inner; + // Lazily, the first time this gets called, run the actual string formatting. + self.string.get_or_insert_with(|| { + let mut s = String::new(); + drop(s.write_fmt(*inner)); + s + }) + } + } + + unsafe impl<'a> BoxMeUp for PanicPayload<'a> { + fn take_box(&mut self) -> *mut (dyn Any + Send) { + // 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 contents = mem::take(self.fill()); + Box::into_raw(Box::new(contents)) + } + + fn get(&mut self) -> &(dyn Any + Send) { + self.fill() + } + } + + struct StrPanicPayload(&'static str); + + unsafe impl BoxMeUp for StrPanicPayload { + fn take_box(&mut self) -> *mut (dyn Any + Send) { + Box::into_raw(Box::new(self.0)) + } + + fn get(&mut self) -> &(dyn Any + Send) { + &self.0 + } + } + + let loc = info.location().unwrap(); // The current implementation always returns Some + if let Some(error) = info.error() { + crate::sys_common::backtrace::__rust_end_short_backtrace(move || { + rust_panic_error_with_hook(error, loc); + }) + } else { + let msg = info.message().unwrap(); // The current implementation always returns Some + crate::sys_common::backtrace::__rust_end_short_backtrace(move || { + if let Some(msg) = msg.as_str() { + rust_panic_with_hook(&mut StrPanicPayload(msg), info.message(), loc); + } else { + rust_panic_with_hook(&mut PanicPayload::new(msg), info.message(), loc); + } + }) + } +} + /// This is the entry point of panicking for the non-format-string variants of /// panic!() and assert!(). In particular, this is the only entry point that supports /// arbitrary payloads, not just format strings. @@ -624,6 +698,83 @@ fn rust_panic_with_hook( rust_panic(payload) } +/// Central point for dispatching error panics. +/// +/// Executes the primary logic for a panic, including checking for recursive +/// panics, panic hooks, and finally dispatching to the panic runtime to either +/// abort or unwind. +#[cfg(not(bootstrap))] +fn rust_panic_error_with_hook( + error: &(dyn crate::error::Error + 'static), + location: &Location<'_>, +) -> ! { + let (must_abort, panics) = panic_count::increase(); + + // 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 must_abort || panics > 2 { + if panics > 2 { + // Don't try to print the message in this case + // - perhaps that is causing the recursive panics. + rtprintpanic!("thread panicked while processing panic. aborting.\n"); + } else { + // Unfortunately, this does not print a backtrace, because creating + // a `Backtrace` will allocate, which we must to avoid here. + let panicinfo = PanicInfo::error_constructor(error, location); + rtprintpanic!("{}\npanicked after panic::always_abort(), aborting.\n", panicinfo); + } + intrinsics::abort() + } + + unsafe { + let info = PanicInfo::error_constructor(error, location); + let _guard = HOOK_LOCK.read(); + match HOOK { + // Some platforms (like wasm) know that printing to stderr won't ever actually + // print anything, and if that's the case we can skip the default + // hook. Since string formatting happens lazily when calling `payload` + // methods, this means we avoid formatting the string at all! + // (The panic runtime might still call `payload.take_box()` though and trigger + // formatting.) + Hook::Default if panic_output().is_none() => {} + Hook::Default => { + default_hook(&info); + } + Hook::Custom(ptr) => { + (*ptr)(&info); + } + }; + } + + if panics > 1 { + // 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. + rtprintpanic!("thread panicked while panicking. aborting.\n"); + intrinsics::abort() + } + + struct ErrorPanicPayload; + + unsafe impl BoxMeUp for ErrorPanicPayload { + fn take_box(&mut self) -> *mut (dyn Any + Send) { + // ErrorPanicPayload is a zst so this box should not allocate + let data = Box::new(ErrorPanicPayload); + Box::into_raw(data) + } + + fn get(&mut self) -> &(dyn Any + Send) { + &ErrorPanicPayload + } + } + + rust_panic(&mut ErrorPanicPayload) +} + /// This is the entry point for `resume_unwind`. /// It just forwards the payload to the panic runtime. pub fn rust_panic_without_hook(payload: Box) -> ! { diff --git a/src/test/ui/panics/panic-error.rs b/src/test/ui/panics/panic-error.rs new file mode 100644 index 0000000000000..da5195336509d --- /dev/null +++ b/src/test/ui/panics/panic-error.rs @@ -0,0 +1,33 @@ +// run-pass +#![feature(panic_error)] + +use std::panic::PanicInfo; +use std::fmt; + +fn install_panic_hook() { + let old_hook = std::panic::take_hook(); + let new_hook = move |info: &PanicInfo| { + old_hook(info); + if let Some(source) = info.error() { + eprintln!("Error: {}", source); + } + }; + std::panic::set_hook(Box::new(new_hook)); +} + +fn main() { + install_panic_hook(); + let error = MyError; + core::panic::panic_error(error); +} + +#[derive(Debug)] +struct MyError; + +impl core::error::Error for MyError {} + +impl fmt::Display for MyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "my error occurred") + } +}