cc #157428
See also #159334
playground
#![feature(allocator_api, btreemap_alloc)]
use std::alloc::{AllocError, Allocator, Global, Layout};
use std::cell::Cell;
use std::collections::BTreeMap;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr::NonNull;
use std::rc::Rc;
#[derive(Clone)]
struct PanicAllocator {
countdown: Rc<Cell<u32>>,
}
unsafe impl Allocator for PanicAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
Global.allocate(layout)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { Global.deallocate(ptr, layout) }
}
}
impl Drop for PanicAllocator {
fn drop(&mut self) {
let cd = self.countdown.get();
self.countdown.set(cd.saturating_sub(1));
if cd == 1 {
panic!("its the final countdown");
}
}
}
const PANIC_COUNTDOWN_START: u32 = 1;
const ENTRY_COUNT: usize = 137;
fn main() {
let allocator = PanicAllocator {
countdown: Rc::new(Cell::new(0)),
};
let mut map = BTreeMap::new_in(allocator.clone());
for i in 0..ENTRY_COUNT {
map.insert(i, vec![vec![i]]);
}
let insert_count = map.len();
allocator.countdown.set(PANIC_COUNTDOWN_START);
catch_unwind(AssertUnwindSafe(|| {
map.remove(&(ENTRY_COUNT - 1));
}))
.expect_err("removal should panic");
eprintln!("\nSurvived unwind in `remove`");
let elem_count = map.range(..).count();
let broken_len = map.len();
// map did not update the len, so the element should still be there
assert_eq!(insert_count, broken_len);
// but it was removed from the tree!
assert_ne!(elem_count, broken_len);
eprintln!("\nbtree reported len={broken_len}, but only {elem_count} are accessible!\n");
let mut iter = map.into_iter();
for _ in 0..elem_count {
iter.next_back();
}
// make the allocator detect the double free
let _ = vec![0x41; 10000];
}
@rustbot label A-allocators requires-nightly T-libs I-unsound A-panic A-collections A-destructors
cc #157428
See also #159334
playground
@rustbot label A-allocators requires-nightly T-libs I-unsound A-panic A-collections A-destructors