Code change:
is not updated (line 1636) when (line 1623) panics. This lets 's implementation to retain the already dropped element. The bug was introduced in and affects stable Rust versions = 1.52. Here is the that demonstrates the double free without using unsafe Rust code. <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --:
label regression-from-stable-to-stable
Assigning as discussed by and removing . modify labels +P-critical -I-prioritize
Interestingly there was a test for this, but it is wrong. The vec contains the index=5, even though it is the item that panics in its drop implementation. I guess the author of this test thought that returned the new value instead of the old value?
Learning about a double-free I introduced, a year later... :eyes: And honestly, I can clearly remember that when the test was first written, it caught the bug, but I just assumed it was an off-by-one error in the testing code itself, yikes

Related discussion:
text: #[test] fn test_vec_dedup_panicking() { #[derive(Debug)] <del> struct Panic { drop_counter: &'static AtomicU32, </del> <ins> struct Panic<'a> { drop_counter: &'a Cell<u32>, </ins> value: bool, index: usize, } <del> impl PartialEq for Panic { </del> <ins> impl<'a> PartialEq for Panic<'a> { </ins> fn eq(&self, other: &Self) -> bool { self.value == other.value } } <del> impl Drop for Panic { </del> <ins> impl<'a> Drop for Panic<'a> { </ins> fn drop(&mut self) { <del> let x = self.drop_counter.fetch_add(1, Ordering::SeqCst); assert!(x != 4); </del> <ins> self.drop_counter.set(self.drop_counter.get() + 1); if !std::thread::panicking() { assert!(self.index != 4); } </ins> } } <del> static DROP_COUNTER: AtomicU32 = AtomicU32::new(0); </del> <ins> let drop_counter = &Cell::new(0); </ins> let expected = [ <del> Panic { drop_counter: &DROP_COUNTER, value: false, index: 0 }, Panic { drop_counter: &DROP_COUNTER, value: false, index: 5 }, Panic { drop_counter: &DROP_COUNTER, value: true, index: 6 }, Panic { drop_counter: &DROP_COUNTER, value: true, index: 7 }, </del> <ins> Panic { drop_counter, value: false, index: 0 }, Panic { drop_counter, value: false, index: 5 }, Panic { drop_counter, value: true, index: 6 }, Panic { drop_counter, value: true, index: 7 }, </ins> ]; let mut vec = vec![ <del> Panic { drop_counter: &DROP_COUNTER, value: false, index: 0 }, </del> <ins> Panic { drop_counter, value: false, index: 0 }, </ins> // these elements get deduplicated <del> Panic { drop_counter: &DROP_COUNTER, value: false, index: 1 }, Panic { drop_counter: &DROP_COUNTER, value: false, index: 2 }, Panic { drop_counter: &DROP_COUNTER, value: false, index: 3 }, Panic { drop_counter: &DROP_COUNTER, value: false, index: 4 }, // here it panics Panic { drop_counter: &DROP_COUNTER, value: false, index: 5 }, Panic { drop_counter: &DROP_COUNTER, value: true, index: 6 }, Panic { drop_counter: &DROP_COUNTER, value: true, index: 7 }, </del> <ins> Panic { drop_counter, value: false, index: 1 }, Panic { drop_counter, value: false, index: 2 }, Panic { drop_counter, value: false, index: 3 }, Panic { drop_counter, value: false, index: 4 }, // here it panics while dropping the item with index==4 Panic { drop_counter, value: false, index: 5 }, Panic { drop_counter, value: true, index: 6 }, Panic { drop_counter, value: true, index: 7 }, </ins> ]; <del> let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { vec.dedup(); })); </del> <ins> let _ = catch_unwind(AssertUnwindSafe(|| vec.dedup())).unwrap_err(); assert_eq!(drop_counter.get(), 4); </ins> let ok = vec.iter().zip(expected.iter()).all(|(x, y)| x.index == y.index);