Hey, I've found this while scanning the top 5000 downloaded crates with my UB static analyzer.
src/vec/traits.rs:397 and src/boxed/traits.rs:379:
unsafe impl<T, O> Sync for BitVec<T, O>
where
T: BitStore,
O: BitOrder,
{
}
There is no T: Sync bound, and Cell<u8> is a shipped BitStore (src/store.rs:159). So safe code can build a BitVec<Cell<u8>, Lsb0>, share it with another thread because the type claims Sync, call the safe as_raw_slice(&self) on both threads, and write through the &[Cell<u8>] from both. That is a plain data race.
use bitvec::prelude::*;
use std::cell::Cell;
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn two_threads_write_one_cell() {
let bv: BitVec<Cell<u8>, Lsb0> = BitVec::from_element(Cell::new(0u8));
let bv: &'static BitVec<Cell<u8>, Lsb0> = Box::leak(Box::new(bv));
let barrier = Arc::new(Barrier::new(2));
let other = barrier.clone();
let h = thread::spawn(move || {
let raw = bv.as_raw_slice();
other.wait();
raw[0].set(1);
});
let raw = bv.as_raw_slice();
barrier.wait();
raw[0].set(2);
h.join().unwrap();
}
$ cargo +nightly miri test
test two_threads_write_one_cell ... error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `unnamed-2` and (2) retag write of type `u8` on thread `two_threads_wri` at alloc41122
--> library/core/src/cell.rs:516:31
|
516 | mem::replace(unsafe { &mut *self.value.get() }, val)
| ^^^^^^^^^^^^^^^^^^^^^^ (2) just happened here
help: and (1) occurred earlier here
--> tests/bitvec.rs:17:9
|
17 | raw[0].set(1);
| ^^^^^^^^^^^^^
Fix would be to add T: Sync to both Sync impls (and T: Send to the matching Send impls). The Cell and unsynchronized BitStore types then stop being Sync, which is the intent.
Hey, I've found this while scanning the top 5000 downloaded crates with my UB static analyzer.
src/vec/traits.rs:397 and src/boxed/traits.rs:379:
There is no
T: Syncbound, andCell<u8>is a shippedBitStore(src/store.rs:159). So safe code can build aBitVec<Cell<u8>, Lsb0>, share it with another thread because the type claimsSync, call the safeas_raw_slice(&self)on both threads, and write through the&[Cell<u8>]from both. That is a plain data race.Fix would be to add
T: Syncto bothSyncimpls (andT: Sendto the matchingSendimpls). TheCelland unsynchronizedBitStoretypes then stop beingSync, which is the intent.