Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions library/alloc/src/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,21 +517,21 @@ unsafe extern "Rust" {
/// in response to an allocation error are encouraged to call this function,
/// rather than directly invoking [`panic!`] or similar.
///
/// This function is guaranteed to diverge (not return normally with a value), but depending on
/// global configuration, it may either panic (resulting in unwinding or aborting as per
/// configuration for all panics), or abort the process (with no unwinding).
/// Callers may assume that this function always aborts execution. However, implementors that do not
/// rely on this invariant are encouraged to document this in order to be compatible with users of
/// [`set_alloc_error_hook_unwinding`].

@nia-e nia-e Jul 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @rust-lang/libs-api - this does need fcp after all (see discussion on zulip)

View changes since the review

This comment was marked as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxdexh correctly points out that this tells clients of the global allocator to call handle_alloc_error, not global allocators themselves. Sorry for the noise.

///
/// The default behavior is:
///
/// * If the binary links against `std` (typically the case), then
/// print a message to standard error and abort the process.
/// This behavior can be replaced with [`set_alloc_error_hook`] and [`take_alloc_error_hook`].
/// Future versions of Rust may panic by default instead.
///
/// * If the binary does not link against `std` (all of its crates are marked
/// [`#![no_std]`][no_std]), then call [`panic!`] with a message.
/// [The panic handler] applies as to any panic.
///
/// [`set_alloc_error_hook_unwinding`]: ../../std/alloc/fn.set_alloc_error_hook_unwinding.html
/// [`set_alloc_error_hook`]: ../../std/alloc/fn.set_alloc_error_hook.html
/// [`take_alloc_error_hook`]: ../../std/alloc/fn.take_alloc_error_hook.html
/// [The panic handler]: https://doc.rust-lang.org/reference/runtime.html#the-panic_handler-attribute
Expand Down
6 changes: 4 additions & 2 deletions library/alloctests/tests/c_str_alloc_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#![cfg(not(all(miri, windows)))]
#![feature(alloc_error_hook)]

use std::alloc::{GlobalAlloc, Layout, System, set_alloc_error_hook};
use std::alloc::{GlobalAlloc, Layout, System, set_alloc_error_hook_unwinding};
use std::ffi::CString;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::{AtomicBool, Ordering};
Expand Down Expand Up @@ -52,7 +52,9 @@ static ALLOC: OneShotFailingAlloc = OneShotFailingAlloc;
#[test]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn clone_into_alloc_failure_leaves_target_valid() {
set_alloc_error_hook(|_| panic!("alloc error"));
unsafe {
set_alloc_error_hook_unwinding(|_| panic!("alloc error"));
}

let src = CString::new("a fairly long value").unwrap();
let mut target = CString::new("x").unwrap();
Expand Down
6 changes: 4 additions & 2 deletions library/alloctests/tests/vec_deque_alloc_error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![feature(alloc_error_hook, allocator_api)]

use std::alloc::{AllocError, Allocator, Layout, System, set_alloc_error_hook};
use std::alloc::{AllocError, Allocator, Layout, System, set_alloc_error_hook_unwinding};
use std::collections::VecDeque;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr::NonNull;
Expand Down Expand Up @@ -36,7 +36,9 @@ fn test_shrink_to_unwind() {
}
}

set_alloc_error_hook(|_| panic!("alloc error"));
unsafe {
set_alloc_error_hook_unwinding(|_| panic!("alloc error"));
}

let mut v = VecDeque::with_capacity_in(15, BadAlloc);
v.push_back(1);
Expand Down
38 changes: 36 additions & 2 deletions library/std/src/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ unsafe impl Allocator for System {
unsafe impl GlobalAllocator for System {}

static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
/// Do we abort on unwind from the hook?
static ABORT: AtomicBool = AtomicBool::new(true);
Comment thread
RalfJung marked this conversation as resolved.

/// Registers a custom allocation error hook, replacing any that was previously registered.
///
Expand All @@ -310,8 +312,9 @@ static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
/// The hook function is provided with a [`Layout`] struct which contains information
/// about the allocation that failed.
///
/// The hook function may choose to panic or abort; in the event that it returns normally, this
/// will cause an immediate abort.
/// Regardless of whether the hook aborts, unwinds, or returns, the process will always abort
/// immediately after the hook is executed; an unwind will never propagate out of
/// `handle_alloc_error`.
///
/// Since [`take_alloc_error_hook`] is a safe function that allows retrieving the hook, the hook
/// function must be _sound_ to call even if no memory allocations were attempted.
Expand All @@ -338,6 +341,29 @@ static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
/// ```
#[unstable(feature = "alloc_error_hook", issue = "51245")]
pub fn set_alloc_error_hook(hook: fn(Layout)) {
ABORT.store(true, Ordering::Release);
HOOK.store(hook as *mut (), Ordering::Release);
}

/// Registers a custom allocation error hook, replacing any that was previously registered.
///
/// Unlike [`set_alloc_error_hook`], unwinds from the hook may propagate.
///
/// # Safety
///
/// Unwinding from the allocation error hook is not unsafe per se; however, callers of
/// [`handle_alloc_error`] may assume that that method will never unwind for safety and/or
Comment thread
RalfJung marked this conversation as resolved.
/// correctness. Note that this [also includes][example-1] the [standard library][example-2]
/// at times. Thus, callers must ensure that only code which explicitly promises *not* to
/// rely on this guarantee of `handle_alloc_error` may be called between setting an unwinding
/// hook and unsetting it via [`take_alloc_error_hook`]. This requirement applies across threads,
/// as the hooks are global.
///
/// [example-1]: https://github.com/rust-lang/rust/issues/157203
/// [example-2]: https://github.com/rust-lang/rust/issues/156490
#[unstable(feature = "alloc_error_hook", issue = "51245")]
pub unsafe fn set_alloc_error_hook_unwinding(hook: fn(Layout)) {
ABORT.store(false, Ordering::Release);
HOOK.store(hook as *mut (), Ordering::Release);
}

Expand Down Expand Up @@ -423,6 +449,14 @@ fn default_alloc_error_hook(layout: Layout) {
#[unstable(feature = "alloc_internals", issue = "none")]
pub fn rust_oom(layout: Layout) -> ! {
crate::sys::backtrace::__rust_end_short_backtrace(|| {
use core::mem::DropGuard;

let guard = DropGuard::new((), |_| crate::process::abort());
let abort = ABORT.load(Ordering::Acquire);
// Unwinds permitted, don't arm the guard.
Comment thread
RalfJung marked this conversation as resolved.
if !abort {
DropGuard::dismiss(guard);
}
let hook = HOOK.load(Ordering::Acquire);
let hook: fn(Layout) =
if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } };
Expand Down
4 changes: 3 additions & 1 deletion src/tools/miri/tests/panic/alloc_error_handler_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ impl Drop for Bomb {
#[allow(unreachable_code, unused_variables)]
fn main() {
// This is a particularly tricky hook, since it unwinds, which the default one does not.
set_alloc_error_hook(|_layout| panic!("alloc error hook called"));
unsafe {
set_alloc_error_hook_unwinding(|_layout| panic!("alloc error hook called"));
}

let bomb = Bomb;
handle_alloc_error(Layout::for_value(&0));
Expand Down
7 changes: 5 additions & 2 deletions tests/ui/panics/alloc_error_hook-unwind.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Test that out-of-memory conditions trigger catchable panics with `set_alloc_error_hook`.
//! Test that out-of-memory conditions trigger catchable panics
//! with `set_alloc_error_hook_unwinding`.

//@ run-pass
//@ needs-unwind
Expand All @@ -12,7 +13,9 @@ use std::mem::forget;
use std::panic::catch_unwind;

fn main() {
std::alloc::set_alloc_error_hook(|_| panic!());
unsafe {
std::alloc::set_alloc_error_hook_unwinding(|_| panic!());
}

let panic = catch_unwind(|| {
// This is guaranteed to exceed even the size of the address space
Expand Down
Loading