Skip to content

Commit b2476c9

Browse files
committed
Report allocation errors as panics
1 parent b0889cb commit b2476c9

File tree

6 files changed

+111
-29
lines changed

6 files changed

+111
-29
lines changed

library/alloc/Cargo.toml

+3
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

+76-9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ use core::ptr::{self, NonNull};
1212
#[doc(inline)]
1313
pub use core::alloc::*;
1414

15+
#[cfg(not(no_global_oom_handling))]
16+
use core::any::Any;
17+
#[cfg(not(no_global_oom_handling))]
18+
use core::panic::PanicPayload;
19+
1520
#[cfg(test)]
1621
mod tests;
1722

@@ -333,14 +338,78 @@ unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
333338
}
334339
}
335340

336-
// # Allocation error handler
341+
/// Payload passed to the panic handler when `handle_alloc_error` is called.
342+
#[unstable(feature = "panic_oom_payload", issue = "110730")]
343+
#[derive(Debug)]
344+
pub struct AllocErrorPanicPayload {
345+
layout: Layout,
346+
}
347+
348+
impl AllocErrorPanicPayload {
349+
/// Internal function for the standard library to clone a payload.
350+
#[unstable(feature = "std_internals", issue = "none")]
351+
#[doc(hidden)]
352+
pub fn internal_clone(&self) -> Self {
353+
AllocErrorPanicPayload { layout: self.layout }
354+
}
337355

356+
/// Returns the [`Layout`] of the allocation attempt that caused the error.
357+
#[unstable(feature = "panic_oom_payload", issue = "110730")]
358+
pub fn layout(&self) -> Layout {
359+
self.layout
360+
}
361+
}
362+
363+
#[unstable(feature = "std_internals", issue = "none")]
338364
#[cfg(not(no_global_oom_handling))]
339-
extern "Rust" {
340-
// This is the magic symbol to call the global alloc error handler. rustc generates
341-
// it to call `__rg_oom` if there is a `#[alloc_error_handler]`, or to call the
342-
// default implementations below (`__rdl_oom`) otherwise.
343-
fn __rust_alloc_error_handler(size: usize, align: usize) -> !;
365+
unsafe impl PanicPayload for AllocErrorPanicPayload {
366+
fn take_box(&mut self) -> *mut (dyn Any + Send) {
367+
use crate::boxed::Box;
368+
Box::into_raw(Box::new(self.internal_clone()))
369+
}
370+
371+
fn get(&mut self) -> &(dyn Any + Send) {
372+
self
373+
}
374+
}
375+
376+
// # Allocation error handler
377+
378+
#[cfg(all(not(no_global_oom_handling), not(test)))]
379+
fn rust_oom(layout: Layout) -> ! {
380+
if cfg!(feature = "panic_immediate_abort") {
381+
core::intrinsics::abort()
382+
}
383+
384+
extern "Rust" {
385+
// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call
386+
// that gets resolved to the `#[panic_handler]` function.
387+
#[lang = "panic_impl"]
388+
fn panic_impl(pi: &core::panic::PanicInfo<'_>) -> !;
389+
390+
// This symbol is emitted by rustc next to __rust_alloc_error_handler.
391+
// Its value depends on the -Zoom={panic,abort} compiler option.
392+
static __rust_alloc_error_handler_should_panic: u8;
393+
}
394+
395+
// Hack to work around issues with the lifetime of Arguments.
396+
match format_args!("memory allocation of {} bytes failed", layout.size()) {
397+
fmt => {
398+
// Create a PanicInfo with a custom payload for the panic handler.
399+
let can_unwind = unsafe { __rust_alloc_error_handler_should_panic != 0 };
400+
let mut pi = core::panic::PanicInfo::internal_constructor(
401+
Some(&fmt),
402+
core::panic::Location::caller(),
403+
can_unwind,
404+
/* force_no_backtrace */ false,
405+
);
406+
let payload = AllocErrorPanicPayload { layout };
407+
pi.set_payload(&payload);
408+
409+
// SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call.
410+
unsafe { panic_impl(&pi) }
411+
}
412+
}
344413
}
345414

346415
/// Signal a memory allocation error.
@@ -378,9 +447,7 @@ pub const fn handle_alloc_error(layout: Layout) -> ! {
378447
}
379448

380449
fn rt_error(layout: Layout) -> ! {
381-
unsafe {
382-
__rust_alloc_error_handler(layout.size(), layout.align());
383-
}
450+
rust_oom(layout);
384451
}
385452

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

library/alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
#![feature(maybe_uninit_slice)]
139139
#![feature(maybe_uninit_uninit_array)]
140140
#![feature(maybe_uninit_uninit_array_transpose)]
141+
#![feature(panic_internals)]
141142
#![feature(pattern)]
142143
#![feature(pointer_byte_offsets)]
143144
#![feature(ptr_internals)]

library/std/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ llvm-libunwind = ["unwind/llvm-libunwind"]
7272
system-llvm-libunwind = ["unwind/system-llvm-libunwind"]
7373

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

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

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@
349349
#![feature(get_mut_unchecked)]
350350
#![feature(map_try_insert)]
351351
#![feature(new_uninit)]
352+
#![feature(panic_oom_payload)]
352353
#![feature(slice_concat_trait)]
353354
#![feature(thin_box)]
354355
#![feature(try_reserve_kind)]

library/std/src/panicking.rs

+29-19
Original file line numberDiff line numberDiff line change
@@ -248,19 +248,24 @@ fn default_hook(info: &PanicInfo<'_>) {
248248

249249
// The current implementation always returns `Some`.
250250
let location = info.location().unwrap();
251-
252-
let msg = match info.payload().downcast_ref::<&'static str>() {
253-
Some(s) => *s,
254-
None => match info.payload().downcast_ref::<String>() {
255-
Some(s) => &s[..],
256-
None => "Box<dyn Any>",
257-
},
258-
};
259251
let thread = thread_info::current_thread();
260252
let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>");
261253

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

265270
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
266271

@@ -543,6 +548,8 @@ pub fn panicking() -> bool {
543548
#[cfg(not(test))]
544549
#[panic_handler]
545550
pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
551+
use alloc::alloc::AllocErrorPanicPayload;
552+
546553
struct FormatStringPayload<'a> {
547554
inner: &'a fmt::Arguments<'a>,
548555
string: Option<String>,
@@ -569,8 +576,7 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
569576
unsafe impl<'a> PanicPayload for FormatStringPayload<'a> {
570577
fn take_box(&mut self) -> *mut (dyn Any + Send) {
571578
// We do two allocations here, unfortunately. But (a) they're required with the current
572-
// scheme, and (b) we don't handle panic + OOM properly anyway (see comment in
573-
// begin_panic below).
579+
// scheme, and (b) OOM uses its own separate payload type which doesn't allocate.
574580
let contents = mem::take(self.fill());
575581
Box::into_raw(Box::new(contents))
576582
}
@@ -595,9 +601,17 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
595601
let loc = info.location().unwrap(); // The current implementation always returns Some
596602
let msg = info.message().unwrap(); // The current implementation always returns Some
597603
crate::sys_common::backtrace::__rust_end_short_backtrace(move || {
598-
// FIXME: can we just pass `info` along rather than taking it apart here, only to have
599-
// `rust_panic_with_hook` construct a new `PanicInfo`?
600-
if let Some(msg) = msg.as_str() {
604+
if let Some(payload) = info.payload().downcast_ref::<AllocErrorPanicPayload>() {
605+
rust_panic_with_hook(
606+
&mut payload.internal_clone(),
607+
info.message(),
608+
loc,
609+
info.can_unwind(),
610+
info.force_no_backtrace(),
611+
);
612+
} else if let Some(msg) = msg.as_str() {
613+
// FIXME: can we just pass `info` along rather than taking it apart here, only to have
614+
// `rust_panic_with_hook` construct a new `PanicInfo`?
601615
rust_panic_with_hook(
602616
&mut StaticStrPayload(msg),
603617
info.message(),
@@ -657,11 +671,7 @@ pub const fn begin_panic<M: Any + Send>(msg: M) -> ! {
657671

658672
unsafe impl<A: Send + 'static> PanicPayload for Payload<A> {
659673
fn take_box(&mut self) -> *mut (dyn Any + Send) {
660-
// Note that this should be the only allocation performed in this code path. Currently
661-
// this means that panic!() on OOM will invoke this code path, but then again we're not
662-
// really ready for panic on OOM anyway. If we do start doing this, then we should
663-
// propagate this allocation to be performed in the parent of this thread instead of the
664-
// thread that's panicking.
674+
// Note that this should be the only allocation performed in this code path.
665675
let data = match self.inner.take() {
666676
Some(a) => Box::new(a) as Box<dyn Any + Send>,
667677
None => process::abort(),

0 commit comments

Comments
 (0)