Skip to content

Commit 0c99575

Browse files
committed
[libc++] Avoid lifetime UB in __thread_local_data()
Detected on many lld tests with -fsanitize-memory-use-after-dtor. Also https://lab.llvm.org/buildbot/#/builders/sanitizer-x86_64-linux-fast after D122869 will report a lot of them. Threads may outlive static variables. Even if ~__thread_specific_ptr() does nothing, lifetime of members ends with ~ and accessing the value is UB https://eel.is/c++draft/basic.life#1 ``` ==9214==WARNING: MemorySanitizer: use-of-uninitialized-value #0 0x557e1cec4539 in __libcpp_tls_set ../include/c++/v1/__threading_support:428:12 #1 0x557e1cec4539 in set_pointer ../include/c++/v1/thread:196:5 #2 0x557e1cec4539 in void* std::__msan::__thread_proxy< std::__msan::tuple<...>, llvm::parallel::detail::(anonymous namespace)::ThreadPoolExecutor::ThreadPoolExecutor(llvm::ThreadPoolStrategy)::'lambda'()::operator()() const::'lambda'()> >(void*) ../include/c++/v1/thread:285:27 Memory was marked as uninitialized #0 0x557e10a0759d in __sanitizer_dtor_callback compiler-rt/lib/msan/msan_interceptors.cpp:940:5 #1 0x557e1d8c478d in std::__msan::__thread_specific_ptr<std::__msan::__thread_struct>::~__thread_specific_ptr() libcxx/include/thread:188:1 #2 0x557e10a07dc0 in MSanCxaAtExitWrapper(void*) compiler-rt/lib/msan/msan_interceptors.cpp:1151:3 ``` The test needs D123979 or -fsanitize-memory-param-retval enabled by default. Reviewed By: ldionne, #libc Differential Revision: https://reviews.llvm.org/D122864
1 parent 09e75d3 commit 0c99575

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

libcxx/src/thread.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,13 @@ sleep_for(const chrono::nanoseconds& ns)
115115
__thread_specific_ptr<__thread_struct>&
116116
__thread_local_data()
117117
{
118-
static __thread_specific_ptr<__thread_struct> __p;
119-
return __p;
118+
// Even though __thread_specific_ptr's destructor doesn't actually destroy
119+
// anything (see comments there), we can't call it at all because threads may
120+
// outlive the static variable and calling its destructor means accessing an
121+
// object outside of its lifetime, which is UB.
122+
alignas(__thread_specific_ptr<__thread_struct>) static char __b[sizeof(__thread_specific_ptr<__thread_struct>)];
123+
static __thread_specific_ptr<__thread_struct>* __p = new (__b) __thread_specific_ptr<__thread_struct>();
124+
return *__p;
120125
}
121126

122127
// __thread_struct_imp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// UNSUPPORTED: libcpp-has-no-threads
10+
// UNSUPPORTED: c++03
11+
12+
#include "make_test_thread.h"
13+
14+
void func() {}
15+
16+
struct T {
17+
~T() {
18+
// __thread_local_data is expected to be destroyed as it was created
19+
// from the main(). Now trigger another access.
20+
support::make_test_thread(func).join();
21+
}
22+
} t;
23+
24+
int main(int, char**) {
25+
// Triggers construction of __thread_local_data.
26+
support::make_test_thread(func).join();
27+
28+
return 0;
29+
}

0 commit comments

Comments
 (0)