diff --git a/crates/oxc_allocator/src/pool/fixed_size.rs b/crates/oxc_allocator/src/pool/fixed_size.rs index 0d6be9f9cc773..766f3a5a86ace 100644 --- a/crates/oxc_allocator/src/pool/fixed_size.rs +++ b/crates/oxc_allocator/src/pool/fixed_size.rs @@ -1,5 +1,7 @@ use std::{ - alloc::{self, GlobalAlloc, Layout, System}, + alloc::{GlobalAlloc, Layout, System}, + error::Error, + fmt, mem::{self, ManuallyDrop}, ptr::NonNull, sync::{ @@ -15,6 +17,26 @@ use crate::{ generated::fixed_size_constants::{BLOCK_ALIGN, BLOCK_SIZE, RAW_METADATA_SIZE}, }; +/// Error returned when a fixed-size allocator cannot be created due to allocation failure. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AllocError { + /// The layout of the allocation that failed. + pub layout: Layout, +} + +impl fmt::Display for AllocError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "memory allocation failed for fixed-size allocator: requested {} bytes with {} byte alignment", + self.layout.size(), + self.layout.align() + ) + } +} + +impl Error for AllocError {} + const TWO_GIB: usize = 1 << 31; const FOUR_GIB: usize = 1 << 32; @@ -53,7 +75,7 @@ impl FixedSizeAllocatorPool { // Protect against IDs wrapping around. // TODO: Does this work? Do we need it anyway? assert!(id < u32::MAX, "Created too many allocators"); - FixedSizeAllocator::new(id) + FixedSizeAllocator::try_new(id).unwrap() }); // Unwrap `FixedSizeAllocator`. @@ -187,9 +209,11 @@ struct FixedSizeAllocator { } impl FixedSizeAllocator { - /// Create a new [`FixedSizeAllocator`]. + /// Try to create a new [`FixedSizeAllocator`]. + /// + /// Returns `Err(AllocError)` if memory allocation fails. #[expect(clippy::items_after_statements)] - fn new(id: u32) -> Self { + fn try_new(id: u32) -> Result { // Only support little-endian systems. `Allocator::from_raw_parts` includes this same assertion. // This module is only compiled on 64-bit little-endian systems, so it should be impossible for // this panic to occur. But we want to make absolutely sure that if there's a mistake elsewhere, @@ -203,9 +227,7 @@ impl FixedSizeAllocator { // Allocate block of memory. // SAFETY: `ALLOC_LAYOUT` does not have zero size. let alloc_ptr = unsafe { System.alloc(ALLOC_LAYOUT) }; - let Some(alloc_ptr) = NonNull::new(alloc_ptr) else { - alloc::handle_alloc_error(ALLOC_LAYOUT); - }; + let alloc_ptr = NonNull::new(alloc_ptr).ok_or(AllocError { layout: ALLOC_LAYOUT })?; // All code in the rest of this function is infallible, so the allocation will always end up // owned by a `FixedSizeAllocator`, which takes care of freeing the memory correctly on drop @@ -254,7 +276,7 @@ impl FixedSizeAllocator { metadata_ptr.write(metadata); } - Self { allocator } + Ok(Self { allocator }) } /// Reset this [`FixedSizeAllocator`]. diff --git a/crates/oxc_allocator/src/pool/mod.rs b/crates/oxc_allocator/src/pool/mod.rs index 5c558da03de39..73d4ce7989fd6 100644 --- a/crates/oxc_allocator/src/pool/mod.rs +++ b/crates/oxc_allocator/src/pool/mod.rs @@ -66,7 +66,8 @@ impl AllocatorPool { /// /// # Panics /// - /// Panics if the underlying mutex is poisoned. + /// * Panics if the underlying mutex is poisoned. + /// * Panics if a new allocator needs to be created but memory allocation fails. pub fn get(&self) -> AllocatorGuard<'_> { let allocator = match &self.0 { AllocatorPoolInner::Standard(pool) => pool.get(),