Skip to content

Commit 348a58b

Browse files
committed
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
1 parent a7033ee commit 348a58b

File tree

2 files changed

+43
-49
lines changed

2 files changed

+43
-49
lines changed

src/cell/unsafe_cell.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::mem::ManuallyDrop;
2+
13
use crate::rt;
24

35
/// A checked version of `std::cell::UnsafeCell`.
@@ -115,7 +117,18 @@ impl<T> UnsafeCell<T> {
115117

116118
/// Unwraps the value.
117119
pub fn into_inner(self) -> T {
118-
self.data.into_inner()
120+
// Observe that we have mutable access.
121+
self.get_mut();
122+
123+
// We would like to destructure self. This is not possible because of
124+
// the Drop implementation. Work around it using unsafe. Note that we
125+
// extract `self.state` even though we don't use it. This prevents it
126+
// from leaking.
127+
let self_ = ManuallyDrop::new(self);
128+
let _state = unsafe { std::ptr::read(&self_.state) };
129+
let data = unsafe { std::ptr::read(&self_.data) };
130+
131+
data.into_inner()
119132
}
120133
}
121134

@@ -214,6 +227,13 @@ impl<T> From<T> for UnsafeCell<T> {
214227
}
215228
}
216229

230+
impl<T: ?Sized> Drop for UnsafeCell<T> {
231+
fn drop(&mut self) {
232+
// Observe that we have mutable access.
233+
self.get_mut();
234+
}
235+
}
236+
217237
impl<T: ?Sized> ConstPtr<T> {
218238
/// Dereference the raw pointer.
219239
///

tests/unsafe_cell.rs

+22-48
Original file line numberDiff line numberDiff line change
@@ -60,54 +60,6 @@ fn atomic_causality_success() {
6060
});
6161
}
6262

63-
#[test]
64-
#[should_panic]
65-
fn atomic_causality_fail() {
66-
struct Chan {
67-
data: UnsafeCell<usize>,
68-
guard: AtomicUsize,
69-
}
70-
71-
impl Chan {
72-
fn set(&self) {
73-
unsafe {
74-
self.data.with_mut(|v| {
75-
*v += 123;
76-
});
77-
}
78-
79-
self.guard.store(1, Release);
80-
}
81-
82-
fn get(&self) {
83-
unsafe {
84-
self.data.with(|v| {
85-
assert_eq!(*v, 123);
86-
});
87-
}
88-
}
89-
}
90-
91-
loom::model(|| {
92-
let chan = Arc::new(Chan {
93-
data: UnsafeCell::new(0),
94-
guard: AtomicUsize::new(0),
95-
});
96-
97-
let th = {
98-
let chan = chan.clone();
99-
thread::spawn(move || chan.set())
100-
};
101-
102-
// Try getting before joining
103-
chan.get();
104-
105-
th.join().unwrap();
106-
107-
chan.get();
108-
});
109-
}
110-
11163
#[derive(Clone)]
11264
struct Data(Arc<UnsafeCell<usize>>);
11365

@@ -333,3 +285,25 @@ fn unsafe_cell_access_after_sync() {
333285
}
334286
});
335287
}
288+
289+
#[test]
290+
#[should_panic]
291+
fn unsafe_cell_access_during_drop() {
292+
loom::model(|| {
293+
let x = UnsafeCell::new(());
294+
let _guard = x.get();
295+
drop(x);
296+
});
297+
}
298+
299+
// Unfortunately we cannot repeat the previous test with `into_inner` instead of
300+
// Drop because it leads to a second panic when UnsafeCell::drop runs and guard
301+
// is still alive. The second panic leads to an abort.
302+
303+
#[test]
304+
fn unsafe_cell_into_inner_ok() {
305+
loom::model(|| {
306+
let x = UnsafeCell::new(0usize);
307+
x.into_inner();
308+
});
309+
}

0 commit comments

Comments
 (0)