matklad / once_cell

Rust library for single assignment cells and lazy statics without macros
Apache License 2.0
1.84k stars 110 forks source link

Lazy can cause UB when initialization fails, and program retries initialization #46

Closed KamilaBorowska closed 5 years ago

KamilaBorowska commented 5 years ago

The following program causes UB:

use once_cell::sync::Lazy;

static HI: Lazy<i32> = Lazy::new(|| {
    panic!();
});

fn main() {
    let _ = std::panic::catch_unwind(|| println!("{}", *HI));
    println!("{}", *HI);
}

This is because of incorrect use of unreachable_unchecked when Lazy structure is poisoned.

        pub fn force(this: &Lazy<T, F>) -> &T {
            // Safe because closure is guaranteed to be called at most once
            // so we only call `F` once, this also guarantees no race conditions
            this.cell.get_or_init(|| unsafe {
                match (*this.init.get()).take() {
                    Some(f) => f(),
                    None => unreachable_unchecked(),
                }
            })
        }

This should panic instead of causing unreachable UB.

matklad commented 5 years ago

Ouch, excellent catch!