Document reentrancy in *Arena::alloc_from_iter

This commit is contained in:
Nadrieril 2023-12-02 03:03:16 +01:00
parent 7058df2f4e
commit c1774a137d

View File

@ -197,23 +197,24 @@ impl<T> TypedArena<T> {
start_ptr start_ptr
} }
/// Allocates the elements of this iterator into a contiguous slice in the `TypedArena`.
///
/// Note: for reasons of reentrancy and panic safety we collect into a `SmallVec<[_; 8]>` before
/// storing the elements in the arena.
#[inline] #[inline]
pub fn alloc_from_iter<I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] { pub fn alloc_from_iter<I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] {
// This implementation is entirely separate to // Despite the similarlty with `DroplessArena`, we cannot reuse their fast case. The reason
// `DroplessIterator::alloc_from_iter`, even though conceptually they // is subtle: these arenas are reentrant. In other words, `iter` may very well be holding a
// are the same. // reference to `self` and adding elements to the arena during iteration.
// //
// `DroplessIterator` (in the fast case) writes elements from the // For this reason, if we pre-allocated any space for the elements of this iterator, we'd
// iterator one at a time into the allocated memory. That's easy // have to track that some uninitialized elements are followed by some initialized elements,
// because the elements don't implement `Drop`. But for `TypedArena` // else we might accidentally drop uninitialized memory if something panics or if the
// they do implement `Drop`, which means that if the iterator panics we // iterator doesn't fill all the length we expected.
// could end up with some allocated-but-uninitialized elements, which
// will then cause UB in `TypedArena::drop`.
// //
// Instead we use an approach where any iterator panic will occur // So we collect all the elements beforehand, which takes care of reentrancy and panic
// before the memory is allocated. This function is much less hot than // safety. This function is much less hot than `DroplessArena::alloc_from_iter`, so it
// `DroplessArena::alloc_from_iter`, so it doesn't need to be // doesn't need to be hyper-optimized.
// hyper-optimized.
assert!(mem::size_of::<T>() != 0); assert!(mem::size_of::<T>() != 0);
let mut vec: SmallVec<[_; 8]> = iter.into_iter().collect(); let mut vec: SmallVec<[_; 8]> = iter.into_iter().collect();
@ -485,8 +486,9 @@ impl DroplessArena {
/// # Safety /// # Safety
/// ///
/// The caller must ensure that `mem` is valid for writes up to /// The caller must ensure that `mem` is valid for writes up to `size_of::<T>() * len`, and that
/// `size_of::<T>() * len`. /// that memory stays allocated and not shared for the lifetime of `self`. This must hold even
/// if `iter.next()` allocates onto `self`.
#[inline] #[inline]
unsafe fn write_from_iter<T, I: Iterator<Item = T>>( unsafe fn write_from_iter<T, I: Iterator<Item = T>>(
&self, &self,
@ -516,6 +518,8 @@ impl DroplessArena {
#[inline] #[inline]
pub fn alloc_from_iter<T, I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] { pub fn alloc_from_iter<T, I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] {
// Warning: this function is reentrant: `iter` could hold a reference to `&self` and
// allocate additional elements while we're iterating.
let iter = iter.into_iter(); let iter = iter.into_iter();
assert!(mem::size_of::<T>() != 0); assert!(mem::size_of::<T>() != 0);
assert!(!mem::needs_drop::<T>()); assert!(!mem::needs_drop::<T>());
@ -524,7 +528,7 @@ impl DroplessArena {
match size_hint { match size_hint {
(min, Some(max)) if min == max => { (min, Some(max)) if min == max => {
// We know the exact number of elements the iterator will produce here // We know the exact number of elements the iterator expects to produce here.
let len = min; let len = min;
if len == 0 { if len == 0 {
@ -532,10 +536,15 @@ impl DroplessArena {
} }
let mem = self.alloc_raw(Layout::array::<T>(len).unwrap()) as *mut T; let mem = self.alloc_raw(Layout::array::<T>(len).unwrap()) as *mut T;
// SAFETY: `write_from_iter` doesn't touch `self`. It only touches the slice we just
// reserved. If the iterator panics or doesn't output `len` elements, this will
// leave some unallocated slots in the arena, which is fine because we do not call
// `drop`.
unsafe { self.write_from_iter(iter, len, mem) } unsafe { self.write_from_iter(iter, len, mem) }
} }
(_, _) => { (_, _) => {
outline(move || -> &mut [T] { outline(move || -> &mut [T] {
// Takes care of reentrancy.
let mut vec: SmallVec<[_; 8]> = iter.collect(); let mut vec: SmallVec<[_; 8]> = iter.collect();
if vec.is_empty() { if vec.is_empty() {
return &mut []; return &mut [];