Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 23a5a0e

Browse files
committedNov 24, 2024
Auto merge of rust-lang#132597 - lukas-code:btree-plug-leak, r=jhpratt
btree: don't leak value if destructor of key panics This PR fixes a regression from rust-lang#84904. The `BTreeMap` already attempts to handle panicking destructors of the key-value pairs by continuing to execute the remaining destructors after one destructor panicked. However, after rust-lang#84904 the destructor of a value in a key-value pair gets skipped if the destructor of the key panics, only continuing with the next key-value pair. This PR reverts to the behavior before rust-lang#84904 to also drop the corresponding value if the destructor of a key panics. This avoids potential memory leaks and can fix the soundness of programs that rely on the destructors being executed (even though this should not be relied upon, because the std collections currently do not guarantee that the remaining elements are dropped after a panic in a destructor). cc `@Amanieu` because you had opinions on panicking destructors
2 parents d05e8e8 + 9cdbf39 commit 23a5a0e

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed
 

‎alloc/src/collections/btree/map/tests.rs

+48
Original file line numberDiff line numberDiff line change
@@ -2270,6 +2270,54 @@ fn test_into_iter_drop_leak_height_0() {
22702270
assert_eq!(e.dropped(), 1);
22712271
}
22722272

2273+
#[test]
2274+
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
2275+
fn test_into_iter_drop_leak_kv_panic_in_key() {
2276+
let a_k = CrashTestDummy::new(0);
2277+
let a_v = CrashTestDummy::new(1);
2278+
let b_k = CrashTestDummy::new(2);
2279+
let b_v = CrashTestDummy::new(3);
2280+
let c_k = CrashTestDummy::new(4);
2281+
let c_v = CrashTestDummy::new(5);
2282+
let mut map = BTreeMap::new();
2283+
map.insert(a_k.spawn(Panic::Never), a_v.spawn(Panic::Never));
2284+
map.insert(b_k.spawn(Panic::InDrop), b_v.spawn(Panic::Never));
2285+
map.insert(c_k.spawn(Panic::Never), c_v.spawn(Panic::Never));
2286+
2287+
catch_unwind(move || drop(map.into_iter())).unwrap_err();
2288+
2289+
assert_eq!(a_k.dropped(), 1);
2290+
assert_eq!(a_v.dropped(), 1);
2291+
assert_eq!(b_k.dropped(), 1);
2292+
assert_eq!(b_v.dropped(), 1);
2293+
assert_eq!(c_k.dropped(), 1);
2294+
assert_eq!(c_v.dropped(), 1);
2295+
}
2296+
2297+
#[test]
2298+
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
2299+
fn test_into_iter_drop_leak_kv_panic_in_val() {
2300+
let a_k = CrashTestDummy::new(0);
2301+
let a_v = CrashTestDummy::new(1);
2302+
let b_k = CrashTestDummy::new(2);
2303+
let b_v = CrashTestDummy::new(3);
2304+
let c_k = CrashTestDummy::new(4);
2305+
let c_v = CrashTestDummy::new(5);
2306+
let mut map = BTreeMap::new();
2307+
map.insert(a_k.spawn(Panic::Never), a_v.spawn(Panic::Never));
2308+
map.insert(b_k.spawn(Panic::Never), b_v.spawn(Panic::InDrop));
2309+
map.insert(c_k.spawn(Panic::Never), c_v.spawn(Panic::Never));
2310+
2311+
catch_unwind(move || drop(map.into_iter())).unwrap_err();
2312+
2313+
assert_eq!(a_k.dropped(), 1);
2314+
assert_eq!(a_v.dropped(), 1);
2315+
assert_eq!(b_k.dropped(), 1);
2316+
assert_eq!(b_v.dropped(), 1);
2317+
assert_eq!(c_k.dropped(), 1);
2318+
assert_eq!(c_v.dropped(), 1);
2319+
}
2320+
22732321
#[test]
22742322
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
22752323
fn test_into_iter_drop_leak_height_1() {

‎alloc/src/collections/btree/node.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -1173,11 +1173,25 @@ impl<K, V, NodeType> Handle<NodeRef<marker::Dying, K, V, NodeType>, marker::KV>
11731173
/// The node that the handle refers to must not yet have been deallocated.
11741174
#[inline]
11751175
pub unsafe fn drop_key_val(mut self) {
1176+
// Run the destructor of the value even if the destructor of the key panics.
1177+
struct Dropper<'a, T>(&'a mut MaybeUninit<T>);
1178+
impl<T> Drop for Dropper<'_, T> {
1179+
#[inline]
1180+
fn drop(&mut self) {
1181+
unsafe {
1182+
self.0.assume_init_drop();
1183+
}
1184+
}
1185+
}
1186+
11761187
debug_assert!(self.idx < self.node.len());
11771188
let leaf = self.node.as_leaf_dying();
11781189
unsafe {
1179-
leaf.keys.get_unchecked_mut(self.idx).assume_init_drop();
1180-
leaf.vals.get_unchecked_mut(self.idx).assume_init_drop();
1190+
let key = leaf.keys.get_unchecked_mut(self.idx);
1191+
let val = leaf.vals.get_unchecked_mut(self.idx);
1192+
let _guard = Dropper(val);
1193+
key.assume_init_drop();
1194+
// dropping the guard will drop the value
11811195
}
11821196
}
11831197
}

0 commit comments

Comments
 (0)
Failed to load comments.