Skip to content

Commit 29b15e6

Browse files
Rollup merge of #162462 - maxdexh:issue-162452-vec-into-iter-into-deque, r=Darksonn
Fix unsoundness in `VecDeque::from_iter(vec::IntoIter)` Adds a safety requirement to `VecDeque::from_contiguous_raw_parts_in` to ensure that the `VecDeque` it creates upholds the safety invariants. In particular, either `head < capacity` or `head == capacity == 0`. Refactors `vec::IntoIter::into_vecdeque` to uphold that invariant (and splits the unsafe blocks into multiple parts, because the safety comment was messy & incomplete). Actual fix is the addition of `|| len == 0`. I would add a regression test, but the specialization is behind `cfg(not(test))`. I cannot see why this was done, as there are no comments documenting this... Fixes #162452 r? libs
2 parents 745de6e + 8bd0a5a commit 29b15e6

3 files changed

Lines changed: 54 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: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -213,25 +213,39 @@ 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. Because `init.start` = `0`, it follows that either
226+
// `init.start` < `cap` or `cap` = `init.start` = `0`; thus the range
227+
// satisfies the requirements of `from_contiguous_raw_parts_in`.
228+
0..this.len()
229+
} else {
230+
// SAFETY: `this.ptr` and `this.end` are created via offsets of `this.buf`,
231+
// so they point to the same allocation. We have `this.buf` ≤ `this.ptr` ≤ `this.end`,
232+
// so this cannot wrap, and will produce a well-formed range that spans exactly
233+
// the elements of this iterator.
234+
//
235+
// Additionally, due to `end ≤ buf + cap`, we have `init.start` ≤ `init.end` ≤ `cap`.
236+
// Due to the length check above, `init.start < cap`, so the range satisfies the
237+
// requirements of `from_contiguous_raw_parts_in`.
238+
unsafe { this.ptr.offset_from_unsigned(this.buf)..this.end.offset_from_unsigned(buf) }
239+
};
240+
241+
let cap = this.cap;
242+
// SAFETY: `this` is forgotten afterwards, so we can move out the allocator.
243+
let alloc = unsafe { ManuallyDrop::take(&mut this.alloc) };
244+
245+
// SAFETY: This allocation originally came from a `Vec`, so it satisfies all
246+
// requirements for the `buf` pointer with capacity `cap` allocated in `alloc`.
247+
// Correctness of `initialized` was shown above.
248+
unsafe { VecDeque::from_contiguous_raw_parts_in(buf, initialized, cap, alloc) }
235249
}
236250
}
237251

library/alloctests/tests/vec_deque.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2495,3 +2495,18 @@ fn truncate_to_range_inclusive_end_overflow() {
24952495
let mut v: VecDeque<_> = (0..6).collect();
24962496
v.truncate_to_range(0..=usize::MAX);
24972497
}
2498+
2499+
#[test]
2500+
fn issue_162452_vec_deque_from_empty_vec_into_iter() {
2501+
for n in 1..20 {
2502+
let v = Vec::from_iter(0..n);
2503+
2504+
let mut it = v.into_iter();
2505+
for _ in &mut it {}
2506+
2507+
let mut d: VecDeque<_> = it.collect();
2508+
2509+
d.push_back(n);
2510+
assert_eq!(Some(n), d.pop_front());
2511+
}
2512+
}

0 commit comments

Comments
 (0)