Skip to content

Commit c18104a

Browse files
committed
Auto merge of #109507 - Amanieu:panic-oom-payload, r=davidtwco
Report allocation errors as panics OOM is now reported as a panic but with a custom payload type (`AllocErrorPanicPayload`) which holds the layout that was passed to `handle_alloc_error`. This should be review one commit at a time: - The first commit adds `AllocErrorPanicPayload` and changes allocation errors to always be reported as panics. - The second commit removes `#[alloc_error_handler]` and the `alloc_error_hook` API. ACP: rust-lang/libs-team#192 Closes #51540 Closes #51245
2 parents 2c3fdc6 + 2c6d707 commit c18104a

File tree

10 files changed

+112
-124
lines changed

10 files changed

+112
-124
lines changed

alloc/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ compiler-builtins-mem = ['compiler_builtins/mem']
3535
compiler-builtins-c = ["compiler_builtins/c"]
3636
compiler-builtins-no-asm = ["compiler_builtins/no-asm"]
3737
compiler-builtins-mangled-names = ["compiler_builtins/mangled-names"]
38+
39+
# Make panics and failed asserts immediately abort without formatting any message
40+
panic_immediate_abort = ["core/panic_immediate_abort"]

alloc/src/alloc.rs

+77-17
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ use core::ptr::{self, NonNull};
1414
#[doc(inline)]
1515
pub use core::alloc::*;
1616

17+
#[cfg(not(no_global_oom_handling))]
18+
use core::any::Any;
19+
#[cfg(not(no_global_oom_handling))]
20+
use core::panic::BoxMeUp;
21+
1722
#[cfg(test)]
1823
mod tests;
1924

@@ -343,28 +348,84 @@ pub(crate) unsafe fn box_free<T: ?Sized, A: Allocator>(ptr: Unique<T>, alloc: A)
343348
}
344349
}
345350

346-
// # Allocation error handler
351+
/// Payload passed to the panic handler when `handle_alloc_error` is called.
352+
#[unstable(feature = "panic_oom_payload", issue = "none")]
353+
#[derive(Debug)]
354+
pub struct AllocErrorPanicPayload {
355+
layout: Layout,
356+
}
357+
358+
impl AllocErrorPanicPayload {
359+
/// Internal function for the standard library to clone a payload.
360+
#[unstable(feature = "std_internals", issue = "none")]
361+
#[doc(hidden)]
362+
pub fn internal_clone(&self) -> Self {
363+
AllocErrorPanicPayload { layout: self.layout }
364+
}
347365

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

356424
/// Abort on memory allocation error or failure.
357425
///
358426
/// Callers of memory allocation APIs wishing to abort computation
359427
/// in response to an allocation error are encouraged to call this function,
360428
/// rather than directly invoking `panic!` or similar.
361-
///
362-
/// The default behavior of this function is to print a message to standard error
363-
/// and abort the process.
364-
/// It can be replaced with [`set_alloc_error_hook`] and [`take_alloc_error_hook`].
365-
///
366-
/// [`set_alloc_error_hook`]: ../../std/alloc/fn.set_alloc_error_hook.html
367-
/// [`take_alloc_error_hook`]: ../../std/alloc/fn.take_alloc_error_hook.html
368429
#[stable(feature = "global_alloc", since = "1.28.0")]
369430
#[rustc_const_unstable(feature = "const_alloc_error", issue = "92523")]
370431
#[cfg(all(not(no_global_oom_handling), not(test)))]
@@ -375,9 +436,7 @@ pub const fn handle_alloc_error(layout: Layout) -> ! {
375436
}
376437

377438
fn rt_error(layout: Layout) -> ! {
378-
unsafe {
379-
__rust_alloc_error_handler(layout.size(), layout.align());
380-
}
439+
rust_oom(layout);
381440
}
382441

383442
unsafe { core::intrinsics::const_eval_select((layout,), ct_error, rt_error) }
@@ -387,6 +446,7 @@ pub const fn handle_alloc_error(layout: Layout) -> ! {
387446
#[cfg(all(not(no_global_oom_handling), test))]
388447
pub use std::alloc::handle_alloc_error;
389448

449+
#[cfg(bootstrap)]
390450
#[cfg(all(not(no_global_oom_handling), not(test)))]
391451
#[doc(hidden)]
392452
#[allow(unused_attributes)]
@@ -398,7 +458,7 @@ pub mod __alloc_error_handler {
398458
pub unsafe fn __rdl_oom(size: usize, _align: usize) -> ! {
399459
extern "Rust" {
400460
// This symbol is emitted by rustc next to __rust_alloc_error_handler.
401-
// Its value depends on the -Zoom={panic,abort} compiler option.
461+
// Its value depends on the -Zoom={unwind,abort} compiler option.
402462
static __rust_alloc_error_handler_should_panic: u8;
403463
}
404464

alloc/src/lib.rs

+1
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)]

core/src/macros/mod.rs

-10
Original file line numberDiff line numberDiff line change
@@ -1531,16 +1531,6 @@ pub(crate) mod builtin {
15311531
/* compiler built-in */
15321532
}
15331533

1534-
/// Attribute macro applied to a function to register it as a handler for allocation failure.
1535-
///
1536-
/// See also [`std::alloc::handle_alloc_error`](../../../std/alloc/fn.handle_alloc_error.html).
1537-
#[unstable(feature = "alloc_error_handler", issue = "51540")]
1538-
#[allow_internal_unstable(rustc_attrs)]
1539-
#[rustc_builtin_macro]
1540-
pub macro alloc_error_handler($item:item) {
1541-
/* compiler built-in */
1542-
}
1543-
15441534
/// Keeps the item it's applied to if the passed path is accessible, and removes it otherwise.
15451535
#[unstable(
15461536
feature = "cfg_accessible",

core/src/prelude/v1.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,7 @@ pub use crate::macros::builtin::{RustcDecodable, RustcEncodable};
7676
// Do not `doc(no_inline)` so that they become doc items on their own
7777
// (no public module for them to be re-exported from).
7878
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
79-
pub use crate::macros::builtin::{
80-
alloc_error_handler, bench, derive, global_allocator, test, test_case,
81-
};
79+
pub use crate::macros::builtin::{bench, derive, global_allocator, test, test_case};
8280

8381
#[unstable(feature = "derive_const", issue = "none")]
8482
pub use crate::macros::builtin::derive_const;

std/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ llvm-libunwind = ["unwind/llvm-libunwind"]
6767
system-llvm-libunwind = ["unwind/system-llvm-libunwind"]
6868

6969
# Make panics and failed asserts immediately abort without formatting any message
70-
panic_immediate_abort = ["core/panic_immediate_abort"]
70+
panic_immediate_abort = ["alloc/panic_immediate_abort"]
7171

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

std/src/alloc.rs

+1-72
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@
5757
#![stable(feature = "alloc_module", since = "1.28.0")]
5858

5959
use core::intrinsics;
60+
use core::ptr;
6061
use core::ptr::NonNull;
61-
use core::sync::atomic::{AtomicPtr, Ordering};
62-
use core::{mem, ptr};
6362

6463
#[stable(feature = "alloc_module", since = "1.28.0")]
6564
#[doc(inline)]
@@ -286,76 +285,6 @@ unsafe impl Allocator for System {
286285
}
287286
}
288287

289-
static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
290-
291-
/// Registers a custom allocation error hook, replacing any that was previously registered.
292-
///
293-
/// The allocation error hook is invoked when an infallible memory allocation fails, before
294-
/// the runtime aborts. The default hook prints a message to standard error,
295-
/// but this behavior can be customized with the [`set_alloc_error_hook`] and
296-
/// [`take_alloc_error_hook`] functions.
297-
///
298-
/// The hook is provided with a `Layout` struct which contains information
299-
/// about the allocation that failed.
300-
///
301-
/// The allocation error hook is a global resource.
302-
///
303-
/// # Examples
304-
///
305-
/// ```
306-
/// #![feature(alloc_error_hook)]
307-
///
308-
/// use std::alloc::{Layout, set_alloc_error_hook};
309-
///
310-
/// fn custom_alloc_error_hook(layout: Layout) {
311-
/// panic!("memory allocation of {} bytes failed", layout.size());
312-
/// }
313-
///
314-
/// set_alloc_error_hook(custom_alloc_error_hook);
315-
/// ```
316-
#[unstable(feature = "alloc_error_hook", issue = "51245")]
317-
pub fn set_alloc_error_hook(hook: fn(Layout)) {
318-
HOOK.store(hook as *mut (), Ordering::SeqCst);
319-
}
320-
321-
/// Unregisters the current allocation error hook, returning it.
322-
///
323-
/// *See also the function [`set_alloc_error_hook`].*
324-
///
325-
/// If no custom hook is registered, the default hook will be returned.
326-
#[unstable(feature = "alloc_error_hook", issue = "51245")]
327-
pub fn take_alloc_error_hook() -> fn(Layout) {
328-
let hook = HOOK.swap(ptr::null_mut(), Ordering::SeqCst);
329-
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } }
330-
}
331-
332-
fn default_alloc_error_hook(layout: Layout) {
333-
extern "Rust" {
334-
// This symbol is emitted by rustc next to __rust_alloc_error_handler.
335-
// Its value depends on the -Zoom={panic,abort} compiler option.
336-
static __rust_alloc_error_handler_should_panic: u8;
337-
}
338-
339-
#[allow(unused_unsafe)]
340-
if unsafe { __rust_alloc_error_handler_should_panic != 0 } {
341-
panic!("memory allocation of {} bytes failed", layout.size());
342-
} else {
343-
rtprintpanic!("memory allocation of {} bytes failed\n", layout.size());
344-
}
345-
}
346-
347-
#[cfg(not(test))]
348-
#[doc(hidden)]
349-
#[alloc_error_handler]
350-
#[unstable(feature = "alloc_internals", issue = "none")]
351-
pub fn rust_oom(layout: Layout) -> ! {
352-
let hook = HOOK.load(Ordering::SeqCst);
353-
let hook: fn(Layout) =
354-
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } };
355-
hook(layout);
356-
crate::process::abort()
357-
}
358-
359288
#[cfg(not(test))]
360289
#[doc(hidden)]
361290
#[allow(unused_attributes)]

std/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,6 @@
236236
//
237237
// Language features:
238238
// tidy-alphabetical-start
239-
#![feature(alloc_error_handler)]
240239
#![feature(allocator_internals)]
241240
#![feature(allow_internal_unsafe)]
242241
#![feature(allow_internal_unstable)]
@@ -319,6 +318,7 @@
319318
#![feature(get_mut_unchecked)]
320319
#![feature(map_try_insert)]
321320
#![feature(new_uninit)]
321+
#![feature(panic_oom_payload)]
322322
#![feature(slice_concat_trait)]
323323
#![feature(thin_box)]
324324
#![feature(try_reserve_kind)]

std/src/panicking.rs

+26-17
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

@@ -524,6 +529,8 @@ pub fn panicking() -> bool {
524529
#[cfg(not(test))]
525530
#[panic_handler]
526531
pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
532+
use alloc::alloc::AllocErrorPanicPayload;
533+
527534
struct PanicPayload<'a> {
528535
inner: &'a fmt::Arguments<'a>,
529536
string: Option<String>,
@@ -550,8 +557,7 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
550557
unsafe impl<'a> BoxMeUp for PanicPayload<'a> {
551558
fn take_box(&mut self) -> *mut (dyn Any + Send) {
552559
// We do two allocations here, unfortunately. But (a) they're required with the current
553-
// scheme, and (b) we don't handle panic + OOM properly anyway (see comment in
554-
// begin_panic below).
560+
// scheme, and (b) OOM uses its own separate payload type which doesn't allocate.
555561
let contents = mem::take(self.fill());
556562
Box::into_raw(Box::new(contents))
557563
}
@@ -576,7 +582,14 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
576582
let loc = info.location().unwrap(); // The current implementation always returns Some
577583
let msg = info.message().unwrap(); // The current implementation always returns Some
578584
crate::sys_common::backtrace::__rust_end_short_backtrace(move || {
579-
if let Some(msg) = msg.as_str() {
585+
if let Some(payload) = info.payload().downcast_ref::<AllocErrorPanicPayload>() {
586+
rust_panic_with_hook(
587+
&mut payload.internal_clone(),
588+
info.message(),
589+
loc,
590+
info.can_unwind(),
591+
);
592+
} else if let Some(msg) = msg.as_str() {
580593
rust_panic_with_hook(&mut StrPanicPayload(msg), info.message(), loc, info.can_unwind());
581594
} else {
582595
rust_panic_with_hook(
@@ -623,11 +636,7 @@ pub const fn begin_panic<M: Any + Send>(msg: M) -> ! {
623636

624637
unsafe impl<A: Send + 'static> BoxMeUp for PanicPayload<A> {
625638
fn take_box(&mut self) -> *mut (dyn Any + Send) {
626-
// Note that this should be the only allocation performed in this code path. Currently
627-
// this means that panic!() on OOM will invoke this code path, but then again we're not
628-
// really ready for panic on OOM anyway. If we do start doing this, then we should
629-
// propagate this allocation to be performed in the parent of this thread instead of the
630-
// thread that's panicking.
639+
// Note that this should be the only allocation performed in this code path.
631640
let data = match self.inner.take() {
632641
Some(a) => Box::new(a) as Box<dyn Any + Send>,
633642
None => process::abort(),

std/src/prelude/v1.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,7 @@ pub use core::prelude::v1::{RustcDecodable, RustcEncodable};
6060
// Do not `doc(no_inline)` so that they become doc items on their own
6161
// (no public module for them to be re-exported from).
6262
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
63-
pub use core::prelude::v1::{
64-
alloc_error_handler, bench, derive, global_allocator, test, test_case,
65-
};
63+
pub use core::prelude::v1::{bench, derive, global_allocator, test, test_case};
6664

6765
#[unstable(feature = "derive_const", issue = "none")]
6866
pub use core::prelude::v1::derive_const;

0 commit comments

Comments
 (0)