From 32b859c96d1cfdb8a0e8c108fd8989ddf054be9e Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 26 Jan 2026 14:56:37 -0800 Subject: [PATCH 1/4] Add some more OOM-handling `Box<[T]>` constructor variants Part of https://github.com/bytecodealliance/wasmtime/issues/12069 --- cranelift/bitset/src/compound.rs | 8 +- crates/core/src/alloc.rs | 6 +- crates/core/src/alloc/boxed.rs | 328 +++++++++++++++++++++++++------ 3 files changed, 281 insertions(+), 61 deletions(-) diff --git a/cranelift/bitset/src/compound.rs b/cranelift/bitset/src/compound.rs index ed5991821fa9..5ebe10bf1f7a 100644 --- a/cranelift/bitset/src/compound.rs +++ b/cranelift/bitset/src/compound.rs @@ -295,7 +295,7 @@ impl CompoundBitSet { let to_grow = cmp::max(to_grow, 4); let new_len = self.elems.len() + to_grow; - match wasmtime_core::alloc::new_boxed_slice_from_iter( + match wasmtime_core::alloc::new_boxed_slice_from_iter_with_len( new_len, self.elems .iter() @@ -306,8 +306,10 @@ impl CompoundBitSet { self.elems = new_elems; Ok(()) } - Err(wasmtime_core::alloc::BoxedSliceFromIterError::Oom(oom)) => Err(oom), - Err(wasmtime_core::alloc::BoxedSliceFromIterError::TooFewItems) => unreachable!(), + Err(wasmtime_core::alloc::BoxedSliceFromIterWithLenError::Oom(oom)) => Err(oom), + Err(wasmtime_core::alloc::BoxedSliceFromIterWithLenError::TooFewItems) => { + unreachable!() + } } } diff --git a/crates/core/src/alloc.rs b/crates/core/src/alloc.rs index e5260512e1de..695c8bb39836 100644 --- a/crates/core/src/alloc.rs +++ b/crates/core/src/alloc.rs @@ -5,7 +5,11 @@ mod boxed; mod try_new; mod vec; -pub use boxed::{BoxedSliceFromIterError, new_boxed_slice_from_iter}; +pub use boxed::{ + BoxedSliceFromFallibleIterError, BoxedSliceFromIterWithLenError, + new_boxed_slice_from_fallible_iter, new_boxed_slice_from_iter, + new_boxed_slice_from_iter_with_len, +}; pub use try_new::{TryNew, try_new}; pub use vec::Vec; diff --git a/crates/core/src/alloc/boxed.rs b/crates/core/src/alloc/boxed.rs index 840e063fc735..8bebac9e8c1b 100644 --- a/crates/core/src/alloc/boxed.rs +++ b/crates/core/src/alloc/boxed.rs @@ -1,6 +1,9 @@ use super::{TryNew, try_alloc}; use crate::error::OutOfMemory; -use core::{alloc::Layout, mem::MaybeUninit}; +use core::{ + alloc::Layout, + mem::{self, MaybeUninit}, +}; use std_alloc::boxed::Box; /// Allocate an `Box>` with uninitialized contents, returning @@ -41,7 +44,7 @@ impl TryNew for Box { fn new_uninit_boxed_slice(len: usize) -> Result]>, OutOfMemory> { let layout = Layout::array::>(len) - .map_err(|_| OutOfMemory::new(len.saturating_mul(core::mem::size_of::())))?; + .map_err(|_| OutOfMemory::new(len.saturating_mul(mem::size_of::())))?; if layout.size() == 0 { // NB: no actual allocation takes place when boxing zero-sized @@ -63,39 +66,161 @@ fn new_uninit_boxed_slice(len: usize) -> Result]>, OutOfM Ok(boxed) } +/// RAII guard to handle dropping the initialized elements of the boxed +/// slice in the cases where we get too few items or the iterator panics. +struct DropGuard { + boxed: Box<[MaybeUninit]>, + init_len: usize, +} + +impl Drop for DropGuard { + fn drop(&mut self) { + debug_assert!(self.init_len <= self.boxed.len()); + + if !mem::needs_drop::() { + return; + } + + for elem in self.boxed.iter_mut().take(self.init_len) { + // Safety: the elements in `self.boxed[..self.init_len]` are + // valid and initialized and will not be used again. + unsafe { + core::ptr::drop_in_place(elem.as_mut_ptr()); + } + } + } +} + +impl DropGuard { + fn new(len: usize) -> Result { + Ok(DropGuard { + boxed: new_uninit_boxed_slice(len)?, + init_len: 0, + }) + } + + /// Finish this guard and take its boxed slice out. + fn finish(mut self) -> Box<[MaybeUninit]> { + self.init_len = 0; + let boxed = mem::take(&mut self.boxed); + mem::forget(self); + boxed + } + + /// Reallocate this guard's boxed slice such that its length doubles. + fn double(&mut self) -> Result<(), OutOfMemory> { + let new_len = self.boxed.len().saturating_mul(2); + let new_len = core::cmp::max(new_len, 4); + self.realloc(new_len) + } + + /// Shrink this guard's boxed slice to exactly the initalized length. + fn shrink_to_fit(&mut self) -> Result<(), OutOfMemory> { + self.realloc(self.init_len) + } + + /// Reallocate this guard's boxed slice to `new_len`. + /// + /// The number of initialized elements will remain the same, and `new_len` + /// must be greater than or equal to the initialized length. + fn realloc(&mut self, new_len: usize) -> Result<(), OutOfMemory> { + assert!(self.init_len <= new_len); + + if new_len == self.boxed.len() { + return Ok(()); + } + + let old_layout = Layout::array::(self.boxed.len()) + .expect("already have an allocation with this layout so should be able to recreate it"); + let new_layout = Layout::array::(new_len) + .map_err(|_| OutOfMemory::new(mem::size_of::().saturating_mul(new_len)))?; + debug_assert_eq!(old_layout.align(), new_layout.align()); + + // Temporarily take the boxed slice out of `self` and reset + // `self.init_len` so that if we panic during reallocation, we never get + // mixed up and see invalid state on `self` inside our `Drop` + // implementation. + let init_len = mem::take(&mut self.init_len); + let boxed = mem::take(&mut self.boxed); + let ptr = Box::into_raw(boxed); + + // Handle zero-sized reallocations, since the global `realloc` function + // does not. + if new_layout.size() == 0 { + debug_assert!(mem::size_of::() == 0 || new_len == 0); + if new_len == 0 { + debug_assert_eq!(self.boxed.len(), 0); + debug_assert_eq!(self.init_len, 0); + } else { + self.boxed = Box::new_uninit_slice(new_len); + self.init_len = init_len; + } + return Ok(()); + } + + // Safety: `ptr` was allocated by the global allocator, its memory block + // is described by `old_layout`, the new size is non-zero, and the new + // size will not overflow `isize::MAX` when rounded up to the layout's + // alignment (this is checked in the fallible construction of + // `new_layout`). + let new_ptr = + unsafe { std_alloc::alloc::realloc(ptr.cast::(), old_layout, new_layout.size()) }; + + // Update `self` based on whether the allocation succeeded or not, + // either inserting in the new slice or replacing the old slice. + if new_ptr.is_null() { + // Safety: The allocation failed so we retain ownership of `ptr`, + // which was a valid boxed slice and we can safely make it a boxed + // slice again. The block's contents were not modified, so the old + // `init_len` remains valid. + self.boxed = unsafe { Box::from_raw(ptr) }; + self.init_len = init_len; + Err(OutOfMemory::new(new_layout.size())) + } else { + let new_ptr = new_ptr.cast::>(); + let new_ptr = core::ptr::slice_from_raw_parts_mut(new_ptr, new_len); + // Safety: The allocation succeeded, `new_ptr` was allocated by the + // global allocator and points to a valid boxed slice of length + // `new_len`, and the old allocation's contents were copied over to + // the new allocation so the old `init_len` remains valid. + self.boxed = unsafe { Box::from_raw(new_ptr) }; + self.init_len = init_len; + Ok(()) + } + } +} + /// An error returned by [`new_boxed_slice_from_iter`]. #[derive(Debug)] -pub enum BoxedSliceFromIterError { +pub enum BoxedSliceFromIterWithLenError { /// The iterator did not yield enough items to fill the boxed slice. TooFewItems, /// Failed to allocate space for the boxed slice. Oom(OutOfMemory), } -impl From for BoxedSliceFromIterError { +impl From for BoxedSliceFromIterWithLenError { fn from(oom: OutOfMemory) -> Self { Self::Oom(oom) } } -impl core::fmt::Display for BoxedSliceFromIterError { +impl core::fmt::Display for BoxedSliceFromIterWithLenError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - BoxedSliceFromIterError::TooFewItems => { + Self::TooFewItems => { f.write_str("The iterator did not yield enough items to fill the boxed slice") } - BoxedSliceFromIterError::Oom(_) => { - f.write_str("Failed to allocate space for the boxed slice") - } + Self::Oom(_) => f.write_str("Failed to allocate space for the boxed slice"), } } } -impl core::error::Error for BoxedSliceFromIterError { +impl core::error::Error for BoxedSliceFromIterWithLenError { fn cause(&self) -> Option<&dyn core::error::Error> { match self { - BoxedSliceFromIterError::TooFewItems => None, - BoxedSliceFromIterError::Oom(oom) => Some(oom), + Self::TooFewItems => None, + Self::Oom(oom) => Some(oom), } } } @@ -107,63 +232,115 @@ impl core::error::Error for BoxedSliceFromIterError { /// /// The iterator is dropped after `len` elements have been yielded, this /// function does not check that the iterator yields exactly `len` elements. -pub fn new_boxed_slice_from_iter( +pub fn new_boxed_slice_from_iter_with_len( len: usize, iter: impl IntoIterator, -) -> Result, BoxedSliceFromIterError> { - /// RAII guard to handle dropping the initialized elements of the boxed - /// slice in the cases where we get too few items or the iterator panics. - struct DropGuard { - boxed: Box<[MaybeUninit]>, - init_len: usize, +) -> Result, BoxedSliceFromIterWithLenError> { + let mut guard = DropGuard::new(len)?; + assert_eq!(len, guard.boxed.len()); + + for (i, elem) in iter.into_iter().enumerate().take(len) { + debug_assert!(i < len); + debug_assert_eq!(guard.init_len, i); + guard.boxed[i].write(elem); + guard.init_len += 1; } - impl Drop for DropGuard { - fn drop(&mut self) { - debug_assert!(self.init_len <= self.boxed.len()); + debug_assert!(guard.init_len <= guard.boxed.len()); - if !core::mem::needs_drop::() { - return; - } + if guard.init_len < guard.boxed.len() { + return Err(BoxedSliceFromIterWithLenError::TooFewItems); + } - for elem in self.boxed.iter_mut().take(self.init_len) { - // Safety: the elements in `self.boxed[..self.init_len]` are - // valid and initialized and will not be used again. - unsafe { - core::ptr::drop_in_place(elem.as_mut_ptr()); - } - } + debug_assert_eq!(guard.init_len, guard.boxed.len()); + let boxed = guard.finish(); + + // Safety: we initialized all elements. + let boxed = unsafe { boxed.assume_init() }; + + Ok(boxed) +} + +/// An error returned by [`new_boxed_slice_from_fallible_iter`]. +#[derive(Debug)] +pub enum BoxedSliceFromFallibleIterError { + /// The fallible iterator produced an error. + IterError(E), + /// Failed to allocate space for the boxed slice. + Oom(OutOfMemory), +} + +impl From for BoxedSliceFromFallibleIterError { + fn from(oom: OutOfMemory) -> Self { + Self::Oom(oom) + } +} + +impl core::fmt::Display for BoxedSliceFromFallibleIterError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::IterError(_) => f.write_str("The fallible iterator produced an error"), + Self::Oom(_) => f.write_str("Failed to allocate space for the boxed slice"), + } + } +} + +impl core::error::Error for BoxedSliceFromFallibleIterError +where + E: core::error::Error, +{ + fn cause(&self) -> Option<&dyn core::error::Error> { + match self { + Self::IterError(e) => Some(e), + Self::Oom(oom) => Some(oom), } } +} + +impl BoxedSliceFromFallibleIterError { + /// Flatten this error into its inner OOM. + pub fn flatten(self) -> OutOfMemory { + match self { + Self::IterError(oom) | Self::Oom(oom) => oom, + } + } +} + +/// Create a `Box<[T]>` from the given iterator's `Result` items. +/// +/// Returns an error on allocation failure or if an iterator item is an `Err`. +pub fn new_boxed_slice_from_fallible_iter( + iter: impl IntoIterator>, +) -> Result, BoxedSliceFromFallibleIterError> { + let iter = iter.into_iter(); - let mut guard = DropGuard { - boxed: new_uninit_boxed_slice(len)?, - init_len: 0, - }; + let (min, max) = iter.size_hint(); + let len = max.unwrap_or_else(|| min); + + let mut guard = DropGuard::new(len)?; assert_eq!(len, guard.boxed.len()); - for (i, elem) in iter.into_iter().enumerate().take(len) { - debug_assert!(i < len); + for (i, result) in iter.enumerate() { debug_assert_eq!(guard.init_len, i); + let elem = match result { + Ok(x) => x, + Err(e) => return Err(BoxedSliceFromFallibleIterError::IterError(e)), + }; + + if i >= guard.boxed.len() { + guard.double()?; + } + debug_assert!(i < guard.boxed.len()); guard.boxed[i].write(elem); guard.init_len += 1; } debug_assert!(guard.init_len <= guard.boxed.len()); - - if guard.init_len < guard.boxed.len() { - return Err(BoxedSliceFromIterError::TooFewItems); - } - + guard.shrink_to_fit()?; debug_assert_eq!(guard.init_len, guard.boxed.len()); // Take the boxed slice out of the guard. - let boxed = { - guard.init_len = 0; - let boxed = core::mem::take(&mut guard.boxed); - core::mem::forget(guard); - boxed - }; + let boxed = guard.finish(); // Safety: we initialized all elements. let boxed = unsafe { boxed.assume_init() }; @@ -171,6 +348,21 @@ pub fn new_boxed_slice_from_iter( Ok(boxed) } +/// Create a `Box<[T]>` from the given iterator's elements. +/// +/// Returns an error on allocation failure. +pub fn new_boxed_slice_from_iter( + iter: impl IntoIterator, +) -> Result, OutOfMemory> { + let iter = iter + .into_iter() + .map(Result::::Ok); + new_boxed_slice_from_fallible_iter(iter).map_err(|e| match e { + BoxedSliceFromFallibleIterError::Oom(oom) => oom, + BoxedSliceFromFallibleIterError::IterError(_) => unreachable!(), + }) +} + #[cfg(test)] mod tests { use super::*; @@ -199,20 +391,20 @@ mod tests { } #[test] - fn new_boxed_slice_from_iter_smoke_test() { - let slice = new_boxed_slice_from_iter(3, [42, 36, 1337]).unwrap(); + fn new_boxed_slice_from_iter_with_len_smoke_test() { + let slice = new_boxed_slice_from_iter_with_len(3, [42, 36, 1337]).unwrap(); assert_eq!(&*slice, &[42, 36, 1337]); } #[test] - fn new_boxed_slice_from_iter_with_too_few_elems() { + fn new_boxed_slice_from_iter_with_len_with_too_few_elems() { let (a_dropped, a) = SetFlagOnDrop::new(); let (b_dropped, b) = SetFlagOnDrop::new(); let (c_dropped, c) = SetFlagOnDrop::new(); - match new_boxed_slice_from_iter(4, [a, b, c]) { - Err(BoxedSliceFromIterError::TooFewItems) => {} - Ok(_) | Err(BoxedSliceFromIterError::Oom(_)) => unreachable!(), + match new_boxed_slice_from_iter_with_len(4, [a, b, c]) { + Err(BoxedSliceFromIterWithLenError::TooFewItems) => {} + Ok(_) | Err(BoxedSliceFromIterWithLenError::Oom(_)) => unreachable!(), } assert!(a_dropped.get()); @@ -221,12 +413,12 @@ mod tests { } #[test] - fn new_boxed_slice_from_iter_with_too_many_elems() { + fn new_boxed_slice_from_iter_with_len_with_too_many_elems() { let (a_dropped, a) = SetFlagOnDrop::new(); let (b_dropped, b) = SetFlagOnDrop::new(); let (c_dropped, c) = SetFlagOnDrop::new(); - let slice = new_boxed_slice_from_iter(2, [a, b, c]).unwrap(); + let slice = new_boxed_slice_from_iter_with_len(2, [a, b, c]).unwrap(); assert!(!a_dropped.get()); assert!(!b_dropped.get()); @@ -238,4 +430,26 @@ mod tests { assert!(b_dropped.get()); assert!(c_dropped.get()); } + + #[test] + fn new_boxed_slice_from_iter_smoke_test() { + let slice = new_boxed_slice_from_iter([10, 20, 30]).unwrap(); + assert_eq!(&*slice, &[10, 20, 30]); + } + + #[test] + fn new_boxed_slice_from_fallible_iter_smoke_test() { + let slice = + new_boxed_slice_from_fallible_iter::<_, &str>([Ok(10), Ok(20), Ok(30)]).unwrap(); + assert_eq!(&*slice, &[10, 20, 30]); + } + + #[test] + fn new_boxed_slice_from_fallible_iter_error() { + let result = new_boxed_slice_from_fallible_iter::<_, u32>([Ok(10), Ok(20), Err(30)]); + let Err(BoxedSliceFromFallibleIterError::IterError(err)) = result else { + panic!("unexpected result: {result:?}"); + }; + assert_eq!(err, 30); + } } From 9071604512df17e6f94646166a92a7d61d7d94ab Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 27 Jan 2026 11:10:44 -0800 Subject: [PATCH 2/4] Reimplement boxed slice helpers on top of `Vec` --- crates/core/src/alloc/boxed.rs | 278 +++++++++++++-------------------- 1 file changed, 108 insertions(+), 170 deletions(-) diff --git a/crates/core/src/alloc/boxed.rs b/crates/core/src/alloc/boxed.rs index 8bebac9e8c1b..5e038cc1e4e1 100644 --- a/crates/core/src/alloc/boxed.rs +++ b/crates/core/src/alloc/boxed.rs @@ -1,4 +1,4 @@ -use super::{TryNew, try_alloc}; +use super::{TryNew, Vec, try_alloc}; use crate::error::OutOfMemory; use core::{ alloc::Layout, @@ -42,150 +42,114 @@ impl TryNew for Box { } } -fn new_uninit_boxed_slice(len: usize) -> Result]>, OutOfMemory> { - let layout = Layout::array::>(len) - .map_err(|_| OutOfMemory::new(len.saturating_mul(mem::size_of::())))?; +use drop_guard::DropGuard; +mod drop_guard { + use super::*; - if layout.size() == 0 { - // NB: no actual allocation takes place when boxing zero-sized - // types. - return Ok(Box::new_uninit_slice(len)); + /// RAII guard to handle dropping the already-initialized elements when we get + /// too few items or an iterator panics while constructing a boxed slice. + pub struct DropGuard { + vec: Vec, } - // Safety: we just ensured that the new length is non-zero. - debug_assert_ne!(layout.size(), 0); - let ptr = unsafe { try_alloc(layout)? }; - - let ptr = ptr.cast::>().as_ptr(); - let ptr = core::ptr::slice_from_raw_parts_mut(ptr, len); - - // Safety: `ptr` points to a memory block that is valid for - // `[MaybeUninit; len]` and which was allocated by the global memory - // allocator. - let boxed = unsafe { Box::from_raw(ptr) }; - Ok(boxed) -} - -/// RAII guard to handle dropping the initialized elements of the boxed -/// slice in the cases where we get too few items or the iterator panics. -struct DropGuard { - boxed: Box<[MaybeUninit]>, - init_len: usize, -} - -impl Drop for DropGuard { - fn drop(&mut self) { - debug_assert!(self.init_len <= self.boxed.len()); - - if !mem::needs_drop::() { - return; + impl DropGuard { + pub fn new(len: usize) -> Result { + let mut vec = Vec::new(); + vec.reserve_exact(len)?; + Ok(DropGuard { vec }) } - for elem in self.boxed.iter_mut().take(self.init_len) { - // Safety: the elements in `self.boxed[..self.init_len]` are - // valid and initialized and will not be used again. - unsafe { - core::ptr::drop_in_place(elem.as_mut_ptr()); - } + pub fn init_len(&self) -> usize { + self.vec.len() } - } -} - -impl DropGuard { - fn new(len: usize) -> Result { - Ok(DropGuard { - boxed: new_uninit_boxed_slice(len)?, - init_len: 0, - }) - } - /// Finish this guard and take its boxed slice out. - fn finish(mut self) -> Box<[MaybeUninit]> { - self.init_len = 0; - let boxed = mem::take(&mut self.boxed); - mem::forget(self); - boxed - } + pub fn capacity(&self) -> usize { + self.vec.capacity() + } - /// Reallocate this guard's boxed slice such that its length doubles. - fn double(&mut self) -> Result<(), OutOfMemory> { - let new_len = self.boxed.len().saturating_mul(2); - let new_len = core::cmp::max(new_len, 4); - self.realloc(new_len) - } + pub fn push(&mut self, value: T) -> Result<(), OutOfMemory> { + self.vec.push(value) + } - /// Shrink this guard's boxed slice to exactly the initalized length. - fn shrink_to_fit(&mut self) -> Result<(), OutOfMemory> { - self.realloc(self.init_len) - } + /// Finish this guard and take its boxed slice out. + /// + /// Panics if `self.init_len() != self.capacity()`. Call + /// `self.shrink_to_fit()` if necessary. + pub fn finish(mut self) -> Box<[T]> { + assert_eq!(self.init_len(), self.capacity()); + let vec = mem::take(&mut self.vec); + mem::forget(self); + let (ptr, len, cap) = vec.into_raw_parts(); + debug_assert_eq!(len, cap); + let ptr = core::ptr::slice_from_raw_parts_mut(ptr, len); + unsafe { Box::from_raw(ptr) } + } - /// Reallocate this guard's boxed slice to `new_len`. - /// - /// The number of initialized elements will remain the same, and `new_len` - /// must be greater than or equal to the initialized length. - fn realloc(&mut self, new_len: usize) -> Result<(), OutOfMemory> { - assert!(self.init_len <= new_len); + /// Shrink this guard's allocation such that `self.init_len() == + /// self.capacity()`. + pub fn shrink_to_fit(&mut self) -> Result<(), OutOfMemory> { + if self.init_len() == self.capacity() { + return Ok(()); + } - if new_len == self.boxed.len() { - return Ok(()); - } + let len = self.init_len(); + let cap = self.capacity(); + let vec = mem::take(&mut self.vec); + + let old_layout = Layout::array::(cap).expect( + "already have an allocation with this layout so should be able to recreate it", + ); + let new_layout = Layout::array::(len) + .expect("if `cap` is fine for an array layout, then `len` must be as well"); + debug_assert_eq!(old_layout.align(), new_layout.align()); + + // Handle zero-sized reallocations, since the global `realloc` function + // does not. + if new_layout.size() == 0 { + debug_assert!(mem::size_of::() == 0 || len == 0); + if len == 0 { + debug_assert_eq!(self.capacity(), 0); + debug_assert_eq!(self.init_len(), 0); + } else { + debug_assert_eq!(mem::size_of::(), 0); + let ptr = core::ptr::dangling_mut::(); + debug_assert!(!ptr.is_null()); + debug_assert!(ptr.is_aligned()); + // Safety: T's dangling pointer is always non-null and aligned. + self.vec = unsafe { Vec::from_raw_parts(ptr, len, len) }; + } + debug_assert_eq!(self.capacity(), self.init_len()); + return Ok(()); + } - let old_layout = Layout::array::(self.boxed.len()) - .expect("already have an allocation with this layout so should be able to recreate it"); - let new_layout = Layout::array::(new_len) - .map_err(|_| OutOfMemory::new(mem::size_of::().saturating_mul(new_len)))?; - debug_assert_eq!(old_layout.align(), new_layout.align()); - - // Temporarily take the boxed slice out of `self` and reset - // `self.init_len` so that if we panic during reallocation, we never get - // mixed up and see invalid state on `self` inside our `Drop` - // implementation. - let init_len = mem::take(&mut self.init_len); - let boxed = mem::take(&mut self.boxed); - let ptr = Box::into_raw(boxed); - - // Handle zero-sized reallocations, since the global `realloc` function - // does not. - if new_layout.size() == 0 { - debug_assert!(mem::size_of::() == 0 || new_len == 0); - if new_len == 0 { - debug_assert_eq!(self.boxed.len(), 0); - debug_assert_eq!(self.init_len, 0); + let (ptr, _len, _cap) = vec.into_raw_parts(); + debug_assert_eq!(len, _len); + debug_assert_eq!(cap, _cap); + + // Safety: `ptr` was allocated by the global allocator, its memory block + // is described by `old_layout`, the new size is non-zero, and the new + // size will not overflow `isize::MAX` when rounded up to the layout's + // alignment (this is checked in the construction of `new_layout`). + let new_ptr = unsafe { + std_alloc::alloc::realloc(ptr.cast::(), old_layout, new_layout.size()) + }; + + // Update `self` based on whether the reallocation succeeded or not, + // either inserting the vec or reconstructing and replacing the old one. + if new_ptr.is_null() { + // Safety: The allocation failed so we retain ownership of `ptr`, + // which was a valid vec and we can safely make it a vec again. + self.vec = unsafe { Vec::from_raw_parts(ptr, len, cap) }; + Err(OutOfMemory::new(new_layout.size())) } else { - self.boxed = Box::new_uninit_slice(new_len); - self.init_len = init_len; + let new_ptr = new_ptr.cast::(); + // Safety: The allocation succeeded, `new_ptr` was reallocated by + // the global allocator and points to a valid boxed slice of length + // `len`. + self.vec = unsafe { Vec::from_raw_parts(new_ptr, len, len) }; + debug_assert_eq!(self.capacity(), self.init_len()); + Ok(()) } - return Ok(()); - } - - // Safety: `ptr` was allocated by the global allocator, its memory block - // is described by `old_layout`, the new size is non-zero, and the new - // size will not overflow `isize::MAX` when rounded up to the layout's - // alignment (this is checked in the fallible construction of - // `new_layout`). - let new_ptr = - unsafe { std_alloc::alloc::realloc(ptr.cast::(), old_layout, new_layout.size()) }; - - // Update `self` based on whether the allocation succeeded or not, - // either inserting in the new slice or replacing the old slice. - if new_ptr.is_null() { - // Safety: The allocation failed so we retain ownership of `ptr`, - // which was a valid boxed slice and we can safely make it a boxed - // slice again. The block's contents were not modified, so the old - // `init_len` remains valid. - self.boxed = unsafe { Box::from_raw(ptr) }; - self.init_len = init_len; - Err(OutOfMemory::new(new_layout.size())) - } else { - let new_ptr = new_ptr.cast::>(); - let new_ptr = core::ptr::slice_from_raw_parts_mut(new_ptr, new_len); - // Safety: The allocation succeeded, `new_ptr` was allocated by the - // global allocator and points to a valid boxed slice of length - // `new_len`, and the old allocation's contents were copied over to - // the new allocation so the old `init_len` remains valid. - self.boxed = unsafe { Box::from_raw(new_ptr) }; - self.init_len = init_len; - Ok(()) } } } @@ -237,28 +201,18 @@ pub fn new_boxed_slice_from_iter_with_len( iter: impl IntoIterator, ) -> Result, BoxedSliceFromIterWithLenError> { let mut guard = DropGuard::new(len)?; - assert_eq!(len, guard.boxed.len()); + assert_eq!(len, guard.capacity()); - for (i, elem) in iter.into_iter().enumerate().take(len) { - debug_assert!(i < len); - debug_assert_eq!(guard.init_len, i); - guard.boxed[i].write(elem); - guard.init_len += 1; + for elem in iter.into_iter().take(len) { + guard.push(elem).expect("reserved capacity"); } - debug_assert!(guard.init_len <= guard.boxed.len()); - - if guard.init_len < guard.boxed.len() { + if guard.init_len() < guard.capacity() { return Err(BoxedSliceFromIterWithLenError::TooFewItems); } - debug_assert_eq!(guard.init_len, guard.boxed.len()); - let boxed = guard.finish(); - - // Safety: we initialized all elements. - let boxed = unsafe { boxed.assume_init() }; - - Ok(boxed) + debug_assert_eq!(guard.init_len(), guard.capacity()); + Ok(guard.finish()) } /// An error returned by [`new_boxed_slice_from_fallible_iter`]. @@ -318,34 +272,18 @@ pub fn new_boxed_slice_from_fallible_iter( let len = max.unwrap_or_else(|| min); let mut guard = DropGuard::new(len)?; - assert_eq!(len, guard.boxed.len()); + assert_eq!(len, guard.capacity()); - for (i, result) in iter.enumerate() { - debug_assert_eq!(guard.init_len, i); - let elem = match result { - Ok(x) => x, - Err(e) => return Err(BoxedSliceFromFallibleIterError::IterError(e)), - }; - - if i >= guard.boxed.len() { - guard.double()?; - } - debug_assert!(i < guard.boxed.len()); - guard.boxed[i].write(elem); - guard.init_len += 1; + for result in iter { + let elem = result.map_err(BoxedSliceFromFallibleIterError::IterError)?; + guard.push(elem)?; } - debug_assert!(guard.init_len <= guard.boxed.len()); + debug_assert!(guard.init_len() <= guard.capacity()); guard.shrink_to_fit()?; - debug_assert_eq!(guard.init_len, guard.boxed.len()); - - // Take the boxed slice out of the guard. - let boxed = guard.finish(); - - // Safety: we initialized all elements. - let boxed = unsafe { boxed.assume_init() }; + debug_assert_eq!(guard.init_len(), guard.capacity()); - Ok(boxed) + Ok(guard.finish()) } /// Create a `Box<[T]>` from the given iterator's elements. From 0eb292f5172fe08bb9c4108bd602968a7c4c4756 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 27 Jan 2026 14:36:51 -0800 Subject: [PATCH 3/4] Rename `DropGuard` to `BoxedSliceBuilder` --- crates/core/src/alloc/boxed.rs | 50 ++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/crates/core/src/alloc/boxed.rs b/crates/core/src/alloc/boxed.rs index 5e038cc1e4e1..c0d179214ac6 100644 --- a/crates/core/src/alloc/boxed.rs +++ b/crates/core/src/alloc/boxed.rs @@ -42,21 +42,24 @@ impl TryNew for Box { } } -use drop_guard::DropGuard; -mod drop_guard { +use boxed_slice_builder::BoxedSliceBuilder; +mod boxed_slice_builder { use super::*; - /// RAII guard to handle dropping the already-initialized elements when we get - /// too few items or an iterator panics while constructing a boxed slice. - pub struct DropGuard { + /// Builder for constructing and initalizing a boxed slice. + /// + /// Also acts as an RAII guard to handle dropping the already-initialized + /// elements when we get too few items or an iterator panics during + /// construction. + pub struct BoxedSliceBuilder { vec: Vec, } - impl DropGuard { + impl BoxedSliceBuilder { pub fn new(len: usize) -> Result { let mut vec = Vec::new(); vec.reserve_exact(len)?; - Ok(DropGuard { vec }) + Ok(BoxedSliceBuilder { vec }) } pub fn init_len(&self) -> usize { @@ -71,7 +74,7 @@ mod drop_guard { self.vec.push(value) } - /// Finish this guard and take its boxed slice out. + /// Finish this builder and take its boxed slice out. /// /// Panics if `self.init_len() != self.capacity()`. Call /// `self.shrink_to_fit()` if necessary. @@ -85,7 +88,7 @@ mod drop_guard { unsafe { Box::from_raw(ptr) } } - /// Shrink this guard's allocation such that `self.init_len() == + /// Shrink this builder's allocation such that `self.init_len() == /// self.capacity()`. pub fn shrink_to_fit(&mut self) -> Result<(), OutOfMemory> { if self.init_len() == self.capacity() { @@ -135,7 +138,8 @@ mod drop_guard { }; // Update `self` based on whether the reallocation succeeded or not, - // either inserting the vec or reconstructing and replacing the old one. + // either inserting the new vec or reconstructing and replacing the + // old one. if new_ptr.is_null() { // Safety: The allocation failed so we retain ownership of `ptr`, // which was a valid vec and we can safely make it a vec again. @@ -200,19 +204,19 @@ pub fn new_boxed_slice_from_iter_with_len( len: usize, iter: impl IntoIterator, ) -> Result, BoxedSliceFromIterWithLenError> { - let mut guard = DropGuard::new(len)?; - assert_eq!(len, guard.capacity()); + let mut builder = BoxedSliceBuilder::new(len)?; + assert_eq!(len, builder.capacity()); for elem in iter.into_iter().take(len) { - guard.push(elem).expect("reserved capacity"); + builder.push(elem).expect("reserved capacity"); } - if guard.init_len() < guard.capacity() { + if builder.init_len() < builder.capacity() { return Err(BoxedSliceFromIterWithLenError::TooFewItems); } - debug_assert_eq!(guard.init_len(), guard.capacity()); - Ok(guard.finish()) + debug_assert_eq!(builder.init_len(), builder.capacity()); + Ok(builder.finish()) } /// An error returned by [`new_boxed_slice_from_fallible_iter`]. @@ -271,19 +275,19 @@ pub fn new_boxed_slice_from_fallible_iter( let (min, max) = iter.size_hint(); let len = max.unwrap_or_else(|| min); - let mut guard = DropGuard::new(len)?; - assert_eq!(len, guard.capacity()); + let mut builder = BoxedSliceBuilder::new(len)?; + assert_eq!(len, builder.capacity()); for result in iter { let elem = result.map_err(BoxedSliceFromFallibleIterError::IterError)?; - guard.push(elem)?; + builder.push(elem)?; } - debug_assert!(guard.init_len() <= guard.capacity()); - guard.shrink_to_fit()?; - debug_assert_eq!(guard.init_len(), guard.capacity()); + debug_assert!(builder.init_len() <= builder.capacity()); + builder.shrink_to_fit()?; + debug_assert_eq!(builder.init_len(), builder.capacity()); - Ok(guard.finish()) + Ok(builder.finish()) } /// Create a `Box<[T]>` from the given iterator's elements. From fd976b892a4feda54f8b9d6291ef9294c684fe18 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 27 Jan 2026 15:02:52 -0800 Subject: [PATCH 4/4] Fix `Vec::into_raw_parts` polyfill Need to forget `self` or else we will double-free the buffer. --- crates/core/src/alloc/vec.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/core/src/alloc/vec.rs b/crates/core/src/alloc/vec.rs index fc126892bb1b..82bdc8af6908 100644 --- a/crates/core/src/alloc/vec.rs +++ b/crates/core/src/alloc/vec.rs @@ -1,6 +1,6 @@ use crate::error::OutOfMemory; use core::{ - fmt, + fmt, mem, ops::{Deref, DerefMut, Index, IndexMut}, }; use std_alloc::vec::Vec as StdVec; @@ -47,7 +47,7 @@ impl Vec { OutOfMemory::new( self.len() .saturating_add(additional) - .saturating_mul(core::mem::size_of::()), + .saturating_mul(mem::size_of::()), ) }) } @@ -89,6 +89,7 @@ impl Vec { let ptr = self.as_mut_ptr(); let len = self.len(); let cap = self.capacity(); + mem::forget(self); (ptr, len, cap) }