Rollup merge of #108475 - Sp00ph:fix_shrink_to, r=thomcc

Fix `VecDeque::shrink_to` and add tests.

Fixes #108453.

Also adds both a specific test with the code from #108453 and an exhaustive test that checks all possible head positions, lengths and target capacities for deques with capacity 16.

cc `@trinity-1686a` `@scottmcm`
This commit is contained in:
Matthias Krüger 2023-02-26 12:05:00 +01:00 committed by GitHub
commit be23b326dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 56 deletions

View File

@ -944,21 +944,22 @@ pub fn shrink_to(&mut self, min_capacity: usize) {
return; return;
} }
if target_cap < self.capacity() {
// There are three cases of interest: // There are three cases of interest:
// All elements are out of desired bounds // All elements are out of desired bounds
// Elements are contiguous, and head is out of desired bounds // Elements are contiguous, and tail is out of desired bounds
// Elements are discontiguous, and tail is out of desired bounds // Elements are discontiguous
// //
// At all other times, element positions are unaffected. // At all other times, element positions are unaffected.
//
// Indicates that elements at the head should be moved.
// `head` and `len` are at most `isize::MAX` and `target_cap < self.capacity()`, so nothing can
// overflow.
let tail_outside = (target_cap + 1..=self.capacity()).contains(&(self.head + self.len)); let tail_outside = (target_cap + 1..=self.capacity()).contains(&(self.head + self.len));
// Move elements from out of desired bounds (positions after target_cap)
if self.len == 0 { if self.len == 0 {
self.head = 0; self.head = 0;
} else if self.head >= target_cap && tail_outside { } else if self.head >= target_cap && tail_outside {
// Head and tail are both out of bounds, so copy all of them to the front.
//
// H := head // H := head
// L := last element // L := last element
// H L // H L
@ -966,11 +967,15 @@ pub fn shrink_to(&mut self, min_capacity: usize) {
// H L // H L
// [o o o o o o o . ] // [o o o o o o o . ]
unsafe { unsafe {
// nonoverlapping because self.head >= target_cap >= self.len // nonoverlapping because `self.head >= target_cap >= self.len`.
self.copy_nonoverlapping(self.head, 0, self.len); self.copy_nonoverlapping(self.head, 0, self.len);
} }
self.head = 0; self.head = 0;
} else if self.head < target_cap && tail_outside { } else if self.head < target_cap && tail_outside {
// Head is in bounds, tail is out of bounds.
// Copy the overflowing part to the beginning of the
// buffer. This won't overlap because `target_cap >= self.len`.
//
// H := head // H := head
// L := last element // L := last element
// H L // H L
@ -981,29 +986,31 @@ pub fn shrink_to(&mut self, min_capacity: usize) {
unsafe { unsafe {
self.copy_nonoverlapping(target_cap, 0, len); self.copy_nonoverlapping(target_cap, 0, len);
} }
} else if self.head >= target_cap { } else if !self.is_contiguous() {
// The head slice is at least partially out of bounds, tail is in bounds.
// Copy the head backwards so it lines up with the target capacity.
// This won't overlap because `target_cap >= self.len`.
//
// H := head // H := head
// L := last element // L := last element
// L H // L H
// [o o o o o . . . . . . . . . o o ] // [o o o o o . . . . . . . . . o o ]
// L H // L H
// [o o o o o . o o ] // [o o o o o . o o ]
let len = self.capacity() - self.head; let head_len = self.capacity() - self.head;
let new_head = target_cap - len; let new_head = target_cap - head_len;
unsafe { unsafe {
// can't use copy_nonoverlapping here for the same reason // can't use `copy_nonoverlapping()` here because the new and old
// as in `handle_capacity_increase()` // regions for the head might overlap.
self.copy(self.head, new_head, len); self.copy(self.head, new_head, head_len);
} }
self.head = new_head; self.head = new_head;
} }
self.buf.shrink_to_fit(target_cap); self.buf.shrink_to_fit(target_cap);
debug_assert!(self.head < self.capacity() || self.capacity() == 0); debug_assert!(self.head < self.capacity() || self.capacity() == 0);
debug_assert!(self.len <= self.capacity()); debug_assert!(self.len <= self.capacity());
} }
}
/// Shortens the deque, keeping the first `len` elements and dropping /// Shortens the deque, keeping the first `len` elements and dropping
/// the rest. /// the rest.

View File

@ -748,6 +748,48 @@ fn test_drain() {
} }
} }
#[test]
fn issue_108453() {
let mut deque = VecDeque::with_capacity(10);
deque.push_back(1u8);
deque.push_back(2);
deque.push_back(3);
deque.push_front(10);
deque.push_front(9);
deque.shrink_to(9);
assert_eq!(deque.into_iter().collect::<Vec<_>>(), vec![9, 10, 1, 2, 3]);
}
#[test]
fn test_shrink_to() {
// test deques with capacity 16 with all possible head positions, lengths and target capacities.
let cap = 16;
for len in 0..cap {
for head in 0..cap {
let expected = (1..=len).collect::<VecDeque<_>>();
for target_cap in len..cap {
let mut deque = VecDeque::with_capacity(cap);
// currently, `with_capacity` always allocates the exact capacity if it's greater than 8.
assert_eq!(deque.capacity(), cap);
// we can let the head point anywhere in the buffer since the deque is empty.
deque.head = head;
deque.extend(1..=len);
deque.shrink_to(target_cap);
assert_eq!(deque, expected);
}
}
}
}
#[test] #[test]
fn test_shrink_to_fit() { fn test_shrink_to_fit() {
// This test checks that every single combination of head and tail position, // This test checks that every single combination of head and tail position,