Skip to content

Another bug in BTreeMap from allocator panicking on drop; this time in remove #159368

Description

@maxdexh

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-allocatorsArea: Custom and system allocatorsA-collectionsArea: `std::collections`A-destructorsArea: Destructors (`Drop`, …)A-panicArea: Panicking machineryC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-libsRelevant to the library team, which will review and decide on the PR/issue.needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.requires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions