about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOrson Peters <orsonpeters@gmail.com>2025-05-28 14:39:51 +0200
committerOrson Peters <orsonpeters@gmail.com>2025-05-28 14:39:51 +0200
commitb0f6b69b813aae1b7525d222ca1d2ba9c1fa25f1 (patch)
treec5b717ffa7892b17a927e114ab6e591c74a1ee2b
parentcb678b94c332548e3c3cefc22637929004c975d6 (diff)
downloadrust-b0f6b69b813aae1b7525d222ca1d2ba9c1fa25f1.tar.gz
rust-b0f6b69b813aae1b7525d222ca1d2ba9c1fa25f1.zip
Do not move thread-locals before dropping
-rw-r--r--library/std/src/sys/thread_local/native/lazy.rs84
1 files changed, 52 insertions, 32 deletions
diff --git a/library/std/src/sys/thread_local/native/lazy.rs b/library/std/src/sys/thread_local/native/lazy.rs
index 51294285ba0..0cb7fa0ef24 100644
--- a/library/std/src/sys/thread_local/native/lazy.rs
+++ b/library/std/src/sys/thread_local/native/lazy.rs
@@ -1,9 +1,9 @@
-use crate::cell::UnsafeCell;
-use crate::hint::unreachable_unchecked;
+use crate::cell::{Cell, UnsafeCell};
+use crate::mem::MaybeUninit;
 use crate::ptr;
 use crate::sys::thread_local::{abort_on_dtor_unwind, destructors};
 
-pub unsafe trait DestroyedState: Sized {
+pub unsafe trait DestroyedState: Sized + Copy {
     fn register_dtor<T>(s: &Storage<T, Self>);
 }
 
@@ -19,15 +19,18 @@ unsafe impl DestroyedState for () {
     }
 }
 
-enum State<T, D> {
-    Initial,
-    Alive(T),
+#[derive(Copy, Clone)]
+enum State<D> {
+    Uninitialized,
+    Initializing,
+    Alive,
     Destroyed(D),
 }
 
 #[allow(missing_debug_implementations)]
 pub struct Storage<T, D> {
-    state: UnsafeCell<State<T, D>>,
+    state: Cell<State<D>>,
+    value: UnsafeCell<MaybeUninit<T>>,
 }
 
 impl<T, D> Storage<T, D>
@@ -35,7 +38,10 @@ where
     D: DestroyedState,
 {
     pub const fn new() -> Storage<T, D> {
-        Storage { state: UnsafeCell::new(State::Initial) }
+        Storage {
+            state: Cell::new(State::Uninitialized),
+            value: UnsafeCell::new(MaybeUninit::uninit()),
+        }
     }
 
     /// Gets a pointer to the TLS value, potentially initializing it with the
@@ -49,35 +55,45 @@ where
     /// The `self` reference must remain valid until the TLS destructor is run.
     #[inline]
     pub unsafe fn get_or_init(&self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
-        let state = unsafe { &*self.state.get() };
-        match state {
-            State::Alive(v) => v,
-            State::Destroyed(_) => ptr::null(),
-            State::Initial => unsafe { self.initialize(i, f) },
+        if let State::Alive = self.state.get() {
+            self.value.get().cast()
+        } else {
+            self.get_or_init_slow(i, f)
         }
     }
 
     #[cold]
-    unsafe fn initialize(&self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
-        // Perform initialization
-
-        let v = i.and_then(Option::take).unwrap_or_else(f);
+    fn get_or_init_slow(&self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
+        // Ensure we have unique access to an uninitialized value.
+        match self.state.get() {
+            State::Uninitialized => self.state.set(State::Initializing),
+            State::Initializing => panic!("thread_local initializer recursively depends on itself"),
+            State::Alive => return self.value.get().cast(),
+            State::Destroyed(_) => return ptr::null(),
+        }
 
-        let old = unsafe { self.state.get().replace(State::Alive(v)) };
-        match old {
-            // If the variable is not being recursively initialized, register
-            // the destructor. This might be a noop if the value does not need
-            // destruction.
-            State::Initial => D::register_dtor(self),
-            // Else, drop the old value. This might be changed to a panic.
-            val => drop(val),
+        struct BackToUninitOnPanic<'a, D>(&'a Cell<State<D>>);
+        impl<'a, D> Drop for BackToUninitOnPanic<'a, D> {
+            fn drop(&mut self) {
+                self.0.set(State::Uninitialized);
+            }
         }
 
-        // SAFETY: the state was just set to `Alive`
+        // Get the initial value, making sure that we restore the state to uninitialized
+        // should f panic.
+        let on_panic = BackToUninitOnPanic(&self.state);
+        let v = i.and_then(Option::take).unwrap_or_else(f);
+        crate::mem::forget(on_panic);
+
+        // SAFETY: we are !Sync so we have exclusive access to self.value. We also ensured
+        // that the state was uninitialized so we aren't replacing a value we must keep alive.
         unsafe {
-            let State::Alive(v) = &*self.state.get() else { unreachable_unchecked() };
-            v
+            self.value.get().write(MaybeUninit::new(v));
         }
+
+        self.state.set(State::Alive);
+        D::register_dtor(self);
+        self.value.get().cast()
     }
 }
 
@@ -92,9 +108,13 @@ unsafe extern "C" fn destroy<T>(ptr: *mut u8) {
     // Print a nice abort message if a panic occurs.
     abort_on_dtor_unwind(|| {
         let storage = unsafe { &*(ptr as *const Storage<T, ()>) };
-        // Update the state before running the destructor as it may attempt to
-        // access the variable.
-        let val = unsafe { storage.state.get().replace(State::Destroyed(())) };
-        drop(val);
+        if let State::Alive = storage.state.replace(State::Destroyed(())) {
+            // SAFETY: we ensured the state was Alive, and prevented running the destructor
+            // twice by updating the state to Destroyed. This is necessary as the destructor
+            // may attempt to access the variable.
+            unsafe {
+                crate::ptr::drop_in_place(storage.value.get().cast::<T>());
+            }
+        }
     })
 }