Change:
register_dtor_fallback(t, dtor); } <del> // macOS's analog of the above linux function is this _tlv_atexit function. // The disassembly of thread_local globals in C++ (at least produced by // clang) will have this show up in the output. </del> <ins> // This implementation is very similar to register_dtor_fallback in // sys_common/thread_local.rs. The main difference is that we want to hook into // macOS's analog of the above linux function, _tlv_atexit. OSX will run the // registered dtors before any TLS slots get freed, and when the main thread // exits. // // Unfortunately, calling _tlv_atexit while tls dtors are running is UB. The // workaround below is to register, via _tlv_atexit, a custom DTOR list once per // thread. thread_local dtors are pushed to the DTOR list without calling // _tlv_atexit. </ins> #[cfg(target_os = "macos")] pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) { <ins> use cell::Cell; use ptr; #[thread_local] static REGISTERED: Cell<bool> = Cell::new(false); if !REGISTERED.get() { _tlv_atexit(run_dtors, ptr::null_mut()); REGISTERED.set(true); } type List = Vec<(*mut u8, unsafe extern fn(*mut u8))>; #[thread_local] static DTORS: Cell<*mut List> = Cell::new(ptr::null_mut()); if DTORS.get().is_null() { let v: Box<List> = box Vec::new(); DTORS.set(Box::into_raw(v)); } </ins> extern { fn _tlv_atexit(dtor: unsafe extern fn(*mut u8), arg: *mut u8); } <del> _tlv_atexit(dtor, t); </del> <ins> let list: &mut List = &mut *DTORS.get(); list.push((t, dtor)); unsafe extern fn run_dtors(_: *mut u8) { let mut ptr = DTORS.replace(ptr::null_mut()); while !ptr.is_null() { let list = Box::from_raw(ptr); for (ptr, dtor) in list.into_iter() { dtor(ptr); } ptr = DTORS.replace(ptr::null_mut()); } } </ins> } pub fn requires_move_before_drop() -> bool { <del> // The macOS implementation of TLS apparently had an odd aspect to it // where the pointer we have may be overwritten while this destructor // is running. Specifically if a TLS destructor re-accesses TLS it may // trigger a re-initialization of all TLS variables, paving over at // least some destroyed ones with initial values. // // This means that if we drop a TLS value in place on macOS that we could // revert the value to its original state halfway through the // destructor, which would be bad! // // Hence, we use `ptr::read` on macOS (to move to a "safe" location) // instead of drop_in_place. cfg!(target_os = "macos") </del> <ins> false </ins> }

Discussion:
Moved from because the issue is not specific to . Repro: toolchain: rustc 1.33.0-nightly ( 2019-01-10) OSX 10.14 If thread is changed to access before exiting, then the ASAN error goes away. <details<summaryAddress Sanitizer Log</summary<p</p</details
Might be related to / caused by
Looking at the for and , it sure looks like UB to call during which lldb shows is happening in this example. This is worse than just not running tls destructors for the main thread. It is unsound AFAICT.
Thanks for debugging this I wonder if we can fix this on our end? We might be able to prevent new TLS slots from being initialized after a thread has exited perhaps?
I don't know how to reliably detect that a thread is exiting using the APIs that OSX provides (there could be a way). The logic in Apple's looks similar to what already exists in , except that handles destructor registering during thread exit properly AFAICT. Perhaps we could call into that . FWIW C++ has similar problems with "static local" thread locals when compiled by appleclang.
Makes sense yeah, perhaps as a fallback solution we could just fix this issue for Rust-spawned threads? For those we know exactly when they're exiting (or at least when user-written Rust code has ceased executing) and that may be enough too! Skipping may also be reasonable and we already do that on a number of other platforms too
visiting for T-compiler triage. Retagged as T-libs.
Filed an apple bug report.