From 76314579a2400e7c10c9be5d0a2b9072b7ca3bd1 Mon Sep 17 00:00:00 2001
From: Jane Lusby <jlusby@yaah.dev>
Date: Thu, 6 Jan 2022 13:34:57 -0800
Subject: [PATCH] PoC: implement core::panic::panic_error

---
 compiler/rustc_hir/src/lang_items.rs |   1 +
 compiler/rustc_span/src/symbol.rs    |   1 +
 library/core/src/panic.rs            |  13 +++
 library/core/src/panic/panic_info.rs |  61 ++++++++++-
 library/core/src/panicking.rs        |  33 ++++++
 library/std/src/lib.rs               |   1 +
 library/std/src/panicking.rs         | 151 +++++++++++++++++++++++++++
 src/test/ui/panics/panic-error.rs    |  33 ++++++
 8 files changed, 290 insertions(+), 4 deletions(-)
 create mode 100644 src/test/ui/panics/panic-error.rs

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<E: 'static + crate::error::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<T: 'static>(&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::<String>() {
             Some(s) => &s[..],
+            #[cfg(not(bootstrap))]
+            None if info.error().is_some() => "non-recoverable runtime Error",
             None => "Box<dyn Any>",
         },
     };
@@ -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<String>,
+    }
+
+    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<dyn Any + Send>) -> ! {
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")
+    }
+}