Skip to content

Commit be23b32

Browse files
authored
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`
2 parents c815e03 + 4a4f43e commit be23b32

File tree

2 files changed

+104
-55
lines changed

2 files changed

+104
-55
lines changed

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

+62-55
Original file line numberDiff line numberDiff line change
@@ -944,65 +944,72 @@ impl<T, A: Allocator> VecDeque<T, A> {
944944
return;
945945
}
946946

947-
if target_cap < self.capacity() {
948-
// There are three cases of interest:
949-
// All elements are out of desired bounds
950-
// Elements are contiguous, and head is out of desired bounds
951-
// Elements are discontiguous, and tail is out of desired bounds
947+
// There are three cases of interest:
948+
// All elements are out of desired bounds
949+
// Elements are contiguous, and tail is out of desired bounds
950+
// Elements are discontiguous
951+
//
952+
// At all other times, element positions are unaffected.
953+
954+
// `head` and `len` are at most `isize::MAX` and `target_cap < self.capacity()`, so nothing can
955+
// overflow.
956+
let tail_outside = (target_cap + 1..=self.capacity()).contains(&(self.head + self.len));
957+
958+
if self.len == 0 {
959+
self.head = 0;
960+
} else if self.head >= target_cap && tail_outside {
961+
// Head and tail are both out of bounds, so copy all of them to the front.
952962
//
953-
// At all other times, element positions are unaffected.
963+
// H := head
964+
// L := last element
965+
// H L
966+
// [. . . . . . . . o o o o o o o . ]
967+
// H L
968+
// [o o o o o o o . ]
969+
unsafe {
970+
// nonoverlapping because `self.head >= target_cap >= self.len`.
971+
self.copy_nonoverlapping(self.head, 0, self.len);
972+
}
973+
self.head = 0;
974+
} else if self.head < target_cap && tail_outside {
975+
// Head is in bounds, tail is out of bounds.
976+
// Copy the overflowing part to the beginning of the
977+
// buffer. This won't overlap because `target_cap >= self.len`.
954978
//
955-
// Indicates that elements at the head should be moved.
956-
957-
let tail_outside = (target_cap + 1..=self.capacity()).contains(&(self.head + self.len));
958-
// Move elements from out of desired bounds (positions after target_cap)
959-
if self.len == 0 {
960-
self.head = 0;
961-
} else if self.head >= target_cap && tail_outside {
962-
// H := head
963-
// L := last element
964-
// H L
965-
// [. . . . . . . . o o o o o o o . ]
966-
// H L
967-
// [o o o o o o o . ]
968-
unsafe {
969-
// nonoverlapping because self.head >= target_cap >= self.len
970-
self.copy_nonoverlapping(self.head, 0, self.len);
971-
}
972-
self.head = 0;
973-
} else if self.head < target_cap && tail_outside {
974-
// H := head
975-
// L := last element
976-
// H L
977-
// [. . . o o o o o o o . . . . . . ]
978-
// L H
979-
// [o o . o o o o o ]
980-
let len = self.head + self.len - target_cap;
981-
unsafe {
982-
self.copy_nonoverlapping(target_cap, 0, len);
983-
}
984-
} else if self.head >= target_cap {
985-
// H := head
986-
// L := last element
987-
// L H
988-
// [o o o o o . . . . . . . . . o o ]
989-
// L H
990-
// [o o o o o . o o ]
991-
let len = self.capacity() - self.head;
992-
let new_head = target_cap - len;
993-
unsafe {
994-
// can't use copy_nonoverlapping here for the same reason
995-
// as in `handle_capacity_increase()`
996-
self.copy(self.head, new_head, len);
997-
}
998-
self.head = new_head;
979+
// H := head
980+
// L := last element
981+
// H L
982+
// [. . . o o o o o o o . . . . . . ]
983+
// L H
984+
// [o o . o o o o o ]
985+
let len = self.head + self.len - target_cap;
986+
unsafe {
987+
self.copy_nonoverlapping(target_cap, 0, len);
999988
}
1000-
1001-
self.buf.shrink_to_fit(target_cap);
1002-
1003-
debug_assert!(self.head < self.capacity() || self.capacity() == 0);
1004-
debug_assert!(self.len <= self.capacity());
989+
} else if !self.is_contiguous() {
990+
// The head slice is at least partially out of bounds, tail is in bounds.
991+
// Copy the head backwards so it lines up with the target capacity.
992+
// This won't overlap because `target_cap >= self.len`.
993+
//
994+
// H := head
995+
// L := last element
996+
// L H
997+
// [o o o o o . . . . . . . . . o o ]
998+
// L H
999+
// [o o o o o . o o ]
1000+
let head_len = self.capacity() - self.head;
1001+
let new_head = target_cap - head_len;
1002+
unsafe {
1003+
// can't use `copy_nonoverlapping()` here because the new and old
1004+
// regions for the head might overlap.
1005+
self.copy(self.head, new_head, head_len);
1006+
}
1007+
self.head = new_head;
10051008
}
1009+
self.buf.shrink_to_fit(target_cap);
1010+
1011+
debug_assert!(self.head < self.capacity() || self.capacity() == 0);
1012+
debug_assert!(self.len <= self.capacity());
10061013
}
10071014

10081015
/// Shortens the deque, keeping the first `len` elements and dropping

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

+42
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,48 @@ fn test_drain() {
748748
}
749749
}
750750

751+
#[test]
752+
fn issue_108453() {
753+
let mut deque = VecDeque::with_capacity(10);
754+
755+
deque.push_back(1u8);
756+
deque.push_back(2);
757+
deque.push_back(3);
758+
759+
deque.push_front(10);
760+
deque.push_front(9);
761+
762+
deque.shrink_to(9);
763+
764+
assert_eq!(deque.into_iter().collect::<Vec<_>>(), vec![9, 10, 1, 2, 3]);
765+
}
766+
767+
#[test]
768+
fn test_shrink_to() {
769+
// test deques with capacity 16 with all possible head positions, lengths and target capacities.
770+
let cap = 16;
771+
772+
for len in 0..cap {
773+
for head in 0..cap {
774+
let expected = (1..=len).collect::<VecDeque<_>>();
775+
776+
for target_cap in len..cap {
777+
let mut deque = VecDeque::with_capacity(cap);
778+
// currently, `with_capacity` always allocates the exact capacity if it's greater than 8.
779+
assert_eq!(deque.capacity(), cap);
780+
781+
// we can let the head point anywhere in the buffer since the deque is empty.
782+
deque.head = head;
783+
deque.extend(1..=len);
784+
785+
deque.shrink_to(target_cap);
786+
787+
assert_eq!(deque, expected);
788+
}
789+
}
790+
}
791+
}
792+
751793
#[test]
752794
fn test_shrink_to_fit() {
753795
// This test checks that every single combination of head and tail position,

0 commit comments

Comments
 (0)