diff --git a/CHANGELOG.md b/CHANGELOG.md index b062121d8a..0d3463f466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed unsoundness in `IndexMap::retain` in the context of panicking predicate. - Added `swap_remove()` to `IndexMap` and `IndexSet`. - Deprecated `.remove()` in `IndexMap` and `IndexSet` in favour of `.swap_remove()`. -- Fixed `IndexMap::truncate` leading to an inconsistent state. +- Fixed `IndexMap::truncate` leading to an inconsistent state and not being exception-safe. - Fixed `Deque::make_contigous` leading to an inconsistent state. - Added `resize_with` to `Vec`. - Added `retain_back` (aka `truncate_front`) to `Deque`. diff --git a/src/index_map.rs b/src/index_map.rs index 2961901f32..3ed2c476bc 100644 --- a/src/index_map.rs +++ b/src/index_map.rs @@ -1584,16 +1584,23 @@ where /// assert_eq!(iter.next(), None); /// ``` pub fn truncate(&mut self, len: usize) { - self.core.entries.truncate(len); - - if N > self.core.entries.len() { - for pos in self.core.indices.iter_mut() { - if pos.maybe_valid.index() >= len { - *pos = Pos::none(); + struct DropGuard<'a, K: Eq + Hash, V, S: BuildHasher, const N: usize>( + &'a mut IndexMap, + ); + impl<'a, K: Eq + Hash, V, S: BuildHasher, const N: usize> Drop for DropGuard<'a, K, V, S, N> { + fn drop(&mut self) { + let len = self.0.core.entries.len(); + if N > self.0.core.entries.len() { + for pos in self.0.core.indices.iter_mut() { + if pos.maybe_valid.index() >= len { + *pos = Pos::none(); + } + } } + self.0.core.reinsert_all(); } } - self.core.reinsert_all(); + DropGuard(self).0.core.entries.truncate(len); } /* Private API */ @@ -2686,4 +2693,46 @@ mod tests { println!("survived; map.len={}", map.len()); } + + #[test] + fn truncate_exception_safe() { + #[derive(Debug, PartialEq, Eq)] + struct Item { + data: Box, + bomb: bool, + } + impl Drop for Item { + fn drop(&mut self) { + if self.bomb { + panic!("controlled drop panic"); + } + } + } + let mut map = FnvIndexMap::::new(); + map.insert( + 1, + Item { + data: Box::new(11), + bomb: false, + }, + ) + .unwrap(); + map.insert( + 2, + Item { + data: Box::new(22), + bomb: true, + }, + ) + .unwrap(); + assert!(catch_unwind(AssertUnwindSafe(|| map.truncate(1))).is_err()); + assert_eq!(map.get(&2), None); + assert_eq!( + map.get(&1), + Some(&Item { + data: Box::new(11), + bomb: false + }) + ); + } }