Fix VecDeque::shrink_to
UB when handle_alloc_error
unwinds. · model-checking/verify-rust-std@d77b1cc (original) (raw)
`@@ -982,6 +982,8 @@ impl<T, A: Allocator> VecDeque<T, A> {
`
982
982
`` // head
and len
are at most isize::MAX
and target_cap < self.capacity()
, so nothing can
``
983
983
`// overflow.
`
984
984
`let tail_outside = (target_cap + 1..=self.capacity()).contains(&(self.head + self.len));
`
``
985
`+
// Used in the drop guard below.
`
``
986
`+
let old_head = self.head;
`
985
987
``
986
988
`if self.len == 0 {
`
987
989
`self.head = 0;
`
`@@ -1034,12 +1036,74 @@ impl<T, A: Allocator> VecDeque<T, A> {
`
1034
1036
`}
`
1035
1037
`self.head = new_head;
`
1036
1038
`}
`
1037
``
`-
self.buf.shrink_to_fit(target_cap);
`
``
1039
+
``
1040
`+
struct Guard<'a, T, A: Allocator> {
`
``
1041
`+
deque: &'a mut VecDeque<T, A>,
`
``
1042
`+
old_head: usize,
`
``
1043
`+
target_cap: usize,
`
``
1044
`+
}
`
``
1045
+
``
1046
`+
impl<T, A: Allocator> Drop for Guard<'_, T, A> {
`
``
1047
`+
#[cold]
`
``
1048
`+
fn drop(&mut self) {
`
``
1049
`+
unsafe {
`
``
1050
`` +
// SAFETY: This is only called if buf.shrink_to_fit
unwinds,
``
``
1051
`` +
// which is the only time it's safe to call abort_shrink
.
``
``
1052
`+
self.deque.abort_shrink(self.old_head, self.target_cap)
`
``
1053
`+
}
`
``
1054
`+
}
`
``
1055
`+
}
`
``
1056
+
``
1057
`+
let guard = Guard { deque: self, old_head, target_cap };
`
``
1058
+
``
1059
`+
guard.deque.buf.shrink_to_fit(target_cap);
`
``
1060
+
``
1061
`+
// Don't drop the guard if we didn't unwind.
`
``
1062
`+
mem::forget(guard);
`
1038
1063
``
1039
1064
`debug_assert!(self.head < self.capacity() || self.capacity() == 0);
`
1040
1065
`debug_assert!(self.len <= self.capacity());
`
1041
1066
`}
`
1042
1067
``
``
1068
`` +
/// Reverts the deque back into a consistent state in case shrink_to
failed.
``
``
1069
`+
/// This is necessary to prevent UB if the backing allocator returns an error
`
``
1070
`` +
/// from shrink
and handle_alloc_error
subsequently unwinds (see #123369).
``
``
1071
`+
///
`
``
1072
`` +
/// old_head
refers to the head index before shrink_to
was called. target_cap
``
``
1073
`+
/// is the capacity that it was trying to shrink to.
`
``
1074
`+
unsafe fn abort_shrink(&mut self, old_head: usize, target_cap: usize) {
`
``
1075
`+
// Moral equivalent of self.head + self.len <= target_cap. Won't overflow
`
``
1076
`` +
// because self.len <= target_cap
.
``
``
1077
`+
if self.head <= target_cap - self.len {
`
``
1078
`+
// The deque's buffer is contiguous, so no need to copy anything around.
`
``
1079
`+
return;
`
``
1080
`+
}
`
``
1081
+
``
1082
`` +
// shrink_to
already copied the head to fit into the new capacity, so this won't overflow.
``
``
1083
`+
let head_len = target_cap - self.head;
`
``
1084
`` +
// self.head > target_cap - self.len
=> self.len > target_cap - self.head =: head_len
so this must be positive.
``
``
1085
`+
let tail_len = self.len - head_len;
`
``
1086
+
``
1087
`+
if tail_len <= cmp::min(head_len, self.capacity() - target_cap) {
`
``
1088
`` +
// There's enough spare capacity to copy the tail to the back (because tail_len < self.capacity() - target_cap
),
``
``
1089
`` +
// and copying the tail should be cheaper than copying the head (because tail_len <= head_len
).
``
``
1090
+
``
1091
`+
unsafe {
`
``
1092
`+
// The old tail and the new tail can't overlap because the head slice lies between them. The
`
``
1093
`` +
// head slice ends at target_cap
, so that's where we copy to.
``
``
1094
`+
self.copy_nonoverlapping(0, target_cap, tail_len);
`
``
1095
`+
}
`
``
1096
`+
} else {
`
``
1097
`+
// Either there's not enough spare capacity to make the deque contiguous, or the head is shorter than the tail
`
``
1098
`+
// (and therefore hopefully cheaper to copy).
`
``
1099
`+
unsafe {
`
``
1100
`` +
// The old and the new head slice can overlap, so we can't use copy_nonoverlapping
here.
``
``
1101
`+
self.copy(self.head, old_head, head_len);
`
``
1102
`+
self.head = old_head;
`
``
1103
`+
}
`
``
1104
`+
}
`
``
1105
`+
}
`
``
1106
+
1043
1107
`` /// Shortens the deque, keeping the first len
elements and dropping
``
1044
1108
`/// the rest.
`
1045
1109
`///
`