Skip to content

Commit 4dbde84

Browse files
authored
Rollup merge of rust-lang#106918 - dtolnay:heapretain, r=the8472
Rebuild BinaryHeap on unwind from retain This closes the hole identified in rust-lang#71503 (comment) which had made it possible for the caller to end up with a heap in invalid state. As of rust-lang#105851, heaps in invalid state are not supposed to exist.
2 parents 1868e91 + fa2ff4d commit 4dbde84

File tree

2 files changed

+37
-6
lines changed

2 files changed

+37
-6
lines changed

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

+18-6
Original file line numberDiff line numberDiff line change
@@ -851,18 +851,30 @@ impl<T: Ord> BinaryHeap<T> {
851851
where
852852
F: FnMut(&T) -> bool,
853853
{
854-
let mut first_removed = self.len();
854+
struct RebuildOnDrop<'a, T: Ord> {
855+
heap: &'a mut BinaryHeap<T>,
856+
first_removed: usize,
857+
}
858+
859+
let mut guard = RebuildOnDrop { first_removed: self.len(), heap: self };
860+
855861
let mut i = 0;
856-
self.data.retain(|e| {
862+
guard.heap.data.retain(|e| {
857863
let keep = f(e);
858-
if !keep && i < first_removed {
859-
first_removed = i;
864+
if !keep && i < guard.first_removed {
865+
guard.first_removed = i;
860866
}
861867
i += 1;
862868
keep
863869
});
864-
// data[0..first_removed] is untouched, so we only need to rebuild the tail:
865-
self.rebuild_tail(first_removed);
870+
871+
impl<'a, T: Ord> Drop for RebuildOnDrop<'a, T> {
872+
fn drop(&mut self) {
873+
// data[..first_removed] is untouched, so we only need to
874+
// rebuild the tail:
875+
self.heap.rebuild_tail(self.first_removed);
876+
}
877+
}
866878
}
867879
}
868880

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

+19
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,25 @@ fn test_retain() {
474474
assert!(a.is_empty());
475475
}
476476

477+
#[test]
478+
fn test_retain_catch_unwind() {
479+
let mut heap = BinaryHeap::from(vec![3, 1, 2]);
480+
481+
// Removes the 3, then unwinds out of retain.
482+
let _ = catch_unwind(AssertUnwindSafe(|| {
483+
heap.retain(|e| {
484+
if *e == 1 {
485+
panic!();
486+
}
487+
false
488+
});
489+
}));
490+
491+
// Naively this would be [1, 2] (an invalid heap) if BinaryHeap delegates to
492+
// Vec's retain impl and then does not rebuild the heap after that unwinds.
493+
assert_eq!(heap.into_vec(), [2, 1]);
494+
}
495+
477496
// old binaryheap failed this test
478497
//
479498
// Integrity means that all elements are present after a comparison panics,

0 commit comments

Comments
 (0)