Skip to content

Commit 8cef65f

Browse files
committed
Auto merge of #77801 - fusion-engineering-forks:pin-mutex, r=Mark-Simulacrum
Enforce no-move rule of ReentrantMutex using Pin and fix UB in stdio A `sys_common::ReentrantMutex` may not be moved after initializing it with `.init()`. This was not enforced, but only stated as a requirement in the comments on the unsafe functions. This change enforces this no-moving rule using `Pin`, by changing `&self` to a `Pin` in the `init()` and `lock()` functions. This uncovered a bug I introduced in #77154: stdio.rs (the only user of ReentrantMutex) called `init()` on its ReentrantMutexes while constructing them in the intializer of `SyncOnceCell::get_or_init`, which would move them afterwards. Interestingly, the ReentrantMutex unit tests already had the same bug, so this invalid usage has been tested on all (CI-tested) platforms for a long time. Apparently this doesn't break badly on any of the major platforms, but it does break the rules.\* To be able to keep using SyncOnceCell, this adds a `SyncOnceCell::get_or_init_pin` function, which makes it possible to work with pinned values inside a (pinned) SyncOnceCell. Whether this function should be public or not and what its exact behaviour and interface should be if it would be public is something I'd like to leave for a separate issue or PR. In this PR, this function is internal-only and marked with `pub(crate)`. \* Note: That bug is now included in 1.48, while this patch can only make it to ~~1.49~~ 1.50. We should consider the implications of 1.48 shipping with a wrong usage of `pthread_mutex_t` / `CRITICAL_SECTION` / .. which technically invokes UB according to their specification. The risk is very low, considering the objects are not 'used' (locked) before the move, and the ReentrantMutex unit tests have verified this works fine in practice. Edit: This has been backported and included in 1.48. And soon 1.49 too. --- In future changes, I want to push this usage of Pin further inside `sys` instead of only `sys_common`, and apply it to all 'unmovable' objects there (`Mutex`, `Condvar`, `RwLock`). Also, while `sys_common`'s mutexes and condvars are already taken care of by #77147 and #77648, its `RwLock` should still be made movable or get pinned.
2 parents d32c320 + 67c18fd commit 8cef65f

File tree

5 files changed

+129
-82
lines changed

5 files changed

+129
-82
lines changed

library/std/src/io/stdio.rs

+32-32
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::cell::{Cell, RefCell};
99
use crate::fmt;
1010
use crate::io::{self, BufReader, Initializer, IoSlice, IoSliceMut, LineWriter};
1111
use crate::lazy::SyncOnceCell;
12+
use crate::pin::Pin;
1213
use crate::sync::atomic::{AtomicBool, Ordering};
1314
use crate::sync::{Arc, Mutex, MutexGuard};
1415
use crate::sys::stdio;
@@ -490,7 +491,7 @@ pub struct Stdout {
490491
// FIXME: this should be LineWriter or BufWriter depending on the state of
491492
// stdout (tty or not). Note that if this is not line buffered it
492493
// should also flush-on-panic or some form of flush-on-abort.
493-
inner: &'static ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>,
494+
inner: Pin<&'static ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>>,
494495
}
495496

496497
/// A locked reference to the `Stdout` handle.
@@ -550,25 +551,29 @@ pub struct StdoutLock<'a> {
550551
pub fn stdout() -> Stdout {
551552
static INSTANCE: SyncOnceCell<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> =
552553
SyncOnceCell::new();
554+
555+
fn cleanup() {
556+
if let Some(instance) = INSTANCE.get() {
557+
// Flush the data and disable buffering during shutdown
558+
// by replacing the line writer by one with zero
559+
// buffering capacity.
560+
// We use try_lock() instead of lock(), because someone
561+
// might have leaked a StdoutLock, which would
562+
// otherwise cause a deadlock here.
563+
if let Some(lock) = Pin::static_ref(instance).try_lock() {
564+
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
565+
}
566+
}
567+
}
568+
553569
Stdout {
554-
inner: INSTANCE.get_or_init(|| unsafe {
555-
let _ = sys_common::at_exit(|| {
556-
if let Some(instance) = INSTANCE.get() {
557-
// Flush the data and disable buffering during shutdown
558-
// by replacing the line writer by one with zero
559-
// buffering capacity.
560-
// We use try_lock() instead of lock(), because someone
561-
// might have leaked a StdoutLock, which would
562-
// otherwise cause a deadlock here.
563-
if let Some(lock) = instance.try_lock() {
564-
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
565-
}
566-
}
567-
});
568-
let r = ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw())));
569-
r.init();
570-
r
571-
}),
570+
inner: Pin::static_ref(&INSTANCE).get_or_init_pin(
571+
|| unsafe {
572+
let _ = sys_common::at_exit(cleanup);
573+
ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw())))
574+
},
575+
|mutex| unsafe { mutex.init() },
576+
),
572577
}
573578
}
574579

@@ -700,7 +705,7 @@ impl fmt::Debug for StdoutLock<'_> {
700705
/// an error.
701706
#[stable(feature = "rust1", since = "1.0.0")]
702707
pub struct Stderr {
703-
inner: &'static ReentrantMutex<RefCell<StderrRaw>>,
708+
inner: Pin<&'static ReentrantMutex<RefCell<StderrRaw>>>,
704709
}
705710

706711
/// A locked reference to the `Stderr` handle.
@@ -756,21 +761,16 @@ pub struct StderrLock<'a> {
756761
/// ```
757762
#[stable(feature = "rust1", since = "1.0.0")]
758763
pub fn stderr() -> Stderr {
759-
// Note that unlike `stdout()` we don't use `Lazy` here which registers a
760-
// destructor. Stderr is not buffered nor does the `stderr_raw` type consume
761-
// any owned resources, so there's no need to run any destructors at some
762-
// point in the future.
763-
//
764-
// This has the added benefit of allowing `stderr` to be usable during
765-
// process shutdown as well!
764+
// Note that unlike `stdout()` we don't use `at_exit` here to register a
765+
// destructor. Stderr is not buffered , so there's no need to run a
766+
// destructor for flushing the buffer
766767
static INSTANCE: SyncOnceCell<ReentrantMutex<RefCell<StderrRaw>>> = SyncOnceCell::new();
767768

768769
Stderr {
769-
inner: INSTANCE.get_or_init(|| unsafe {
770-
let r = ReentrantMutex::new(RefCell::new(stderr_raw()));
771-
r.init();
772-
r
773-
}),
770+
inner: Pin::static_ref(&INSTANCE).get_or_init_pin(
771+
|| unsafe { ReentrantMutex::new(RefCell::new(stderr_raw())) },
772+
|mutex| unsafe { mutex.init() },
773+
),
774774
}
775775
}
776776

library/std/src/lazy.rs

+55
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
mem::MaybeUninit,
1111
ops::{Deref, Drop},
1212
panic::{RefUnwindSafe, UnwindSafe},
13+
pin::Pin,
1314
sync::Once,
1415
};
1516

@@ -297,6 +298,60 @@ impl<T> SyncOnceCell<T> {
297298
Ok(unsafe { self.get_unchecked() })
298299
}
299300

301+
/// Internal-only API that gets the contents of the cell, initializing it
302+
/// in two steps with `f` and `g` if the cell was empty.
303+
///
304+
/// `f` is called to construct the value, which is then moved into the cell
305+
/// and given as a (pinned) mutable reference to `g` to finish
306+
/// initialization.
307+
///
308+
/// This allows `g` to inspect an manipulate the value after it has been
309+
/// moved into its final place in the cell, but before the cell is
310+
/// considered initialized.
311+
///
312+
/// # Panics
313+
///
314+
/// If `f` or `g` panics, the panic is propagated to the caller, and the
315+
/// cell remains uninitialized.
316+
///
317+
/// With the current implementation, if `g` panics, the value from `f` will
318+
/// not be dropped. This should probably be fixed if this is ever used for
319+
/// a type where this matters.
320+
///
321+
/// It is an error to reentrantly initialize the cell from `f`. The exact
322+
/// outcome is unspecified. Current implementation deadlocks, but this may
323+
/// be changed to a panic in the future.
324+
pub(crate) fn get_or_init_pin<F, G>(self: Pin<&Self>, f: F, g: G) -> Pin<&T>
325+
where
326+
F: FnOnce() -> T,
327+
G: FnOnce(Pin<&mut T>),
328+
{
329+
if let Some(value) = self.get_ref().get() {
330+
// SAFETY: The inner value was already initialized, and will not be
331+
// moved anymore.
332+
return unsafe { Pin::new_unchecked(value) };
333+
}
334+
335+
let slot = &self.value;
336+
337+
// Ignore poisoning from other threads
338+
// If another thread panics, then we'll be able to run our closure
339+
self.once.call_once_force(|_| {
340+
let value = f();
341+
// SAFETY: We use the Once (self.once) to guarantee unique access
342+
// to the UnsafeCell (slot).
343+
let value: &mut T = unsafe { (&mut *slot.get()).write(value) };
344+
// SAFETY: The value has been written to its final place in
345+
// self.value. We do not to move it anymore, which we promise here
346+
// with a Pin<&mut T>.
347+
g(unsafe { Pin::new_unchecked(value) });
348+
});
349+
350+
// SAFETY: The inner value has been initialized, and will not be moved
351+
// anymore.
352+
unsafe { Pin::new_unchecked(self.get_ref().get_unchecked()) }
353+
}
354+
300355
/// Consumes the `SyncOnceCell`, returning the wrapped value. Returns
301356
/// `None` if the cell was empty.
302357
///

library/std/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@
266266
#![feature(format_args_nl)]
267267
#![feature(gen_future)]
268268
#![feature(generator_trait)]
269+
#![feature(get_mut_unchecked)]
269270
#![feature(global_asm)]
270271
#![feature(hashmap_internals)]
271272
#![feature(int_error_internals)]
@@ -293,6 +294,7 @@
293294
#![feature(panic_info_message)]
294295
#![feature(panic_internals)]
295296
#![feature(panic_unwind)]
297+
#![feature(pin_static_ref)]
296298
#![feature(prelude_import)]
297299
#![feature(ptr_internals)]
298300
#![feature(raw)]

library/std/src/sys_common/remutex.rs

+20-35
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#[cfg(all(test, not(target_os = "emscripten")))]
22
mod tests;
33

4-
use crate::fmt;
5-
use crate::marker;
4+
use crate::marker::PhantomPinned;
65
use crate::ops::Deref;
76
use crate::panic::{RefUnwindSafe, UnwindSafe};
7+
use crate::pin::Pin;
88
use crate::sys::mutex as sys;
99

1010
/// A re-entrant mutual exclusion
@@ -15,6 +15,7 @@ use crate::sys::mutex as sys;
1515
pub struct ReentrantMutex<T> {
1616
inner: sys::ReentrantMutex,
1717
data: T,
18+
_pinned: PhantomPinned,
1819
}
1920

2021
unsafe impl<T: Send> Send for ReentrantMutex<T> {}
@@ -37,10 +38,10 @@ impl<T> RefUnwindSafe for ReentrantMutex<T> {}
3738
/// guarded data.
3839
#[must_use = "if unused the ReentrantMutex will immediately unlock"]
3940
pub struct ReentrantMutexGuard<'a, T: 'a> {
40-
lock: &'a ReentrantMutex<T>,
41+
lock: Pin<&'a ReentrantMutex<T>>,
4142
}
4243

43-
impl<T> !marker::Send for ReentrantMutexGuard<'_, T> {}
44+
impl<T> !Send for ReentrantMutexGuard<'_, T> {}
4445

4546
impl<T> ReentrantMutex<T> {
4647
/// Creates a new reentrant mutex in an unlocked state.
@@ -51,7 +52,11 @@ impl<T> ReentrantMutex<T> {
5152
/// once this mutex is in its final resting place, and only then are the
5253
/// lock/unlock methods safe.
5354
pub const unsafe fn new(t: T) -> ReentrantMutex<T> {
54-
ReentrantMutex { inner: sys::ReentrantMutex::uninitialized(), data: t }
55+
ReentrantMutex {
56+
inner: sys::ReentrantMutex::uninitialized(),
57+
data: t,
58+
_pinned: PhantomPinned,
59+
}
5560
}
5661

5762
/// Initializes this mutex so it's ready for use.
@@ -60,8 +65,8 @@ impl<T> ReentrantMutex<T> {
6065
///
6166
/// Unsafe to call more than once, and must be called after this will no
6267
/// longer move in memory.
63-
pub unsafe fn init(&self) {
64-
self.inner.init();
68+
pub unsafe fn init(self: Pin<&mut Self>) {
69+
self.get_unchecked_mut().inner.init()
6570
}
6671

6772
/// Acquires a mutex, blocking the current thread until it is able to do so.
@@ -76,9 +81,9 @@ impl<T> ReentrantMutex<T> {
7681
/// If another user of this mutex panicked while holding the mutex, then
7782
/// this call will return failure if the mutex would otherwise be
7883
/// acquired.
79-
pub fn lock(&self) -> ReentrantMutexGuard<'_, T> {
84+
pub fn lock(self: Pin<&Self>) -> ReentrantMutexGuard<'_, T> {
8085
unsafe { self.inner.lock() }
81-
ReentrantMutexGuard::new(&self)
86+
ReentrantMutexGuard { lock: self }
8287
}
8388

8489
/// Attempts to acquire this lock.
@@ -93,8 +98,12 @@ impl<T> ReentrantMutex<T> {
9398
/// If another user of this mutex panicked while holding the mutex, then
9499
/// this call will return failure if the mutex would otherwise be
95100
/// acquired.
96-
pub fn try_lock(&self) -> Option<ReentrantMutexGuard<'_, T>> {
97-
if unsafe { self.inner.try_lock() } { Some(ReentrantMutexGuard::new(&self)) } else { None }
101+
pub fn try_lock(self: Pin<&Self>) -> Option<ReentrantMutexGuard<'_, T>> {
102+
if unsafe { self.inner.try_lock() } {
103+
Some(ReentrantMutexGuard { lock: self })
104+
} else {
105+
None
106+
}
98107
}
99108
}
100109

@@ -107,30 +116,6 @@ impl<T> Drop for ReentrantMutex<T> {
107116
}
108117
}
109118

110-
impl<T: fmt::Debug + 'static> fmt::Debug for ReentrantMutex<T> {
111-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112-
match self.try_lock() {
113-
Some(guard) => f.debug_struct("ReentrantMutex").field("data", &*guard).finish(),
114-
None => {
115-
struct LockedPlaceholder;
116-
impl fmt::Debug for LockedPlaceholder {
117-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118-
f.write_str("<locked>")
119-
}
120-
}
121-
122-
f.debug_struct("ReentrantMutex").field("data", &LockedPlaceholder).finish()
123-
}
124-
}
125-
}
126-
}
127-
128-
impl<'mutex, T> ReentrantMutexGuard<'mutex, T> {
129-
fn new(lock: &'mutex ReentrantMutex<T>) -> ReentrantMutexGuard<'mutex, T> {
130-
ReentrantMutexGuard { lock }
131-
}
132-
}
133-
134119
impl<T> Deref for ReentrantMutexGuard<'_, T> {
135120
type Target = T;
136121

library/std/src/sys_common/remutex/tests.rs

+20-15
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
use crate::boxed::Box;
12
use crate::cell::RefCell;
3+
use crate::pin::Pin;
24
use crate::sync::Arc;
35
use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
46
use crate::thread;
57

68
#[test]
79
fn smoke() {
810
let m = unsafe {
9-
let m = ReentrantMutex::new(());
10-
m.init();
11+
let mut m = Box::pin(ReentrantMutex::new(()));
12+
m.as_mut().init();
1113
m
1214
};
15+
let m = m.as_ref();
1316
{
1417
let a = m.lock();
1518
{
@@ -27,18 +30,19 @@ fn smoke() {
2730
#[test]
2831
fn is_mutex() {
2932
let m = unsafe {
30-
let m = Arc::new(ReentrantMutex::new(RefCell::new(0)));
31-
m.init();
32-
m
33+
// FIXME: Simplify this if Arc gets a Arc::get_pin_mut.
34+
let mut m = Arc::new(ReentrantMutex::new(RefCell::new(0)));
35+
Pin::new_unchecked(Arc::get_mut_unchecked(&mut m)).init();
36+
Pin::new_unchecked(m)
3337
};
3438
let m2 = m.clone();
35-
let lock = m.lock();
39+
let lock = m.as_ref().lock();
3640
let child = thread::spawn(move || {
37-
let lock = m2.lock();
41+
let lock = m2.as_ref().lock();
3842
assert_eq!(*lock.borrow(), 4950);
3943
});
4044
for i in 0..100 {
41-
let lock = m.lock();
45+
let lock = m.as_ref().lock();
4246
*lock.borrow_mut() += i;
4347
}
4448
drop(lock);
@@ -48,20 +52,21 @@ fn is_mutex() {
4852
#[test]
4953
fn trylock_works() {
5054
let m = unsafe {
51-
let m = Arc::new(ReentrantMutex::new(()));
52-
m.init();
53-
m
55+
// FIXME: Simplify this if Arc gets a Arc::get_pin_mut.
56+
let mut m = Arc::new(ReentrantMutex::new(()));
57+
Pin::new_unchecked(Arc::get_mut_unchecked(&mut m)).init();
58+
Pin::new_unchecked(m)
5459
};
5560
let m2 = m.clone();
56-
let _lock = m.try_lock();
57-
let _lock2 = m.try_lock();
61+
let _lock = m.as_ref().try_lock();
62+
let _lock2 = m.as_ref().try_lock();
5863
thread::spawn(move || {
59-
let lock = m2.try_lock();
64+
let lock = m2.as_ref().try_lock();
6065
assert!(lock.is_none());
6166
})
6267
.join()
6368
.unwrap();
64-
let _lock3 = m.try_lock();
69+
let _lock3 = m.as_ref().try_lock();
6570
}
6671

6772
pub struct Answer<'a>(pub ReentrantMutexGuard<'a, RefCell<u32>>);

0 commit comments

Comments
 (0)