[Clang][TSan] Spurious read of std::nullptr_t member storage causes false-positive data race
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
Clang miscompiles trivial copy/move construction/assignment for non-union class types containing `std::nullptr_t` members when TSan is enabled. In particular, it emits a call to `llvm.memcpy.p0.p0.i64` with arguments that read even the bytes of memory locations occupied by `std::nullptr_t` objects. The member-wise copy specified in [[class.copy.assign] paragraph 12](https://wg21.link/class.copy.assign#12) does not imply anything for accessing the source `std::nullptr_t` member different from lvalue-to-rvalue conversion, which does not access the memory location ([[conv.lval] paragraph 3](https://wg21.link/conv.lval#3)).
**Compiler Explorer:** https://godbolt.org/z/Y8bx3KE63
### Source (`/app/example.cpp`)
```cpp
#include
struct A {
decltype(nullptr) p;
} b;
int main(void) {
A a;
std::thread t([&] {
for (int i = 0; i < 128; ++i) {
a.p = 0;
}
});
for (int i = 0; i < 128; ++i) {
b = a;
}
t.join();
}
```
### Compiler invocation
```bash
clang++ -std=c++26 -fsanitize=thread /app/example.cpp
```
### Compiler output (actual and expected)
(clean compile)
### Run invocation and output
```console
$ ./a.out
==================
WARNING: ThreadSanitizer: data race (pid=1)
Read of size 8 at 0x7fffffffe990 by main thread:
#0 __tsan_memcpy /root/llvm-project/compiler-rt/lib/tsan/rtl/tsan_interceptors_memintrinsics.cpp:27:3 (output.s+0x607de)
#1 main /app/example.cpp:13:7 (output.s+0xe9640)
Previous write of size 8 at 0x7fffffffe990 by thread T1:
#0 main::$_0::operator()() const /app/example.cpp:9:11 (output.s+0xe9c3d)
#1 void std::__invoke_impl(std::__invoke_other, main::$_0&&) /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/invoke.h:63:14 (output.s+0xe9bd5)
#2 std::__invoke_result::type std::__invoke(main::$_0&&) /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/invoke.h:98:14 (output.s+0xe9b45)
#3 void std::thread::_Invoker>::_M_invoke<0ul>(std::_Index_tuple<0ul>) /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/std_thread.h:303:13 (output.s+0xe9afd)
#4 std::thread::_Invoker>::operator()() /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/std_thread.h:310:11 (output.s+0xe9aa5)
#5 std::thread::_State_impl>>::_M_run() /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/std_thread.h:255:13 (output.s+0xe9969)
#6 (libstdc++.so.6+0xf9585) (BuildId: 107b21dc20d33c8591215fed2771491c02556e91)
Location is stack of main thread.
Thread T1 (tid=3, finished) created by main thread at:
#0 pthread_create /root/llvm-project/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1082:3 (output.s+0x653da)
#1 std::thread::_M_start_thread(std::unique_ptr>, void (*)()) (libstdc++.so.6+0xf9658) (BuildId: 107b21dc20d33c8591215fed2771491c02556e91)
#2 main /app/example.cpp:7:15 (output.s+0xe9619)
SUMMARY: ThreadSanitizer: data race /app/example.cpp:13:7 in main
==================
ThreadSanitizer: reported 1 warnings
```
### Compiler version info (`clang++ -v`)
```
clang version 24.0.0git (https://github.com/llvm/llvm-project.git ceaff22ed2c3ceb84da3e9334f511bc3f5b757cd)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /cefs/62/62ce70ab7c0e0c4ed12dbff9_clang-assertions-trunk-20260807/bin
Build config: +assertions
Found candidate GCC installation: /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0
Selected GCC installation: /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Candidate multilib: x32;@mx32
Selected multilib: .;@m64
```
Contributor guide
Research direction
Start with the /app/example.cpp reproducer and run the shown clang++ -std=c++26 -fsanitize=thread invocation. Inspect how Clang lowers trivial copy/move operations for the std::nullptr_t member, using the reported compiler-rt/lib/tsan/rtl/tsan_interceptors_memintrinsics.cpp stack entry as context. Done means the reproducer no longer reports a false data race while the clean compile behavior is preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100