From 348a58b6ba13b6324b147f07b0466d60ad02ca5e Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 24 Apr 2024 18:00:01 +0200 Subject: [PATCH] Detect simultaneous borrow and drop of UnsafeCell Dropping an UnsafeCell or calling into_inner can be seen as mutable access, which is not allowed to happen while another borrow exists. The downside of this change is the added Drop implementation of UnsafeCell. This restricts some uses of UnsafeCell that were previously allowed related to drop check. I removed one of the tests because it stops working with this change due to double panic. Fixing it is awkward. I went with an approach using unsafe. It is possible to implement this safely but it has too much overhead. We would have to wrap the data field with Option to allow safely taking. This is fine but we would also need to Box it because the type parameter T is ?Sized, which cannot be put into Option. fixes #349 --- src/cell/unsafe_cell.rs | 22 ++++++++++++- tests/unsafe_cell.rs | 70 +++++++++++++---------------------------- 2 files changed, 43 insertions(+), 49 deletions(-) diff --git a/src/cell/unsafe_cell.rs b/src/cell/unsafe_cell.rs index 2f12660e..27728900 100644 --- a/src/cell/unsafe_cell.rs +++ b/src/cell/unsafe_cell.rs @@ -1,3 +1,5 @@ +use std::mem::ManuallyDrop; + use crate::rt; /// A checked version of `std::cell::UnsafeCell`. @@ -115,7 +117,18 @@ impl UnsafeCell { /// Unwraps the value. pub fn into_inner(self) -> T { - self.data.into_inner() + // Observe that we have mutable access. + self.get_mut(); + + // We would like to destructure self. This is not possible because of + // the Drop implementation. Work around it using unsafe. Note that we + // extract `self.state` even though we don't use it. This prevents it + // from leaking. + let self_ = ManuallyDrop::new(self); + let _state = unsafe { std::ptr::read(&self_.state) }; + let data = unsafe { std::ptr::read(&self_.data) }; + + data.into_inner() } } @@ -214,6 +227,13 @@ impl From for UnsafeCell { } } +impl Drop for UnsafeCell { + fn drop(&mut self) { + // Observe that we have mutable access. + self.get_mut(); + } +} + impl ConstPtr { /// Dereference the raw pointer. /// diff --git a/tests/unsafe_cell.rs b/tests/unsafe_cell.rs index 16c390a8..0ce63018 100644 --- a/tests/unsafe_cell.rs +++ b/tests/unsafe_cell.rs @@ -60,54 +60,6 @@ fn atomic_causality_success() { }); } -#[test] -#[should_panic] -fn atomic_causality_fail() { - struct Chan { - data: UnsafeCell, - guard: AtomicUsize, - } - - impl Chan { - fn set(&self) { - unsafe { - self.data.with_mut(|v| { - *v += 123; - }); - } - - self.guard.store(1, Release); - } - - fn get(&self) { - unsafe { - self.data.with(|v| { - assert_eq!(*v, 123); - }); - } - } - } - - loom::model(|| { - let chan = Arc::new(Chan { - data: UnsafeCell::new(0), - guard: AtomicUsize::new(0), - }); - - let th = { - let chan = chan.clone(); - thread::spawn(move || chan.set()) - }; - - // Try getting before joining - chan.get(); - - th.join().unwrap(); - - chan.get(); - }); -} - #[derive(Clone)] struct Data(Arc>); @@ -333,3 +285,25 @@ fn unsafe_cell_access_after_sync() { } }); } + +#[test] +#[should_panic] +fn unsafe_cell_access_during_drop() { + loom::model(|| { + let x = UnsafeCell::new(()); + let _guard = x.get(); + drop(x); + }); +} + +// Unfortunately we cannot repeat the previous test with `into_inner` instead of +// Drop because it leads to a second panic when UnsafeCell::drop runs and guard +// is still alive. The second panic leads to an abort. + +#[test] +fn unsafe_cell_into_inner_ok() { + loom::model(|| { + let x = UnsafeCell::new(0usize); + x.into_inner(); + }); +}