Skip to content

Commit 5d7774e

Browse files
committed
Refactor vec::IntoIter::into_vecdeque to fix unsoundness
1 parent a3e94c2 commit 5d7774e

2 files changed

Lines changed: 38 additions & 20 deletions

File tree

library/alloc/src/collections/vec_deque/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,7 @@ impl<T, A: Allocator> VecDeque<T, A> {
949949
/// `Vec::from_raw_parts_in`, but takes a *range* of elements that are
950950
/// initialized rather than only supporting `0..len`. Requires that
951951
/// `initialized.start` ≤ `initialized.end` ≤ `capacity`.
952+
/// Also, `initialized.start` < `capacity`, unless both are 0.
952953
#[inline]
953954
#[cfg(not(test))]
954955
pub(crate) unsafe fn from_contiguous_raw_parts_in(
@@ -959,9 +960,13 @@ impl<T, A: Allocator> VecDeque<T, A> {
959960
) -> Self {
960961
debug_assert!(initialized.start <= initialized.end);
961962
debug_assert!(initialized.end <= capacity);
963+
debug_assert!(initialized.start == 0 && capacity == 0 || initialized.start < capacity);
962964

963965
// SAFETY: Our safety precondition guarantees the range length won't wrap,
964-
// and that the allocation is valid for use in `RawVec`.
966+
// that the allocation is valid for use in `RawVec` with `alloc`,
967+
// and that the range contains valid elements.
968+
// We have `head`, `len` ≤ `cap`, since `start`, `end` ≤ `cap`.
969+
// Also, `head` < `cap` unless `head` = `cap` = `0`.
965970
unsafe {
966971
VecDeque {
967972
head: WrappedIndex::from_arbitrary_number(initialized.start),

library/alloc/src/vec/into_iter.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -213,25 +213,38 @@ impl<T, A: Allocator> IntoIter<T, A> {
213213
// Keep our `Drop` impl from dropping the elements and the allocator
214214
let mut this = ManuallyDrop::new(self);
215215

216-
// SAFETY: This allocation originally came from a `Vec`, so it passes
217-
// all those checks. We have `this.buf` ≤ `this.ptr` ≤ `this.end`,
218-
// so the `offset_from_unsigned`s below cannot wrap, and will produce a well-formed
219-
// range. `end` ≤ `buf + cap`, so the range will be in-bounds.
220-
// Taking `alloc` is ok because nothing else is going to look at it,
221-
// since our `Drop` impl isn't going to run so there's no more code.
222-
unsafe {
223-
let buf = this.buf.as_ptr();
224-
let initialized = if T::IS_ZST {
225-
// All the pointers are the same for ZSTs, so it's fine to
226-
// say that they're all at the beginning of the "allocation".
227-
0..this.len()
228-
} else {
229-
this.ptr.offset_from_unsigned(this.buf)..this.end.offset_from_unsigned(buf)
230-
};
231-
let cap = this.cap;
232-
let alloc = ManuallyDrop::take(&mut this.alloc);
233-
VecDeque::from_contiguous_raw_parts_in(buf, initialized, cap, alloc)
234-
}
216+
let buf = this.buf.as_ptr();
217+
let initialized = if T::IS_ZST || this.len() == 0 {
218+
// All the pointers are the same for ZSTs, so it's fine to
219+
// say that they're all at the beginning of the "allocation".
220+
// For non-ZSTs, we have length 0, so we can choose the (empty)
221+
// range to be at the start of the buffer.
222+
//
223+
// Due to `0` ≤ `this.len()` ≤ `this.cap`, the range is well-formed,
224+
// and due to the argument above it spans exactly the elements of
225+
// this iterator. If `cap` ≤ `init.start`, then `cap` = `init.start` = `0`,
226+
// so the range satisfies the requirements of `from_contiguous_raw_parts_in`.
227+
0..this.len()
228+
} else {
229+
// SAFETY: `this.ptr` and `this.end` are created via offsets of `this.buf`,
230+
// so they point to the same allocation. We have `this.buf` ≤ `this.ptr` ≤ `this.end`,
231+
// so this cannot wrap, and will produce a well-formed range that spans exactly
232+
// the elements of this iterator.
233+
//
234+
// Additionally, due to `end ≤ buf + cap`, we have `init.start` ≤ `init.end` ≤ `cap`.
235+
// Due to the length check above, `init.start < cap`, so the range satisfies the
236+
// requirements of `from_contiguous_raw_parts_in`.
237+
unsafe { this.ptr.offset_from_unsigned(this.buf)..this.end.offset_from_unsigned(buf) }
238+
};
239+
240+
let cap = this.cap;
241+
// SAFETY: `this` is forgotten afterwards, so we can move out the allocator.
242+
let alloc = unsafe { ManuallyDrop::take(&mut this.alloc) };
243+
244+
// SAFETY: This allocation originally came from a `Vec`, so it satisfies all
245+
// requirements for the `buf` pointer with capacity `cap` allocated in `alloc`.
246+
// Correctness of `initialized` was shown above.
247+
unsafe { VecDeque::from_contiguous_raw_parts_in(buf, initialized, cap, alloc) }
235248
}
236249
}
237250

0 commit comments

Comments
 (0)