TSAN false positive with g_atomic_pointer_get/g_atomic_pointer_set under Clang
Compile and run the following program with clang -fsanitize=thread -g repro.c $(pkgconf --cflags --libs glib-2.0) && ./a.out
#include <glib.h>
static const char *str;
static void *worker(gpointer x) {
g_atomic_pointer_set(&str, "foo");
return NULL;
}
int main(void) {
GThread *t = g_thread_new("test", &worker, NULL);
const char *z = g_atomic_pointer_get(&str);
g_print("%s\n", z);
g_thread_join(t);
}
Expected result: no errors.
Actual result:
(null)
==================
WARNING: ThreadSanitizer: data race (pid=16967)
Write of size 8 at 0x5615d465d3a0 by thread T1:
#0 worker repro.c:4:5 (a.out+0xc53de)
#1 g_thread_proxy glib/gthread.c:805:20 (libglib-2.0.so.0+0xb1acc)
Previous read of size 8 at 0x5615d465d3a0 by main thread:
#0 main repro.c:9:21 (a.out+0xc5364)
Location is global 'str' of size 8 at 0x5615d465d3a0 (a.out+0x000000b253a0)
Thread T1 'test' (tid=16970, running) created by main thread at:
#0 pthread_create <null> (a.out+0x842f4)
#1 g_system_thread_new glib/gthread-posix.c:1190:9 (libglib-2.0.so.0+0xe4bf9)
#2 g_thread_new_internal glib/gthread.c:892:21 (libglib-2.0.so.0+0xb1b7c)
#3 g_thread_new glib/gthread.c:848 (libglib-2.0.so.0+0xb1b7c)
#4 main repro.c:8:18 (a.out+0xc5347)
SUMMARY: ThreadSanitizer: data race repro.c:4:5 in worker
==================
ThreadSanitizer: reported 1 warnings
The above trace was obtained from current master (2.61.1-297-g78b1278d) but also exists in 2.60.0 and before. Clang 8.0.1 was used, GCC 9.1.0 did not trigger the false positive.
The pre-processor output from Clang is
# 2 "repro.c" 2
static const char *str;
static void *worker(gpointer x) {
(__extension__ ({ typedef char _GStaticAssertCompileTimeAssertion_1[(sizeof *(&str) == sizeof (gpointer)) ? 1 : -1] __attribute__((__unused__)); (void) (0 ? (gpointer) *(&str) : ((void*)0)); *(&str) = (__typeof__ (*(&str))) (gsize) ("foo"); __sync_synchronize (); }));
return ((void*)0);
}
int main(void) {
GThread *t = g_thread_new("test", &worker, ((void*)0));
const char *z = (__extension__ ({ typedef char _GStaticAssertCompileTimeAssertion_2[(sizeof *(&str) == sizeof (gpointer)) ? 1 : -1] __attribute__((__unused__)); __sync_synchronize (); (gpointer) *(&str); }));
g_print("%s\n", z);
g_thread_join(t);
}
Whereas GCC produces:
# 2 "repro.c" 2
static const char *str;
static void *worker(gpointer x) {
(__extension__ ({ typedef char _GStaticAssertCompileTimeAssertion_1[(sizeof *(&str) == sizeof (gpointer)) ? 1 : -1] __attribute__((__unused__)); (void) (0 ? (gpointer) *(&str) :
# 4 "repro.c" 3 4
((void *)0)
# 4 "repro.c"
); __atomic_store_8 ((&str), (gsize) ("foo"), 5); }));
return
# 5 "repro.c" 3 4
((void *)0)
# 5 "repro.c"
;
}
int main(void) {
GThread *t = g_thread_new("test", &worker,
# 8 "repro.c" 3 4
((void *)0)
# 8 "repro.c"
);
const char *z = (__extension__ ({ typedef char _GStaticAssertCompileTimeAssertion_2[(sizeof *(&str) == sizeof (gpointer)) ? 1 : -1] __attribute__((__unused__)); guint64 gapg_temp = __atomic_load_8 ((&str), 5); (gpointer) gapg_temp; }));
g_print("%s\n", z);
g_thread_join(t);
}
The generated assembly on x86-64 has the same movq
instruction, but on Clang it adds an unnecessary mfence
barrier. The Clang false positive can be avoided by using __atomic_load
and __atomic_store
builtins. (__atomic_load_8
for example is not handled.)