diff --git a/library/core/src/panic/panic_info.rs b/library/core/src/panic/panic_info.rs index 0d385c9d18744..12cef7a1c3bf6 100644 --- a/library/core/src/panic/panic_info.rs +++ b/library/core/src/panic/panic_info.rs @@ -1,4 +1,5 @@ use crate::any::Any; +use crate::error::Error; use crate::fmt; use crate::panic::Location; @@ -30,6 +31,7 @@ use crate::panic::Location; pub struct PanicInfo<'a> { payload: &'a (dyn Any + Send), message: Option<&'a fmt::Arguments<'a>>, + source: Option<&'a (dyn Error + 'static)>, location: &'a Location<'a>, can_unwind: bool, } @@ -45,10 +47,11 @@ impl<'a> PanicInfo<'a> { pub fn internal_constructor( message: Option<&'a fmt::Arguments<'a>>, location: &'a Location<'a>, + source: Option<&'a (dyn Error + 'static)>, can_unwind: bool, ) -> Self { struct NoPayload; - PanicInfo { location, message, payload: &NoPayload, can_unwind } + PanicInfo { location, message, payload: &NoPayload, source, can_unwind } } #[unstable( @@ -62,6 +65,12 @@ impl<'a> PanicInfo<'a> { self.payload = info; } + #[unstable(feature = "error_in_panic", issue = "none")] + /// The [`Error`] that caused this panic, if known. + pub fn source(&self) -> Option<&(dyn Error + 'static)> { + self.source + } + /// Returns the payload associated with the panic. /// /// This will commonly, but not always, be a `&'static str` or [`String`]. diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index efeb726ab8eb8..f7614951cf253 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -26,6 +26,7 @@ issue = "none" )] +use crate::error::Error; use crate::fmt; use crate::panic::{Location, PanicInfo}; @@ -54,19 +55,37 @@ pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! { 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::internal_constructor(Some(&fmt), Location::caller(), None, true); + + // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call. + unsafe { panic_impl(&pi) } +} + +#[cold] +// If panic_immediate_abort, inline the abort call, +// otherwise avoid inlining because of it is cold path. +#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))] +#[cfg_attr(feature = "panic_immediate_abort", inline)] +#[track_caller] +#[unstable(feature = "error_in_panic", issue = "none")] +pub fn panic_source(fmt: fmt::Arguments<'_>, source: &(dyn Error + 'static)) -> ! { + if cfg!(feature = "panic_immediate_abort") { + super::intrinsics::abort() } - let pi = PanicInfo::internal_constructor(Some(&fmt), Location::caller(), true); + let pi = PanicInfo::internal_constructor(Some(&fmt), Location::caller(), Some(source), true); // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call. unsafe { panic_impl(&pi) } } +// 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<'_>) -> !; +} + /// Like `panic_fmt`, but for non-unwinding panics. /// /// Has to be a separate function so that it can carry the `rustc_nounwind` attribute. @@ -82,15 +101,8 @@ pub fn panic_nounwind_fmt(fmt: fmt::Arguments<'_>) -> ! { 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<'_>) -> !; - } - // PanicInfo with the `can_unwind` flag set to false forces an abort. - let pi = PanicInfo::internal_constructor(Some(&fmt), Location::caller(), false); + let pi = PanicInfo::internal_constructor(Some(&fmt), Location::caller(), None, false); // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call. unsafe { panic_impl(&pi) } diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 71f3576c93d4b..383a44dd03d0b 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -278,6 +278,7 @@ #![feature(duration_constants)] #![feature(error_generic_member_access)] #![feature(error_in_core)] +#![feature(error_in_panic)] #![feature(error_iter)] #![feature(exact_size_is_empty)] #![feature(exclusive_wrapper)] diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index a46a29cbad608..6c2501da21a78 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -13,6 +13,7 @@ use crate::panic::BacktraceStyle; use core::panic::{BoxMeUp, Location, PanicInfo}; use crate::any::Any; +use crate::error::{Error, Report}; use crate::fmt; use crate::intrinsics; use crate::mem::{self, ManuallyDrop}; @@ -258,7 +259,10 @@ fn default_hook(info: &PanicInfo<'_>) { let write = |err: &mut dyn crate::io::Write| { let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}"); - + if let Some(source) = info.source() { + let report = Report::new(source).pretty(true); + let _ = writeln!(err, "\nSource: {report}\n"); + } static FIRST_PANIC: AtomicBool = AtomicBool::new(true); match backtrace { @@ -577,12 +581,19 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { 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, info.can_unwind()); + rust_panic_with_hook( + &mut StrPanicPayload(msg), + info.message(), + loc, + info.source(), + info.can_unwind(), + ); } else { rust_panic_with_hook( &mut PanicPayload::new(msg), info.message(), loc, + info.source(), info.can_unwind(), ); } @@ -608,7 +619,7 @@ pub const fn begin_panic(msg: M) -> ! { let loc = Location::caller(); return crate::sys_common::backtrace::__rust_end_short_backtrace(move || { - rust_panic_with_hook(&mut PanicPayload::new(msg), None, loc, true) + rust_panic_with_hook(&mut PanicPayload::new(msg), None, loc, None, true) }); struct PanicPayload { @@ -653,6 +664,7 @@ fn rust_panic_with_hook( payload: &mut dyn BoxMeUp, message: Option<&fmt::Arguments<'_>>, location: &Location<'_>, + source: Option<&(dyn Error + 'static)>, can_unwind: bool, ) -> ! { let (must_abort, panics) = panic_count::increase(); @@ -670,13 +682,13 @@ fn rust_panic_with_hook( } else { // Unfortunately, this does not print a backtrace, because creating // a `Backtrace` will allocate, which we must to avoid here. - let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind); + let panicinfo = PanicInfo::internal_constructor(message, location, source, can_unwind); rtprintpanic!("{panicinfo}\npanicked after panic::always_abort(), aborting.\n"); } crate::sys::abort_internal(); } - let mut info = PanicInfo::internal_constructor(message, location, can_unwind); + let mut info = PanicInfo::internal_constructor(message, location, source, can_unwind); let hook = HOOK.read().unwrap_or_else(PoisonError::into_inner); match *hook { // Some platforms (like wasm) know that printing to stderr won't ever actually diff --git a/tests/ui/errors/issue-103169-error-in-panic.rs b/tests/ui/errors/issue-103169-error-in-panic.rs new file mode 100644 index 0000000000000..5589c32b33ad8 --- /dev/null +++ b/tests/ui/errors/issue-103169-error-in-panic.rs @@ -0,0 +1,50 @@ +// run-fail +// needs-run-enabled +// check-run-results + +#![allow(unused_imports)] +#![feature(core_panic)] +#![feature(error_in_core)] +#![feature(error_in_panic)] + +extern crate core; + +use core::error; +use core::panicking::panic_source; + +use std::error::Error; + +#[derive(Debug)] +struct MyErr { + super_source: SourceError, +} + +#[derive(Debug)] +struct SourceError {} + +use std::fmt; +impl fmt::Display for MyErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "my source error message") + } +} + +impl error::Error for MyErr { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.super_source) + } +} + +impl fmt::Display for SourceError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "my source's source error message") + } +} + +impl error::Error for SourceError {} + +fn main() { + let source_error = SourceError {}; + let source = MyErr { super_source: source_error }; + panic_source(format_args!("here's my panic error message"), &source); +} diff --git a/tests/ui/errors/issue-103169-error-in-panic.run.stderr b/tests/ui/errors/issue-103169-error-in-panic.run.stderr new file mode 100644 index 0000000000000..c3747a42da778 --- /dev/null +++ b/tests/ui/errors/issue-103169-error-in-panic.run.stderr @@ -0,0 +1,8 @@ +thread 'main' panicked at 'here's my panic error message', $DIR/issue-103169-error-in-panic.rs:49:5 + +Source: my source error message + +Caused by: + my source's source error message + +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace