Skip to content

Commit c5faf3a

Browse files
authored
Rollup merge of #152258 - asder8215:vecdeque_splice_151758, r=joboet
fixed VecDeque::splice() not filling the buffer correctly when resizing the buffer on start = end range This PR fixes #151758. The issue came from `Splice::move_tail`, which as joboet pointed out: > The issue is in move_tail, which resizes the buffer, but fails to account for the resulting hole. The problem with reserving more space through `VecDeque`'s `buf.reserve()` is that it doesn't update `VecDeque`'s `head`, which means that this code in `move_tail`: ```rust deque.wrap_copy( deque.to_physical_idx(tail_start), deque.to_physical_idx(new_tail_start), self.tail_len, ); ``` could move over the section of data that `tail_start..tail_start + self.tail_len` of the buffer is supposed to be held at to the incorrect destination since all `.to_physical_idx()` is doing is a wrapping add on the `VecDeque`'s head with the passed in `idx` value. To avoid this I decided to use `VecDeque::reserve` to both allocate more space into the `VecDeque` if necessary and update head appropriately. However, `VecDeque::reserve` internally relies on the `VecDeque`'s `len` field. Earlier in `VecDeque::splice`, it modifies the `VecDeque`'s `len` constructing the drain via `Drain::new` (as it does a `mem::replace` on `deque.len` with the start bound of the passed in `range`). The `VecDeque`'s `len` can also be potentially modified in the earlier `Splice::fill()` call if it does any replacement towards elements within the passed in `range` value for `VecDeque::splice()`. I needed to temporarily restore the `VecDeque`'s `len` to its actual len, so that `VecDeque::reserve` can work properly. Afterward, you can bring back the `VecDeque`'s `len` to what its value was before and fill the gap appropriately with the rest of the `replace_with` content. r? @joboet
2 parents 4efe3dc + d0a33ab commit c5faf3a

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

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

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,48 @@ impl<T, A: Allocator> Drain<'_, T, A> {
138138
/// self.deque must be valid.
139139
unsafe fn move_tail(&mut self, additional: usize) {
140140
let deque = unsafe { self.deque.as_mut() };
141-
let tail_start = deque.len + self.drain_len;
142-
deque.buf.reserve(tail_start + self.tail_len, additional);
141+
142+
// `Drain::new` modifies the deque's len (so does `Drain::fill` here)
143+
// directly with the start bound of the range passed into
144+
// `VecDeque::splice`. This causes a few different issue:
145+
// - Most notably, there will be a hole at the end of the
146+
// buffer when our buffer resizes in the case that our
147+
// data wraps around.
148+
// - We cannot use `VecDeque::reserve` directly because
149+
// how it reserves more space and updates the `VecDeque`'s
150+
// `head` field accordingly depends on the `VecDeque`'s
151+
// actual `len`.
152+
// - We cannot just directly modify `VecDeque`'s `len` and
153+
// and call `VecDeque::reserve` afterward because if
154+
// `VecDeque::reserve` panics on capacity overflow,
155+
// well now our `VecDeque`'s head does not get updated
156+
// and we still have a potential hole at the end of the
157+
// buffer.
158+
// Therefore, we manually reserve additional space (if necessary)
159+
// based on calculating the actual `len` of the `VecDeque` and adjust
160+
// `VecDeque`'s len right *after* the panicking region of `VecDeque::reserve`
161+
// (that is `RawVec` `reserve()` call)
162+
163+
let drain_start = deque.len;
164+
let tail_start = drain_start + self.drain_len;
165+
166+
// Actual VecDeque's len = drain_start + tail_len + drain_len
167+
let actual_len = drain_start + self.tail_len + self.drain_len;
168+
let new_cap = actual_len.checked_add(additional).expect("capacity overflow");
169+
let old_cap = deque.capacity();
170+
171+
if new_cap > old_cap {
172+
deque.buf.reserve(actual_len, additional);
173+
// If new_cap doesn't panic, we can safely set the `VecDeque` len to its
174+
// actual len; this needs to be done in order to set deque.head correctly
175+
// on `VecDeque::handle_capacity_increase`
176+
deque.len = actual_len;
177+
// SAFETY: this cannot panic since our internal buffer's new_cap should
178+
// be bigger than the passed in old_cap
179+
unsafe {
180+
deque.handle_capacity_increase(old_cap);
181+
}
182+
}
143183

144184
let new_tail_start = tail_start + additional;
145185
unsafe {
@@ -149,6 +189,9 @@ impl<T, A: Allocator> Drain<'_, T, A> {
149189
self.tail_len,
150190
);
151191
}
192+
193+
// revert the `VecDeque` len to what it was before
194+
deque.len = drain_start;
152195
self.drain_len += additional;
153196
}
154197
}

library/alloctests/tests/vec_deque.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,3 +2347,16 @@ fn test_splice_wrapping() {
23472347

23482348
assert_eq!(Vec::from(vec), [7, 8, 9]);
23492349
}
2350+
2351+
#[test]
2352+
fn test_splice_wrapping_and_resize() {
2353+
let mut vec = VecDeque::new();
2354+
2355+
for i in [1; 6] {
2356+
vec.push_front(i);
2357+
}
2358+
2359+
vec.splice(1..1, [2, 3, 4]);
2360+
2361+
assert_eq!(Vec::from(vec), [1, 2, 3, 4, 1, 1, 1, 1, 1])
2362+
}

0 commit comments

Comments
 (0)