Report allocation errors as panics
This commit is contained in:
parent
2816486986
commit
c9a6e41026
6 changed files with 107 additions and 27 deletions
|
@ -35,3 +35,6 @@ compiler-builtins-mem = ['compiler_builtins/mem']
|
||||||
compiler-builtins-c = ["compiler_builtins/c"]
|
compiler-builtins-c = ["compiler_builtins/c"]
|
||||||
compiler-builtins-no-asm = ["compiler_builtins/no-asm"]
|
compiler-builtins-no-asm = ["compiler_builtins/no-asm"]
|
||||||
compiler-builtins-mangled-names = ["compiler_builtins/mangled-names"]
|
compiler-builtins-mangled-names = ["compiler_builtins/mangled-names"]
|
||||||
|
|
||||||
|
# Make panics and failed asserts immediately abort without formatting any message
|
||||||
|
panic_immediate_abort = ["core/panic_immediate_abort"]
|
||||||
|
|
|
@ -14,6 +14,11 @@ use core::ptr::{self, NonNull};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use core::alloc::*;
|
pub use core::alloc::*;
|
||||||
|
|
||||||
|
#[cfg(not(no_global_oom_handling))]
|
||||||
|
use core::any::Any;
|
||||||
|
#[cfg(not(no_global_oom_handling))]
|
||||||
|
use core::panic::BoxMeUp;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
@ -343,14 +348,77 @@ pub(crate) unsafe fn box_free<T: ?Sized, A: Allocator>(ptr: Unique<T>, alloc: A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Payload passed to the panic handler when `handle_alloc_error` is called.
|
||||||
|
#[unstable(feature = "panic_oom_payload", issue = "none")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AllocErrorPanicPayload {
|
||||||
|
layout: Layout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AllocErrorPanicPayload {
|
||||||
|
/// Internal function for the standard library to clone a payload.
|
||||||
|
#[unstable(feature = "std_internals", issue = "none")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn internal_clone(&self) -> Self {
|
||||||
|
AllocErrorPanicPayload { layout: self.layout }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`Layout`] of the allocation attempt that caused the error.
|
||||||
|
#[unstable(feature = "panic_oom_payload", issue = "none")]
|
||||||
|
pub fn layout(&self) -> Layout {
|
||||||
|
self.layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "std_internals", issue = "none")]
|
||||||
|
#[cfg(not(no_global_oom_handling))]
|
||||||
|
unsafe impl BoxMeUp for AllocErrorPanicPayload {
|
||||||
|
fn take_box(&mut self) -> *mut (dyn Any + Send) {
|
||||||
|
use crate::boxed::Box;
|
||||||
|
Box::into_raw(Box::new(self.internal_clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&mut self) -> &(dyn Any + Send) {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// # Allocation error handler
|
// # Allocation error handler
|
||||||
|
|
||||||
#[cfg(not(no_global_oom_handling))]
|
#[cfg(all(not(no_global_oom_handling), not(test)))]
|
||||||
extern "Rust" {
|
fn rust_oom(layout: Layout) -> ! {
|
||||||
// This is the magic symbol to call the global alloc error handler. rustc generates
|
if cfg!(feature = "panic_immediate_abort") {
|
||||||
// it to call `__rg_oom` if there is a `#[alloc_error_handler]`, or to call the
|
core::intrinsics::abort()
|
||||||
// default implementations below (`__rdl_oom`) otherwise.
|
}
|
||||||
fn __rust_alloc_error_handler(size: usize, align: usize) -> !;
|
|
||||||
|
extern "Rust" {
|
||||||
|
// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call
|
||||||
|
// that gets resolved to the `#[panic_handler]` function.
|
||||||
|
#[lang = "panic_impl"]
|
||||||
|
fn panic_impl(pi: &core::panic::PanicInfo<'_>) -> !;
|
||||||
|
|
||||||
|
// This symbol is emitted by rustc next to __rust_alloc_error_handler.
|
||||||
|
// Its value depends on the -Zoom={panic,abort} compiler option.
|
||||||
|
static __rust_alloc_error_handler_should_panic: u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hack to work around issues with the lifetime of Arguments.
|
||||||
|
match format_args!("memory allocation of {} bytes failed", layout.size()) {
|
||||||
|
fmt => {
|
||||||
|
// Create a PanicInfo with a custom payload for the panic handler.
|
||||||
|
let can_unwind = unsafe { __rust_alloc_error_handler_should_panic != 0 };
|
||||||
|
let mut pi = core::panic::PanicInfo::internal_constructor(
|
||||||
|
Some(&fmt),
|
||||||
|
core::panic::Location::caller(),
|
||||||
|
can_unwind,
|
||||||
|
);
|
||||||
|
let payload = AllocErrorPanicPayload { layout };
|
||||||
|
pi.set_payload(&payload);
|
||||||
|
|
||||||
|
// SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call.
|
||||||
|
unsafe { panic_impl(&pi) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Abort on memory allocation error or failure.
|
/// Abort on memory allocation error or failure.
|
||||||
|
@ -375,9 +443,7 @@ pub const fn handle_alloc_error(layout: Layout) -> ! {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rt_error(layout: Layout) -> ! {
|
fn rt_error(layout: Layout) -> ! {
|
||||||
unsafe {
|
rust_oom(layout);
|
||||||
__rust_alloc_error_handler(layout.size(), layout.align());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { core::intrinsics::const_eval_select((layout,), ct_error, rt_error) }
|
unsafe { core::intrinsics::const_eval_select((layout,), ct_error, rt_error) }
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
#![feature(maybe_uninit_slice)]
|
#![feature(maybe_uninit_slice)]
|
||||||
#![feature(maybe_uninit_uninit_array)]
|
#![feature(maybe_uninit_uninit_array)]
|
||||||
#![feature(maybe_uninit_uninit_array_transpose)]
|
#![feature(maybe_uninit_uninit_array_transpose)]
|
||||||
|
#![feature(panic_internals)]
|
||||||
#![feature(pattern)]
|
#![feature(pattern)]
|
||||||
#![feature(pointer_byte_offsets)]
|
#![feature(pointer_byte_offsets)]
|
||||||
#![feature(provide_any)]
|
#![feature(provide_any)]
|
||||||
|
|
|
@ -67,7 +67,7 @@ llvm-libunwind = ["unwind/llvm-libunwind"]
|
||||||
system-llvm-libunwind = ["unwind/system-llvm-libunwind"]
|
system-llvm-libunwind = ["unwind/system-llvm-libunwind"]
|
||||||
|
|
||||||
# Make panics and failed asserts immediately abort without formatting any message
|
# Make panics and failed asserts immediately abort without formatting any message
|
||||||
panic_immediate_abort = ["core/panic_immediate_abort"]
|
panic_immediate_abort = ["alloc/panic_immediate_abort"]
|
||||||
|
|
||||||
# Enable std_detect default features for stdarch/crates/std_detect:
|
# Enable std_detect default features for stdarch/crates/std_detect:
|
||||||
# https://github.com/rust-lang/stdarch/blob/master/crates/std_detect/Cargo.toml
|
# https://github.com/rust-lang/stdarch/blob/master/crates/std_detect/Cargo.toml
|
||||||
|
|
|
@ -319,6 +319,7 @@
|
||||||
#![feature(get_mut_unchecked)]
|
#![feature(get_mut_unchecked)]
|
||||||
#![feature(map_try_insert)]
|
#![feature(map_try_insert)]
|
||||||
#![feature(new_uninit)]
|
#![feature(new_uninit)]
|
||||||
|
#![feature(panic_oom_payload)]
|
||||||
#![feature(slice_concat_trait)]
|
#![feature(slice_concat_trait)]
|
||||||
#![feature(thin_box)]
|
#![feature(thin_box)]
|
||||||
#![feature(try_reserve_kind)]
|
#![feature(try_reserve_kind)]
|
||||||
|
|
|
@ -245,19 +245,24 @@ fn default_hook(info: &PanicInfo<'_>) {
|
||||||
|
|
||||||
// The current implementation always returns `Some`.
|
// The current implementation always returns `Some`.
|
||||||
let location = info.location().unwrap();
|
let location = info.location().unwrap();
|
||||||
|
|
||||||
let msg = match info.payload().downcast_ref::<&'static str>() {
|
|
||||||
Some(s) => *s,
|
|
||||||
None => match info.payload().downcast_ref::<String>() {
|
|
||||||
Some(s) => &s[..],
|
|
||||||
None => "Box<dyn Any>",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let thread = thread_info::current_thread();
|
let thread = thread_info::current_thread();
|
||||||
let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>");
|
let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>");
|
||||||
|
|
||||||
let write = |err: &mut dyn crate::io::Write| {
|
let write = |err: &mut dyn crate::io::Write| {
|
||||||
|
// Use the panic message directly if available, otherwise take it from
|
||||||
|
// the payload.
|
||||||
|
if let Some(msg) = info.message() {
|
||||||
let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}");
|
let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}");
|
||||||
|
} else {
|
||||||
|
let msg = if let Some(s) = info.payload().downcast_ref::<&'static str>() {
|
||||||
|
*s
|
||||||
|
} else if let Some(s) = info.payload().downcast_ref::<String>() {
|
||||||
|
&s[..]
|
||||||
|
} else {
|
||||||
|
"Box<dyn Any>"
|
||||||
|
};
|
||||||
|
let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}");
|
||||||
|
}
|
||||||
|
|
||||||
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
|
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
|
||||||
|
|
||||||
|
@ -524,6 +529,8 @@ pub fn panicking() -> bool {
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
|
pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
|
||||||
|
use alloc::alloc::AllocErrorPanicPayload;
|
||||||
|
|
||||||
struct PanicPayload<'a> {
|
struct PanicPayload<'a> {
|
||||||
inner: &'a fmt::Arguments<'a>,
|
inner: &'a fmt::Arguments<'a>,
|
||||||
string: Option<String>,
|
string: Option<String>,
|
||||||
|
@ -550,8 +557,7 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
|
||||||
unsafe impl<'a> BoxMeUp for PanicPayload<'a> {
|
unsafe impl<'a> BoxMeUp for PanicPayload<'a> {
|
||||||
fn take_box(&mut self) -> *mut (dyn Any + Send) {
|
fn take_box(&mut self) -> *mut (dyn Any + Send) {
|
||||||
// We do two allocations here, unfortunately. But (a) they're required with the current
|
// 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
|
// scheme, and (b) OOM uses its own separate payload type which doesn't allocate.
|
||||||
// begin_panic below).
|
|
||||||
let contents = mem::take(self.fill());
|
let contents = mem::take(self.fill());
|
||||||
Box::into_raw(Box::new(contents))
|
Box::into_raw(Box::new(contents))
|
||||||
}
|
}
|
||||||
|
@ -576,7 +582,14 @@ pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! {
|
||||||
let loc = info.location().unwrap(); // The current implementation always returns Some
|
let loc = info.location().unwrap(); // The current implementation always returns Some
|
||||||
let msg = info.message().unwrap(); // The current implementation always returns Some
|
let msg = info.message().unwrap(); // The current implementation always returns Some
|
||||||
crate::sys_common::backtrace::__rust_end_short_backtrace(move || {
|
crate::sys_common::backtrace::__rust_end_short_backtrace(move || {
|
||||||
if let Some(msg) = msg.as_str() {
|
if let Some(payload) = info.payload().downcast_ref::<AllocErrorPanicPayload>() {
|
||||||
|
rust_panic_with_hook(
|
||||||
|
&mut payload.internal_clone(),
|
||||||
|
info.message(),
|
||||||
|
loc,
|
||||||
|
info.can_unwind(),
|
||||||
|
);
|
||||||
|
} else if let Some(msg) = msg.as_str() {
|
||||||
rust_panic_with_hook(&mut StrPanicPayload(msg), info.message(), loc, info.can_unwind());
|
rust_panic_with_hook(&mut StrPanicPayload(msg), info.message(), loc, info.can_unwind());
|
||||||
} else {
|
} else {
|
||||||
rust_panic_with_hook(
|
rust_panic_with_hook(
|
||||||
|
@ -623,11 +636,7 @@ pub const fn begin_panic<M: Any + Send>(msg: M) -> ! {
|
||||||
|
|
||||||
unsafe impl<A: Send + 'static> BoxMeUp for PanicPayload<A> {
|
unsafe impl<A: Send + 'static> BoxMeUp for PanicPayload<A> {
|
||||||
fn take_box(&mut self) -> *mut (dyn Any + Send) {
|
fn take_box(&mut self) -> *mut (dyn Any + Send) {
|
||||||
// Note that this should be the only allocation performed in this code path. Currently
|
// Note that this should be the only allocation performed in this code path.
|
||||||
// this means that panic!() on OOM will invoke this code path, but then again we're not
|
|
||||||
// really ready for panic on OOM anyway. If we do start doing this, then we should
|
|
||||||
// propagate this allocation to be performed in the parent of this thread instead of the
|
|
||||||
// thread that's panicking.
|
|
||||||
let data = match self.inner.take() {
|
let data = match self.inner.take() {
|
||||||
Some(a) => Box::new(a) as Box<dyn Any + Send>,
|
Some(a) => Box::new(a) as Box<dyn Any + Send>,
|
||||||
None => process::abort(),
|
None => process::abort(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue