Skip to content

Commit cce89c9

Browse files
committed
Report allocation errors as panics
1 parent 56d507d commit cce89c9

File tree

6 files changed

+107
-27
lines changed

6 files changed

+107
-27
lines changed

library/alloc/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ compiler-builtins-c = ["compiler_builtins/c"]
3636
compiler-builtins-no-asm = ["compiler_builtins/no-asm"]
3737
compiler-builtins-mangled-names = ["compiler_builtins/mangled-names"]
3838
compiler-builtins-weak-intrinsics = ["compiler_builtins/weak-intrinsics"]
39+
40+
# Make panics and failed asserts immediately abort without formatting any message
41+
panic_immediate_abort = ["core/panic_immediate_abort"]

library/alloc/src/alloc.rs

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ use core::ptr::{self, NonNull};
1616
#[doc(inline)]
1717
pub use core::alloc::*;
1818

19+
#[cfg(not(no_global_oom_handling))]
20+
use core::any::Any;
21+
#[cfg(not(no_global_oom_handling))]
22+
use core::panic::BoxMeUp;
23+
1924
#[cfg(test)]
2025
mod tests;
2126

@@ -354,14 +359,77 @@ unsafe fn box_free<T: ?Sized, A: Allocator>(ptr: Unique<T>, alloc: A) {
354359
}
355360
}
356361

357-
// # Allocation error handler
362+
/// Payload passed to the panic handler when `handle_alloc_error` is called.
363+
#[unstable(feature = "panic_oom_payload", issue = "110730")]
364+
#[derive(Debug)]
365+
pub struct AllocErrorPanicPayload {
366+
layout: Layout,
367+
}
368+
369+
impl AllocErrorPanicPayload {
370+
/// Internal function for the standard library to clone a payload.
371+
#[unstable(feature = "std_internals", issue = "none")]
372+
#[doc(hidden)]
373+
pub fn internal_clone(&self) -> Self {
374+
AllocErrorPanicPayload { layout: self.layout }
375+
}
358376

377+
/// Returns the [`Layout`] of the allocation attempt that caused the error.
378+
#[unstable(feature = "panic_oom_payload", issue = "110730")]
379+
pub fn layout(&self) -> Layout {
380+
self.layout
381+
}
382+
}
383+
384+
#[unstable(feature = "std_internals", issue = "none")]
359385
#[cfg(not(no_global_oom_handling))]
360-
extern "Rust" {
361-
// This is the magic symbol to call the global alloc error handler. rustc generates
362-
// it to call `__rg_oom` if there is a `#[alloc_error_handler]`, or to call the
363-
// default implementations below (`__rdl_oom`) otherwise.
364-
fn __rust_alloc_error_handler(size: usize, align: usize) -> !;
386+
unsafe impl BoxMeUp for AllocErrorPanicPayload {
387+
fn take_box(&mut self) -> *mut (dyn Any + Send) {
388+
use crate::boxed::Box;
389+
Box::into_raw(Box::new(self.internal_clone()))
390+
}
391+
392+
fn get(&mut self) -> &(dyn Any + Send) {
393+
self
394+
}
395+
}
396+
397+
// # Allocation error handler
398+
399+
#[cfg(all(not(no_global_oom_handling), not(test)))]
400+
fn rust_oom(layout: Layout) -> ! {
401+
if cfg!(feature = "panic_immediate_abort") {
402+
core::intrinsics::abort()
403+
}
404+
405+
extern "Rust" {
406+
// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call
407+
// that gets resolved to the `#[panic_handler]` function.
408+
#[lang = "panic_impl"]
409+
fn panic_impl(pi: &core::panic::PanicInfo<'_>) -> !;
410+
411+
// This symbol is emitted by rustc next to __rust_alloc_error_handler.
412+
// Its value depends on the -Zoom={panic,abort} compiler option.
413+
static __rust_alloc_error_handler_should_panic: u8;
414+
}
415+
416+
// Hack to work around issues with the lifetime of Arguments.
417+
match format_args!("memory allocation of {} bytes failed", layout.size()) {
418+
fmt => {
419+
// Create a PanicInfo with a custom payload for the panic handler.
420+
let can_unwind = unsafe { __rust_alloc_error_handler_should_panic != 0 };
421+
let mut pi = core::panic::PanicInfo::internal_constructor(
422+
Some(&fmt),
423+
core::panic::Location::caller(),
424+
can_unwind,
425+
);
426+
let payload = AllocErrorPanicPayload { layout };
427+
pi.set_payload(&payload);
428+
429+
// SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call.
430+
unsafe { panic_impl(&pi) }
431+
}
432+
}
365433
}
366434

367435
/// Abort on memory allocation error or failure.
@@ -386,9 +454,7 @@ pub const fn handle_alloc_error(layout: Layout) -> ! {
386454
}
387455

388456
fn rt_error(layout: Layout) -> ! {
389-
unsafe {
390-
__rust_alloc_error_handler(layout.size(), layout.align());
391-
}
457+
rust_oom(layout);
392458
}
393459

394460
unsafe { core::intrinsics::const_eval_select((layout,), ct_error, rt_error) }

library/alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
#![feature(maybe_uninit_slice)]
136136
#![feature(maybe_uninit_uninit_array)]
137137
#![feature(maybe_uninit_uninit_array_transpose)]
138+
#![feature(panic_internals)]
138139
#![feature(pattern)]
139140
#![feature(pointer_byte_offsets)]
140141
#![feature(provide_any)]

library/std/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ llvm-libunwind = ["unwind/llvm-libunwind"]
7070
system-llvm-libunwind = ["unwind/system-llvm-libunwind"]
7171

7272
# Make panics and failed asserts immediately abort without formatting any message
73-
panic_immediate_abort = ["core/panic_immediate_abort"]
73+
panic_immediate_abort = ["alloc/panic_immediate_abort"]
7474

7575
# Enable std_detect default features for stdarch/crates/std_detect:
7676
# https://github.com/rust-lang/stdarch/blob/master/crates/std_detect/Cargo.toml

library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@
322322
#![feature(get_mut_unchecked)]
323323
#![feature(map_try_insert)]
324324
#![feature(new_uninit)]
325+
#![feature(panic_oom_payload)]
325326
#![feature(slice_concat_trait)]
326327
#![feature(thin_box)]
327328
#![feature(try_reserve_kind)]

library/std/src/panicking.rs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -245,19 +245,24 @@ fn default_hook(info: &PanicInfo<'_>) {
245245

246246
// The current implementation always returns `Some`.
247247
let location = info.location().unwrap();
248-
249-
let msg = match info.payload().downcast_ref::<&'static str>() {
250-
Some(s) => *s,
251-
None => match info.payload().downcast_ref::<String>() {
252-
Some(s) => &s[..],
253-
None => "Box<dyn Any>",
254-
},
255-
};
256248
let thread = thread_info::current_thread();
257249
let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>");
258250

259251
let write = |err: &mut dyn crate::io::Write| {
260-
let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}");
252+
// Use the panic message directly if available, otherwise take it from
253+
// the payload.
254+
if let Some(msg) = info.message() {
255+
let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}");
256+
} else {
257+
let msg = if let Some(s) = info.payload().downcast_ref::<&'static str>() {
258+
*s
259+
} else if let Some(s) = info.payload().downcast_ref::<String>() {
260+
&s[..]
261+
} else {
262+
"Box<dyn Any>"
263+
};
264+
let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}");
265+
}
261266

262267
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
263268

@@ -539,6 +544,8 @@ pub fn panicking() -> bool {
539544
#[cfg(not(test))]
540545
#[panic_handler]
541546
pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
547+
use alloc::alloc::AllocErrorPanicPayload;
548+
542549
struct PanicPayload<'a> {
543550
inner: &'a fmt::Arguments<'a>,
544551
string: Option<String>,
@@ -565,8 +572,7 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
565572
unsafe impl<'a> BoxMeUp for PanicPayload<'a> {
566573
fn take_box(&mut self) -> *mut (dyn Any + Send) {
567574
// We do two allocations here, unfortunately. But (a) they're required with the current
568-
// scheme, and (b) we don't handle panic + OOM properly anyway (see comment in
569-
// begin_panic below).
575+
// scheme, and (b) OOM uses its own separate payload type which doesn't allocate.
570576
let contents = mem::take(self.fill());
571577
Box::into_raw(Box::new(contents))
572578
}
@@ -591,7 +597,14 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
591597
let loc = info.location().unwrap(); // The current implementation always returns Some
592598
let msg = info.message().unwrap(); // The current implementation always returns Some
593599
crate::sys_common::backtrace::__rust_end_short_backtrace(move || {
594-
if let Some(msg) = msg.as_str() {
600+
if let Some(payload) = info.payload().downcast_ref::<AllocErrorPanicPayload>() {
601+
rust_panic_with_hook(
602+
&mut payload.internal_clone(),
603+
info.message(),
604+
loc,
605+
info.can_unwind(),
606+
);
607+
} else if let Some(msg) = msg.as_str() {
595608
rust_panic_with_hook(&mut StrPanicPayload(msg), info.message(), loc, info.can_unwind());
596609
} else {
597610
rust_panic_with_hook(
@@ -638,11 +651,7 @@ pub const fn begin_panic<M: Any + Send>(msg: M) -> ! {
638651

639652
unsafe impl<A: Send + 'static> BoxMeUp for PanicPayload<A> {
640653
fn take_box(&mut self) -> *mut (dyn Any + Send) {
641-
// Note that this should be the only allocation performed in this code path. Currently
642-
// this means that panic!() on OOM will invoke this code path, but then again we're not
643-
// really ready for panic on OOM anyway. If we do start doing this, then we should
644-
// propagate this allocation to be performed in the parent of this thread instead of the
645-
// thread that's panicking.
654+
// Note that this should be the only allocation performed in this code path.
646655
let data = match self.inner.take() {
647656
Some(a) => Box::new(a) as Box<dyn Any + Send>,
648657
None => process::abort(),

0 commit comments

Comments
 (0)