Skip to content

Commit c1774a1

Browse files
committed
Document reentrancy in *Arena::alloc_from_iter
1 parent 7058df2 commit c1774a1

File tree

1 file changed

+25
-16
lines changed
  • compiler/rustc_arena/src

1 file changed

+25
-16
lines changed

compiler/rustc_arena/src/lib.rs

+25-16
Original file line numberDiff line numberDiff line change
@@ -197,23 +197,24 @@ impl<T> TypedArena<T> {
197197
start_ptr
198198
}
199199

200+
/// Allocates the elements of this iterator into a contiguous slice in the `TypedArena`.
201+
///
202+
/// Note: for reasons of reentrancy and panic safety we collect into a `SmallVec<[_; 8]>` before
203+
/// storing the elements in the arena.
200204
#[inline]
201205
pub fn alloc_from_iter<I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] {
202-
// This implementation is entirely separate to
203-
// `DroplessIterator::alloc_from_iter`, even though conceptually they
204-
// are the same.
206+
// Despite the similarlty with `DroplessArena`, we cannot reuse their fast case. The reason
207+
// is subtle: these arenas are reentrant. In other words, `iter` may very well be holding a
208+
// reference to `self` and adding elements to the arena during iteration.
205209
//
206-
// `DroplessIterator` (in the fast case) writes elements from the
207-
// iterator one at a time into the allocated memory. That's easy
208-
// because the elements don't implement `Drop`. But for `TypedArena`
209-
// they do implement `Drop`, which means that if the iterator panics we
210-
// could end up with some allocated-but-uninitialized elements, which
211-
// will then cause UB in `TypedArena::drop`.
210+
// For this reason, if we pre-allocated any space for the elements of this iterator, we'd
211+
// have to track that some uninitialized elements are followed by some initialized elements,
212+
// else we might accidentally drop uninitialized memory if something panics or if the
213+
// iterator doesn't fill all the length we expected.
212214
//
213-
// Instead we use an approach where any iterator panic will occur
214-
// before the memory is allocated. This function is much less hot than
215-
// `DroplessArena::alloc_from_iter`, so it doesn't need to be
216-
// hyper-optimized.
215+
// So we collect all the elements beforehand, which takes care of reentrancy and panic
216+
// safety. This function is much less hot than `DroplessArena::alloc_from_iter`, so it
217+
// doesn't need to be hyper-optimized.
217218
assert!(mem::size_of::<T>() != 0);
218219

219220
let mut vec: SmallVec<[_; 8]> = iter.into_iter().collect();
@@ -485,8 +486,9 @@ impl DroplessArena {
485486

486487
/// # Safety
487488
///
488-
/// The caller must ensure that `mem` is valid for writes up to
489-
/// `size_of::<T>() * len`.
489+
/// The caller must ensure that `mem` is valid for writes up to `size_of::<T>() * len`, and that
490+
/// that memory stays allocated and not shared for the lifetime of `self`. This must hold even
491+
/// if `iter.next()` allocates onto `self`.
490492
#[inline]
491493
unsafe fn write_from_iter<T, I: Iterator<Item = T>>(
492494
&self,
@@ -516,6 +518,8 @@ impl DroplessArena {
516518

517519
#[inline]
518520
pub fn alloc_from_iter<T, I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] {
521+
// Warning: this function is reentrant: `iter` could hold a reference to `&self` and
522+
// allocate additional elements while we're iterating.
519523
let iter = iter.into_iter();
520524
assert!(mem::size_of::<T>() != 0);
521525
assert!(!mem::needs_drop::<T>());
@@ -524,18 +528,23 @@ impl DroplessArena {
524528

525529
match size_hint {
526530
(min, Some(max)) if min == max => {
527-
// We know the exact number of elements the iterator will produce here
531+
// We know the exact number of elements the iterator expects to produce here.
528532
let len = min;
529533

530534
if len == 0 {
531535
return &mut [];
532536
}
533537

534538
let mem = self.alloc_raw(Layout::array::<T>(len).unwrap()) as *mut T;
539+
// SAFETY: `write_from_iter` doesn't touch `self`. It only touches the slice we just
540+
// reserved. If the iterator panics or doesn't output `len` elements, this will
541+
// leave some unallocated slots in the arena, which is fine because we do not call
542+
// `drop`.
535543
unsafe { self.write_from_iter(iter, len, mem) }
536544
}
537545
(_, _) => {
538546
outline(move || -> &mut [T] {
547+
// Takes care of reentrancy.
539548
let mut vec: SmallVec<[_; 8]> = iter.collect();
540549
if vec.is_empty() {
541550
return &mut [];

0 commit comments

Comments
 (0)