Skip to content

Commit 9e1c436

Browse files
committed
Auto merge of #74225 - poliorcetics:std-thread-unsafe-op-in-unsafe-fn, r=joshtriplett
Std/thread: deny unsafe op in unsafe fn Partial fix of #73904. This encloses `unsafe` operations in `unsafe fn` in `libstd/thread`. `@rustbot` modify labels: F-unsafe-block-in-unsafe-fn
2 parents c6622d1 + d01bd19 commit 9e1c436

File tree

2 files changed

+127
-44
lines changed

2 files changed

+127
-44
lines changed

library/std/src/thread/local.rs

+107-36
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,23 @@ mod lazy {
289289
}
290290

291291
pub unsafe fn get(&self) -> Option<&'static T> {
292-
(*self.inner.get()).as_ref()
292+
// SAFETY: The caller must ensure no reference is ever handed out to
293+
// the inner cell nor mutable reference to the Option<T> inside said
294+
// cell. This make it safe to hand a reference, though the lifetime
295+
// of 'static is itself unsafe, making the get method unsafe.
296+
unsafe { (*self.inner.get()).as_ref() }
293297
}
294298

299+
/// The caller must ensure that no reference is active: this method
300+
/// needs unique access.
295301
pub unsafe fn initialize<F: FnOnce() -> T>(&self, init: F) -> &'static T {
296302
// Execute the initialization up front, *then* move it into our slot,
297303
// just in case initialization fails.
298304
let value = init();
299305
let ptr = self.inner.get();
300306

307+
// SAFETY:
308+
//
301309
// note that this can in theory just be `*ptr = Some(value)`, but due to
302310
// the compiler will currently codegen that pattern with something like:
303311
//
@@ -310,22 +318,36 @@ mod lazy {
310318
// value (an aliasing violation). To avoid setting the "I'm running a
311319
// destructor" flag we just use `mem::replace` which should sequence the
312320
// operations a little differently and make this safe to call.
313-
let _ = mem::replace(&mut *ptr, Some(value));
314-
315-
// After storing `Some` we want to get a reference to the contents of
316-
// what we just stored. While we could use `unwrap` here and it should
317-
// always work it empirically doesn't seem to always get optimized away,
318-
// which means that using something like `try_with` can pull in
319-
// panicking code and cause a large size bloat.
320-
match *ptr {
321-
Some(ref x) => x,
322-
None => hint::unreachable_unchecked(),
321+
//
322+
// The precondition also ensures that we are the only one accessing
323+
// `self` at the moment so replacing is fine.
324+
unsafe {
325+
let _ = mem::replace(&mut *ptr, Some(value));
326+
}
327+
328+
// SAFETY: With the call to `mem::replace` it is guaranteed there is
329+
// a `Some` behind `ptr`, not a `None` so `unreachable_unchecked`
330+
// will never be reached.
331+
unsafe {
332+
// After storing `Some` we want to get a reference to the contents of
333+
// what we just stored. While we could use `unwrap` here and it should
334+
// always work it empirically doesn't seem to always get optimized away,
335+
// which means that using something like `try_with` can pull in
336+
// panicking code and cause a large size bloat.
337+
match *ptr {
338+
Some(ref x) => x,
339+
None => hint::unreachable_unchecked(),
340+
}
323341
}
324342
}
325343

344+
/// The other methods hand out references while taking &self.
345+
/// As such, callers of this method must ensure no `&` and `&mut` are
346+
/// available and used at the same time.
326347
#[allow(unused)]
327348
pub unsafe fn take(&mut self) -> Option<T> {
328-
(*self.inner.get()).take()
349+
// SAFETY: See doc comment for this method.
350+
unsafe { (*self.inner.get()).take() }
329351
}
330352
}
331353
}
@@ -356,10 +378,17 @@ pub mod statik {
356378
}
357379

358380
pub unsafe fn get(&self, init: fn() -> T) -> Option<&'static T> {
359-
let value = match self.inner.get() {
360-
Some(ref value) => value,
361-
None => self.inner.initialize(init),
381+
// SAFETY: The caller must ensure no reference is ever handed out to
382+
// the inner cell nor mutable reference to the Option<T> inside said
383+
// cell. This make it safe to hand a reference, though the lifetime
384+
// of 'static is itself unsafe, making the get method unsafe.
385+
let value = unsafe {
386+
match self.inner.get() {
387+
Some(ref value) => value,
388+
None => self.inner.initialize(init),
389+
}
362390
};
391+
363392
Some(value)
364393
}
365394
}
@@ -414,9 +443,18 @@ pub mod fast {
414443
}
415444

416445
pub unsafe fn get<F: FnOnce() -> T>(&self, init: F) -> Option<&'static T> {
417-
match self.inner.get() {
418-
Some(val) => Some(val),
419-
None => self.try_initialize(init),
446+
// SAFETY: See the definitions of `LazyKeyInner::get` and
447+
// `try_initialize` for more informations.
448+
//
449+
// The caller must ensure no mutable references are ever active to
450+
// the inner cell or the inner T when this is called.
451+
// The `try_initialize` is dependant on the passed `init` function
452+
// for this.
453+
unsafe {
454+
match self.inner.get() {
455+
Some(val) => Some(val),
456+
None => self.try_initialize(init),
457+
}
420458
}
421459
}
422460

@@ -429,8 +467,10 @@ pub mod fast {
429467
// LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722
430468
#[inline(never)]
431469
unsafe fn try_initialize<F: FnOnce() -> T>(&self, init: F) -> Option<&'static T> {
432-
if !mem::needs_drop::<T>() || self.try_register_dtor() {
433-
Some(self.inner.initialize(init))
470+
// SAFETY: See comment above (this function doc).
471+
if !mem::needs_drop::<T>() || unsafe { self.try_register_dtor() } {
472+
// SAFETY: See comment above (his function doc).
473+
Some(unsafe { self.inner.initialize(init) })
434474
} else {
435475
None
436476
}
@@ -442,8 +482,12 @@ pub mod fast {
442482
unsafe fn try_register_dtor(&self) -> bool {
443483
match self.dtor_state.get() {
444484
DtorState::Unregistered => {
445-
// dtor registration happens before initialization.
446-
register_dtor(self as *const _ as *mut u8, destroy_value::<T>);
485+
// SAFETY: dtor registration happens before initialization.
486+
// Passing `self` as a pointer while using `destroy_value<T>`
487+
// is safe because the function will build a pointer to a
488+
// Key<T>, which is the type of self and so find the correct
489+
// size.
490+
unsafe { register_dtor(self as *const _ as *mut u8, destroy_value::<T>) };
447491
self.dtor_state.set(DtorState::Registered);
448492
true
449493
}
@@ -459,13 +503,21 @@ pub mod fast {
459503
unsafe extern "C" fn destroy_value<T>(ptr: *mut u8) {
460504
let ptr = ptr as *mut Key<T>;
461505

506+
// SAFETY:
507+
//
508+
// The pointer `ptr` has been built just above and comes from
509+
// `try_register_dtor` where it is originally a Key<T> coming from `self`,
510+
// making it non-NUL and of the correct type.
511+
//
462512
// Right before we run the user destructor be sure to set the
463513
// `Option<T>` to `None`, and `dtor_state` to `RunningOrHasRun`. This
464514
// causes future calls to `get` to run `try_initialize_drop` again,
465515
// which will now fail, and return `None`.
466-
let value = (*ptr).inner.take();
467-
(*ptr).dtor_state.set(DtorState::RunningOrHasRun);
468-
drop(value);
516+
unsafe {
517+
let value = (*ptr).inner.take();
518+
(*ptr).dtor_state.set(DtorState::RunningOrHasRun);
519+
drop(value);
520+
}
469521
}
470522
}
471523

@@ -503,21 +555,30 @@ pub mod os {
503555
Key { os: OsStaticKey::new(Some(destroy_value::<T>)), marker: marker::PhantomData }
504556
}
505557

558+
/// It is a requirement for the caller to ensure that no mutable
559+
/// reference is active when this method is called.
506560
pub unsafe fn get(&'static self, init: fn() -> T) -> Option<&'static T> {
507-
let ptr = self.os.get() as *mut Value<T>;
561+
// SAFETY: See the documentation for this method.
562+
let ptr = unsafe { self.os.get() as *mut Value<T> };
508563
if ptr as usize > 1 {
509-
if let Some(ref value) = (*ptr).inner.get() {
564+
// SAFETY: the check ensured the pointer is safe (its destructor
565+
// is not running) + it is coming from a trusted source (self).
566+
if let Some(ref value) = unsafe { (*ptr).inner.get() } {
510567
return Some(value);
511568
}
512569
}
513-
self.try_initialize(init)
570+
// SAFETY: At this point we are sure we have no value and so
571+
// initializing (or trying to) is safe.
572+
unsafe { self.try_initialize(init) }
514573
}
515574

516575
// `try_initialize` is only called once per os thread local variable,
517576
// except in corner cases where thread_local dtors reference other
518577
// thread_local's, or it is being recursively initialized.
519578
unsafe fn try_initialize(&'static self, init: fn() -> T) -> Option<&'static T> {
520-
let ptr = self.os.get() as *mut Value<T>;
579+
// SAFETY: No mutable references are ever handed out meaning getting
580+
// the value is ok.
581+
let ptr = unsafe { self.os.get() as *mut Value<T> };
521582
if ptr as usize == 1 {
522583
// destructor is running
523584
return None;
@@ -528,29 +589,39 @@ pub mod os {
528589
// local copy, so do that now.
529590
let ptr: Box<Value<T>> = box Value { inner: LazyKeyInner::new(), key: self };
530591
let ptr = Box::into_raw(ptr);
531-
self.os.set(ptr as *mut u8);
592+
// SAFETY: At this point we are sure there is no value inside
593+
// ptr so setting it will not affect anyone else.
594+
unsafe {
595+
self.os.set(ptr as *mut u8);
596+
}
532597
ptr
533598
} else {
534599
// recursive initialization
535600
ptr
536601
};
537602

538-
Some((*ptr).inner.initialize(init))
603+
// SAFETY: ptr has been ensured as non-NUL just above an so can be
604+
// dereferenced safely.
605+
unsafe { Some((*ptr).inner.initialize(init)) }
539606
}
540607
}
541608

542609
unsafe extern "C" fn destroy_value<T: 'static>(ptr: *mut u8) {
610+
// SAFETY:
611+
//
543612
// The OS TLS ensures that this key contains a NULL value when this
544613
// destructor starts to run. We set it back to a sentinel value of 1 to
545614
// ensure that any future calls to `get` for this thread will return
546615
// `None`.
547616
//
548617
// Note that to prevent an infinite loop we reset it back to null right
549618
// before we return from the destructor ourselves.
550-
let ptr = Box::from_raw(ptr as *mut Value<T>);
551-
let key = ptr.key;
552-
key.os.set(1 as *mut u8);
553-
drop(ptr);
554-
key.os.set(ptr::null_mut());
619+
unsafe {
620+
let ptr = Box::from_raw(ptr as *mut Value<T>);
621+
let key = ptr.key;
622+
key.os.set(1 as *mut u8);
623+
drop(ptr);
624+
key.os.set(ptr::null_mut());
625+
}
555626
}
556627
}

library/std/src/thread/mod.rs

+20-8
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
//! [`with`]: LocalKey::with
145145
146146
#![stable(feature = "rust1", since = "1.0.0")]
147+
#![deny(unsafe_op_in_unsafe_fn)]
147148

148149
#[cfg(all(test, not(target_os = "emscripten")))]
149150
mod tests;
@@ -456,14 +457,23 @@ impl Builder {
456457
imp::Thread::set_name(name);
457458
}
458459

459-
thread_info::set(imp::guard::current(), their_thread);
460+
// SAFETY: the stack guard passed is the one for the current thread.
461+
// This means the current thread's stack and the new thread's stack
462+
// are properly set and protected from each other.
463+
thread_info::set(unsafe { imp::guard::current() }, their_thread);
460464
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
461465
crate::sys_common::backtrace::__rust_begin_short_backtrace(f)
462466
}));
463-
*their_packet.get() = Some(try_result);
467+
// SAFETY: `their_packet` as been built just above and moved by the
468+
// closure (it is an Arc<...>) and `my_packet` will be stored in the
469+
// same `JoinInner` as this closure meaning the mutation will be
470+
// safe (not modify it and affect a value far away).
471+
unsafe { *their_packet.get() = Some(try_result) };
464472
};
465473

466474
Ok(JoinHandle(JoinInner {
475+
// SAFETY:
476+
//
467477
// `imp::Thread::new` takes a closure with a `'static` lifetime, since it's passed
468478
// through FFI or otherwise used with low-level threading primitives that have no
469479
// notion of or way to enforce lifetimes.
@@ -475,12 +485,14 @@ impl Builder {
475485
// Similarly, the `sys` implementation must guarantee that no references to the closure
476486
// exist after the thread has terminated, which is signaled by `Thread::join`
477487
// returning.
478-
native: Some(imp::Thread::new(
479-
stack_size,
480-
mem::transmute::<Box<dyn FnOnce() + 'a>, Box<dyn FnOnce() + 'static>>(Box::new(
481-
main,
482-
)),
483-
)?),
488+
native: unsafe {
489+
Some(imp::Thread::new(
490+
stack_size,
491+
mem::transmute::<Box<dyn FnOnce() + 'a>, Box<dyn FnOnce() + 'static>>(
492+
Box::new(main),
493+
),
494+
)?)
495+
},
484496
thread: my_thread,
485497
packet: Packet(my_packet),
486498
}))

0 commit comments

Comments
 (0)