diff options
Diffstat (limited to 'test/tsan')
190 files changed, 7165 insertions, 0 deletions
diff --git a/test/tsan/CMakeLists.txt b/test/tsan/CMakeLists.txt new file mode 100644 index 000000000000..29c0821b3c5a --- /dev/null +++ b/test/tsan/CMakeLists.txt @@ -0,0 +1,27 @@ +set(TSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +if(NOT COMPILER_RT_STANDALONE_BUILD) + list(APPEND TSAN_TEST_DEPS tsan) +endif() +if(COMPILER_RT_HAS_LIBCXX_SOURCES AND + COMPILER_RT_TEST_COMPILER_ID STREQUAL "Clang") + list(APPEND TSAN_TEST_DEPS libcxx_tsan) + set(TSAN_HAS_LIBCXX True) +else() + set(TSAN_HAS_LIBCXX False) +endif() + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg) + +if(COMPILER_RT_INCLUDE_TESTS) + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg) + list(APPEND TSAN_TEST_DEPS TsanUnitTests) +endif() + +add_lit_testsuite(check-tsan "Running ThreadSanitizer tests" + ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${TSAN_TEST_DEPS}) +set_target_properties(check-tsan PROPERTIES FOLDER "TSan tests") diff --git a/test/tsan/Linux/lit.local.cfg b/test/tsan/Linux/lit.local.cfg new file mode 100644 index 000000000000..57271b8078a4 --- /dev/null +++ b/test/tsan/Linux/lit.local.cfg @@ -0,0 +1,9 @@ +def getRoot(config): + if not config.parent: + return config + return getRoot(config.parent) + +root = getRoot(config) + +if root.host_os not in ['Linux']: + config.unsupported = True diff --git a/test/tsan/Linux/mutex_robust.cc b/test/tsan/Linux/mutex_robust.cc new file mode 100644 index 000000000000..5ca5e70d49a7 --- /dev/null +++ b/test/tsan/Linux/mutex_robust.cc @@ -0,0 +1,36 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> + +pthread_mutex_t m; + +void *thr(void *p) { + pthread_mutex_lock(&m); + return 0; +} + +int main() { + pthread_mutexattr_t a; + pthread_mutexattr_init(&a); + pthread_mutexattr_setrobust(&a, PTHREAD_MUTEX_ROBUST); + pthread_mutex_init(&m, &a); + pthread_t th; + pthread_create(&th, 0, thr, 0); + sleep(1); + if (pthread_mutex_lock(&m) != EOWNERDEAD) { + fprintf(stderr, "not EOWNERDEAD\n"); + exit(1); + } + pthread_join(th, 0); + fprintf(stderr, "DONE\n"); +} + +// This is a correct code, and tsan must not bark. +// CHECK-NOT: WARNING: ThreadSanitizer +// CHECK-NOT: EOWNERDEAD +// CHECK: DONE +// CHECK-NOT: WARNING: ThreadSanitizer + diff --git a/test/tsan/Linux/mutex_robust2.cc b/test/tsan/Linux/mutex_robust2.cc new file mode 100644 index 000000000000..0914c1763604 --- /dev/null +++ b/test/tsan/Linux/mutex_robust2.cc @@ -0,0 +1,41 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> + +pthread_mutex_t m; +int x; + +void *thr(void *p) { + pthread_mutex_lock(&m); + x = 42; + return 0; +} + +int main() { + pthread_mutexattr_t a; + pthread_mutexattr_init(&a); + pthread_mutexattr_setrobust(&a, PTHREAD_MUTEX_ROBUST); + pthread_mutex_init(&m, &a); + pthread_t th; + pthread_create(&th, 0, thr, 0); + sleep(1); + if (pthread_mutex_trylock(&m) != EOWNERDEAD) { + fprintf(stderr, "not EOWNERDEAD\n"); + exit(1); + } + x = 43; + pthread_join(th, 0); + fprintf(stderr, "DONE\n"); +} + +// This is a false positive, tsan must not bark at the data race. +// But currently it does. +// CHECK-NOT: WARNING: ThreadSanitizer WARNING: double lock of mutex +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NOT: EOWNERDEAD +// CHECK: DONE +// CHECK-NOT: WARNING: ThreadSanitizer + diff --git a/test/tsan/Linux/user_fopen.cc b/test/tsan/Linux/user_fopen.cc new file mode 100644 index 000000000000..c0ff267ff88b --- /dev/null +++ b/test/tsan/Linux/user_fopen.cc @@ -0,0 +1,34 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <stdlib.h> + +// Defined by tsan. +extern "C" FILE *__interceptor_fopen(const char *file, const char *mode); +extern "C" int __interceptor_fileno(FILE *f); + +extern "C" FILE *fopen(const char *file, const char *mode) { + static int first = 0; + if (__sync_lock_test_and_set(&first, 1) == 0) + printf("user fopen\n"); + return __interceptor_fopen(file, mode); +} + +extern "C" int fileno(FILE *f) { + static int first = 0; + if (__sync_lock_test_and_set(&first, 1) == 0) + printf("user fileno\n"); + return 1; +} + +int main() { + FILE *f = fopen("/dev/zero", "r"); + if (f) { + char buf; + fread(&buf, 1, 1, f); + fclose(f); + } +} + +// CHECK: user fopen +// CHECK-NOT: ThreadSanitizer + diff --git a/test/tsan/Linux/user_malloc.cc b/test/tsan/Linux/user_malloc.cc new file mode 100644 index 000000000000..c671bfcdd17a --- /dev/null +++ b/test/tsan/Linux/user_malloc.cc @@ -0,0 +1,27 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdio.h> + +// Defined by tsan. +extern "C" void *__interceptor_malloc(unsigned long size); +extern "C" void __interceptor_free(void *p); + +extern "C" void *malloc(unsigned long size) { + static int first = 0; + if (__sync_lock_test_and_set(&first, 1) == 0) + printf("user malloc\n"); + return __interceptor_malloc(size); +} + +extern "C" void free(void *p) { + __interceptor_free(p); +} + +int main() { + volatile char *p = (char*)malloc(10); + p[0] = 0; + free((void*)p); +} + +// CHECK: user malloc +// CHECK-NOT: ThreadSanitizer + diff --git a/test/tsan/Unit/lit.site.cfg.in b/test/tsan/Unit/lit.site.cfg.in new file mode 100644 index 000000000000..9498105653a1 --- /dev/null +++ b/test/tsan/Unit/lit.site.cfg.in @@ -0,0 +1,14 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +# Load common config for all compiler-rt unit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/unittests/lit.common.unit.configured") + +# Setup config name. +config.name = 'ThreadSanitizer-Unit' + +# Setup test source and exec root. For unit tests, we define +# it as build directory with ASan unit tests. +# FIXME: De-hardcode this path. +config.test_exec_root = "@COMPILER_RT_BINARY_DIR@/lib/tsan/tests" +config.test_source_root = config.test_exec_root diff --git a/test/tsan/aligned_vs_unaligned_race.cc b/test/tsan/aligned_vs_unaligned_race.cc new file mode 100644 index 000000000000..f82542ed2b85 --- /dev/null +++ b/test/tsan/aligned_vs_unaligned_race.cc @@ -0,0 +1,34 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// Race between an aligned access and an unaligned access, which +// touches the same memory region. +// This is a real race which is not detected by tsan. +// https://code.google.com/p/thread-sanitizer/issues/detail?id=17 +#include <pthread.h> +#include <stdio.h> +#include <stdint.h> + +uint64_t Global[2]; + +void *Thread1(void *x) { + Global[1]++; + return NULL; +} + +void *Thread2(void *x) { + char *p1 = reinterpret_cast<char *>(&Global[0]); + uint64_t *p4 = reinterpret_cast<uint64_t *>(p1 + 1); + (*p4)++; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("Pass\n"); + // CHECK-NOT: ThreadSanitizer: data race + // CHECK: Pass + return 0; +} diff --git a/test/tsan/allocator_returns_null.cc b/test/tsan/allocator_returns_null.cc new file mode 100644 index 000000000000..cde706bc8a1d --- /dev/null +++ b/test/tsan/allocator_returns_null.cc @@ -0,0 +1,64 @@ +// Test the behavior of malloc/calloc/realloc when the allocation size is huge. +// By default (allocator_may_return_null=0) the process should crash. +// With allocator_may_return_null=1 the allocator should return 0. +// +// RUN: %clangxx_tsan -O0 %s -o %t +// RUN: not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH +// RUN: TSAN_OPTIONS=allocator_may_return_null=0 not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH +// RUN: TSAN_OPTIONS=allocator_may_return_null=0 not %run %t calloc 2>&1 | FileCheck %s --check-prefix=CHECK-cCRASH +// RUN: TSAN_OPTIONS=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 | FileCheck %s --check-prefix=CHECK-coCRASH +// RUN: TSAN_OPTIONS=allocator_may_return_null=0 not %run %t realloc 2>&1 | FileCheck %s --check-prefix=CHECK-rCRASH +// RUN: TSAN_OPTIONS=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mrCRASH + +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <limits> +int main(int argc, char **argv) { + volatile size_t size = std::numeric_limits<size_t>::max() - 10000; + assert(argc == 2); + char *x = 0; + if (!strcmp(argv[1], "malloc")) { + fprintf(stderr, "malloc:\n"); + x = (char*)malloc(size); + } + if (!strcmp(argv[1], "calloc")) { + fprintf(stderr, "calloc:\n"); + x = (char*)calloc(size / 4, 4); + } + + if (!strcmp(argv[1], "calloc-overflow")) { + fprintf(stderr, "calloc-overflow:\n"); + volatile size_t kMaxSizeT = std::numeric_limits<size_t>::max(); + size_t kArraySize = 4096; + volatile size_t kArraySize2 = kMaxSizeT / kArraySize + 10; + x = (char*)calloc(kArraySize, kArraySize2); + } + + if (!strcmp(argv[1], "realloc")) { + fprintf(stderr, "realloc:\n"); + x = (char*)realloc(0, size); + } + if (!strcmp(argv[1], "realloc-after-malloc")) { + fprintf(stderr, "realloc-after-malloc:\n"); + char *t = (char*)malloc(100); + *t = 42; + x = (char*)realloc(t, size); + assert(*t == 42); + } + fprintf(stderr, "x: %p\n", x); + return x != 0; +} +// CHECK-mCRASH: malloc: +// CHECK-mCRASH: ThreadSanitizer's allocator is terminating the process +// CHECK-cCRASH: calloc: +// CHECK-cCRASH: ThreadSanitizer's allocator is terminating the process +// CHECK-coCRASH: calloc-overflow: +// CHECK-coCRASH: ThreadSanitizer's allocator is terminating the process +// CHECK-rCRASH: realloc: +// CHECK-rCRASH: ThreadSanitizer's allocator is terminating the process +// CHECK-mrCRASH: realloc-after-malloc: +// CHECK-mrCRASH: ThreadSanitizer's allocator is terminating the process + diff --git a/test/tsan/atexit.cc b/test/tsan/atexit.cc new file mode 100644 index 000000000000..69acb4dd783f --- /dev/null +++ b/test/tsan/atexit.cc @@ -0,0 +1,29 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +class Logger { + public: + Logger() { + fprintf(stderr, "Logger ctor\n"); + } + + ~Logger() { + fprintf(stderr, "Logger dtor\n"); + } +}; + +Logger logger; + +void log_from_atexit() { + fprintf(stderr, "In log_from_atexit\n"); +} + +int main() { + atexit(log_from_atexit); +} + +// CHECK: Logger ctor +// CHECK: In log_from_atexit +// CHECK: Logger dtor diff --git a/test/tsan/atexit2.cc b/test/tsan/atexit2.cc new file mode 100644 index 000000000000..6f74c5f9f6e5 --- /dev/null +++ b/test/tsan/atexit2.cc @@ -0,0 +1,26 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +int n; +const int N = 10000; + +static void atexit1() { + n++; +} + +static void atexit0() { + fprintf(stderr, "run count: %d\n", n); +} + +int main() { + atexit(atexit0); + for (int i = 0; i < N; i++) + atexit(atexit1); +} + +// CHECK-NOT: FATAL: ThreadSanitizer +// CHECK-NOT: WARNING: ThreadSanitizer +// CHECK: run count: 10000 + diff --git a/test/tsan/atomic_free.cc b/test/tsan/atomic_free.cc new file mode 100644 index 000000000000..1dcf887c41d5 --- /dev/null +++ b/test/tsan/atomic_free.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +void *Thread(void *a) { + __atomic_fetch_add((int*)a, 1, __ATOMIC_SEQ_CST); + return 0; +} + +int main() { + int *a = new int(0); + pthread_t t; + pthread_create(&t, 0, Thread, a); + sleep(1); + delete a; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/atomic_free2.cc b/test/tsan/atomic_free2.cc new file mode 100644 index 000000000000..c50be6bba940 --- /dev/null +++ b/test/tsan/atomic_free2.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +void *Thread(void *a) { + sleep(1); + __atomic_fetch_add((int*)a, 1, __ATOMIC_SEQ_CST); + return 0; +} + +int main() { + int *a = new int(0); + pthread_t t; + pthread_create(&t, 0, Thread, a); + delete a; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: heap-use-after-free diff --git a/test/tsan/atomic_norace.cc b/test/tsan/atomic_norace.cc new file mode 100644 index 000000000000..d9ccda58883c --- /dev/null +++ b/test/tsan/atomic_norace.cc @@ -0,0 +1,61 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +const int kTestCount = 4; +typedef long long T; +T atomics[kTestCount * 2]; + +void Test(int test, T *p, bool main_thread) { + volatile T sink; + if (test == 0) { + if (main_thread) + __atomic_fetch_add(p, 1, __ATOMIC_RELAXED); + else + __atomic_fetch_add(p, 1, __ATOMIC_RELAXED); + } else if (test == 1) { + if (main_thread) + __atomic_exchange_n(p, 1, __ATOMIC_ACQ_REL); + else + __atomic_exchange_n(p, 1, __ATOMIC_ACQ_REL); + } else if (test == 2) { + if (main_thread) + sink = __atomic_load_n(p, __ATOMIC_SEQ_CST); + else + __atomic_store_n(p, 1, __ATOMIC_SEQ_CST); + } else if (test == 3) { + if (main_thread) + sink = __atomic_load_n(p, __ATOMIC_SEQ_CST); + else + sink = *p; + } +} + +void *Thread(void *p) { + for (int i = 0; i < kTestCount; i++) { + Test(i, &atomics[i], false); + } + sleep(2); + for (int i = 0; i < kTestCount; i++) { + fprintf(stderr, "Test %d reverse\n", i); + Test(i, &atomics[kTestCount + i], false); + } + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + sleep(1); + for (int i = 0; i < kTestCount; i++) { + fprintf(stderr, "Test %d\n", i); + Test(i, &atomics[i], true); + } + for (int i = 0; i < kTestCount; i++) { + Test(i, &atomics[kTestCount + i], true); + } + pthread_join(t, 0); +} + +// CHECK-NOT: ThreadSanitizer: data race diff --git a/test/tsan/atomic_race.cc b/test/tsan/atomic_race.cc new file mode 100644 index 000000000000..9cee8ed8272f --- /dev/null +++ b/test/tsan/atomic_race.cc @@ -0,0 +1,80 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> +#include <stdio.h> + +const int kTestCount = 4; +typedef long long T; +T atomics[kTestCount * 2]; + +void Test(int test, T *p, bool main_thread) { + volatile T sink; + if (test == 0) { + if (main_thread) + __atomic_fetch_add(p, 1, __ATOMIC_RELAXED); + else + *p = 42; + } else if (test == 1) { + if (main_thread) + __atomic_fetch_add(p, 1, __ATOMIC_RELAXED); + else + sink = *p; + } else if (test == 2) { + if (main_thread) + sink = __atomic_load_n(p, __ATOMIC_SEQ_CST); + else + *p = 42; + } else if (test == 3) { + if (main_thread) + __atomic_store_n(p, 1, __ATOMIC_SEQ_CST); + else + sink = *p; + } +} + +void *Thread(void *p) { + for (int i = 0; i < kTestCount; i++) { + Test(i, &atomics[i], false); + } + sleep(4); + for (int i = 0; i < kTestCount; i++) { + fprintf(stderr, "Test %d reverse\n", i); + Test(i, &atomics[kTestCount + i], false); + } + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + sleep(1); + for (int i = 0; i < kTestCount; i++) { + fprintf(stderr, "Test %d\n", i); + Test(i, &atomics[i], true); + } + for (int i = 0; i < kTestCount; i++) { + Test(i, &atomics[kTestCount + i], true); + } + pthread_join(t, 0); +} + +// CHECK: Test 0 +// CHECK: ThreadSanitizer: data race +// CHECK-NOT: SUMMARY{{.*}}tsan_interface_atomic +// CHECK: Test 1 +// CHECK: ThreadSanitizer: data race +// CHECK-NOT: SUMMARY{{.*}}tsan_interface_atomic +// CHECK: Test 2 +// CHECK: ThreadSanitizer: data race +// CHECK-NOT: SUMMARY{{.*}}tsan_interface_atomic +// CHECK: Test 3 +// CHECK: ThreadSanitizer: data race +// CHECK-NOT: SUMMARY{{.*}}tsan_interface_atomic +// CHECK: Test 0 reverse +// CHECK: ThreadSanitizer: data race +// CHECK: Test 1 reverse +// CHECK: ThreadSanitizer: data race +// CHECK: Test 2 reverse +// CHECK: ThreadSanitizer: data race +// CHECK: Test 3 reverse +// CHECK: ThreadSanitizer: data race diff --git a/test/tsan/atomic_stack.cc b/test/tsan/atomic_stack.cc new file mode 100644 index 000000000000..7e3176f8e784 --- /dev/null +++ b/test/tsan/atomic_stack.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + sleep(1); + __atomic_fetch_add(&Global, 1, __ATOMIC_RELAXED); + return NULL; +} + +void *Thread2(void *x) { + Global++; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Atomic write of size 4 +// CHECK: #0 __tsan_atomic32_fetch_add +// CHECK: #1 Thread1 diff --git a/test/tsan/barrier.cc b/test/tsan/barrier.cc new file mode 100644 index 000000000000..d8c2b6ffe514 --- /dev/null +++ b/test/tsan/barrier.cc @@ -0,0 +1,37 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// CHECK-NOT: ThreadSanitizer: data race +// CHECK: DONE + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> + +const int kSize = 4; +volatile int kIter = 10; // prevent unwinding +int data[2][kSize]; +pthread_barrier_t barrier; + +void *thr(void *p) { + int idx = (int)(long)p; + for (int i = 0; i < kIter; i++) { + int *prev = data[i % 2]; + int *curr = data[(i + 1) % 2]; + int left = idx - 1 >= 0 ? prev[idx - 1] : 0; + int right = idx + 1 < kSize ? prev[idx + 1] : 0; + curr[idx] = (left + right) / 2; + pthread_barrier_wait(&barrier); + } + return 0; +} + +int main() { + pthread_barrier_init(&barrier, 0, kSize); + pthread_t th[kSize]; + for (int i = 0; i < kSize; i++) + pthread_create(&th[i], 0, thr, (void*)(long)i); + for (int i = 0; i < kSize; i++) + pthread_join(th[i], 0); + pthread_barrier_destroy(&barrier); + fprintf(stderr, "DONE\n"); +} diff --git a/test/tsan/bench.h b/test/tsan/bench.h new file mode 100644 index 000000000000..5ae0dd813db9 --- /dev/null +++ b/test/tsan/bench.h @@ -0,0 +1,59 @@ +#include <pthread.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdio.h> +#include <time.h> + +int bench_nthread; +int bench_niter; +int grow_clock_var; +pthread_barrier_t glow_clock_barrier; + +void bench(); // defined by user +void start_thread_group(int nth, void(*f)(int tid)); +void grow_clock_worker(int tid); + +int main(int argc, char **argv) { + bench_nthread = 2; + if (argc > 1) + bench_nthread = atoi(argv[1]); + bench_niter = 100; + if (argc > 2) + bench_niter = atoi(argv[2]); + + // Grow thread's clock. + int clock_size = 10; + if (argc > 1) + clock_size = 1000; + pthread_barrier_init(&glow_clock_barrier, 0, clock_size); + start_thread_group(clock_size, grow_clock_worker); + pthread_barrier_destroy(&glow_clock_barrier); + __atomic_load_n(&grow_clock_var, __ATOMIC_ACQUIRE); + + timespec tp0; + clock_gettime(CLOCK_MONOTONIC, &tp0); + bench(); + timespec tp1; + clock_gettime(CLOCK_MONOTONIC, &tp1); + unsigned long long t = + (tp1.tv_sec * 1000000000ULL + tp1.tv_nsec) - + (tp0.tv_sec * 1000000000ULL + tp0.tv_nsec); + fprintf(stderr, "%llu ns/iter\n", t / bench_niter); + fprintf(stderr, "DONE\n"); +} + +void start_thread_group(int nth, void(*f)(int tid)) { + pthread_t *th = (pthread_t*)malloc(nth * sizeof(pthread_t)); + for (int i = 0; i < nth; i++) + pthread_create(&th[i], 0, (void*(*)(void*))f, (void*)(long)i); + for (int i = 0; i < nth; i++) + pthread_join(th[i], 0); +} + +void grow_clock_worker(int tid) { + int res = pthread_barrier_wait(&glow_clock_barrier); + if (res == PTHREAD_BARRIER_SERIAL_THREAD) + __atomic_store_n(&grow_clock_var, 0, __ATOMIC_RELEASE); +} + diff --git a/test/tsan/bench_acquire_only.cc b/test/tsan/bench_acquire_only.cc new file mode 100644 index 000000000000..5cd6bd74ebee --- /dev/null +++ b/test/tsan/bench_acquire_only.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +int x; + +void thread(int tid) { + for (int i = 0; i < bench_niter; i++) + __atomic_load_n(&x, __ATOMIC_ACQUIRE); +} + +void bench() { + __atomic_store_n(&x, 0, __ATOMIC_RELEASE); + start_thread_group(bench_nthread, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_acquire_release.cc b/test/tsan/bench_acquire_release.cc new file mode 100644 index 000000000000..9e53a7b26efa --- /dev/null +++ b/test/tsan/bench_acquire_release.cc @@ -0,0 +1,18 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +int x; + +void thread(int tid) { + for (int i = 0; i < bench_niter; i++) + __atomic_fetch_add(&x, 1, __ATOMIC_ACQ_REL); +} + +void bench() { + start_thread_group(bench_nthread, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_local_mutex.cc b/test/tsan/bench_local_mutex.cc new file mode 100644 index 000000000000..0fa1db0c883c --- /dev/null +++ b/test/tsan/bench_local_mutex.cc @@ -0,0 +1,27 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +pthread_mutex_t *mtx; +const int kStride = 16; + +void thread(int tid) { + for (int i = 0; i < bench_niter; i++) { + pthread_mutex_lock(&mtx[tid * kStride]); + pthread_mutex_unlock(&mtx[tid * kStride]); + } +} + +void bench() { + mtx = (pthread_mutex_t*)malloc(bench_nthread * kStride * sizeof(*mtx)); + for (int i = 0; i < bench_nthread; i++) { + pthread_mutex_init(&mtx[i * kStride], 0); + pthread_mutex_lock(&mtx[i * kStride]); + pthread_mutex_unlock(&mtx[i * kStride]); + } + start_thread_group(bench_nthread, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_mutex.cc b/test/tsan/bench_mutex.cc new file mode 100644 index 000000000000..324d53fd7f28 --- /dev/null +++ b/test/tsan/bench_mutex.cc @@ -0,0 +1,28 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +pthread_mutex_t mtx; +pthread_cond_t cv; +int x; + +void thread(int tid) { + for (int i = 0; i < bench_niter; i++) { + pthread_mutex_lock(&mtx); + while (x != i * 2 + tid) + pthread_cond_wait(&cv, &mtx); + x++; + pthread_cond_signal(&cv); + pthread_mutex_unlock(&mtx); + } +} + +void bench() { + pthread_mutex_init(&mtx, 0); + pthread_cond_init(&cv, 0); + start_thread_group(2, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_release_only.cc b/test/tsan/bench_release_only.cc new file mode 100644 index 000000000000..0a86f73f249e --- /dev/null +++ b/test/tsan/bench_release_only.cc @@ -0,0 +1,23 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +int *x; +const int kStride = 32; + +void thread(int tid) { + __atomic_load_n(&x[tid * kStride], __ATOMIC_ACQUIRE); + for (int i = 0; i < bench_niter; i++) + __atomic_store_n(&x[tid * kStride], 0, __ATOMIC_RELEASE); +} + +void bench() { + x = (int*)malloc(bench_nthread * kStride * sizeof(x[0])); + for (int i = 0; i < bench_nthread; i++) + __atomic_store_n(&x[i * kStride], 0, __ATOMIC_RELEASE); + start_thread_group(bench_nthread, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_rwmutex.cc b/test/tsan/bench_rwmutex.cc new file mode 100644 index 000000000000..818ee8c82bc1 --- /dev/null +++ b/test/tsan/bench_rwmutex.cc @@ -0,0 +1,25 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +pthread_rwlock_t mtx; + +void thread(int tid) { + for (int i = 0; i < bench_niter; i++) { + pthread_rwlock_rdlock(&mtx); + pthread_rwlock_unlock(&mtx); + } +} + +void bench() { + pthread_rwlock_init(&mtx, 0); + pthread_rwlock_wrlock(&mtx); + pthread_rwlock_unlock(&mtx); + pthread_rwlock_rdlock(&mtx); + pthread_rwlock_unlock(&mtx); + start_thread_group(bench_nthread, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_shadow_flush.cc b/test/tsan/bench_shadow_flush.cc new file mode 100644 index 000000000000..0f412bbe82f9 --- /dev/null +++ b/test/tsan/bench_shadow_flush.cc @@ -0,0 +1,48 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include <pthread.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <stdio.h> +#include <time.h> +#include <sys/mman.h> + +const long kSmallPage = 4 << 10; +const long kLargePage = 2 << 20; +const long kStride = 1 << 10; + +typedef unsigned long uptr; + +int main(int argc, const char **argv) { + uptr mem_size = 4 << 20; + if (argc > 1) + mem_size = (uptr)atoi(argv[1]) << 20; + uptr stride = kSmallPage; + if (argc > 2) + stride = (uptr)atoi(argv[2]) << 10; + int niter = 1; + if (argc > 3) + niter = atoi(argv[3]); + int stride2 = 1; + if (argc > 4) + stride2 = atoi(argv[4]); + + uptr sz = mem_size + stride2 * kStride + kLargePage; + void *p = mmap(0, sz, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + uptr a = ((uptr)p + kLargePage - 1) & ~(kLargePage - 1); + volatile char *mem = (volatile char *)a; + + for (int i = 0; i < niter; i++) { + for (uptr off = 0; off < mem_size; off += stride) { + for (uptr off2 = 0; off2 < stride2; off2++) + mem[off + off2 * kStride] = 42; + } + } + + fprintf(stderr, "DONE\n"); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_single_writer.cc b/test/tsan/bench_single_writer.cc new file mode 100644 index 000000000000..0d3810a03ad0 --- /dev/null +++ b/test/tsan/bench_single_writer.cc @@ -0,0 +1,23 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +int x; + +void thread(int tid) { + if (tid == 0) { + for (int i = 0; i < bench_niter; i++) + __atomic_store_n(&x, 0, __ATOMIC_RELEASE); + } else { + for (int i = 0; i < bench_niter; i++) + __atomic_load_n(&x, __ATOMIC_ACQUIRE); + } +} + +void bench() { + start_thread_group(bench_nthread, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/bench_ten_mutexes.cc b/test/tsan/bench_ten_mutexes.cc new file mode 100644 index 000000000000..876f1365ee43 --- /dev/null +++ b/test/tsan/bench_ten_mutexes.cc @@ -0,0 +1,26 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s + +#include "bench.h" + +const int kMutex = 10; +pthread_mutex_t mtx[kMutex]; + +void thread(int tid) { + for (int i = 0; i < bench_niter; i++) { + int idx = (i % kMutex); + if (tid == 0) + idx = kMutex - idx - 1; + pthread_mutex_lock(&mtx[idx]); + pthread_mutex_unlock(&mtx[idx]); + } +} + +void bench() { + for (int i = 0; i < kMutex; i++) + pthread_mutex_init(&mtx[i], 0); + start_thread_group(2, thread); +} + +// CHECK: DONE + diff --git a/test/tsan/benign_race.cc b/test/tsan/benign_race.cc new file mode 100644 index 000000000000..b6cba19aa96e --- /dev/null +++ b/test/tsan/benign_race.cc @@ -0,0 +1,39 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +int WTFGlobal; + +extern "C" { +void AnnotateBenignRaceSized(const char *f, int l, + void *mem, unsigned int size, const char *desc); +void WTFAnnotateBenignRaceSized(const char *f, int l, + void *mem, unsigned int size, + const char *desc); +} + + +void *Thread(void *x) { + Global = 42; + WTFGlobal = 142; + return 0; +} + +int main() { + AnnotateBenignRaceSized(__FILE__, __LINE__, + &Global, sizeof(Global), "Race on Global"); + WTFAnnotateBenignRaceSized(__FILE__, __LINE__, + &WTFGlobal, sizeof(WTFGlobal), + "Race on WTFGlobal"); + pthread_t t; + pthread_create(&t, 0, Thread, 0); + sleep(1); + Global = 43; + WTFGlobal = 143; + pthread_join(t, 0); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/blacklist.cc b/test/tsan/blacklist.cc new file mode 100644 index 000000000000..d6ca383cb758 --- /dev/null +++ b/test/tsan/blacklist.cc @@ -0,0 +1,30 @@ +// Test blacklist functionality for TSan. + +// RUN: echo "fun:*Blacklisted_Thread2*" > %t.blacklist +// RUN: %clangxx_tsan -O1 %s -fsanitize-blacklist=%t.blacklist -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +int Global; + +void *Thread1(void *x) { + Global++; + return NULL; +} + +void *Blacklisted_Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Blacklisted_Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("PASS\n"); + return 0; +} + +// CHECK-NOT: ThreadSanitizer: data race diff --git a/test/tsan/blacklist2.cc b/test/tsan/blacklist2.cc new file mode 100644 index 000000000000..1092561e55bb --- /dev/null +++ b/test/tsan/blacklist2.cc @@ -0,0 +1,49 @@ +// Test that blacklisted functions are still contained in the stack trace. + +// RUN: echo "fun:*Blacklisted_Thread2*" > %t.blacklist +// RUN: echo "fun:*CallTouchGlobal*" >> %t.blacklist + +// RUN: %clangxx_tsan -O1 %s -fsanitize-blacklist=%t.blacklist -o %t +// RUN: %deflake %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + sleep(1); + // CHECK: ThreadSanitizer: data race + // CHECK: Write of size 4 + // CHECK: #0 Thread1{{.*}}blacklist2.cc:[[@LINE+1]] + Global++; + return NULL; +} + +void TouchGlobal() { + // CHECK: Previous write of size 4 + // CHECK: #0 TouchGlobal{{.*}}blacklist2.cc:[[@LINE+1]] + Global--; +} + +void CallTouchGlobal() { + // CHECK: #1 CallTouchGlobal{{.*}}blacklist2.cc:[[@LINE+1]] + TouchGlobal(); +} + +void *Blacklisted_Thread2(void *x) { + Global--; + // CHECK: #2 Blacklisted_Thread2{{.*}}blacklist2.cc:[[@LINE+1]] + CallTouchGlobal(); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Blacklisted_Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("PASS\n"); + return 0; +} diff --git a/test/tsan/cond.c b/test/tsan/cond.c new file mode 100644 index 000000000000..05ea672c6c1c --- /dev/null +++ b/test/tsan/cond.c @@ -0,0 +1,53 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK-NOT: ThreadSanitizer WARNING: double lock +// CHECK-NOT: ThreadSanitizer WARNING: mutex unlock by another thread +// CHECK: OK + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> + +pthread_mutex_t m; +pthread_cond_t c; +int x; + +void *thr1(void *p) { + int i; + + for (i = 0; i < 10; i += 2) { + pthread_mutex_lock(&m); + while (x != i) + pthread_cond_wait(&c, &m); + x = i + 1; + pthread_cond_signal(&c); + pthread_mutex_unlock(&m); + } + return 0; +} + +void *thr2(void *p) { + int i; + + for (i = 1; i < 10; i += 2) { + pthread_mutex_lock(&m); + while (x != i) + pthread_cond_wait(&c, &m); + x = i + 1; + pthread_mutex_unlock(&m); + pthread_cond_broadcast(&c); + } + return 0; +} + +int main() { + pthread_t th1, th2; + + pthread_mutex_init(&m, 0); + pthread_cond_init(&c, 0); + pthread_create(&th1, 0, thr1, 0); + pthread_create(&th2, 0, thr2, 0); + pthread_join(th1, 0); + pthread_join(th2, 0); + fprintf(stderr, "OK\n"); +} diff --git a/test/tsan/cond_cancel.c b/test/tsan/cond_cancel.c new file mode 100644 index 000000000000..397cad4b1838 --- /dev/null +++ b/test/tsan/cond_cancel.c @@ -0,0 +1,37 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// CHECK-NOT: WARNING +// CHECK: OK + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> + +pthread_mutex_t m; +pthread_cond_t c; +int x; + +void *thr1(void *p) { + pthread_mutex_lock(&m); + pthread_cleanup_push((void(*)(void *arg))pthread_mutex_unlock, &m); + while (x == 0) + pthread_cond_wait(&c, &m); + pthread_cleanup_pop(1); + return 0; +} + +int main() { + pthread_t th; + + pthread_mutex_init(&m, 0); + pthread_cond_init(&c, 0); + + pthread_create(&th, 0, thr1, 0); + sleep(1); // let it block on cond var + pthread_cancel(th); + + pthread_join(th, 0); + pthread_mutex_lock(&m); + pthread_mutex_unlock(&m); + fprintf(stderr, "OK\n"); +} diff --git a/test/tsan/cond_race.cc b/test/tsan/cond_race.cc new file mode 100644 index 000000000000..fa42fafca4d2 --- /dev/null +++ b/test/tsan/cond_race.cc @@ -0,0 +1,40 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +// CHECK-NOT: unlock of unlocked mutex +// CHECK: ThreadSanitizer: data race +// CHECK: pthread_cond_signal + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> + +struct Ctx { + pthread_mutex_t m; + pthread_cond_t c; + bool done; +}; + +void *thr(void *p) { + Ctx *c = (Ctx*)p; + pthread_mutex_lock(&c->m); + c->done = true; + pthread_mutex_unlock(&c->m); + pthread_cond_signal(&c->c); + return 0; +} + +int main() { + Ctx *c = new Ctx(); + pthread_mutex_init(&c->m, 0); + pthread_cond_init(&c->c, 0); + pthread_t th; + pthread_create(&th, 0, thr, c); + pthread_mutex_lock(&c->m); + while (!c->done) + pthread_cond_wait(&c->c, &c->m); + pthread_mutex_unlock(&c->m); + // w/o this sleep, it can be reported as use-after-free + sleep(1); + delete c; + pthread_join(th, 0); +} diff --git a/test/tsan/cond_version.c b/test/tsan/cond_version.c new file mode 100644 index 000000000000..2282c3ad738d --- /dev/null +++ b/test/tsan/cond_version.c @@ -0,0 +1,44 @@ +// RUN: %clang_tsan -O1 %s -o %t -lrt && %run %t 2>&1 | FileCheck %s +// Test that pthread_cond is properly intercepted, +// previously there were issues with versioned symbols. +// CHECK: OK + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <time.h> +#include <errno.h> + +int main() { + typedef unsigned long long u64; + pthread_mutex_t m; + pthread_cond_t c; + pthread_condattr_t at; + struct timespec ts0, ts1, ts2; + int res; + u64 sleep; + + pthread_mutex_init(&m, 0); + pthread_condattr_init(&at); + pthread_condattr_setclock(&at, CLOCK_MONOTONIC); + pthread_cond_init(&c, &at); + + clock_gettime(CLOCK_MONOTONIC, &ts0); + ts1 = ts0; + ts1.tv_sec += 2; + + pthread_mutex_lock(&m); + do { + res = pthread_cond_timedwait(&c, &m, &ts1); + } while (res == 0); + pthread_mutex_unlock(&m); + + clock_gettime(CLOCK_MONOTONIC, &ts2); + sleep = (u64)ts2.tv_sec * 1000000000 + ts2.tv_nsec - + ((u64)ts0.tv_sec * 1000000000 + ts0.tv_nsec); + if (res != ETIMEDOUT) + exit(printf("bad return value %d, want %d\n", res, ETIMEDOUT)); + if (sleep < 1000000000) + exit(printf("bad sleep duration %lluns, want %dns\n", sleep, 1000000000)); + fprintf(stderr, "OK\n"); +} diff --git a/test/tsan/deadlock_detector_stress_test.cc b/test/tsan/deadlock_detector_stress_test.cc new file mode 100644 index 000000000000..53624782e07b --- /dev/null +++ b/test/tsan/deadlock_detector_stress_test.cc @@ -0,0 +1,596 @@ +// RUN: %clangxx_tsan %s -o %t -DLockType=PthreadMutex +// RUN: TSAN_OPTIONS=detect_deadlocks=1 %deflake %run %t | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOT-SECOND +// TSAN_OPTIONS="detect_deadlocks=1 second_deadlock_stack=1" %deflake %run %t | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-SECOND +// RUN: %clangxx_tsan %s -o %t -DLockType=PthreadSpinLock +// RUN: TSAN_OPTIONS=detect_deadlocks=1 %deflake %run %t | FileCheck %s +// RUN: %clangxx_tsan %s -o %t -DLockType=PthreadRWLock +// RUN: TSAN_OPTIONS=detect_deadlocks=1 %deflake %run %t | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-RD +// RUN: %clangxx_tsan %s -o %t -DLockType=PthreadRecursiveMutex +// RUN: TSAN_OPTIONS=detect_deadlocks=1 %deflake %run %t | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-REC +#include <pthread.h> +#undef NDEBUG +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <new> + +#ifndef LockType +#define LockType PthreadMutex +#endif + +// You can optionally pass [test_number [iter_count]] on command line. +static int test_number = -1; +static int iter_count = 100000; + +class PthreadMutex { + public: + explicit PthreadMutex(bool recursive = false) { + if (recursive) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + assert(0 == pthread_mutex_init(&mu_, &attr)); + } else { + assert(0 == pthread_mutex_init(&mu_, 0)); + } + } + ~PthreadMutex() { + assert(0 == pthread_mutex_destroy(&mu_)); + (void)padding_; + } + static bool supports_read_lock() { return false; } + static bool supports_recursive_lock() { return false; } + void lock() { assert(0 == pthread_mutex_lock(&mu_)); } + void unlock() { assert(0 == pthread_mutex_unlock(&mu_)); } + bool try_lock() { return 0 == pthread_mutex_trylock(&mu_); } + void rdlock() { assert(0); } + void rdunlock() { assert(0); } + bool try_rdlock() { assert(0); } + + private: + pthread_mutex_t mu_; + char padding_[64 - sizeof(pthread_mutex_t)]; +}; + +class PthreadRecursiveMutex : public PthreadMutex { + public: + PthreadRecursiveMutex() : PthreadMutex(true) { } + static bool supports_recursive_lock() { return true; } +}; + +class PthreadSpinLock { + public: + PthreadSpinLock() { assert(0 == pthread_spin_init(&mu_, 0)); } + ~PthreadSpinLock() { + assert(0 == pthread_spin_destroy(&mu_)); + (void)padding_; + } + static bool supports_read_lock() { return false; } + static bool supports_recursive_lock() { return false; } + void lock() { assert(0 == pthread_spin_lock(&mu_)); } + void unlock() { assert(0 == pthread_spin_unlock(&mu_)); } + bool try_lock() { return 0 == pthread_spin_trylock(&mu_); } + void rdlock() { assert(0); } + void rdunlock() { assert(0); } + bool try_rdlock() { assert(0); } + + private: + pthread_spinlock_t mu_; + char padding_[64 - sizeof(pthread_spinlock_t)]; +}; + +class PthreadRWLock { + public: + PthreadRWLock() { assert(0 == pthread_rwlock_init(&mu_, 0)); } + ~PthreadRWLock() { + assert(0 == pthread_rwlock_destroy(&mu_)); + (void)padding_; + } + static bool supports_read_lock() { return true; } + static bool supports_recursive_lock() { return false; } + void lock() { assert(0 == pthread_rwlock_wrlock(&mu_)); } + void unlock() { assert(0 == pthread_rwlock_unlock(&mu_)); } + bool try_lock() { return 0 == pthread_rwlock_trywrlock(&mu_); } + void rdlock() { assert(0 == pthread_rwlock_rdlock(&mu_)); } + void rdunlock() { assert(0 == pthread_rwlock_unlock(&mu_)); } + bool try_rdlock() { return 0 == pthread_rwlock_tryrdlock(&mu_); } + + private: + pthread_rwlock_t mu_; + char padding_[64 - sizeof(pthread_rwlock_t)]; +}; + +class LockTest { + public: + LockTest() : n_(), locks_() {} + void Init(size_t n) { + n_ = n; + locks_ = new LockType*[n_]; + for (size_t i = 0; i < n_; i++) + locks_[i] = new LockType; + } + ~LockTest() { + for (size_t i = 0; i < n_; i++) + delete locks_[i]; + delete [] locks_; + } + void L(size_t i) { + assert(i < n_); + locks_[i]->lock(); + } + + void U(size_t i) { + assert(i < n_); + locks_[i]->unlock(); + } + + void RL(size_t i) { + assert(i < n_); + locks_[i]->rdlock(); + } + + void RU(size_t i) { + assert(i < n_); + locks_[i]->rdunlock(); + } + + void *A(size_t i) { + assert(i < n_); + return locks_[i]; + } + + bool T(size_t i) { + assert(i < n_); + return locks_[i]->try_lock(); + } + + // Simple lock order onversion. + void Test1() { + if (test_number > 0 && test_number != 1) return; + fprintf(stderr, "Starting Test1\n"); + // CHECK: Starting Test1 + Init(5); + fprintf(stderr, "Expecting lock inversion: %p %p\n", A(0), A(1)); + // CHECK: Expecting lock inversion: [[A1:0x[a-f0-9]*]] [[A2:0x[a-f0-9]*]] + Lock_0_1(); + Lock_1_0(); + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion (potential deadlock) + // CHECK: Cycle in lock order graph: [[M1:M[0-9]+]] ([[A1]]) => [[M2:M[0-9]+]] ([[A2]]) => [[M1]] + // CHECK: Mutex [[M2]] acquired here while holding mutex [[M1]] + // CHECK: #0 pthread_ + // CHECK-SECOND: Mutex [[M1]] previously acquired by the same thread here: + // CHECK-SECOND: #0 pthread_ + // CHECK-NOT-SECOND: second_deadlock_stack=1 to get more informative warning message + // CHECK-NOT-SECOND-NOT: #0 pthread_ + // CHECK: Mutex [[M1]] acquired here while holding mutex [[M2]] + // CHECK: #0 pthread_ + // CHECK-SECOND: Mutex [[M2]] previously acquired by the same thread here: + // CHECK-SECOND: #0 pthread_ + // CHECK-NOT-SECOND-NOT: #0 pthread_ + // CHECK-NOT: WARNING: ThreadSanitizer: + } + + // Simple lock order inversion with 3 locks. + void Test2() { + if (test_number > 0 && test_number != 2) return; + fprintf(stderr, "Starting Test2\n"); + // CHECK: Starting Test2 + Init(5); + fprintf(stderr, "Expecting lock inversion: %p %p %p\n", A(0), A(1), A(2)); + // CHECK: Expecting lock inversion: [[A1:0x[a-f0-9]*]] [[A2:0x[a-f0-9]*]] [[A3:0x[a-f0-9]*]] + Lock2(0, 1); + Lock2(1, 2); + Lock2(2, 0); + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion (potential deadlock) + // CHECK: Cycle in lock order graph: [[M1:M[0-9]+]] ([[A1]]) => [[M2:M[0-9]+]] ([[A2]]) => [[M3:M[0-9]+]] ([[A3]]) => [[M1]] + // CHECK-NOT: WARNING: ThreadSanitizer: + } + + // Lock order inversion with lots of new locks created (but not used) + // between. Since the new locks are not used we should still detect the + // deadlock. + void Test3() { + if (test_number > 0 && test_number != 3) return; + fprintf(stderr, "Starting Test3\n"); + // CHECK: Starting Test3 + Init(5); + Lock_0_1(); + L(2); + CreateAndDestroyManyLocks(); + U(2); + Lock_1_0(); + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion (potential deadlock) + // CHECK-NOT: WARNING: ThreadSanitizer: + } + + // lock l0=>l1; then create and use lots of locks; then lock l1=>l0. + // The deadlock epoch should have changed and we should not report anything. + void Test4() { + if (test_number > 0 && test_number != 4) return; + fprintf(stderr, "Starting Test4\n"); + // CHECK: Starting Test4 + Init(5); + Lock_0_1(); + L(2); + CreateLockUnlockAndDestroyManyLocks(); + U(2); + Lock_1_0(); + // CHECK-NOT: WARNING: ThreadSanitizer: + } + + void Test5() { + if (test_number > 0 && test_number != 5) return; + fprintf(stderr, "Starting Test5\n"); + // CHECK: Starting Test5 + Init(5); + RunThreads(&LockTest::Lock_0_1, &LockTest::Lock_1_0); + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion + // CHECK: Cycle in lock order graph: [[M1:M[0-9]+]] ({{.*}}) => [[M2:M[0-9]+]] ({{.*}}) => [[M1]] + // CHECK: Mutex [[M2]] acquired here while holding mutex [[M1]] in thread [[T1:T[0-9]+]] + // CHECK: Mutex [[M1]] acquired here while holding mutex [[M2]] in thread [[T2:T[0-9]+]] + // CHECK: Thread [[T1]] {{.*}} created by main thread + // CHECK: Thread [[T2]] {{.*}} created by main thread + // CHECK-NOT: WARNING: ThreadSanitizer: + } + + void Test6() { + if (test_number > 0 && test_number != 6) return; + fprintf(stderr, "Starting Test6: 3 threads lock/unlock private mutexes\n"); + // CHECK: Starting Test6 + Init(100); + // CHECK-NOT: WARNING: ThreadSanitizer: + RunThreads(&LockTest::Lock1_Loop_0, &LockTest::Lock1_Loop_1, + &LockTest::Lock1_Loop_2); + } + + void Test7() { + if (test_number > 0 && test_number != 7) return; + fprintf(stderr, "Starting Test7\n"); + // CHECK: Starting Test7 + Init(10); + L(0); T(1); U(1); U(0); + T(1); L(0); U(1); U(0); + // CHECK-NOT: WARNING: ThreadSanitizer: + fprintf(stderr, "No cycle: 0=>1\n"); + // CHECK: No cycle: 0=>1 + + T(2); L(3); U(3); U(2); + L(3); T(2); U(3); U(2); + // CHECK-NOT: WARNING: ThreadSanitizer: + fprintf(stderr, "No cycle: 2=>3\n"); + // CHECK: No cycle: 2=>3 + + T(4); L(5); U(4); U(5); + L(5); L(4); U(4); U(5); + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion + fprintf(stderr, "Have cycle: 4=>5\n"); + // CHECK: Have cycle: 4=>5 + + L(7); L(6); U(6); U(7); + T(6); L(7); U(6); U(7); + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion + fprintf(stderr, "Have cycle: 6=>7\n"); + // CHECK: Have cycle: 6=>7 + } + + void Test8() { + if (test_number > 0 && test_number != 8) return; + if (!LockType::supports_read_lock()) return; + fprintf(stderr, "Starting Test8\n"); + Init(5); + // CHECK-RD: Starting Test8 + RL(0); L(1); RU(0); U(1); + L(1); RL(0); RU(0); U(1); + // CHECK-RD: WARNING: ThreadSanitizer: lock-order-inversion + fprintf(stderr, "Have cycle: 0=>1\n"); + // CHECK-RD: Have cycle: 0=>1 + + RL(2); RL(3); RU(2); RU(3); + RL(3); RL(2); RU(2); RU(3); + // CHECK-RD: WARNING: ThreadSanitizer: lock-order-inversion + fprintf(stderr, "Have cycle: 2=>3\n"); + // CHECK-RD: Have cycle: 2=>3 + } + + void Test9() { + if (test_number > 0 && test_number != 9) return; + if (!LockType::supports_recursive_lock()) return; + fprintf(stderr, "Starting Test9\n"); + // CHECK-REC: Starting Test9 + Init(5); + L(0); L(0); L(0); L(1); U(1); U(0); U(0); U(0); + L(1); L(1); L(1); L(0); U(0); U(1); U(1); U(1); + // CHECK-REC: WARNING: ThreadSanitizer: lock-order-inversion + } + + void Test10() { + if (test_number > 0 && test_number != 10) return; + fprintf(stderr, "Starting Test10: 4 threads lock/unlock 4 private mutexes, one under another\n"); + // CHECK: Starting Test10 + Init(100); + // CHECK-NOT: WARNING: ThreadSanitizer: + RunThreads(&LockTest::Test10_Thread1, &LockTest::Test10_Thread2, + &LockTest::Test10_Thread3, &LockTest::Test10_Thread4); + } + void Test10_Thread1() { Test10_Thread(0); } + void Test10_Thread2() { Test10_Thread(10); } + void Test10_Thread3() { Test10_Thread(20); } + void Test10_Thread4() { Test10_Thread(30); } + void Test10_Thread(size_t m) { + for (int i = 0; i < iter_count; i++) { + L(m + 0); + L(m + 1); + L(m + 2); + L(m + 3); + U(m + 3); + U(m + 2); + U(m + 1); + U(m + 0); + } + } + + void Test11() { + if (test_number > 0 && test_number != 11) return; + fprintf(stderr, "Starting Test11: 4 threads lock/unlock 4 private mutexes, all under another private mutex\n"); + // CHECK: Starting Test11 + Init(500); + // CHECK-NOT: WARNING: ThreadSanitizer: + RunThreads(&LockTest::Test11_Thread1, &LockTest::Test11_Thread2, + &LockTest::Test11_Thread3, &LockTest::Test11_Thread4); + } + void Test11_Thread1() { Test10_Thread(0); } + void Test11_Thread2() { Test10_Thread(10); } + void Test11_Thread3() { Test10_Thread(20); } + void Test11_Thread4() { Test10_Thread(30); } + void Test11_Thread(size_t m) { + for (int i = 0; i < iter_count; i++) { + L(m); + L(m + 100); + U(m + 100); + L(m + 200); + U(m + 200); + L(m + 300); + U(m + 300); + L(m + 400); + U(m + 500); + U(m); + } + } + + void Test12() { + if (test_number > 0 && test_number != 12) return; + if (!LockType::supports_read_lock()) return; + fprintf(stderr, "Starting Test12: 4 threads read lock/unlock 4 shared mutexes, one under another\n"); + // CHECK-RD: Starting Test12 + Init(500); + // CHECK-RD-NOT: WARNING: ThreadSanitizer: + RunThreads(&LockTest::Test12_Thread, &LockTest::Test12_Thread, + &LockTest::Test12_Thread, &LockTest::Test12_Thread); + } + void Test12_Thread() { + for (int i = 0; i < iter_count; i++) { + RL(000); + RL(100); + RL(200); + RL(300); + RU(300); + RU(200); + RU(100); + RU(000); + } + } + + void Test13() { + if (test_number > 0 && test_number != 13) return; + if (!LockType::supports_read_lock()) return; + fprintf(stderr, "Starting Test13: 4 threads read lock/unlock 4 shared mutexes, all under another shared mutex\n"); + // CHECK-RD: Starting Test13 + Init(500); + // CHECK-RD-NOT: WARNING: ThreadSanitizer: + RunThreads(&LockTest::Test13_Thread, &LockTest::Test13_Thread, + &LockTest::Test13_Thread, &LockTest::Test13_Thread); + } + void Test13_Thread() { + for (int i = 0; i < iter_count; i++) { + RL(0); + RL(100); + RU(100); + RL(200); + RU(200); + RL(300); + RU(300); + RL(400); + RU(400); + RU(0); + } + } + + void Test14() { + if (test_number > 0 && test_number != 14) return; + fprintf(stderr, "Starting Test14: create lots of locks in 4 threads\n"); + Init(10); + // CHECK-RD: Starting Test14 + RunThreads(&LockTest::CreateAndDestroyLocksLoop, + &LockTest::CreateAndDestroyLocksLoop, + &LockTest::CreateAndDestroyLocksLoop, + &LockTest::CreateAndDestroyLocksLoop); + } + + void Test15() { + if (test_number > 0 && test_number != 15) return; + if (!LockType::supports_read_lock()) return; + fprintf(stderr, "Starting Test15: recursive rlock\n"); + // DISABLEDCHECK-RD: Starting Test15 + Init(5); + RL(0); RL(0); RU(0); RU(0); // Recusrive reader lock. + RL(0); RL(0); RL(0); RU(0); RU(0); RU(0); // Recusrive reader lock. + } + + // More detailed output test. + void Test16() { + if (test_number > 0 && test_number != 16) return; + fprintf(stderr, "Starting Test16: detailed output test with two locks\n"); + // CHECK: Starting Test16 + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion + // CHECK: acquired here while holding mutex + // CHECK: LockTest::Acquire1 + // CHECK-NEXT: LockTest::Acquire_0_then_1 + // CHECK-SECOND: previously acquired by the same thread here + // CHECK-SECOND: LockTest::Acquire0 + // CHECK-SECOND-NEXT: LockTest::Acquire_0_then_1 + // CHECK: acquired here while holding mutex + // CHECK: LockTest::Acquire0 + // CHECK-NEXT: LockTest::Acquire_1_then_0 + // CHECK-SECOND: previously acquired by the same thread here + // CHECK-SECOND: LockTest::Acquire1 + // CHECK-SECOND-NEXT: LockTest::Acquire_1_then_0 + Init(5); + Acquire_0_then_1(); + U(0); U(1); + Acquire_1_then_0(); + U(0); U(1); + } + + // More detailed output test. + void Test17() { + if (test_number > 0 && test_number != 17) return; + fprintf(stderr, "Starting Test17: detailed output test with three locks\n"); + // CHECK: Starting Test17 + // CHECK: WARNING: ThreadSanitizer: lock-order-inversion + // CHECK: LockTest::Acquire1 + // CHECK-NEXT: LockTest::Acquire_0_then_1 + // CHECK: LockTest::Acquire2 + // CHECK-NEXT: LockTest::Acquire_1_then_2 + // CHECK: LockTest::Acquire0 + // CHECK-NEXT: LockTest::Acquire_2_then_0 + Init(5); + Acquire_0_then_1(); + U(0); U(1); + Acquire_1_then_2(); + U(1); U(2); + Acquire_2_then_0(); + U(0); U(2); + } + + __attribute__((noinline)) void Acquire2() { L(2); } + __attribute__((noinline)) void Acquire1() { L(1); } + __attribute__((noinline)) void Acquire0() { L(0); } + __attribute__((noinline)) void Acquire_1_then_0() { Acquire1(); Acquire0(); } + __attribute__((noinline)) void Acquire_0_then_1() { Acquire0(); Acquire1(); } + __attribute__((noinline)) void Acquire_1_then_2() { Acquire1(); Acquire2(); } + __attribute__((noinline)) void Acquire_2_then_0() { Acquire2(); Acquire0(); } + + // This test creates, locks, unlocks and destroys lots of mutexes. + void Test18() { + if (test_number > 0 && test_number != 18) return; + fprintf(stderr, "Starting Test18: create, lock and destroy 4 locks; all in " + "4 threads in a loop\n"); + RunThreads(&LockTest::Test18_Thread, &LockTest::Test18_Thread, + &LockTest::Test18_Thread, &LockTest::Test18_Thread); + } + + void Test18_Thread() { + LockType *l = new LockType[4]; + for (size_t i = 0; i < iter_count / 100; i++) { + for (int i = 0; i < 4; i++) l[i].lock(); + for (int i = 0; i < 4; i++) l[i].unlock(); + for (int i = 0; i < 4; i++) l[i].~LockType(); + for (int i = 0; i < 4; i++) new ((void*)&l[i]) LockType(); + } + delete [] l; + } + + private: + void Lock2(size_t l1, size_t l2) { L(l1); L(l2); U(l2); U(l1); } + void Lock_0_1() { Lock2(0, 1); } + void Lock_1_0() { sleep(1); Lock2(1, 0); } + void Lock1_Loop(size_t i, size_t n_iter) { + for (size_t it = 0; it < n_iter; it++) { + // if ((it & (it - 1)) == 0) fprintf(stderr, "%zd", i); + L(i); + U(i); + } + // fprintf(stderr, "\n"); + } + void Lock1_Loop_0() { Lock1_Loop(0, iter_count); } + void Lock1_Loop_1() { Lock1_Loop(10, iter_count); } + void Lock1_Loop_2() { Lock1_Loop(20, iter_count); } + + void CreateAndDestroyManyLocks() { + LockType *create_many_locks_but_never_acquire = + new LockType[kDeadlockGraphSize]; + (void)create_many_locks_but_never_acquire; + delete [] create_many_locks_but_never_acquire; + } + + void CreateAndDestroyLocksLoop() { + for (size_t it = 0; it <= iter_count; it++) { + LockType some_locks[10]; + (void)some_locks; + } + } + + void CreateLockUnlockAndDestroyManyLocks() { + LockType many_locks[kDeadlockGraphSize]; + for (size_t i = 0; i < kDeadlockGraphSize; i++) { + many_locks[i].lock(); + many_locks[i].unlock(); + } + } + + // LockTest Member function callback. + struct CB { + void (LockTest::*f)(); + LockTest *lt; + }; + + // Thread function with CB. + static void *Thread(void *param) { + CB *cb = (CB*)param; + (cb->lt->*cb->f)(); + return NULL; + } + + void RunThreads(void (LockTest::*f1)(), void (LockTest::*f2)(), + void (LockTest::*f3)() = 0, void (LockTest::*f4)() = 0) { + const int kNumThreads = 4; + pthread_t t[kNumThreads]; + CB cb[kNumThreads] = {{f1, this}, {f2, this}, {f3, this}, {f4, this}}; + for (int i = 0; i < kNumThreads && cb[i].f; i++) + pthread_create(&t[i], 0, Thread, &cb[i]); + for (int i = 0; i < kNumThreads && cb[i].f; i++) + pthread_join(t[i], 0); + } + + static const size_t kDeadlockGraphSize = 4096; + size_t n_; + LockType **locks_; +}; + +int main(int argc, char **argv) { + if (argc > 1) + test_number = atoi(argv[1]); + if (argc > 2) + iter_count = atoi(argv[2]); + LockTest().Test1(); + LockTest().Test2(); + LockTest().Test3(); + LockTest().Test4(); + LockTest().Test5(); + LockTest().Test6(); + LockTest().Test7(); + LockTest().Test8(); + LockTest().Test9(); + LockTest().Test10(); + LockTest().Test11(); + LockTest().Test12(); + LockTest().Test13(); + LockTest().Test14(); + LockTest().Test15(); + LockTest().Test16(); + LockTest().Test17(); + LockTest().Test18(); + fprintf(stderr, "ALL-DONE\n"); + // CHECK: ALL-DONE +} diff --git a/test/tsan/deep_stack1.cc b/test/tsan/deep_stack1.cc new file mode 100644 index 000000000000..1d00a0e856d5 --- /dev/null +++ b/test/tsan/deep_stack1.cc @@ -0,0 +1,44 @@ +// RUN: %clangxx_tsan -O1 %s -o %t -DORDER1 && %deflake %run %t | FileCheck %s +// RUN: %clangxx_tsan -O1 %s -o %t -DORDER2 && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +volatile int X; +volatile int N; +void (*volatile F)(); + +static void foo() { + if (--N == 0) + X = 42; + else + F(); +} + +void *Thread(void *p) { +#ifdef ORDER1 + sleep(1); +#endif + F(); + return 0; +} + +int main() { + N = 50000; + F = foo; + pthread_t t; + pthread_attr_t a; + pthread_attr_init(&a); + pthread_attr_setstacksize(&a, N * 256 + (1 << 20)); + pthread_create(&t, &a, Thread, 0); +#ifdef ORDER2 + sleep(1); +#endif + X = 43; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: #100 foo +// We must output suffucuently large stack (at least 100 frames) + diff --git a/test/tsan/default_options.cc b/test/tsan/default_options.cc new file mode 100644 index 000000000000..77bdcd5086ba --- /dev/null +++ b/test/tsan/default_options.cc @@ -0,0 +1,32 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +extern "C" const char *__tsan_default_options() { + return "report_bugs=0"; +} + +int Global; + +void *Thread1(void *x) { + Global = 42; + return NULL; +} + +void *Thread2(void *x) { + Global = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/deflake.bash b/test/tsan/deflake.bash new file mode 100755 index 000000000000..9731fa53e589 --- /dev/null +++ b/test/tsan/deflake.bash @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# This script is used to deflake inherently flaky tsan tests. +# It is invoked from lit tests as: +# %deflake mybinary +# which is then substituted by lit to: +# $(dirname %s)/deflake.bash mybinary +# The script runs the target program up to 10 times, +# until it fails (i.e. produces a race report). + +for i in $(seq 1 10); do + OUT=`$@ 2>&1` + if [[ $? != 0 ]]; then + echo "$OUT" + exit 0 + fi +done +exit 1 diff --git a/test/tsan/dlclose.cc b/test/tsan/dlclose.cc new file mode 100644 index 000000000000..1a93fe6617e1 --- /dev/null +++ b/test/tsan/dlclose.cc @@ -0,0 +1,58 @@ +// RUN: %clangxx_tsan -O1 %s -DBUILD_SO -fPIC -shared -o %t-so.so +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s + +// If we mention TSAN_OPTIONS, the test won't run from test_output.sh script. + +// Test case for +// https://code.google.com/p/thread-sanitizer/issues/detail?id=80 + +#ifdef BUILD_SO + +#include <stdio.h> + +extern "C" +void sofunc() { + fprintf(stderr, "HELLO FROM SO\n"); +} + +#else // BUILD_SO + +#include <dlfcn.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> +#include <string> + +void *lib; +void *lib2; + +struct Closer { + ~Closer() { + dlclose(lib); + fprintf(stderr, "CLOSED SO\n"); + } +}; +static Closer c; + +int main(int argc, char *argv[]) { + lib = dlopen((std::string(argv[0]) + std::string("-so.so")).c_str(), + RTLD_NOW|RTLD_NODELETE); + if (lib == 0) { + printf("error in dlopen: %s\n", dlerror()); + return 1; + } + void *f = dlsym(lib, "sofunc"); + if (f == 0) { + printf("error in dlsym: %s\n", dlerror()); + return 1; + } + ((void(*)())f)(); + return 0; +} + +#endif // BUILD_SO + +// CHECK: HELLO FROM SO +// CHECK-NOT: Inconsistency detected by ld.so +// CHECK: CLOSED SO + diff --git a/test/tsan/fd_close_norace.cc b/test/tsan/fd_close_norace.cc new file mode 100644 index 000000000000..7238d64b432b --- /dev/null +++ b/test/tsan/fd_close_norace.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +void *Thread1(void *x) { + int f = open("/dev/random", O_RDONLY); + close(f); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + int f = open("/dev/random", O_RDONLY); + close(f); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race + + diff --git a/test/tsan/fd_close_norace2.cc b/test/tsan/fd_close_norace2.cc new file mode 100644 index 000000000000..bf94fd5512b3 --- /dev/null +++ b/test/tsan/fd_close_norace2.cc @@ -0,0 +1,30 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int pipes[2]; + +void *Thread(void *x) { + // wait for shutown signal + while (read(pipes[0], &x, 1) != 1) { + } + close(pipes[0]); + close(pipes[1]); + return 0; +} + +int main() { + if (pipe(pipes)) + return 1; + pthread_t t; + pthread_create(&t, 0, Thread, 0); + // send shutdown signal + while (write(pipes[1], &t, 1) != 1) { + } + pthread_join(t, 0); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: OK diff --git a/test/tsan/fd_dup_norace.cc b/test/tsan/fd_dup_norace.cc new file mode 100644 index 000000000000..5045325b22b5 --- /dev/null +++ b/test/tsan/fd_dup_norace.cc @@ -0,0 +1,34 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +int fds[2]; + +void *Thread1(void *x) { + char buf; + read(fds[0], &buf, 1); + close(fds[0]); + return 0; +} + +void *Thread2(void *x) { + close(fds[1]); + return 0; +} + +int main() { + fds[0] = open("/dev/random", O_RDONLY); + fds[1] = dup2(fds[0], 100); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/fd_location.cc b/test/tsan/fd_location.cc new file mode 100644 index 000000000000..535329e06409 --- /dev/null +++ b/test/tsan/fd_location.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int fds[2]; + +void *Thread1(void *x) { + write(fds[1], "a", 1); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + close(fds[0]); + close(fds[1]); + return NULL; +} + +int main() { + pipe(fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is file descriptor {{[0-9]+}} created by main thread at: +// CHECK: #0 pipe +// CHECK: #1 main + diff --git a/test/tsan/fd_pipe_norace.cc b/test/tsan/fd_pipe_norace.cc new file mode 100644 index 000000000000..b434703d782a --- /dev/null +++ b/test/tsan/fd_pipe_norace.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int fds[2]; +int X; + +void *Thread1(void *x) { + X = 42; + write(fds[1], "a", 1); + return NULL; +} + +void *Thread2(void *x) { + char buf; + while (read(fds[0], &buf, 1) != 1) { + } + X = 43; + return NULL; +} + +int main() { + pipe(fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/fd_pipe_race.cc b/test/tsan/fd_pipe_race.cc new file mode 100644 index 000000000000..88c4ed4aa398 --- /dev/null +++ b/test/tsan/fd_pipe_race.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int fds[2]; + +void *Thread1(void *x) { + write(fds[1], "a", 1); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + close(fds[0]); + close(fds[1]); + return NULL; +} + +int main() { + pipe(fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 8 +// CHECK: #0 close +// CHECK: #1 Thread2 +// CHECK: Previous read of size 8 +// CHECK: #0 write +// CHECK: #1 Thread1 + + diff --git a/test/tsan/fd_socket_connect_norace.cc b/test/tsan/fd_socket_connect_norace.cc new file mode 100644 index 000000000000..ab2a950f17d6 --- /dev/null +++ b/test/tsan/fd_socket_connect_norace.cc @@ -0,0 +1,45 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +struct sockaddr_in addr; +int X; + +void *ClientThread(void *x) { + int c = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + X = 42; + if (connect(c, (struct sockaddr*)&addr, sizeof(addr))) { + perror("connect"); + exit(1); + } + close(c); + return NULL; +} + +int main() { + int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + addr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + addr.sin_port = INADDR_ANY; + socklen_t len = sizeof(addr); + bind(s, (sockaddr*)&addr, len); + getsockname(s, (sockaddr*)&addr, &len); + listen(s, 10); + pthread_t t; + pthread_create(&t, 0, ClientThread, 0); + int c = accept(s, 0, 0); + X = 42; + pthread_join(t, 0); + close(c); + close(s); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race + diff --git a/test/tsan/fd_socket_norace.cc b/test/tsan/fd_socket_norace.cc new file mode 100644 index 000000000000..0f41c4357354 --- /dev/null +++ b/test/tsan/fd_socket_norace.cc @@ -0,0 +1,52 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +struct sockaddr_in addr; +int X; + +void *ClientThread(void *x) { + X = 42; + int c = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (connect(c, (struct sockaddr*)&addr, sizeof(addr))) { + perror("connect"); + exit(1); + } + if (send(c, "a", 1, 0) != 1) { + perror("send"); + exit(1); + } + close(c); + return NULL; +} + +int main() { + int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + addr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + addr.sin_port = INADDR_ANY; + socklen_t len = sizeof(addr); + bind(s, (sockaddr*)&addr, len); + getsockname(s, (sockaddr*)&addr, &len); + listen(s, 10); + pthread_t t; + pthread_create(&t, 0, ClientThread, 0); + int c = accept(s, 0, 0); + char buf; + while (read(c, &buf, 1) != 1) { + } + X = 43; + close(c); + close(s); + pthread_join(t, 0); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race + diff --git a/test/tsan/fd_socketpair_norace.cc b/test/tsan/fd_socketpair_norace.cc new file mode 100644 index 000000000000..a455d44a3965 --- /dev/null +++ b/test/tsan/fd_socketpair_norace.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> + +int fds[2]; +int X; + +void *Thread1(void *x) { + X = 42; + write(fds[1], "a", 1); + close(fds[1]); + return NULL; +} + +void *Thread2(void *x) { + char buf; + while (read(fds[0], &buf, 1) != 1) { + } + X = 43; + close(fds[0]); + return NULL; +} + +int main() { + socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/fd_stdout_race.cc b/test/tsan/fd_stdout_race.cc new file mode 100644 index 000000000000..d6a2c7c796f1 --- /dev/null +++ b/test/tsan/fd_stdout_race.cc @@ -0,0 +1,41 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +int X; + +void *Thread1(void *x) { + sleep(1); + int f = open("/dev/random", O_RDONLY); + char buf; + read(f, &buf, 1); + close(f); + X = 42; + return NULL; +} + +void *Thread2(void *x) { + X = 43; + write(STDOUT_FILENO, "a", 1); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 +// CHECK: #0 Thread1 +// CHECK: Previous write of size 4 +// CHECK: #0 Thread2 + + diff --git a/test/tsan/fork_atexit.cc b/test/tsan/fork_atexit.cc new file mode 100644 index 000000000000..6801d3ffff7e --- /dev/null +++ b/test/tsan/fork_atexit.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && TSAN_OPTIONS="atexit_sleep_ms=50" %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +void foo() { + printf("CHILD ATEXIT\n"); +} + +void *worker(void *unused) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, NULL, worker, NULL); + int pid = fork(); + if (pid == 0) { + // child + atexit(foo); + fprintf(stderr, "CHILD DONE\n"); + } else { + pthread_join(t, 0); + if (waitpid(pid, 0, 0) == -1) { + perror("waitpid"); + exit(1); + } + fprintf(stderr, "PARENT DONE\n"); + } +} + +// CHECK: CHILD DONE +// CHECK: CHILD ATEXIT +// CHECK: PARENT DONE diff --git a/test/tsan/fork_deadlock.cc b/test/tsan/fork_deadlock.cc new file mode 100644 index 000000000000..cc5b12214cf9 --- /dev/null +++ b/test/tsan/fork_deadlock.cc @@ -0,0 +1,48 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && TSAN_OPTIONS="atexit_sleep_ms=50" %run %t 2>&1 | FileCheck %s +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +int counter; + +static void *incrementer(void *p) { + for (;;) + __sync_fetch_and_add(&counter, 1); + return 0; +} + +static void *watchdog(void *p) { + sleep(100); + fprintf(stderr, "timed out after 100 seconds\n"); + exit(1); + return 0; +} + +int main() { + pthread_t th1, th2; + pthread_create(&th1, 0, incrementer, 0); + pthread_create(&th2, 0, watchdog, 0); + for (int i = 0; i < 10; i++) { + switch (fork()) { + default: // parent + while (wait(0) < 0) {} + fprintf(stderr, "."); + break; + case 0: // child + __sync_fetch_and_add(&counter, 1); + exit(0); + break; + case -1: // error + fprintf(stderr, "failed to fork (%d)\n", errno); + exit(1); + } + } + fprintf(stderr, "OK\n"); +} + +// CHECK: OK + diff --git a/test/tsan/fork_multithreaded.cc b/test/tsan/fork_multithreaded.cc new file mode 100644 index 000000000000..5176a14d6a65 --- /dev/null +++ b/test/tsan/fork_multithreaded.cc @@ -0,0 +1,42 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s -check-prefix=CHECK-DIE +// RUN: %clangxx_tsan -O1 %s -o %t && TSAN_OPTIONS="die_after_fork=0" %run %t 2>&1 | FileCheck %s -check-prefix=CHECK-NODIE +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +static void *sleeper(void *p) { + sleep(10); + return 0; +} + +int main() { + pthread_t th; + pthread_create(&th, 0, sleeper, 0); + switch (fork()) { + default: // parent + while (wait(0) < 0) {} + break; + case 0: // child + { + pthread_t th2; + pthread_create(&th2, 0, sleeper, 0); + exit(0); + break; + } + case -1: // error + fprintf(stderr, "failed to fork (%d)\n", errno); + exit(1); + } + fprintf(stderr, "OK\n"); +} + +// CHECK-DIE: ThreadSanitizer: starting new threads after multi-threaded fork is not supported +// CHECK-DIE: OK + +// CHECK-NODIE-NOT: ThreadSanitizer: starting new threads after multi-threaded fork is not supported +// CHECK-NODIE: OK + diff --git a/test/tsan/fork_multithreaded3.cc b/test/tsan/fork_multithreaded3.cc new file mode 100644 index 000000000000..a651b3c18b4e --- /dev/null +++ b/test/tsan/fork_multithreaded3.cc @@ -0,0 +1,40 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +static void *racer(void *p) { + *(int*)p = 42; + return 0; +} + +int main() { + switch (fork()) { + default: // parent + while (wait(0) < 0) {} + break; + case 0: // child + { + int x = 0; + pthread_t th1, th2; + pthread_create(&th1, 0, racer, &x); + pthread_create(&th2, 0, racer, &x); + pthread_join(th1, 0); + pthread_join(th2, 0); + exit(0); + break; + } + case -1: // error + fprintf(stderr, "failed to fork (%d)\n", errno); + exit(1); + } + fprintf(stderr, "OK\n"); +} + +// CHECK: ThreadSanitizer: data race +// CHECK: OK + diff --git a/test/tsan/free_race.c b/test/tsan/free_race.c new file mode 100644 index 000000000000..663d7bcf2eb9 --- /dev/null +++ b/test/tsan/free_race.c @@ -0,0 +1,49 @@ +// RUN: %clang_tsan -O1 %s -o %t +// RUN: %deflake %run %t | FileCheck %s --check-prefix=CHECK-NOZUPP +// RUN: TSAN_OPTIONS="suppressions=%s.supp print_suppressions=1" %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-SUPP + +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +int *mem; +pthread_mutex_t mtx; + +void *Thread1(void *x) { + pthread_mutex_lock(&mtx); + free(mem); + pthread_mutex_unlock(&mtx); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + pthread_mutex_lock(&mtx); + mem[0] = 42; + pthread_mutex_unlock(&mtx); + return NULL; +} + +int main() { + mem = (int*)malloc(100); + pthread_mutex_init(&mtx, 0); + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + Thread2(0); + pthread_join(t, NULL); + pthread_mutex_destroy(&mtx); + return 0; +} + +// CHECK-NOZUPP: WARNING: ThreadSanitizer: heap-use-after-free +// CHECK-NOZUPP: Write of size 4 at {{.*}} by main thread{{.*}}: +// CHECK-NOZUPP: #0 Thread2 +// CHECK-NOZUPP: #1 main +// CHECK-NOZUPP: Previous write of size 8 at {{.*}} by thread T1{{.*}}: +// CHECK-NOZUPP: #0 free +// CHECK-NOZUPP: #{{(1|2)}} Thread1 +// CHECK-NOZUPP: SUMMARY: ThreadSanitizer: heap-use-after-free{{.*}}Thread2 +// CHECK-SUPP: ThreadSanitizer: Matched 1 suppressions +// CHECK-SUPP: 1 race:^Thread2$ diff --git a/test/tsan/free_race.c.supp b/test/tsan/free_race.c.supp new file mode 100644 index 000000000000..f5d6a4969a41 --- /dev/null +++ b/test/tsan/free_race.c.supp @@ -0,0 +1,2 @@ +# Suppression for a use-after-free in free_race.c +race:^Thread2$ diff --git a/test/tsan/free_race2.c b/test/tsan/free_race2.c new file mode 100644 index 000000000000..de6b2ae1fcbb --- /dev/null +++ b/test/tsan/free_race2.c @@ -0,0 +1,26 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <stdlib.h> + +void __attribute__((noinline)) foo(int *mem) { + free(mem); +} + +void __attribute__((noinline)) bar(int *mem) { + mem[0] = 42; +} + +int main() { + int *mem = (int*)malloc(100); + foo(mem); + bar(mem); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: heap-use-after-free +// CHECK: Write of size 4 at {{.*}} by main thread: +// CHECK: #0 bar +// CHECK: #1 main +// CHECK: Previous write of size 8 at {{.*}} by main thread: +// CHECK: #0 free +// CHECK: #{{1|2}} foo +// CHECK: #{{2|3}} main diff --git a/test/tsan/getline_nohang.cc b/test/tsan/getline_nohang.cc new file mode 100644 index 000000000000..89afbe1a66a8 --- /dev/null +++ b/test/tsan/getline_nohang.cc @@ -0,0 +1,39 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t + +// Make sure TSan doesn't deadlock on a file stream lock at program shutdown. +// See https://code.google.com/p/thread-sanitizer/issues/detail?id=47 +#ifdef __FreeBSD__ +#define _WITH_GETLINE // to declare getline() +#endif + +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +void *thread(void *unused) { + char *line = NULL; + size_t size; + int fd[2]; + pipe(fd); + // Forge a non-standard stream to make sure it's not closed. + FILE *stream = fdopen(fd[0], "r"); + while (1) { + volatile int res = getline(&line, &size, stream); + (void)res; + } + return NULL; +} + +int main() { + pthread_t t; + pthread_attr_t a; + pthread_attr_init(&a); + pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); + pthread_create(&t, &a, thread, NULL); + pthread_attr_destroy(&a); + fprintf(stderr, "DONE\n"); + return 0; + // ThreadSanitizer used to hang here because of a deadlock on a file stream. +} + +// CHECK: DONE diff --git a/test/tsan/global_race.cc b/test/tsan/global_race.cc new file mode 100644 index 000000000000..e12bb1d220f8 --- /dev/null +++ b/test/tsan/global_race.cc @@ -0,0 +1,28 @@ +// RUN: %clangxx_tsan -O1 %s -o %T/global_race.cc.exe && %deflake %run %T/global_race.cc.exe | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +int GlobalData[10]; + +void *Thread(void *a) { + sleep(1); + GlobalData[2] = 42; + return 0; +} + +int main() { + // On FreeBSD, the %p conversion specifier works as 0x%x and thus does not + // match to the format used in the diagnotic message. + fprintf(stderr, "addr=0x%012lx\n", (unsigned long) GlobalData); + pthread_t t; + pthread_create(&t, 0, Thread, 0); + GlobalData[2] = 43; + pthread_join(t, 0); +} + +// CHECK: addr=[[ADDR:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is global 'GlobalData' of size 40 at [[ADDR]] (global_race.cc.exe+0x{{[0-9,a-f]+}}) + diff --git a/test/tsan/global_race2.cc b/test/tsan/global_race2.cc new file mode 100644 index 000000000000..ac994cc0f4c4 --- /dev/null +++ b/test/tsan/global_race2.cc @@ -0,0 +1,28 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +int x; + +void *Thread(void *a) { + sleep(1); + x = 1; + return 0; +} + +int main() { + // On FreeBSD, the %p conversion specifier works as 0x%x and thus does not + // match to the format used in the diagnotic message. + fprintf(stderr, "addr2=0x%012lx\n", (unsigned long) &x); + pthread_t t; + pthread_create(&t, 0, Thread, 0); + x = 0; + pthread_join(t, 0); +} + +// CHECK: addr2=[[ADDR2:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is global 'x' of size 4 at [[ADDR2]] ({{.*}}+0x{{[0-9,a-f]+}}) + diff --git a/test/tsan/global_race3.cc b/test/tsan/global_race3.cc new file mode 100644 index 000000000000..a3222bb3d8cb --- /dev/null +++ b/test/tsan/global_race3.cc @@ -0,0 +1,32 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +namespace XXX { + struct YYY { + static int ZZZ[10]; + }; + int YYY::ZZZ[10]; +} + +void *Thread(void *a) { + sleep(1); + XXX::YYY::ZZZ[0] = 1; + return 0; +} + +int main() { + // On FreeBSD, the %p conversion specifier works as 0x%x and thus does not + // match to the format used in the diagnotic message. + fprintf(stderr, "addr3=0x%012lx\n", (unsigned long) XXX::YYY::ZZZ); + pthread_t t; + pthread_create(&t, 0, Thread, 0); + XXX::YYY::ZZZ[0] = 0; + pthread_join(t, 0); +} + +// CHECK: addr3=[[ADDR3:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is global 'XXX::YYY::ZZZ' of size 40 at [[ADDR3]] ({{.*}}+0x{{[0-9,a-f]+}}) diff --git a/test/tsan/halt_on_error.cc b/test/tsan/halt_on_error.cc new file mode 100644 index 000000000000..3c55c60a457e --- /dev/null +++ b/test/tsan/halt_on_error.cc @@ -0,0 +1,27 @@ +// RUN: %clang_tsan -O1 %s -o %t && TSAN_OPTIONS="$TSAN_OPTIONS halt_on_error=1" %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int X; + +void *Thread(void *x) { + sleep(1); + X = 42; + return 0; +} + +int main() { + fprintf(stderr, "BEFORE\n"); + pthread_t t; + pthread_create(&t, 0, Thread, 0); + X = 43; + pthread_join(t, 0); + fprintf(stderr, "AFTER\n"); + return 0; +} + +// CHECK: BEFORE +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NOT: AFTER + diff --git a/test/tsan/heap_race.cc b/test/tsan/heap_race.cc new file mode 100644 index 000000000000..c3da68f42658 --- /dev/null +++ b/test/tsan/heap_race.cc @@ -0,0 +1,20 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> + +void *Thread(void *a) { + ((int*)a)[0]++; + return NULL; +} + +int main() { + int *p = new int(42); + pthread_t t; + pthread_create(&t, NULL, Thread, p); + p[0]++; + pthread_join(t, NULL); + delete p; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/ignore_free.cc b/test/tsan/ignore_free.cc new file mode 100644 index 000000000000..1df6dce2f5e0 --- /dev/null +++ b/test/tsan/ignore_free.cc @@ -0,0 +1,35 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +extern "C" { +void AnnotateIgnoreReadsBegin(const char *f, int l); +void AnnotateIgnoreReadsEnd(const char *f, int l); +void AnnotateIgnoreWritesBegin(const char *f, int l); +void AnnotateIgnoreWritesEnd(const char *f, int l); +} + +void *Thread(void *p) { + *(int*)p = 42; + return 0; +} + +int main() { + int *p = new int(0); + pthread_t t; + pthread_create(&t, 0, Thread, p); + sleep(1); + AnnotateIgnoreReadsBegin(__FILE__, __LINE__); + AnnotateIgnoreWritesBegin(__FILE__, __LINE__); + free(p); + AnnotateIgnoreReadsEnd(__FILE__, __LINE__); + AnnotateIgnoreWritesEnd(__FILE__, __LINE__); + pthread_join(t, 0); + fprintf(stderr, "OK\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: OK diff --git a/test/tsan/ignore_lib0.cc b/test/tsan/ignore_lib0.cc new file mode 100644 index 000000000000..fe1a35558015 --- /dev/null +++ b/test/tsan/ignore_lib0.cc @@ -0,0 +1,30 @@ +// RUN: %clangxx_tsan -O1 %s -DLIB -fPIC -fno-sanitize=thread -shared -o %T/libignore_lib0.so +// RUN: %clangxx_tsan -O1 %s -L%T -lignore_lib0 -o %t +// RUN: echo running w/o suppressions: +// RUN: LD_LIBRARY_PATH=%T${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} %deflake %run %t | FileCheck %s --check-prefix=CHECK-NOSUPP +// RUN: echo running with suppressions: +// RUN: LD_LIBRARY_PATH=%T${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} TSAN_OPTIONS="$TSAN_OPTIONS suppressions=%s.supp" %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-WITHSUPP + +// Tests that interceptors coming from a library specified in called_from_lib +// suppression are ignored. + +#ifndef LIB + +extern "C" void libfunc(); + +int main() { + libfunc(); +} + +#else // #ifdef LIB + +#include "ignore_lib_lib.h" + +#endif // #ifdef LIB + +// CHECK-NOSUPP: WARNING: ThreadSanitizer: data race +// CHECK-NOSUPP: OK + +// CHECK-WITHSUPP-NOT: WARNING: ThreadSanitizer: data race +// CHECK-WITHSUPP: OK + diff --git a/test/tsan/ignore_lib0.cc.supp b/test/tsan/ignore_lib0.cc.supp new file mode 100644 index 000000000000..7728c926b7de --- /dev/null +++ b/test/tsan/ignore_lib0.cc.supp @@ -0,0 +1,2 @@ +called_from_lib:/libignore_lib0.so + diff --git a/test/tsan/ignore_lib1.cc b/test/tsan/ignore_lib1.cc new file mode 100644 index 000000000000..30a9994b94f5 --- /dev/null +++ b/test/tsan/ignore_lib1.cc @@ -0,0 +1,42 @@ +// RUN: %clangxx_tsan -O1 %s -DLIB -fPIC -fno-sanitize=thread -shared -o %T/libignore_lib1.so +// RUN: %clangxx_tsan -O1 %s -o %t +// RUN: echo running w/o suppressions: +// RUN: %deflake %run %t | FileCheck %s --check-prefix=CHECK-NOSUPP +// RUN: echo running with suppressions: +// RUN: TSAN_OPTIONS="$TSAN_OPTIONS suppressions=%s.supp" %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-WITHSUPP + +// Tests that interceptors coming from a dynamically loaded library specified +// in called_from_lib suppression are ignored. + +#ifndef LIB + +#include <dlfcn.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <libgen.h> +#include <string> + +int main(int argc, char **argv) { + std::string lib = std::string(dirname(argv[0])) + "/libignore_lib1.so"; + void *h = dlopen(lib.c_str(), RTLD_GLOBAL | RTLD_NOW); + if (h == 0) + exit(printf("failed to load the library (%d)\n", errno)); + void (*f)() = (void(*)())dlsym(h, "libfunc"); + if (f == 0) + exit(printf("failed to find the func (%d)\n", errno)); + f(); +} + +#else // #ifdef LIB + +#include "ignore_lib_lib.h" + +#endif // #ifdef LIB + +// CHECK-NOSUPP: WARNING: ThreadSanitizer: data race +// CHECK-NOSUPP: OK + +// CHECK-WITHSUPP-NOT: WARNING: ThreadSanitizer: data race +// CHECK-WITHSUPP: OK + diff --git a/test/tsan/ignore_lib1.cc.supp b/test/tsan/ignore_lib1.cc.supp new file mode 100644 index 000000000000..9f4119ec0bc4 --- /dev/null +++ b/test/tsan/ignore_lib1.cc.supp @@ -0,0 +1,2 @@ +called_from_lib:/libignore_lib1.so$ + diff --git a/test/tsan/ignore_lib2.cc b/test/tsan/ignore_lib2.cc new file mode 100644 index 000000000000..23a0872feabe --- /dev/null +++ b/test/tsan/ignore_lib2.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -DLIB -fPIC -fno-sanitize=thread -shared -o %T/libignore_lib2_0.so +// RUN: %clangxx_tsan -O1 %s -DLIB -fPIC -fno-sanitize=thread -shared -o %T/libignore_lib2_1.so +// RUN: %clangxx_tsan -O1 %s -o %t +// RUN: TSAN_OPTIONS="$TSAN_OPTIONS suppressions=%s.supp" %deflake %run %t | FileCheck %s + +// Tests that called_from_lib suppression matched against 2 libraries +// causes program crash (this is not supported). + +#ifndef LIB + +#include <dlfcn.h> +#include <stdio.h> +#include <libgen.h> +#include <string> + +int main(int argc, char **argv) { + std::string lib0 = std::string(dirname(argv[0])) + "/libignore_lib2_0.so"; + std::string lib1 = std::string(dirname(argv[0])) + "/libignore_lib2_1.so"; + dlopen(lib0.c_str(), RTLD_GLOBAL | RTLD_NOW); + dlopen(lib1.c_str(), RTLD_GLOBAL | RTLD_NOW); + fprintf(stderr, "OK\n"); +} + +#else // #ifdef LIB + +extern "C" void libfunc() { +} + +#endif // #ifdef LIB + +// CHECK: ThreadSanitizer: called_from_lib suppression 'ignore_lib2' is matched against 2 libraries +// CHECK-NOT: OK + diff --git a/test/tsan/ignore_lib2.cc.supp b/test/tsan/ignore_lib2.cc.supp new file mode 100644 index 000000000000..1419c71c67ef --- /dev/null +++ b/test/tsan/ignore_lib2.cc.supp @@ -0,0 +1,2 @@ +called_from_lib:ignore_lib2 + diff --git a/test/tsan/ignore_lib3.cc b/test/tsan/ignore_lib3.cc new file mode 100644 index 000000000000..137109ea6cb4 --- /dev/null +++ b/test/tsan/ignore_lib3.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -DLIB -fPIC -fno-sanitize=thread -shared -o %T/libignore_lib3.so +// RUN: %clangxx_tsan -O1 %s -o %t +// RUN: TSAN_OPTIONS="$TSAN_OPTIONS suppressions=%s.supp" %deflake %run %t | FileCheck %s + +// Tests that unloading of a library matched against called_from_lib suppression +// causes program crash (this is not supported). + +#ifndef LIB + +#include <dlfcn.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <libgen.h> +#include <string> + +int main(int argc, char **argv) { + std::string lib = std::string(dirname(argv[0])) + "/libignore_lib3.so"; + void *h = dlopen(lib.c_str(), RTLD_GLOBAL | RTLD_NOW); + dlclose(h); + fprintf(stderr, "OK\n"); +} + +#else // #ifdef LIB + +extern "C" void libfunc() { +} + +#endif // #ifdef LIB + +// CHECK: ThreadSanitizer: library {{.*}} that was matched against called_from_lib suppression 'ignore_lib3.so' is unloaded +// CHECK-NOT: OK + diff --git a/test/tsan/ignore_lib3.cc.supp b/test/tsan/ignore_lib3.cc.supp new file mode 100644 index 000000000000..975dbcef99fe --- /dev/null +++ b/test/tsan/ignore_lib3.cc.supp @@ -0,0 +1,2 @@ +called_from_lib:ignore_lib3.so + diff --git a/test/tsan/ignore_lib_lib.h b/test/tsan/ignore_lib_lib.h new file mode 100644 index 000000000000..2bfe84dfc0ec --- /dev/null +++ b/test/tsan/ignore_lib_lib.h @@ -0,0 +1,25 @@ +#include <pthread.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +void *volatile mem; +volatile int len; + +void *Thread(void *p) { + while ((p = __atomic_load_n(&mem, __ATOMIC_ACQUIRE)) == 0) + usleep(100); + memset(p, 0, len); + return 0; +} + +extern "C" void libfunc() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + len = 10; + __atomic_store_n(&mem, malloc(len), __ATOMIC_RELEASE); + pthread_join(t, 0); + free(mem); + fprintf(stderr, "OK\n"); +} diff --git a/test/tsan/ignore_malloc.cc b/test/tsan/ignore_malloc.cc new file mode 100644 index 000000000000..0f1fb5e3dfdc --- /dev/null +++ b/test/tsan/ignore_malloc.cc @@ -0,0 +1,38 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +extern "C" { +void AnnotateIgnoreReadsBegin(const char *f, int l); +void AnnotateIgnoreReadsEnd(const char *f, int l); +void AnnotateIgnoreWritesBegin(const char *f, int l); +void AnnotateIgnoreWritesEnd(const char *f, int l); +} + +int *g; + +void *Thread(void *a) { + int *p = 0; + while ((p = __atomic_load_n(&g, __ATOMIC_RELAXED)) == 0) + usleep(100); + *p = 42; + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + AnnotateIgnoreWritesBegin(__FILE__, __LINE__); + int *p = new int(0); + AnnotateIgnoreWritesEnd(__FILE__, __LINE__); + __atomic_store_n(&g, p, __ATOMIC_RELAXED); + pthread_join(t, 0); + delete p; + fprintf(stderr, "OK\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: OK diff --git a/test/tsan/ignore_race.cc b/test/tsan/ignore_race.cc new file mode 100644 index 000000000000..c6e067fabec0 --- /dev/null +++ b/test/tsan/ignore_race.cc @@ -0,0 +1,31 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +extern "C" void AnnotateIgnoreWritesBegin(const char *f, int l); +extern "C" void AnnotateIgnoreWritesEnd(const char *f, int l); +extern "C" void AnnotateIgnoreReadsBegin(const char *f, int l); +extern "C" void AnnotateIgnoreReadsEnd(const char *f, int l); + +void *Thread(void *x) { + AnnotateIgnoreWritesBegin(__FILE__, __LINE__); + AnnotateIgnoreReadsBegin(__FILE__, __LINE__); + Global = 42; + AnnotateIgnoreReadsEnd(__FILE__, __LINE__); + AnnotateIgnoreWritesEnd(__FILE__, __LINE__); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + sleep(1); + Global = 43; + pthread_join(t, 0); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/ignore_sync.cc b/test/tsan/ignore_sync.cc new file mode 100644 index 000000000000..ae24a8c49e75 --- /dev/null +++ b/test/tsan/ignore_sync.cc @@ -0,0 +1,30 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +extern "C" void AnnotateIgnoreSyncBegin(const char*, int); +extern "C" void AnnotateIgnoreSyncEnd(const char*, int); + +int Global; +pthread_mutex_t Mutex = PTHREAD_MUTEX_INITIALIZER; + +void *Thread(void *x) { + AnnotateIgnoreSyncBegin(0, 0); + pthread_mutex_lock(&Mutex); + Global++; + pthread_mutex_unlock(&Mutex); + AnnotateIgnoreSyncEnd(0, 0); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_mutex_lock(&Mutex); + Global++; + pthread_mutex_unlock(&Mutex); + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race + diff --git a/test/tsan/inlined_memcpy_race.cc b/test/tsan/inlined_memcpy_race.cc new file mode 100644 index 000000000000..a95576a83c9a --- /dev/null +++ b/test/tsan/inlined_memcpy_race.cc @@ -0,0 +1,38 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +int x[4], z[4]; + +void *MemCpyThread(void *a) { + memcpy((int*)a, z, 16); + return NULL; +} + +void *MemSetThread(void *a) { + sleep(1); + memset((int*)a, 0, 16); + return NULL; +} + +int main() { + pthread_t t[2]; + // Race on x between memcpy and memset + pthread_create(&t[0], NULL, MemCpyThread, x); + pthread_create(&t[1], NULL, MemSetThread, x); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("PASS\n"); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: #0 memset +// CHECK: #1 MemSetThread +// CHECK: Previous write +// CHECK: #0 memcpy +// CHECK: #1 MemCpyThread + diff --git a/test/tsan/inlined_memcpy_race2.cc b/test/tsan/inlined_memcpy_race2.cc new file mode 100644 index 000000000000..63b560f02269 --- /dev/null +++ b/test/tsan/inlined_memcpy_race2.cc @@ -0,0 +1,38 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +int y[4], z[4]; + +void *MemMoveThread(void *a) { + memmove((int*)a, z, 16); + return NULL; +} + +void *MemSetThread(void *a) { + sleep(1); + memset((int*)a, 0, 16); + return NULL; +} + +int main() { + pthread_t t[2]; + // Race on y between memmove and memset + pthread_create(&t[0], NULL, MemMoveThread, y); + pthread_create(&t[1], NULL, MemSetThread, y); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + + printf("PASS\n"); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: #0 memset +// CHECK: #1 MemSetThread +// CHECK: Previous write +// CHECK: #0 memmove +// CHECK: #1 MemMoveThread diff --git a/test/tsan/interface_atomic_test.c b/test/tsan/interface_atomic_test.c new file mode 100644 index 000000000000..18d860ea02e2 --- /dev/null +++ b/test/tsan/interface_atomic_test.c @@ -0,0 +1,16 @@ +// Test that we can include header with TSan atomic interface. +// RUN: %clang_tsan %s -o %t && %run %t | FileCheck %s +#include <sanitizer/tsan_interface_atomic.h> +#include <stdio.h> + +int main() { + __tsan_atomic32 a; + __tsan_atomic32_store(&a, 100, __tsan_memory_order_release); + int res = __tsan_atomic32_load(&a, __tsan_memory_order_acquire); + if (res == 100) { + // CHECK: PASS + printf("PASS\n"); + return 0; + } + return 1; +} diff --git a/test/tsan/java.h b/test/tsan/java.h new file mode 100644 index 000000000000..d986d08be060 --- /dev/null +++ b/test/tsan/java.h @@ -0,0 +1,21 @@ +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +extern "C" { +typedef unsigned long jptr; // NOLINT +void __tsan_java_preinit(const char *libjvm_path); +void __tsan_java_init(jptr heap_begin, jptr heap_size); +int __tsan_java_fini(); +void __tsan_java_alloc(jptr ptr, jptr size); +void __tsan_java_free(jptr ptr, jptr size); +void __tsan_java_move(jptr src, jptr dst, jptr size); +void __tsan_java_finalize(); +void __tsan_java_mutex_lock(jptr addr); +void __tsan_java_mutex_unlock(jptr addr); +void __tsan_java_mutex_read_lock(jptr addr); +void __tsan_java_mutex_read_unlock(jptr addr); +void __tsan_java_mutex_lock_rec(jptr addr, int rec); +int __tsan_java_mutex_unlock_rec(jptr addr); +} diff --git a/test/tsan/java_alloc.cc b/test/tsan/java_alloc.cc new file mode 100644 index 000000000000..4a606f7940d3 --- /dev/null +++ b/test/tsan/java_alloc.cc @@ -0,0 +1,38 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "java.h" + +int const kHeapSize = 1024 * 1024; + +void stress(jptr addr) { + for (jptr sz = 8; sz <= 32; sz <<= 1) { + for (jptr i = 0; i < kHeapSize / 4 / sz; i++) { + __tsan_java_alloc(addr + i * sz, sz); + } + __tsan_java_move(addr, addr + kHeapSize / 2, kHeapSize / 4); + __tsan_java_free(addr + kHeapSize / 2, kHeapSize / 4); + } +} + +void *Thread(void *p) { + stress((jptr)p); + return 0; +} + +int main() { + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + pthread_t th; + pthread_create(&th, 0, Thread, (void*)(jheap + kHeapSize / 4)); + stress(jheap); + pthread_join(th, 0); + if (__tsan_java_fini() != 0) { + printf("FAILED\n"); + return 1; + } + printf("DONE\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK-NOT: FAILED +// CHECK: DONE diff --git a/test/tsan/java_finalizer.cc b/test/tsan/java_finalizer.cc new file mode 100644 index 000000000000..d5c6a22d1e16 --- /dev/null +++ b/test/tsan/java_finalizer.cc @@ -0,0 +1,27 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "java.h" + +void *Thread(void *p) { + sleep(1); + __tsan_java_finalize(); + *(int*)p = 42; + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc(jheap, kBlockSize); + pthread_t th; + pthread_create(&th, 0, Thread, (void*)jheap); + *(int*)jheap = 43; + pthread_join(th, 0); + __tsan_java_free(jheap, kBlockSize); + fprintf(stderr, "DONE\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/java_lock.cc b/test/tsan/java_lock.cc new file mode 100644 index 000000000000..36a0f8b7bff3 --- /dev/null +++ b/test/tsan/java_lock.cc @@ -0,0 +1,36 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "java.h" +#include <unistd.h> + +jptr varaddr; +jptr lockaddr; + +void *Thread(void *p) { + sleep(1); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 42; + __tsan_java_mutex_unlock(lockaddr); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc(jheap, kBlockSize); + varaddr = jheap; + lockaddr = jheap + 8; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + pthread_join(th, 0); + __tsan_java_free(jheap, kBlockSize); + fprintf(stderr, "DONE\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/java_lock_move.cc b/test/tsan/java_lock_move.cc new file mode 100644 index 000000000000..19c3e35d6633 --- /dev/null +++ b/test/tsan/java_lock_move.cc @@ -0,0 +1,41 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "java.h" + +jptr varaddr; +jptr lockaddr; +jptr varaddr2; +jptr lockaddr2; + +void *Thread(void *p) { + sleep(1); + __tsan_java_mutex_lock(lockaddr2); + *(int*)varaddr2 = 42; + __tsan_java_mutex_unlock(lockaddr2); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 64; + int const kMove = 1024; + __tsan_java_alloc(jheap, kBlockSize); + varaddr = jheap; + lockaddr = jheap + 46; + varaddr2 = varaddr + kMove; + lockaddr2 = lockaddr + kMove; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + __tsan_java_move(varaddr, varaddr2, kBlockSize); + pthread_join(th, 0); + __tsan_java_free(varaddr2, kBlockSize); + printf("DONE\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/java_lock_rec.cc b/test/tsan/java_lock_rec.cc new file mode 100644 index 000000000000..2b0ab0eb92d0 --- /dev/null +++ b/test/tsan/java_lock_rec.cc @@ -0,0 +1,55 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "java.h" +#include <unistd.h> + +jptr varaddr; +jptr lockaddr; + +void *Thread(void *p) { + __tsan_java_mutex_lock(lockaddr); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 42; + int rec = __tsan_java_mutex_unlock_rec(lockaddr); + if (rec != 2) { + printf("FAILED 0 rec=%d\n", rec); + exit(1); + } + sleep(2); + __tsan_java_mutex_lock_rec(lockaddr, rec); + if (*(int*)varaddr != 43) { + printf("FAILED 3 var=%d\n", *(int*)varaddr); + exit(1); + } + __tsan_java_mutex_unlock(lockaddr); + __tsan_java_mutex_unlock(lockaddr); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc(jheap, kBlockSize); + varaddr = jheap; + *(int*)varaddr = 0; + lockaddr = jheap + 8; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + sleep(1); + __tsan_java_mutex_lock(lockaddr); + if (*(int*)varaddr != 42) { + printf("FAILED 1 var=%d\n", *(int*)varaddr); + exit(1); + } + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + pthread_join(th, 0); + __tsan_java_free(jheap, kBlockSize); + printf("DONE\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK-NOT: FAILED +// CHECK: DONE diff --git a/test/tsan/java_lock_rec_race.cc b/test/tsan/java_lock_rec_race.cc new file mode 100644 index 000000000000..841aa396360d --- /dev/null +++ b/test/tsan/java_lock_rec_race.cc @@ -0,0 +1,49 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include "java.h" +#include <unistd.h> + +jptr varaddr; +jptr lockaddr; + +void *Thread(void *p) { + __tsan_java_mutex_lock(lockaddr); + __tsan_java_mutex_lock(lockaddr); + __tsan_java_mutex_lock(lockaddr); + int rec = __tsan_java_mutex_unlock_rec(lockaddr); + if (rec != 3) { + printf("FAILED 0 rec=%d\n", rec); + exit(1); + } + *(int*)varaddr = 42; + sleep(2); + __tsan_java_mutex_lock_rec(lockaddr, rec); + __tsan_java_mutex_unlock(lockaddr); + __tsan_java_mutex_unlock(lockaddr); + __tsan_java_mutex_unlock(lockaddr); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc(jheap, kBlockSize); + varaddr = jheap; + *(int*)varaddr = 0; + lockaddr = jheap + 8; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + sleep(1); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + pthread_join(th, 0); + __tsan_java_free(jheap, kBlockSize); + printf("DONE\n"); + return __tsan_java_fini(); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NOT: FAILED +// CHECK: DONE diff --git a/test/tsan/java_move_overlap.cc b/test/tsan/java_move_overlap.cc new file mode 100644 index 000000000000..12955b4ba0de --- /dev/null +++ b/test/tsan/java_move_overlap.cc @@ -0,0 +1,72 @@ +// RUN: %clangxx_tsan -O1 %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s +// RUN: %run %t arg 2>&1 | FileCheck %s +#include "java.h" + +jptr varaddr1_old; +jptr varaddr2_old; +jptr lockaddr1_old; +jptr lockaddr2_old; +jptr varaddr1_new; +jptr varaddr2_new; +jptr lockaddr1_new; +jptr lockaddr2_new; + +void *Thread(void *p) { + sleep(1); + __tsan_java_mutex_lock(lockaddr1_new); + *(char*)varaddr1_new = 43; + __tsan_java_mutex_unlock(lockaddr1_new); + __tsan_java_mutex_lock(lockaddr2_new); + *(char*)varaddr2_new = 43; + __tsan_java_mutex_unlock(lockaddr2_new); + return 0; +} + +int main(int argc, char **argv) { + int const kHeapSize = 1024 * 1024; + void *jheap = malloc(kHeapSize); + jheap = (char*)jheap + 8; + __tsan_java_init((jptr)jheap, kHeapSize); + const int kBlockSize = 64; + int const kMove = 32; + varaddr1_old = (jptr)jheap; + lockaddr1_old = (jptr)jheap + 1; + varaddr2_old = (jptr)jheap + kBlockSize - 1; + lockaddr2_old = (jptr)jheap + kBlockSize - 16; + varaddr1_new = varaddr1_old + kMove; + lockaddr1_new = lockaddr1_old + kMove; + varaddr2_new = varaddr2_old + kMove; + lockaddr2_new = lockaddr2_old + kMove; + if (argc > 1) { + // Move memory backwards. + varaddr1_old += kMove; + lockaddr1_old += kMove; + varaddr2_old += kMove; + lockaddr2_old += kMove; + varaddr1_new -= kMove; + lockaddr1_new -= kMove; + varaddr2_new -= kMove; + lockaddr2_new -= kMove; + } + __tsan_java_alloc(varaddr1_old, kBlockSize); + + pthread_t th; + pthread_create(&th, 0, Thread, 0); + + __tsan_java_mutex_lock(lockaddr1_old); + *(char*)varaddr1_old = 43; + __tsan_java_mutex_unlock(lockaddr1_old); + __tsan_java_mutex_lock(lockaddr2_old); + *(char*)varaddr2_old = 43; + __tsan_java_mutex_unlock(lockaddr2_old); + + __tsan_java_move(varaddr1_old, varaddr1_new, kBlockSize); + pthread_join(th, 0); + __tsan_java_free(varaddr1_new, kBlockSize); + printf("DONE\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/java_move_overlap_race.cc b/test/tsan/java_move_overlap_race.cc new file mode 100644 index 000000000000..2b3769be6117 --- /dev/null +++ b/test/tsan/java_move_overlap_race.cc @@ -0,0 +1,53 @@ +// RUN: %clangxx_tsan -O1 %s -o %t +// RUN: %deflake %run %t | FileCheck %s +// RUN: %deflake %run %t arg | FileCheck %s +#include "java.h" + +jptr varaddr1_old; +jptr varaddr2_old; +jptr varaddr1_new; +jptr varaddr2_new; + +void *Thread(void *p) { + sleep(1); + *(int*)varaddr1_new = 43; + *(int*)varaddr2_new = 43; + return 0; +} + +int main(int argc, char **argv) { + int const kHeapSize = 1024 * 1024; + void *jheap = malloc(kHeapSize); + jheap = (char*)jheap + 8; + __tsan_java_init((jptr)jheap, kHeapSize); + const int kBlockSize = 64; + int const kMove = 32; + varaddr1_old = (jptr)jheap; + varaddr2_old = (jptr)jheap + kBlockSize - 1; + varaddr1_new = varaddr1_old + kMove; + varaddr2_new = varaddr2_old + kMove; + if (argc > 1) { + // Move memory backwards. + varaddr1_old += kMove; + varaddr2_old += kMove; + varaddr1_new -= kMove; + varaddr2_new -= kMove; + } + __tsan_java_alloc(varaddr1_old, kBlockSize); + + pthread_t th; + pthread_create(&th, 0, Thread, 0); + + *(int*)varaddr1_old = 43; + *(int*)varaddr2_old = 43; + + __tsan_java_move(varaddr1_old, varaddr1_new, kBlockSize); + pthread_join(th, 0); + __tsan_java_free(varaddr1_new, kBlockSize); + printf("DONE\n"); + return __tsan_java_fini(); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/java_race.cc b/test/tsan/java_race.cc new file mode 100644 index 000000000000..ede058e85d8a --- /dev/null +++ b/test/tsan/java_race.cc @@ -0,0 +1,25 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include "java.h" + +void *Thread(void *p) { + *(int*)p = 42; + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc(jheap, kBlockSize); + pthread_t th; + pthread_create(&th, 0, Thread, (void*)jheap); + *(int*)jheap = 43; + pthread_join(th, 0); + __tsan_java_free(jheap, kBlockSize); + fprintf(stderr, "DONE\n"); + return __tsan_java_fini(); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/java_race_move.cc b/test/tsan/java_race_move.cc new file mode 100644 index 000000000000..8a51be92aa10 --- /dev/null +++ b/test/tsan/java_race_move.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include "java.h" + +jptr varaddr; +jptr varaddr2; + +void *Thread(void *p) { + sleep(1); + *(int*)varaddr2 = 42; + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 64; + int const kMove = 1024; + __tsan_java_alloc(jheap, kBlockSize); + varaddr = jheap + 16; + varaddr2 = varaddr + kMove; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + *(int*)varaddr = 43; + __tsan_java_move(varaddr, varaddr2, kBlockSize); + pthread_join(th, 0); + __tsan_java_free(varaddr2, kBlockSize); + fprintf(stderr, "DONE\n"); + return __tsan_java_fini(); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/java_rwlock.cc b/test/tsan/java_rwlock.cc new file mode 100644 index 000000000000..b03afa6e092d --- /dev/null +++ b/test/tsan/java_rwlock.cc @@ -0,0 +1,36 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "java.h" +#include <unistd.h> + +jptr varaddr; +jptr lockaddr; + +void *Thread(void *p) { + sleep(1); + __tsan_java_mutex_read_lock(lockaddr); + *(int*)varaddr = 42; + __tsan_java_mutex_read_unlock(lockaddr); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + jptr jheap = (jptr)malloc(kHeapSize + 8) + 8; + __tsan_java_init(jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc(jheap, kBlockSize); + varaddr = jheap; + lockaddr = jheap + 8; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + pthread_join(th, 0); + __tsan_java_free(jheap, kBlockSize); + printf("DONE\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: DONE diff --git a/test/tsan/libcxx/lit.local.cfg b/test/tsan/libcxx/lit.local.cfg new file mode 100644 index 000000000000..202b44ee116d --- /dev/null +++ b/test/tsan/libcxx/lit.local.cfg @@ -0,0 +1,10 @@ +def getRoot(config): + if not config.parent: + return config + return getRoot(config.parent) + +root = getRoot(config) + +if not root.has_libcxx: + config.unsupported = True + diff --git a/test/tsan/libcxx/std_shared_ptr.cc b/test/tsan/libcxx/std_shared_ptr.cc new file mode 100644 index 000000000000..191a17cc798d --- /dev/null +++ b/test/tsan/libcxx/std_shared_ptr.cc @@ -0,0 +1,24 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <memory> +#include <thread> + +int main() { + int v1 = 0; + int v2 = 0; + std::thread t1; + std::thread t2; + + { + auto thingy = std::make_shared<int>(42); + t1 = std::thread([thingy, &v1] { v1 = *thingy; }); + t2 = std::thread([thingy, &v2] { v2 = *thingy; }); + } + + t1.join(); + t2.join(); + printf("%d %d\n", v1, v2); + // CHECK-NOT: ThreadSanitizer: data race + // CHECK: 42 42 + return 0; +} diff --git a/test/tsan/lit.cfg b/test/tsan/lit.cfg new file mode 100644 index 000000000000..d27500f8e3ea --- /dev/null +++ b/test/tsan/lit.cfg @@ -0,0 +1,65 @@ +# -*- Python -*- + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit_config.fatal( + "No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +# Setup config name. +config.name = 'ThreadSanitizer' + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +# Setup environment variables for running ThreadSanitizer. +tsan_options = "atexit_sleep_ms=0" + +config.environment['TSAN_OPTIONS'] = tsan_options + +# GCC driver doesn't add necessary compile/link flags with -fsanitize=thread. +if config.compiler_id == 'GNU': + extra_cflags = ["-fPIE", "-pthread", "-ldl", "-lstdc++", "-lrt", "-pie"] +else: + extra_cflags = [] + +# Setup default compiler flags used with -fsanitize=thread option. +clang_tsan_cflags = ["-fsanitize=thread", + "-Wall", + "-m64"] + config.debug_info_flags + extra_cflags +clang_tsan_cxxflags = config.cxx_mode_flags + clang_tsan_cflags +# Add additional flags if we're using instrumented libc++. +if config.has_libcxx: + # FIXME: Dehardcode this path somehow. + libcxx_path = os.path.join(config.compiler_rt_obj_root, "lib", + "tsan", "libcxx_tsan") + libcxx_incdir = os.path.join(libcxx_path, "include", "c++", "v1") + libcxx_libdir = os.path.join(libcxx_path, "lib") + libcxx_so = os.path.join(libcxx_libdir, "libc++.so") + clang_tsan_cxxflags += ["-std=c++11", + "-I%s" % libcxx_incdir, + libcxx_so, + "-Wl,-rpath=%s" % libcxx_libdir] + +def build_invocation(compile_flags): + return " " + " ".join([config.clang] + compile_flags) + " " + +config.substitutions.append( ("%clang_tsan ", build_invocation(clang_tsan_cflags)) ) +config.substitutions.append( ("%clangxx_tsan ", build_invocation(clang_tsan_cxxflags)) ) + +# Define CHECK-%os to check for OS-dependent output. +config.substitutions.append( ('CHECK-%os', ("CHECK-" + config.host_os))) + +config.substitutions.append( ("%deflake ", os.path.join(os.path.dirname(__file__), "deflake.bash")) ) + +# Default test suffixes. +config.suffixes = ['.c', '.cc', '.cpp'] + +# ThreadSanitizer tests are currently supported on FreeBSD and Linux only. +if config.host_os not in ['FreeBSD', 'Linux']: + config.unsupported = True diff --git a/test/tsan/lit.site.cfg.in b/test/tsan/lit.site.cfg.in new file mode 100644 index 000000000000..5190b211177d --- /dev/null +++ b/test/tsan/lit.site.cfg.in @@ -0,0 +1,10 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.has_libcxx = @TSAN_HAS_LIBCXX@ + +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/test/tsan/load_shared_lib.cc b/test/tsan/load_shared_lib.cc new file mode 100644 index 000000000000..a27dc1cc6ffb --- /dev/null +++ b/test/tsan/load_shared_lib.cc @@ -0,0 +1,66 @@ +// Check that if the list of shared libraries changes between the two race +// reports, the second report occurring in a new shared library is still +// symbolized correctly. + +// RUN: %clangxx_tsan -O1 %s -DBUILD_SO -fPIC -shared -o %t-so.so +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s + +#ifdef BUILD_SO + +#include <stddef.h> +#include <unistd.h> + +int GLOB_SHARED = 0; + +extern "C" +void *write_from_so(void *unused) { + if (unused) + sleep(1); + GLOB_SHARED++; + return NULL; +} + +#else // BUILD_SO + +#include <dlfcn.h> +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +#include <string> + +int GLOB = 0; + +void *write_glob(void *unused) { + if (unused) + sleep(1); + GLOB++; + return NULL; +} + +void race_two_threads(void *(*access_callback)(void *unused)) { + pthread_t t1, t2; + pthread_create(&t1, NULL, access_callback, (void*)1); + pthread_create(&t2, NULL, access_callback, NULL); + pthread_join(t1, NULL); + pthread_join(t2, NULL); +} + +int main(int argc, char *argv[]) { + std::string path = std::string(argv[0]) + std::string("-so.so"); + race_two_threads(write_glob); + // CHECK: write_glob + void *lib = dlopen(path.c_str(), RTLD_NOW); + if (!lib) { + printf("error in dlopen(): %s\n", dlerror()); + return 1; + } + void *(*write_from_so)(void *unused); + *(void **)&write_from_so = dlsym(lib, "write_from_so"); + race_two_threads(write_from_so); + // CHECK: write_from_so + return 0; +} + +#endif // BUILD_SO diff --git a/test/tsan/longjmp.cc b/test/tsan/longjmp.cc new file mode 100644 index 000000000000..d7371c5e4069 --- /dev/null +++ b/test/tsan/longjmp.cc @@ -0,0 +1,22 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> + +int foo(jmp_buf env) { + longjmp(env, 42); +} + +int main() { + jmp_buf env; + if (setjmp(env) == 42) { + printf("JUMPED\n"); + return 0; + } + foo(env); + printf("FAILED\n"); + return 0; +} + +// CHECK-NOT: FAILED +// CHECK: JUMPED diff --git a/test/tsan/longjmp2.cc b/test/tsan/longjmp2.cc new file mode 100644 index 000000000000..546019b2d11a --- /dev/null +++ b/test/tsan/longjmp2.cc @@ -0,0 +1,24 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> + +int foo(sigjmp_buf env) { + printf("env=%p\n", env); + siglongjmp(env, 42); +} + +int main() { + sigjmp_buf env; + printf("env=%p\n", env); + if (sigsetjmp(env, 1) == 42) { + printf("JUMPED\n"); + return 0; + } + foo(env); + printf("FAILED\n"); + return 0; +} + +// CHECK-NOT: FAILED +// CHECK: JUMPED diff --git a/test/tsan/longjmp3.cc b/test/tsan/longjmp3.cc new file mode 100644 index 000000000000..71d964dbbed9 --- /dev/null +++ b/test/tsan/longjmp3.cc @@ -0,0 +1,48 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> + +void bar(jmp_buf env) { + volatile int x = 42; + longjmp(env, 42); + x++; +} + +void foo(jmp_buf env) { + volatile int x = 42; + bar(env); + x++; +} + +void badguy() { + pthread_mutex_t mtx; + pthread_mutex_init(&mtx, 0); + pthread_mutex_lock(&mtx); + pthread_mutex_destroy(&mtx); +} + +void mymain() { + jmp_buf env; + if (setjmp(env) == 42) { + badguy(); + return; + } + foo(env); + printf("FAILED\n"); +} + +int main() { + volatile int x = 42; + mymain(); + return x; +} + +// CHECK-NOT: FAILED +// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex +// CHECK: #0 pthread_mutex_destroy +// CHECK: #1 badguy +// CHECK: #2 mymain +// CHECK: #3 main + diff --git a/test/tsan/longjmp4.cc b/test/tsan/longjmp4.cc new file mode 100644 index 000000000000..15330f5d83c8 --- /dev/null +++ b/test/tsan/longjmp4.cc @@ -0,0 +1,51 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <setjmp.h> +#include <string.h> + +void bar(jmp_buf env) { + volatile int x = 42; + jmp_buf env2; + memcpy(env2, env, sizeof(jmp_buf)); + longjmp(env2, 42); + x++; +} + +void foo(jmp_buf env) { + volatile int x = 42; + bar(env); + x++; +} + +void badguy() { + pthread_mutex_t mtx; + pthread_mutex_init(&mtx, 0); + pthread_mutex_lock(&mtx); + pthread_mutex_destroy(&mtx); +} + +void mymain() { + jmp_buf env; + if (setjmp(env) == 42) { + badguy(); + return; + } + foo(env); + printf("FAILED\n"); +} + +int main() { + volatile int x = 42; + mymain(); + return x; +} + +// CHECK-NOT: FAILED +// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex +// CHECK: #0 pthread_mutex_destroy +// CHECK: #1 badguy +// CHECK: #2 mymain +// CHECK: #3 main + diff --git a/test/tsan/malloc_overflow.cc b/test/tsan/malloc_overflow.cc new file mode 100644 index 000000000000..dadc94484f01 --- /dev/null +++ b/test/tsan/malloc_overflow.cc @@ -0,0 +1,23 @@ +// RUN: %clangxx_tsan -O1 %s -o %t +// RUN: TSAN_OPTIONS=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <stdlib.h> + +int main() { + void *p = malloc((size_t)-1); + if (p != 0) + printf("FAIL malloc(-1) = %p\n", p); + p = malloc((size_t)-1 / 2); + if (p != 0) + printf("FAIL malloc(-1/2) = %p\n", p); + p = calloc((size_t)-1, (size_t)-1); + if (p != 0) + printf("FAIL calloc(-1, -1) = %p\n", p); + p = calloc((size_t)-1 / 2, (size_t)-1 / 2); + if (p != 0) + printf("FAIL calloc(-1/2, -1/2) = %p\n", p); + printf("OK\n"); +} + +// CHECK-NOT: FAIL +// CHECK-NOT: failed to allocate diff --git a/test/tsan/malloc_stack.cc b/test/tsan/malloc_stack.cc new file mode 100644 index 000000000000..6027360754aa --- /dev/null +++ b/test/tsan/malloc_stack.cc @@ -0,0 +1,25 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +_Atomic(int*) p; + +void *thr(void *a) { + sleep(1); + int *pp = __c11_atomic_load(&p, __ATOMIC_RELAXED); + *pp = 42; + return 0; +} + +int main() { + pthread_t th; + pthread_create(&th, 0, thr, p); + __c11_atomic_store(&p, new int, __ATOMIC_RELAXED); + pthread_join(th, 0); +} + +// CHECK: data race +// CHECK: Previous write +// CHECK: #0 operator new +// CHECK: Location is heap block +// CHECK: #0 operator new diff --git a/test/tsan/map32bit.cc b/test/tsan/map32bit.cc new file mode 100644 index 000000000000..3a76fa2f6d62 --- /dev/null +++ b/test/tsan/map32bit.cc @@ -0,0 +1,41 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <sys/mman.h> + +// Test for issue: +// https://code.google.com/p/thread-sanitizer/issues/detail?id=5 + +void *Thread(void *ptr) { + *(int*)ptr = 42; + return 0; +} + +int main() { + void *ptr = mmap(0, 128 << 10, PROT_READ|PROT_WRITE, + MAP_32BIT|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + fprintf(stderr, "ptr=%p\n", ptr); + if (ptr == MAP_FAILED) { + fprintf(stderr, "mmap failed: %d\n", errno); + return 1; + } + if ((uintptr_t)ptr >= (1ull << 32)) { + fprintf(stderr, "ptr is too high\n"); + return 1; + } + pthread_t t; + pthread_create(&t, 0, Thread, ptr); + sleep(1); + *(int*)ptr = 42; + pthread_join(t, 0); + munmap(ptr, 128 << 10); + fprintf(stderr, "DONE\n"); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: DONE + diff --git a/test/tsan/memcpy_race.cc b/test/tsan/memcpy_race.cc new file mode 100644 index 000000000000..8ec8e0a3e6a5 --- /dev/null +++ b/test/tsan/memcpy_race.cc @@ -0,0 +1,42 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +char *data = new char[10]; +char *data1 = new char[10]; +char *data2 = new char[10]; + +void *Thread1(void *x) { + static volatile int size = 1; + memcpy(data+5, data1, size); + return NULL; +} + +void *Thread2(void *x) { + static volatile int size = 4; + sleep(1); + memcpy(data+3, data2, size); + return NULL; +} + +int main() { + fprintf(stderr, "addr=%p\n", &data[5]); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: addr=[[ADDR:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 1 at [[ADDR]] by thread T2: +// CHECK: #0 memcpy +// CHECK: #1 Thread2 +// CHECK: Previous write of size 1 at [[ADDR]] by thread T1: +// CHECK: #0 memcpy +// CHECK: #1 Thread1 diff --git a/test/tsan/mmap_large.cc b/test/tsan/mmap_large.cc new file mode 100644 index 000000000000..e715ea666231 --- /dev/null +++ b/test/tsan/mmap_large.cc @@ -0,0 +1,20 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdint.h> +#include <stdio.h> +#include <errno.h> +#include <sys/mman.h> + +int main() { + const size_t kLog2Size = 39; + const uintptr_t kLocation = 0x40ULL << kLog2Size; + void *p = mmap( + reinterpret_cast<void*>(kLocation), + 1ULL << kLog2Size, + PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, + -1, 0); + fprintf(stderr, "DONE %p %d\n", p, errno); + return p == MAP_FAILED; +} + +// CHECK: DONE diff --git a/test/tsan/mop_with_offset.cc b/test/tsan/mop_with_offset.cc new file mode 100644 index 000000000000..e44c78b7d1b4 --- /dev/null +++ b/test/tsan/mop_with_offset.cc @@ -0,0 +1,36 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <unistd.h> + +void *Thread1(void *x) { + int *p = (int*)x; + p[0] = 1; + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + char *p = (char*)x; + p[2] = 1; + return NULL; +} + +int main() { + int *data = new int(42); + fprintf(stderr, "ptr1=%p\n", data); + fprintf(stderr, "ptr2=%p\n", (char*)data + 2); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, data); + pthread_create(&t[1], NULL, Thread2, data); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + delete data; +} + +// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]] +// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 1 at [[PTR2]] by thread T2: +// CHECK: Previous write of size 4 at [[PTR1]] by thread T1: diff --git a/test/tsan/mop_with_offset2.cc b/test/tsan/mop_with_offset2.cc new file mode 100644 index 000000000000..a465d5f09471 --- /dev/null +++ b/test/tsan/mop_with_offset2.cc @@ -0,0 +1,36 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <unistd.h> + +void *Thread1(void *x) { + sleep(1); + int *p = (int*)x; + p[0] = 1; + return NULL; +} + +void *Thread2(void *x) { + char *p = (char*)x; + p[2] = 1; + return NULL; +} + +int main() { + int *data = new int(42); + fprintf(stderr, "ptr1=%p\n", data); + fprintf(stderr, "ptr2=%p\n", (char*)data + 2); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, data); + pthread_create(&t[1], NULL, Thread2, data); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + delete data; +} + +// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]] +// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at [[PTR1]] by thread T1: +// CHECK: Previous write of size 1 at [[PTR2]] by thread T2: diff --git a/test/tsan/must_deadlock.cc b/test/tsan/must_deadlock.cc new file mode 100644 index 000000000000..1409800e9b39 --- /dev/null +++ b/test/tsan/must_deadlock.cc @@ -0,0 +1,50 @@ +// Test that the deadlock detector can find a deadlock that actually happened. +// Currently we will fail to report such a deadlock because we check for +// cycles in lock-order graph after pthread_mutex_lock. + +// RUN: %clangxx_tsan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// XFAIL: * +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +pthread_mutex_t mu1, mu2; +pthread_barrier_t barrier; + +void *Thread(void *p) { + // mu2 => mu1 + pthread_mutex_lock(&mu2); + pthread_barrier_wait(&barrier); + pthread_mutex_lock(&mu1); + // CHECK: ThreadSanitizer: lock-order-inversion (potential deadlock) + pthread_mutex_unlock(&mu1); + pthread_mutex_unlock(&mu2); + return p; +} + +int main() { + pthread_mutex_init(&mu1, NULL); + pthread_mutex_init(&mu2, NULL); + pthread_barrier_init(&barrier, 0, 2); + + fprintf(stderr, "This test is going to deadlock and die in 3 seconds\n"); + alarm(3); + + pthread_t t; + pthread_create(&t, 0, Thread, 0); + + // mu1 => mu2 + pthread_mutex_lock(&mu1); + pthread_barrier_wait(&barrier); + pthread_mutex_lock(&mu2); + pthread_mutex_unlock(&mu2); + pthread_mutex_unlock(&mu1); + + pthread_join(t, 0); + + pthread_mutex_destroy(&mu1); + pthread_mutex_destroy(&mu2); + pthread_barrier_destroy(&barrier); + fprintf(stderr, "FAILED\n"); +} diff --git a/test/tsan/mutex_bad_read_lock.cc b/test/tsan/mutex_bad_read_lock.cc new file mode 100644 index 000000000000..84a2976d53e4 --- /dev/null +++ b/test/tsan/mutex_bad_read_lock.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +extern "C" void AnnotateRWLockAcquired(const char *f, int l, void *m, long rw); + +int main() { + int m = 0; + AnnotateRWLockAcquired(__FILE__, __LINE__, &m, 1); + AnnotateRWLockAcquired(__FILE__, __LINE__, &m, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: read lock of a write locked mutex +// CHECK: #0 AnnotateRWLockAcquired +// CHECK: #1 main +// CHECK: Location is stack of main thread. +// CHECK: Mutex {{.*}}) created at: +// CHECK: #0 AnnotateRWLockAcquired +// CHECK: #1 main +// CHECK: SUMMARY: ThreadSanitizer: read lock of a write locked mutex + diff --git a/test/tsan/mutex_bad_read_unlock.cc b/test/tsan/mutex_bad_read_unlock.cc new file mode 100644 index 000000000000..dcee51599d55 --- /dev/null +++ b/test/tsan/mutex_bad_read_unlock.cc @@ -0,0 +1,20 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +extern "C" void AnnotateRWLockAcquired(const char *f, int l, void *m, long rw); +extern "C" void AnnotateRWLockReleased(const char *f, int l, void *m, long rw); + +int main() { + int m = 0; + AnnotateRWLockAcquired(__FILE__, __LINE__, &m, 1); + AnnotateRWLockReleased(__FILE__, __LINE__, &m, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: read unlock of a write locked mutex +// CHECK: #0 AnnotateRWLockReleased +// CHECK: #1 main +// CHECK: Location is stack of main thread. +// CHECK: Mutex {{.*}}) created at: +// CHECK: #0 AnnotateRWLockAcquired +// CHECK: #1 main +// CHECK: SUMMARY: ThreadSanitizer: read unlock of a write locked mutex + diff --git a/test/tsan/mutex_bad_unlock.cc b/test/tsan/mutex_bad_unlock.cc new file mode 100644 index 000000000000..6b483cf17eda --- /dev/null +++ b/test/tsan/mutex_bad_unlock.cc @@ -0,0 +1,18 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +extern "C" void AnnotateRWLockReleased(const char *f, int l, void *m, long rw); + +int main() { + int m = 0; + AnnotateRWLockReleased(__FILE__, __LINE__, &m, 1); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: unlock of an unlocked mutex (or by a wrong thread) +// CHECK: #0 AnnotateRWLockReleased +// CHECK: #1 main +// CHECK: Location is stack of main thread. +// CHECK: Mutex {{.*}} created at: +// CHECK: #0 AnnotateRWLockReleased +// CHECK: #1 main +// CHECK: SUMMARY: ThreadSanitizer: unlock of an unlocked mutex (or by a wrong thread) + diff --git a/test/tsan/mutex_cycle2.c b/test/tsan/mutex_cycle2.c new file mode 100644 index 000000000000..031830d5ec63 --- /dev/null +++ b/test/tsan/mutex_cycle2.c @@ -0,0 +1,35 @@ +// RUN: %clangxx_tsan %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: TSAN_OPTIONS=detect_deadlocks=1 not %run %t 2>&1 | FileCheck %s +// RUN: TSAN_OPTIONS=detect_deadlocks=0 %run %t 2>&1 | FileCheck %s --check-prefix=DISABLED +// RUN: echo "deadlock:main" > %t.sup +// RUN: TSAN_OPTIONS="suppressions=%t.sup" %run %t 2>&1 | FileCheck %s --check-prefix=DISABLED +// RUN: echo "deadlock:zzzz" > %t.sup +// RUN: TSAN_OPTIONS="suppressions=%t.sup" not %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +int main() { + pthread_mutex_t mu1, mu2; + pthread_mutex_init(&mu1, NULL); + pthread_mutex_init(&mu2, NULL); + + // mu1 => mu2 + pthread_mutex_lock(&mu1); + pthread_mutex_lock(&mu2); + pthread_mutex_unlock(&mu2); + pthread_mutex_unlock(&mu1); + + // mu2 => mu1 + pthread_mutex_lock(&mu2); + pthread_mutex_lock(&mu1); + // CHECK: ThreadSanitizer: lock-order-inversion (potential deadlock) + // DISABLED-NOT: ThreadSanitizer + // DISABLED: PASS + pthread_mutex_unlock(&mu1); + pthread_mutex_unlock(&mu2); + + pthread_mutex_destroy(&mu1); + pthread_mutex_destroy(&mu2); + fprintf(stderr, "PASS\n"); +} diff --git a/test/tsan/mutex_destroy_locked.cc b/test/tsan/mutex_destroy_locked.cc new file mode 100644 index 000000000000..b81905ec68fd --- /dev/null +++ b/test/tsan/mutex_destroy_locked.cc @@ -0,0 +1,22 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int main() { + pthread_mutex_t m; + pthread_mutex_init(&m, 0); + pthread_mutex_lock(&m); + pthread_mutex_destroy(&m); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex +// CHECK: #0 pthread_mutex_destroy +// CHECK: #1 main +// CHECK: and: +// CHECK: #0 pthread_mutex_lock +// CHECK: #1 main +// CHECK: Mutex {{.*}} created at: +// CHECK: #0 pthread_mutex_init +// CHECK: #1 main +// CHECK: SUMMARY: ThreadSanitizer: destroy of a locked mutex{{.*}}main diff --git a/test/tsan/mutex_double_lock.cc b/test/tsan/mutex_double_lock.cc new file mode 100644 index 000000000000..c1bebf73706e --- /dev/null +++ b/test/tsan/mutex_double_lock.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +extern "C" void AnnotateRWLockAcquired(const char *f, int l, void *m, long rw); + +void *ThreadFunc(void *m) { + AnnotateRWLockAcquired(__FILE__, __LINE__, m, 1); + return 0; +} + +int main() { + int m = 0; + AnnotateRWLockAcquired(__FILE__, __LINE__, &m, 1); + pthread_t th; + pthread_create(&th, 0, ThreadFunc, &m); + pthread_join(th, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: double lock of a mutex +// CHECK: #0 AnnotateRWLockAcquired +// CHECK: #1 ThreadFunc +// CHECK: Location is stack of main thread. +// CHECK: Mutex {{.*}} created at: +// CHECK: #0 AnnotateRWLockAcquired +// CHECK: #1 main +// CHECK: SUMMARY: ThreadSanitizer: double lock of a mutex {{.*}}mutex_double_lock.cc{{.*}}ThreadFunc + diff --git a/test/tsan/mutexset1.cc b/test/tsan/mutexset1.cc new file mode 100644 index 000000000000..72964edfb1e7 --- /dev/null +++ b/test/tsan/mutexset1.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx); + Global++; + pthread_mutex_unlock(&mtx); + return NULL; +} + +void *Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2: + // CHECK: Mutex [[M1]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset1.cc:[[@LINE+1]] + pthread_mutex_init(&mtx, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx); +} diff --git a/test/tsan/mutexset2.cc b/test/tsan/mutexset2.cc new file mode 100644 index 000000000000..01a5f5df6e94 --- /dev/null +++ b/test/tsan/mutexset2.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx; + +void *Thread1(void *x) { + pthread_mutex_lock(&mtx); + Global++; + pthread_mutex_unlock(&mtx); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T2: + // CHECK: Previous write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Mutex [[M1]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset2.cc:[[@LINE+1]] + pthread_mutex_init(&mtx, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx); +} diff --git a/test/tsan/mutexset3.cc b/test/tsan/mutexset3.cc new file mode 100644 index 000000000000..e14bb1111e32 --- /dev/null +++ b/test/tsan/mutexset3.cc @@ -0,0 +1,45 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_mutex_t mtx2; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx1); + pthread_mutex_lock(&mtx2); + Global++; + pthread_mutex_unlock(&mtx2); + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]], write [[M2:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2: + // CHECK: Mutex [[M1]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset3.cc:[[@LINE+4]] + // CHECK: Mutex [[M2]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset3.cc:[[@LINE+2]] + pthread_mutex_init(&mtx1, 0); + pthread_mutex_init(&mtx2, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_mutex_destroy(&mtx2); +} diff --git a/test/tsan/mutexset4.cc b/test/tsan/mutexset4.cc new file mode 100644 index 000000000000..db860e005d67 --- /dev/null +++ b/test/tsan/mutexset4.cc @@ -0,0 +1,45 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_mutex_t mtx2; + +void *Thread1(void *x) { + pthread_mutex_lock(&mtx1); + pthread_mutex_lock(&mtx2); + Global++; + pthread_mutex_unlock(&mtx2); + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T2: + // CHECK: Previous write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]], write [[M2:M[0-9]+]]): + // CHECK: Mutex [[M1]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset4.cc:[[@LINE+4]] + // CHECK: Mutex [[M2]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset4.cc:[[@LINE+2]] + pthread_mutex_init(&mtx1, 0); + pthread_mutex_init(&mtx2, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_mutex_destroy(&mtx2); +} diff --git a/test/tsan/mutexset5.cc b/test/tsan/mutexset5.cc new file mode 100644 index 000000000000..e1cc2fcacf62 --- /dev/null +++ b/test/tsan/mutexset5.cc @@ -0,0 +1,46 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_mutex_t mtx2; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx1); + Global++; + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + pthread_mutex_lock(&mtx2); + Global--; + pthread_mutex_unlock(&mtx2); + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2 + // CHECK: (mutexes: write [[M2:M[0-9]+]]): + // CHECK: Mutex [[M1]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset5.cc:[[@LINE+4]] + // CHECK: Mutex [[M2]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset5.cc:[[@LINE+5]] + pthread_mutex_init(&mtx1, 0); + pthread_mutex_init(&mtx2, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_mutex_destroy(&mtx2); +} diff --git a/test/tsan/mutexset6.cc b/test/tsan/mutexset6.cc new file mode 100644 index 000000000000..07dcc0a7394d --- /dev/null +++ b/test/tsan/mutexset6.cc @@ -0,0 +1,53 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_spinlock_t mtx2; +pthread_rwlock_t mtx3; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx1); + Global++; + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + pthread_mutex_lock(&mtx1); + pthread_mutex_unlock(&mtx1); + pthread_spin_lock(&mtx2); + pthread_rwlock_rdlock(&mtx3); + Global--; + pthread_spin_unlock(&mtx2); + pthread_rwlock_unlock(&mtx3); + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2 + // CHECK: (mutexes: write [[M2:M[0-9]+]], read [[M3:M[0-9]+]]): + // CHECK: Mutex [[M1]] (0x{{.*}}) created at: + // CHECK: #1 main {{.*}}/mutexset6.cc:[[@LINE+5]] + // CHECK: Mutex [[M2]] (0x{{.*}}) created at: + // CHECK: #1 main {{.*}}/mutexset6.cc:[[@LINE+4]] + // CHECK: Mutex [[M3]] (0x{{.*}}) created at: + // CHECK: #1 main {{.*}}/mutexset6.cc:[[@LINE+3]] + pthread_mutex_init(&mtx1, 0); + pthread_spin_init(&mtx2, 0); + pthread_rwlock_init(&mtx3, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_spin_destroy(&mtx2); + pthread_rwlock_destroy(&mtx3); +} diff --git a/test/tsan/mutexset7.cc b/test/tsan/mutexset7.cc new file mode 100644 index 000000000000..12174844c799 --- /dev/null +++ b/test/tsan/mutexset7.cc @@ -0,0 +1,40 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +__thread int huge[1024*1024]; + +void *Thread1(void *x) { + sleep(1); + Global++; + return NULL; +} + +void *Thread2(void *x) { + pthread_mutex_t *mtx = new pthread_mutex_t; + pthread_mutex_init(mtx, 0); + pthread_mutex_lock(mtx); + Global--; + pthread_mutex_unlock(mtx); + pthread_mutex_destroy(mtx); + delete mtx; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at {{.*}} by thread T1: +// CHECK: Previous write of size 4 at {{.*}} by thread T2 +// CHECK: (mutexes: write [[M1:M[0-9]+]]): +// CHECK: Mutex [[M1]] is already destroyed +// CHECK-NOT: Mutex {{.*}} created at + diff --git a/test/tsan/mutexset8.cc b/test/tsan/mutexset8.cc new file mode 100644 index 000000000000..3e1ab8c5a744 --- /dev/null +++ b/test/tsan/mutexset8.cc @@ -0,0 +1,39 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t *mtx; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(mtx); + Global++; + pthread_mutex_unlock(mtx); + return NULL; +} + +void *Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2: + // CHECK: Mutex [[M1]] (0x{{.*}}) created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset8.cc + mtx = new pthread_mutex_t; + pthread_mutex_init(mtx, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(mtx); + delete mtx; +} diff --git a/test/tsan/printf-1.c b/test/tsan/printf-1.c new file mode 100644 index 000000000000..9116c956e30e --- /dev/null +++ b/test/tsan/printf-1.c @@ -0,0 +1,16 @@ +// RUN: %clang_tsan -O2 %s -o %t +// RUN: ASAN_OPTIONS=check_printf=1 %run %t 2>&1 | FileCheck %s +// RUN: ASAN_OPTIONS=check_printf=0 %run %t 2>&1 | FileCheck %s +// RUN: %run %t 2>&1 | FileCheck %s + +#include <stdio.h> +int main() { + volatile char c = '0'; + volatile int x = 12; + volatile float f = 1.239; + volatile char s[] = "34"; + printf("%c %d %.3f %s\n", c, x, f, s); + return 0; + // Check that printf works fine under Tsan. + // CHECK: 0 12 1.239 34 +} diff --git a/test/tsan/process_sleep.h b/test/tsan/process_sleep.h new file mode 100644 index 000000000000..5938a42bf8b2 --- /dev/null +++ b/test/tsan/process_sleep.h @@ -0,0 +1,7 @@ +#include <time.h> + +static void process_sleep(int sec) { + clock_t beg = clock(); + while((clock() - beg) / CLOCKS_PER_SEC < sec) + usleep(100); +} diff --git a/test/tsan/pthread_atfork_deadlock.c b/test/tsan/pthread_atfork_deadlock.c new file mode 100644 index 000000000000..0f33b9022e5f --- /dev/null +++ b/test/tsan/pthread_atfork_deadlock.c @@ -0,0 +1,33 @@ +// RUN: %clang_tsan -O1 %s -lpthread -o %t && %deflake %run %t | FileCheck %s +// Regression test for +// https://code.google.com/p/thread-sanitizer/issues/detail?id=61 +// When the data race was reported, pthread_atfork() handler used to be +// executed which caused another race report in the same thread, which resulted +// in a deadlock. +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int glob = 0; + +void *worker(void *unused) { + sleep(1); + glob++; + return NULL; +} + +void atfork() { + fprintf(stderr, "ATFORK\n"); + glob++; +} + +int main() { + pthread_atfork(atfork, NULL, NULL); + pthread_t t; + pthread_create(&t, NULL, worker, NULL); + glob++; + pthread_join(t, NULL); + // CHECK: ThreadSanitizer: data race + // CHECK-NOT: ATFORK + return 0; +} diff --git a/test/tsan/race_on_barrier.c b/test/tsan/race_on_barrier.c new file mode 100644 index 000000000000..99b18fe4d8e6 --- /dev/null +++ b/test/tsan/race_on_barrier.c @@ -0,0 +1,31 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +pthread_barrier_t B; +int Global; + +void *Thread1(void *x) { + pthread_barrier_init(&B, 0, 2); + pthread_barrier_wait(&B); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + pthread_barrier_wait(&B); + return NULL; +} + +int main() { + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + Thread2(0); + pthread_join(t, NULL); + pthread_barrier_destroy(&B); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/race_on_barrier2.c b/test/tsan/race_on_barrier2.c new file mode 100644 index 000000000000..98c028e19fdd --- /dev/null +++ b/test/tsan/race_on_barrier2.c @@ -0,0 +1,31 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +pthread_barrier_t B; +int Global; + +void *Thread1(void *x) { + if (pthread_barrier_wait(&B) == PTHREAD_BARRIER_SERIAL_THREAD) + pthread_barrier_destroy(&B); + return NULL; +} + +void *Thread2(void *x) { + if (pthread_barrier_wait(&B) == PTHREAD_BARRIER_SERIAL_THREAD) + pthread_barrier_destroy(&B); + return NULL; +} + +int main() { + pthread_barrier_init(&B, 0, 2); + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + Thread2(0); + pthread_join(t, NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/race_on_heap.cc b/test/tsan/race_on_heap.cc new file mode 100644 index 000000000000..a66e0c4f93f7 --- /dev/null +++ b/test/tsan/race_on_heap.cc @@ -0,0 +1,47 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> + +void *Thread1(void *p) { + *(int*)p = 42; + return 0; +} + +void *Thread2(void *p) { + *(int*)p = 44; + return 0; +} + +void *alloc() { + return malloc(99); +} + +void *AllocThread(void* arg) { + return alloc(); +} + +int main() { + void *p = 0; + pthread_t t[2]; + pthread_create(&t[0], 0, AllocThread, 0); + pthread_join(t[0], &p); + fprintf(stderr, "addr=%p\n", p); + pthread_create(&t[0], 0, Thread1, (char*)p + 16); + pthread_create(&t[1], 0, Thread2, (char*)p + 16); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + return 0; +} + +// CHECK: addr=[[ADDR:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// ... +// CHECK: Location is heap block of size 99 at [[ADDR]] allocated by thread T1: +// CHCEK: #0 malloc +// CHECK: #{{1|2}} alloc +// CHECK: #{{2|3}} AllocThread +// ... +// CHECK: Thread T1 (tid={{.*}}, finished) created by main thread at: +// CHECK: #0 pthread_create +// CHECK: #1 main diff --git a/test/tsan/race_on_mutex.c b/test/tsan/race_on_mutex.c new file mode 100644 index 000000000000..b4adeeb4df72 --- /dev/null +++ b/test/tsan/race_on_mutex.c @@ -0,0 +1,42 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +pthread_mutex_t Mtx; +int Global; + +void *Thread1(void *x) { + pthread_mutex_init(&Mtx, 0); + pthread_mutex_lock(&Mtx); + Global = 42; + pthread_mutex_unlock(&Mtx); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + pthread_mutex_lock(&Mtx); + Global = 43; + pthread_mutex_unlock(&Mtx); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&Mtx); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Atomic read of size 1 at {{.*}} by thread T2: +// CHECK-NEXT: #0 pthread_mutex_lock +// CHECK-NEXT: #1 Thread2{{.*}} {{.*}}race_on_mutex.c:20{{(:3)?}} ({{.*}}) +// CHECK: Previous write of size 1 at {{.*}} by thread T1: +// CHECK-NEXT: #0 pthread_mutex_init {{.*}} ({{.*}}) +// CHECK-NEXT: #1 Thread1{{.*}} {{.*}}race_on_mutex.c:11{{(:3)?}} ({{.*}}) diff --git a/test/tsan/race_on_mutex2.c b/test/tsan/race_on_mutex2.c new file mode 100644 index 000000000000..1796d0c6480b --- /dev/null +++ b/test/tsan/race_on_mutex2.c @@ -0,0 +1,24 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> + +void *Thread(void *x) { + pthread_mutex_lock((pthread_mutex_t*)x); + pthread_mutex_unlock((pthread_mutex_t*)x); + return 0; +} + +int main() { + pthread_mutex_t Mtx; + pthread_mutex_init(&Mtx, 0); + pthread_t t; + pthread_create(&t, 0, Thread, &Mtx); + sleep(1); + pthread_mutex_destroy(&Mtx); + pthread_join(t, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/race_on_puts.cc b/test/tsan/race_on_puts.cc new file mode 100644 index 000000000000..1f2b4db836ed --- /dev/null +++ b/test/tsan/race_on_puts.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +char s[] = "abracadabra"; + +void *Thread0(void *p) { + puts(s); + return 0; +} + +void *Thread1(void *p) { + s[3] = 'z'; + return 0; +} + +int main() { + pthread_t th[2]; + pthread_create(&th[0], 0, Thread0, 0); + pthread_create(&th[1], 0, Thread1, 0); + pthread_join(th[0], 0); + pthread_join(th[1], 0); + fprintf(stderr, "DONE"); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: DONE + diff --git a/test/tsan/race_on_read.cc b/test/tsan/race_on_read.cc new file mode 100644 index 000000000000..1ec0522b9035 --- /dev/null +++ b/test/tsan/race_on_read.cc @@ -0,0 +1,41 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +int fd; +char buf; + +void *Thread(void *x) { + sleep(1); + read(fd, &buf, 1); + return NULL; +} + +int main() { + fd = open("/dev/random", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "failed to open /dev/random (%d)\n", errno); + return 1; + } + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread, NULL); + pthread_create(&t[1], NULL, Thread, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + close(fd); + fprintf(stderr, "DONE\n"); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 1 +// CHECK: #0 read +// CHECK: Previous write of size 1 +// CHECK: #0 read +// CHECK: DONE + diff --git a/test/tsan/race_on_speculative_load.cc b/test/tsan/race_on_speculative_load.cc new file mode 100644 index 000000000000..f816db9e8853 --- /dev/null +++ b/test/tsan/race_on_speculative_load.cc @@ -0,0 +1,27 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t | FileCheck %s +// Regtest for https://code.google.com/p/thread-sanitizer/issues/detail?id=40 +// This is a correct program and tsan should not report a race. +#include <pthread.h> +#include <unistd.h> +#include <stdio.h> +int g; +__attribute__((noinline)) +int foo(int cond) { + if (cond) + return g; + return 0; +} +void *Thread1(void *p) { + long res = foo((long)p); + sleep(1); + return (void*) res; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + g = 1; + pthread_join(t, 0); + printf("PASS\n"); + // CHECK: PASS +} diff --git a/test/tsan/race_on_write.cc b/test/tsan/race_on_write.cc new file mode 100644 index 000000000000..484bbb7ae022 --- /dev/null +++ b/test/tsan/race_on_write.cc @@ -0,0 +1,39 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +int fd; +char buf; + +void *Thread1(void *x) { + buf = 1; + sleep(1); + return NULL; +} + +void *Thread2(void *x) { + write(fd, &buf, 1); + return NULL; +} + +int main() { + fd = open("/dev/null", O_WRONLY); + if (fd < 0) return 1; + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + sleep(1); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + close(fd); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Read of size 1 +// CHECK: #0 write +// CHECK: Previous write of size 1 +// CHECK: #0 Thread1 diff --git a/test/tsan/race_with_finished_thread.cc b/test/tsan/race_with_finished_thread.cc new file mode 100644 index 000000000000..d28760093c42 --- /dev/null +++ b/test/tsan/race_with_finished_thread.cc @@ -0,0 +1,43 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +// Ensure that we can restore a stack of a finished thread. + +int g_data; + +void __attribute__((noinline)) foobar(int *p) { + *p = 42; +} + +void *Thread1(void *x) { + foobar(&g_data); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + g_data = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at {{.*}} by thread T2: +// CHECK: Previous write of size 4 at {{.*}} by thread T1: +// CHECK: #0 foobar +// CHECK: #1 Thread1 +// CHECK: Thread T1 (tid={{.*}}, finished) created by main thread at: +// CHECK: #0 pthread_create +// CHECK: #1 main diff --git a/test/tsan/signal_errno.cc b/test/tsan/signal_errno.cc new file mode 100644 index 000000000000..1fa20f36810b --- /dev/null +++ b/test/tsan/signal_errno.cc @@ -0,0 +1,49 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +pthread_t mainth; +volatile int done; + +static void MyHandler(int, siginfo_t *s, void *c) { + errno = 1; + done = 1; +} + +static void* sendsignal(void *p) { + sleep(1); + pthread_kill(mainth, SIGPROF); + return 0; +} + +static __attribute__((noinline)) void loop() { + while (done == 0) { + volatile char *p = (char*)malloc(1); + p[0] = 0; + free((void*)p); + pthread_yield(); + } +} + +int main() { + mainth = pthread_self(); + struct sigaction act = {}; + act.sa_sigaction = &MyHandler; + sigaction(SIGPROF, &act, 0); + pthread_t th; + pthread_create(&th, 0, sendsignal, 0); + loop(); + pthread_join(th, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: signal handler spoils errno +// CHECK: #0 MyHandler(int, {{(__)?}}siginfo{{(_t)?}}*, void*) {{.*}}signal_errno.cc +// CHECK: main +// CHECK: SUMMARY: ThreadSanitizer: signal handler spoils errno{{.*}}MyHandler + diff --git a/test/tsan/signal_longjmp.cc b/test/tsan/signal_longjmp.cc new file mode 100644 index 000000000000..84b0682dcbaf --- /dev/null +++ b/test/tsan/signal_longjmp.cc @@ -0,0 +1,69 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s + +// Test case for longjumping out of signal handler: +// https://code.google.com/p/thread-sanitizer/issues/detail?id=75 + +#include <setjmp.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/mman.h> + +sigjmp_buf fault_jmp; +volatile int fault_expected; + +void sigfault_handler(int sig) { + if (!fault_expected) + abort(); + + /* just return from sighandler to proper place */ + fault_expected = 0; + siglongjmp(fault_jmp, 1); +} + +#define MUST_FAULT(code) do { \ + fault_expected = 1; \ + if (!sigsetjmp(fault_jmp, 1)) { \ + code; /* should pagefault -> sihandler does longjmp */ \ + fprintf(stderr, "%s not faulted\n", #code); \ + abort(); \ + } else { \ + fprintf(stderr, "%s faulted ok\n", #code); \ + } \ +} while (0) + +int main() { + struct sigaction act; + act.sa_handler = sigfault_handler; + act.sa_flags = 0; + if (sigemptyset(&act.sa_mask)) { + perror("sigemptyset"); + exit(1); + } + + if (sigaction(SIGSEGV, &act, NULL)) { + perror("sigaction"); + exit(1); + } + + void *mem = mmap(0, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANON, + -1, 0); + + MUST_FAULT(((volatile int *volatile)mem)[0] = 0); + MUST_FAULT(((volatile int *volatile)mem)[1] = 1); + MUST_FAULT(((volatile int *volatile)mem)[3] = 1); + + // Ensure that tsan does not think that we are + // in a signal handler. + void *volatile p = malloc(10); + ((volatile int*)p)[1] = 1; + free((void*)p); + + munmap(p, 4096); + + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer +// CHECK: DONE diff --git a/test/tsan/signal_malloc.cc b/test/tsan/signal_malloc.cc new file mode 100644 index 000000000000..06932fba42db --- /dev/null +++ b/test/tsan/signal_malloc.cc @@ -0,0 +1,26 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/types.h> +#include <unistd.h> + +static void handler(int, siginfo_t*, void*) { + // CHECK: WARNING: ThreadSanitizer: signal-unsafe call inside of a signal + // CHECK: #0 malloc + // CHECK: #{{(1|2)}} handler(int, {{(__)?}}siginfo{{(_t)?}}*, void*) {{.*}}signal_malloc.cc:[[@LINE+2]] + // CHECK: SUMMARY: ThreadSanitizer: signal-unsafe call inside of a signal{{.*}}handler + volatile char *p = (char*)malloc(1); + p[0] = 0; + free((void*)p); +} + +int main() { + struct sigaction act = {}; + act.sa_sigaction = &handler; + sigaction(SIGPROF, &act, 0); + kill(getpid(), SIGPROF); + sleep(1); + return 0; +} + diff --git a/test/tsan/signal_recursive.cc b/test/tsan/signal_recursive.cc new file mode 100644 index 000000000000..bbb6807586a5 --- /dev/null +++ b/test/tsan/signal_recursive.cc @@ -0,0 +1,132 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s + +// Test case for recursive signal handlers, adopted from: +// https://code.google.com/p/thread-sanitizer/issues/detail?id=71 + +#include <pthread.h> +#include <semaphore.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> + +#include "process_sleep.h" + +static const int kSigSuspend = SIGUSR1; +static const int kSigRestart = SIGUSR2; +static sigset_t g_suspend_handler_mask; + +static sem_t g_thread_suspend_ack_sem; + +static bool g_busy_thread_received_restart; + +static volatile bool g_busy_thread_garbage_collected; + +static void SaveRegistersInStack() { + // Mono walks thread stacks to detect unreferenced objects. + // If last object reference is kept in register the object will be collected + // This is why threads can't be suspended with something like pthread_suspend +}; + +static void fail(const char *what) { + fprintf(stderr, "FAILED: %s (errno=%d)\n", what, errno); + exit(1); +} + +static void SuspendHandler(int sig) { + int old_errno = errno; + SaveRegistersInStack(); + // Acknowledge that thread is saved and suspended + if (sem_post(&g_thread_suspend_ack_sem) != 0) + fail("sem_post failed"); + + do { + g_busy_thread_received_restart = false; + if (sigsuspend(&g_suspend_handler_mask) != -1 || errno != EINTR) + fail("sigsuspend failed"); + } while (!g_busy_thread_received_restart); + + // Acknowledge that thread restarted + if (sem_post(&g_thread_suspend_ack_sem) != 0) + fail("sem_post failed"); + + g_busy_thread_garbage_collected = true; + + errno = old_errno; +} + +static void RestartHandler(int sig) { + g_busy_thread_received_restart = true; +} + +static void StopWorld(pthread_t thread) { + if (pthread_kill(thread, kSigSuspend) != 0) + fail("pthread_kill failed"); + + while (sem_wait(&g_thread_suspend_ack_sem) != 0) { + if (errno != EINTR) + fail("sem_wait failed"); + } +} + +static void StartWorld(pthread_t thread) { + if (pthread_kill(thread, kSigRestart) != 0) + fail("pthread_kill failed"); + + while (sem_wait(&g_thread_suspend_ack_sem) != 0) { + if (errno != EINTR) + fail("sem_wait failed"); + } +} + +static void CollectGarbage(pthread_t thread) { + StopWorld(thread); + // Walk stacks + process_sleep(1); + StartWorld(thread); +} + +static void Init() { + if (sigfillset(&g_suspend_handler_mask) != 0) + fail("sigfillset failed"); + if (sigdelset(&g_suspend_handler_mask, kSigRestart) != 0) + fail("sigdelset failed"); + if (sem_init(&g_thread_suspend_ack_sem, 0, 0) != 0) + fail("sem_init failed"); + + struct sigaction act = {}; + act.sa_flags = SA_RESTART; + sigfillset(&act.sa_mask); + act.sa_handler = &SuspendHandler; + if (sigaction(kSigSuspend, &act, NULL) != 0) + fail("sigaction failed"); + act.sa_handler = &RestartHandler; + if (sigaction(kSigRestart, &act, NULL) != 0) + fail("sigaction failed"); +} + +void* BusyThread(void *arg) { + (void)arg; + while (!g_busy_thread_garbage_collected) { + usleep(100); // Tsan deadlocks without these sleeps + } + return NULL; +} + +int main(int argc, const char *argv[]) { + Init(); + pthread_t busy_thread; + if (pthread_create(&busy_thread, NULL, &BusyThread, NULL) != 0) + fail("pthread_create failed"); + CollectGarbage(busy_thread); + if (pthread_join(busy_thread, 0) != 0) + fail("pthread_join failed"); + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK-NOT: FAILED +// CHECK-NOT: ThreadSanitizer CHECK failed +// CHECK-NOT: WARNING: ThreadSanitizer: +// CHECK: DONE diff --git a/test/tsan/signal_sync.cc b/test/tsan/signal_sync.cc new file mode 100644 index 000000000000..15387b754dfb --- /dev/null +++ b/test/tsan/signal_sync.cc @@ -0,0 +1,58 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/time.h> +#include <unistd.h> +#include <errno.h> + +volatile int X; + +static void handler(int sig) { + (void)sig; + if (X != 42) + printf("bad"); +} + +static void* thr(void *p) { + for (int i = 0; i != 1000; i++) + usleep(1000); + return 0; +} + +int main() { + const int kThreads = 10; + pthread_t th[kThreads]; + for (int i = 0; i < kThreads; i++) + pthread_create(&th[i], 0, thr, 0); + + X = 42; + + struct sigaction act = {}; + act.sa_handler = &handler; + if (sigaction(SIGPROF, &act, 0)) { + perror("sigaction"); + exit(1); + } + + itimerval t; + t.it_value.tv_sec = 0; + t.it_value.tv_usec = 10; + t.it_interval = t.it_value; + if (setitimer(ITIMER_PROF, &t, 0)) { + perror("setitimer"); + exit(1); + } + + for (int i = 0; i < kThreads; i++) + pthread_join(th[i], 0); + + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: +// CHECK: DONE +// CHECK-NOT: WARNING: ThreadSanitizer: diff --git a/test/tsan/signal_write.cc b/test/tsan/signal_write.cc new file mode 100644 index 000000000000..626d87a7acc7 --- /dev/null +++ b/test/tsan/signal_write.cc @@ -0,0 +1,27 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +static void handler(int, siginfo_t*, void*) { + const char *str = "HELLO FROM SIGNAL\n"; + write(2, str, strlen(str)); +} + +int main() { + struct sigaction act = {}; + act.sa_sigaction = &handler; + sigaction(SIGPROF, &act, 0); + kill(getpid(), SIGPROF); + sleep(1); + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer +// CHECK: HELLO FROM SIGNAL +// CHECK: DONE + diff --git a/test/tsan/sigsuspend.cc b/test/tsan/sigsuspend.cc new file mode 100644 index 000000000000..a5930d4e87aa --- /dev/null +++ b/test/tsan/sigsuspend.cc @@ -0,0 +1,44 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s + +// Always enable asserts. +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include <assert.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <stdio.h> + +static bool signal_handler_ran = false; + +void do_nothing_signal_handler(int signum) { + write(1, "HANDLER\n", 8); + signal_handler_ran = true; +} + +int main() { + const int kSignalToTest = SIGSYS; + assert(SIG_ERR != signal(kSignalToTest, do_nothing_signal_handler)); + sigset_t empty_set; + assert(0 == sigemptyset(&empty_set)); + sigset_t one_signal = empty_set; + assert(0 == sigaddset(&one_signal, kSignalToTest)); + sigset_t old_set; + assert(0 == sigprocmask(SIG_BLOCK, &one_signal, &old_set)); + raise(kSignalToTest); + assert(!signal_handler_ran); + sigset_t all_but_one; + assert(0 == sigfillset(&all_but_one)); + assert(0 == sigdelset(&all_but_one, kSignalToTest)); + sigsuspend(&all_but_one); + assert(signal_handler_ran); + + // Restore the original set. + assert(0 == sigprocmask(SIG_SETMASK, &old_set, NULL)); + printf("DONE\n"); +} + +// CHECK: HANDLER +// CHECK: DONE diff --git a/test/tsan/simple_race.c b/test/tsan/simple_race.c new file mode 100644 index 000000000000..7b60c5ec249e --- /dev/null +++ b/test/tsan/simple_race.c @@ -0,0 +1,29 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + sleep(1); + Global = 42; + return NULL; +} + +void *Thread2(void *x) { + Global = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race + diff --git a/test/tsan/simple_race.cc b/test/tsan/simple_race.cc new file mode 100644 index 000000000000..f711bb5d114d --- /dev/null +++ b/test/tsan/simple_race.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + sleep(1); + Global++; + return NULL; +} + +void *Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: SUMMARY: ThreadSanitizer: data race{{.*}}Thread diff --git a/test/tsan/simple_stack.c b/test/tsan/simple_stack.c new file mode 100644 index 000000000000..87367033b4c9 --- /dev/null +++ b/test/tsan/simple_stack.c @@ -0,0 +1,66 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void __attribute__((noinline)) foo1() { + Global = 42; +} + +void __attribute__((noinline)) bar1() { + volatile int tmp = 42; (void)tmp; + foo1(); +} + +void __attribute__((noinline)) foo2() { + volatile int v = Global; (void)v; +} + +void __attribute__((noinline)) bar2() { + volatile int tmp = 42; (void)tmp; + foo2(); +} + +void *Thread1(void *x) { + sleep(1); + bar1(); + return NULL; +} + +void *Thread2(void *x) { + bar2(); + return NULL; +} + +void StartThread(pthread_t *t, void *(*f)(void*)) { + pthread_create(t, NULL, f, NULL); +} + +int main() { + pthread_t t[2]; + StartThread(&t[0], Thread1); + StartThread(&t[1], Thread2); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Write of size 4 at {{.*}} by thread T1: +// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack.c:9{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack.c:14{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack.c:28{{(:3)?}} ({{.*}}) +// CHECK: Previous read of size 4 at {{.*}} by thread T2: +// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack.c:18{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack.c:23{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread2{{.*}} {{.*}}simple_stack.c:33{{(:3)?}} ({{.*}}) +// CHECK: Thread T1 (tid={{.*}}, running) created by main thread at: +// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) +// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:43{{(:3)?}} ({{.*}}) +// CHECK: Thread T2 ({{.*}}) created by main thread at: +// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) +// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:44{{(:3)?}} ({{.*}}) diff --git a/test/tsan/simple_stack2.cc b/test/tsan/simple_stack2.cc new file mode 100644 index 000000000000..b07d863e4008 --- /dev/null +++ b/test/tsan/simple_stack2.cc @@ -0,0 +1,53 @@ +// RUN: %clangxx_tsan -O1 %s -o %T/simple_stack2.cc.exe && %deflake %run %T/simple_stack2.cc.exe | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void __attribute__((noinline)) foo1() { + Global = 42; +} + +void __attribute__((noinline)) bar1() { + volatile int tmp = 42; + int tmp2 = tmp; + (void)tmp2; + foo1(); +} + +void __attribute__((noinline)) foo2() { + volatile int tmp = Global; + int tmp2 = tmp; + (void)tmp2; +} + +void __attribute__((noinline)) bar2() { + volatile int tmp = 42; + int tmp2 = tmp; + (void)tmp2; + foo2(); +} + +void *Thread1(void *x) { + sleep(1); + bar1(); + return NULL; +} + +int main() { + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + bar2(); + pthread_join(t, NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Write of size 4 at {{.*}} by thread T1: +// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack2.cc:9{{(:3)?}} (simple_stack2.cc.exe+{{.*}}) +// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack2.cc:16{{(:3)?}} (simple_stack2.cc.exe+{{.*}}) +// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack2.cc:34{{(:3)?}} (simple_stack2.cc.exe+{{.*}}) +// CHECK: Previous read of size 4 at {{.*}} by main thread: +// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack2.cc:20{{(:3)?}} (simple_stack2.cc.exe+{{.*}}) +// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack2.cc:29{{(:3)?}} (simple_stack2.cc.exe+{{.*}}) +// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack2.cc:41{{(:3)?}} (simple_stack2.cc.exe+{{.*}}) diff --git a/test/tsan/sleep_sync.cc b/test/tsan/sleep_sync.cc new file mode 100644 index 000000000000..c7614e16bf3e --- /dev/null +++ b/test/tsan/sleep_sync.cc @@ -0,0 +1,30 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int X = 0; + +void MySleep() { + sleep(1); +} + +void *Thread(void *p) { + MySleep(); // Assume the main thread has done the write. + X = 42; + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + X = 43; + pthread_join(t, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// ... +// CHECK: As if synchronized via sleep: +// CHECK-NEXT: #0 sleep +// CHECK-NEXT: #1 MySleep +// CHECK-NEXT: #2 Thread diff --git a/test/tsan/sleep_sync2.cc b/test/tsan/sleep_sync2.cc new file mode 100644 index 000000000000..4e616992ecc9 --- /dev/null +++ b/test/tsan/sleep_sync2.cc @@ -0,0 +1,22 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int X = 0; + +void *Thread(void *p) { + X = 42; + return 0; +} + +int main() { + pthread_t t; + sleep(1); + pthread_create(&t, 0, Thread, 0); + X = 43; + pthread_join(t, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NOT: As if synchronized via sleep diff --git a/test/tsan/stack_race.cc b/test/tsan/stack_race.cc new file mode 100644 index 000000000000..2e02f46a281f --- /dev/null +++ b/test/tsan/stack_race.cc @@ -0,0 +1,22 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <unistd.h> + +void *Thread(void *a) { + sleep(1); + *(int*)a = 43; + return 0; +} + +int main() { + int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread, &Var); + Var = 43; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is stack of main thread. + diff --git a/test/tsan/stack_race2.cc b/test/tsan/stack_race2.cc new file mode 100644 index 000000000000..818db367bab6 --- /dev/null +++ b/test/tsan/stack_race2.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <unistd.h> + +void *Thread2(void *a) { + sleep(1); + *(int*)a = 43; + return 0; +} + +void *Thread(void *a) { + int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread2, &Var); + Var = 42; + pthread_join(t, 0); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is stack of thread T1. + diff --git a/test/tsan/static_init1.cc b/test/tsan/static_init1.cc new file mode 100644 index 000000000000..3e5fb14ba44b --- /dev/null +++ b/test/tsan/static_init1.cc @@ -0,0 +1,27 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> + +struct P { + int x; + int y; +}; + +void *Thread(void *x) { + static P p = {rand(), rand()}; + if (p.x > RAND_MAX || p.y > RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread, 0); + pthread_create(&t[1], 0, Thread, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + printf("PASS\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/static_init2.cc b/test/tsan/static_init2.cc new file mode 100644 index 000000000000..667aed1343dc --- /dev/null +++ b/test/tsan/static_init2.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> + +struct Cache { + int x; + explicit Cache(int x) + : x(x) { + } +}; + +void foo(Cache *my) { + static Cache *c = my ? my : new Cache(rand()); + if (c->x >= RAND_MAX) + exit(1); +} + +void *Thread(void *x) { + foo(new Cache(rand())); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread, 0); + pthread_create(&t[1], 0, Thread, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + printf("PASS\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/static_init3.cc b/test/tsan/static_init3.cc new file mode 100644 index 000000000000..3b9fe62ae2bc --- /dev/null +++ b/test/tsan/static_init3.cc @@ -0,0 +1,47 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <sched.h> + +struct Cache { + int x; +}; + +Cache g_cache; + +Cache *CreateCache() { + g_cache.x = rand(); + return &g_cache; +} + +_Atomic(Cache*) queue; + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + __c11_atomic_store(&queue, c, 0); + return 0; +} + +void *Thread2(void *x) { + Cache *c = 0; + for (;;) { + c = __c11_atomic_load(&queue, 0); + if (c) + break; + sched_yield(); + } + if (c->x >= RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread2, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/static_init4.cc b/test/tsan/static_init4.cc new file mode 100644 index 000000000000..85835a2520f7 --- /dev/null +++ b/test/tsan/static_init4.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <sched.h> + +struct Cache { + int x; + explicit Cache(int x) + : x(x) { + } +}; + +int g_other; + +Cache *CreateCache() { + g_other = rand(); + return new Cache(rand()); +} + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + if (c->x == g_other) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread1, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + printf("PASS\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/static_init5.cc b/test/tsan/static_init5.cc new file mode 100644 index 000000000000..961e3a3b6329 --- /dev/null +++ b/test/tsan/static_init5.cc @@ -0,0 +1,42 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <sched.h> + +struct Cache { + int x; + explicit Cache(int x) + : x(x) { + } +}; + +void *AsyncInit(void *p) { + return new Cache((int)(long)p); +} + +Cache *CreateCache() { + pthread_t t; + pthread_create(&t, 0, AsyncInit, (void*)(long)rand()); + void *res; + pthread_join(t, &res); + return (Cache*)res; +} + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + if (c->x >= RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread1, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + printf("PASS\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/static_init6.cc b/test/tsan/static_init6.cc new file mode 100644 index 000000000000..77253eac173f --- /dev/null +++ b/test/tsan/static_init6.cc @@ -0,0 +1,42 @@ +// RUN: %clangxx_tsan -static-libstdc++ -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <sched.h> + +struct Cache { + int x; + explicit Cache(int x) + : x(x) { + } +}; + +void *AsyncInit(void *p) { + return new Cache((int)(long)p); +} + +Cache *CreateCache() { + pthread_t t; + pthread_create(&t, 0, AsyncInit, (void*)(long)rand()); + void *res; + pthread_join(t, &res); + return (Cache*)res; +} + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + if (c->x >= RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread1, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + printf("PASS\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/sunrpc.cc b/test/tsan/sunrpc.cc new file mode 100644 index 000000000000..579816d64098 --- /dev/null +++ b/test/tsan/sunrpc.cc @@ -0,0 +1,25 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s + +#include <pthread.h> +#include <rpc/types.h> +#include <rpc/xdr.h> +#include <stdio.h> + +void *thr(void *p) { + XDR xdrs; + char buf[100]; + xdrmem_create(&xdrs, buf, sizeof(buf), XDR_ENCODE); + xdr_destroy(&xdrs); + return 0; +} + +int main(int argc, char *argv[]) { + pthread_t th[2]; + pthread_create(&th[0], 0, thr, 0); + pthread_create(&th[1], 0, thr, 0); + pthread_join(th[0], 0); + pthread_join(th[1], 0); + printf("DONE\n"); + // CHECK: DONE + return 0; +} diff --git a/test/tsan/suppress_same_address.cc b/test/tsan/suppress_same_address.cc new file mode 100644 index 000000000000..df19da1cc7ae --- /dev/null +++ b/test/tsan/suppress_same_address.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +volatile int X; + +void *Thread1(void *x) { + sleep(1); + X = 42; + X = 66; + X = 78; + return 0; +} + +void *Thread2(void *x) { + X = 11; + X = 99; + X = 73; + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + Thread2(0); + pthread_join(t, 0); +} + +// CHECK: ThreadSanitizer: reported 1 warnings diff --git a/test/tsan/suppress_same_stacks.cc b/test/tsan/suppress_same_stacks.cc new file mode 100644 index 000000000000..9305650eaa17 --- /dev/null +++ b/test/tsan/suppress_same_stacks.cc @@ -0,0 +1,27 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> + +volatile int N; // Prevent loop unrolling. +int **data; + +void *Thread1(void *x) { + for (int i = 0; i < N; i++) + data[i][0] = 42; + return 0; +} + +int main() { + N = 4; + data = new int*[N]; + for (int i = 0; i < N; i++) + data[i] = new int; + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + Thread1(0); + pthread_join(t, 0); + for (int i = 0; i < N; i++) + delete data[i]; + delete[] data; +} + +// CHECK: ThreadSanitizer: reported 1 warnings diff --git a/test/tsan/suppressions_global.cc b/test/tsan/suppressions_global.cc new file mode 100644 index 000000000000..c808a63d9e84 --- /dev/null +++ b/test/tsan/suppressions_global.cc @@ -0,0 +1,29 @@ +// RUN: %clang_tsan -O1 %s -o %t && TSAN_OPTIONS="$TSAN_OPTIONS suppressions=%s.supp" %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +int RacyGlobal; + +void *Thread1(void *x) { + RacyGlobal = 42; + return NULL; +} + +void *Thread2(void *x) { + RacyGlobal = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); + return 0; +} + +// CHECK-NOT: failed to open suppressions file +// CHECK-NOT: WARNING: ThreadSanitizer: data race + diff --git a/test/tsan/suppressions_global.cc.supp b/test/tsan/suppressions_global.cc.supp new file mode 100644 index 000000000000..5fa8a2e43a93 --- /dev/null +++ b/test/tsan/suppressions_global.cc.supp @@ -0,0 +1,2 @@ +race:RacyGlobal + diff --git a/test/tsan/suppressions_race.cc b/test/tsan/suppressions_race.cc new file mode 100644 index 000000000000..1d72874d9586 --- /dev/null +++ b/test/tsan/suppressions_race.cc @@ -0,0 +1,31 @@ +// RUN: %clang_tsan -O1 %s -o %t && TSAN_OPTIONS="$TSAN_OPTIONS suppressions=%s.supp" %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + sleep(1); + Global = 42; + return NULL; +} + +void *Thread2(void *x) { + Global = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); + return 0; +} + +// CHECK-NOT: failed to open suppressions file +// CHECK-NOT: WARNING: ThreadSanitizer: data race + diff --git a/test/tsan/suppressions_race.cc.supp b/test/tsan/suppressions_race.cc.supp new file mode 100644 index 000000000000..cbdba76ea93a --- /dev/null +++ b/test/tsan/suppressions_race.cc.supp @@ -0,0 +1,2 @@ +race:Thread1 + diff --git a/test/tsan/suppressions_race2.cc b/test/tsan/suppressions_race2.cc new file mode 100644 index 000000000000..4ababddf6311 --- /dev/null +++ b/test/tsan/suppressions_race2.cc @@ -0,0 +1,31 @@ +// RUN: %clang_tsan -O1 %s -o %t && TSAN_OPTIONS="$TSAN_OPTIONS suppressions=%s.supp" %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + Global = 42; + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + Global = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); + return 0; +} + +// CHECK-NOT: failed to open suppressions file +// CHECK-NOT: WARNING: ThreadSanitizer: data race + diff --git a/test/tsan/suppressions_race2.cc.supp b/test/tsan/suppressions_race2.cc.supp new file mode 100644 index 000000000000..b3c4dbc59363 --- /dev/null +++ b/test/tsan/suppressions_race2.cc.supp @@ -0,0 +1,2 @@ +race:Thread2 + diff --git a/test/tsan/test_output.sh b/test/tsan/test_output.sh new file mode 100755 index 000000000000..bce0fe8b5511 --- /dev/null +++ b/test/tsan/test_output.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +ulimit -s 8192 +set -e # fail on any error + +HERE=$(dirname $0) +TSAN_DIR=$(dirname $0)/../../lib/tsan + +# Assume clang and clang++ are in path. +: ${CC:=clang} +: ${CXX:=clang++} +: ${FILECHECK:=FileCheck} + +# TODO: add testing for all of -O0...-O3 +CFLAGS="-fsanitize=thread -O2 -g -Wall" +LDFLAGS="-pthread -ldl -lrt -lm -Wl,--whole-archive $TSAN_DIR/rtl/libtsan.a -Wl,--no-whole-archive" + +test_file() { + SRC=$1 + COMPILER=$2 + echo ----- TESTING $(basename $1) + OBJ=$SRC.o + EXE=$SRC.exe + $COMPILER $SRC $CFLAGS -c -o $OBJ + $COMPILER $OBJ $LDFLAGS -o $EXE + RES=$($EXE 2>&1 || true) + printf "%s\n" "$RES" | $FILECHECK $SRC + if [ "$3" == "" ]; then + rm -f $EXE $OBJ + fi +} + +if [ "$1" == "" ]; then + for c in $HERE/*.{c,cc}; do + if [[ $c == */failing_* ]]; then + echo SKIPPING FAILING TEST $c + continue + fi + if [[ $c == */load_shared_lib.cc ]]; then + echo TEST $c is not supported + continue + fi + if [[ $c == */*blacklist*.cc ]]; then + echo TEST $c is not supported + continue + fi + if [ "`grep "TSAN_OPTIONS" $c`" ]; then + echo SKIPPING $c -- requires TSAN_OPTIONS + continue + fi + if [ "`grep "XFAIL" $c`" ]; then + echo SKIPPING $c -- has XFAIL + continue + fi + COMPILER=$CXX + case $c in + *.c) COMPILER=$CC + esac + test_file $c $COMPILER & + done + for job in `jobs -p`; do + wait $job || exit 1 + done +else + test_file $HERE/$1 $CXX "DUMP" +fi diff --git a/test/tsan/thread_detach.c b/test/tsan/thread_detach.c new file mode 100644 index 000000000000..32cf641b141a --- /dev/null +++ b/test/tsan/thread_detach.c @@ -0,0 +1,20 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +void *Thread(void *x) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + sleep(1); + pthread_detach(t); + printf("PASS\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: thread leak +// CHECK: PASS diff --git a/test/tsan/thread_end_with_ignore.cc b/test/tsan/thread_end_with_ignore.cc new file mode 100644 index 000000000000..79bb08d64bcb --- /dev/null +++ b/test/tsan/thread_end_with_ignore.cc @@ -0,0 +1,24 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +extern "C" void AnnotateIgnoreReadsBegin(const char *f, int l); + +void *Thread(void *x) { + AnnotateIgnoreReadsBegin("", 0); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_join(t, 0); +} + +// CHECK: ThreadSanitizer: thread T1 finished with ignores enabled, created at: +// CHECK: #0 pthread_create +// CHECK: #1 main +// CHECK: Ignore was enabled at: +// CHECK: #0 AnnotateIgnoreReadsBegin +// CHECK: #1 Thread + diff --git a/test/tsan/thread_end_with_ignore2.cc b/test/tsan/thread_end_with_ignore2.cc new file mode 100644 index 000000000000..9387ea488d5a --- /dev/null +++ b/test/tsan/thread_end_with_ignore2.cc @@ -0,0 +1,12 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +extern "C" void AnnotateIgnoreWritesBegin(const char *f, int l); + +int main() { + AnnotateIgnoreWritesBegin("", 0); +} + +// CHECK: ThreadSanitizer: main thread finished with ignores enabled +// CHECK: Ignore was enabled at: +// CHECK: #0 AnnotateIgnoreWritesBegin +// CHECK: #1 main + diff --git a/test/tsan/thread_end_with_ignore3.cc b/test/tsan/thread_end_with_ignore3.cc new file mode 100644 index 000000000000..55688b2a543f --- /dev/null +++ b/test/tsan/thread_end_with_ignore3.cc @@ -0,0 +1,22 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +extern "C" void AnnotateIgnoreReadsBegin(const char *f, int l); +extern "C" void AnnotateIgnoreReadsEnd(const char *f, int l); + +int main() { + AnnotateIgnoreReadsBegin("", 0); + AnnotateIgnoreReadsBegin("", 0); + AnnotateIgnoreReadsEnd("", 0); + AnnotateIgnoreReadsEnd("", 0); + AnnotateIgnoreReadsBegin("", 0); + AnnotateIgnoreReadsBegin("", 0); + AnnotateIgnoreReadsEnd("", 0); +} + +// CHECK: ThreadSanitizer: main thread finished with ignores enabled +// CHECK: Ignore was enabled at: +// CHECK: #0 AnnotateIgnoreReadsBegin +// CHECK: #1 main {{.*}}thread_end_with_ignore3.cc:10 +// CHECK: Ignore was enabled at: +// CHECK: #0 AnnotateIgnoreReadsBegin +// CHECK: #1 main {{.*}}thread_end_with_ignore3.cc:11 + diff --git a/test/tsan/thread_leak.c b/test/tsan/thread_leak.c new file mode 100644 index 000000000000..9b850dd4b567 --- /dev/null +++ b/test/tsan/thread_leak.c @@ -0,0 +1,17 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +void *Thread(void *x) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_join(t, 0); + printf("PASS\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: thread leak diff --git a/test/tsan/thread_leak2.c b/test/tsan/thread_leak2.c new file mode 100644 index 000000000000..fc2942b2a05d --- /dev/null +++ b/test/tsan/thread_leak2.c @@ -0,0 +1,17 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +void *Thread(void *x) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_detach(t); + printf("PASS\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: thread leak diff --git a/test/tsan/thread_leak3.c b/test/tsan/thread_leak3.c new file mode 100644 index 000000000000..f4db484219a0 --- /dev/null +++ b/test/tsan/thread_leak3.c @@ -0,0 +1,17 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +void *Thread(void *x) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + sleep(1); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: thread leak +// CHECK: SUMMARY: ThreadSanitizer: thread leak{{.*}}main diff --git a/test/tsan/thread_leak4.c b/test/tsan/thread_leak4.c new file mode 100644 index 000000000000..0d3b8307000a --- /dev/null +++ b/test/tsan/thread_leak4.c @@ -0,0 +1,18 @@ +// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <unistd.h> +#include <stdio.h> + +void *Thread(void *x) { + sleep(10); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + printf("OK\n"); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: thread leak diff --git a/test/tsan/thread_leak5.c b/test/tsan/thread_leak5.c new file mode 100644 index 000000000000..ca244a9f24e1 --- /dev/null +++ b/test/tsan/thread_leak5.c @@ -0,0 +1,20 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +void *Thread(void *x) { + return 0; +} + +int main() { + volatile int N = 5; // prevent loop unrolling + for (int i = 0; i < N; i++) { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + } + sleep(1); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: thread leak +// CHECK: And 4 more similar thread leaks diff --git a/test/tsan/thread_name.cc b/test/tsan/thread_name.cc new file mode 100644 index 000000000000..a790c668c084 --- /dev/null +++ b/test/tsan/thread_name.cc @@ -0,0 +1,47 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +#if defined(__linux__) +#define USE_PTHREAD_SETNAME_NP __GLIBC_PREREQ(2, 12) +#elif defined(__FreeBSD__) +#include <pthread_np.h> +#define USE_PTHREAD_SETNAME_NP 1 +#define pthread_setname_np pthread_set_name_np +#else +#define USE_PTHREAD_SETNAME_NP 0 +#endif + +extern "C" void AnnotateThreadName(const char *f, int l, const char *name); + +int Global; + +void *Thread1(void *x) { + sleep(1); + AnnotateThreadName(__FILE__, __LINE__, "Thread1"); + Global++; + return NULL; +} + +void *Thread2(void *x) { +#if USE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), "Thread2"); +#else + AnnotateThreadName(__FILE__, __LINE__, "Thread2"); +#endif + Global--; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Thread T1 'Thread1' +// CHECK: Thread T2 'Thread2' diff --git a/test/tsan/thread_name2.cc b/test/tsan/thread_name2.cc new file mode 100644 index 000000000000..6a3dafe9c763 --- /dev/null +++ b/test/tsan/thread_name2.cc @@ -0,0 +1,36 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +#if defined(__FreeBSD__) +#include <pthread_np.h> +#define pthread_setname_np pthread_set_name_np +#endif + +int Global; + +void *Thread1(void *x) { + sleep(1); + Global++; + return 0; +} + +void *Thread2(void *x) { + pthread_setname_np(pthread_self(), "foobar2"); + Global--; + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread2, 0); + pthread_setname_np(t[0], "foobar1"); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Thread T1 'foobar1' +// CHECK: Thread T2 'foobar2' diff --git a/test/tsan/tiny_race.c b/test/tsan/tiny_race.c new file mode 100644 index 000000000000..c10eab15c5a9 --- /dev/null +++ b/test/tsan/tiny_race.c @@ -0,0 +1,21 @@ +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + sleep(1); + Global = 42; + return x; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + Global = 43; + pthread_join(t, 0); + return Global; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/tls_race.cc b/test/tsan/tls_race.cc new file mode 100644 index 000000000000..18589347e806 --- /dev/null +++ b/test/tsan/tls_race.cc @@ -0,0 +1,21 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <unistd.h> + +void *Thread(void *a) { + sleep(1); + *(int*)a = 43; + return 0; +} + +int main() { + static __thread int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread, &Var); + Var = 43; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is TLS of main thread. diff --git a/test/tsan/tls_race2.cc b/test/tsan/tls_race2.cc new file mode 100644 index 000000000000..0ca629ada5cc --- /dev/null +++ b/test/tsan/tls_race2.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <unistd.h> + +void *Thread2(void *a) { + sleep(1); + *(int*)a = 43; + return 0; +} + +void *Thread(void *a) { + static __thread int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread2, &Var); + Var = 42; + pthread_join(t, 0); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is TLS of thread T1. + diff --git a/test/tsan/tsan-vs-gvn.cc b/test/tsan/tsan-vs-gvn.cc new file mode 100644 index 000000000000..950f5d30d4da --- /dev/null +++ b/test/tsan/tsan-vs-gvn.cc @@ -0,0 +1,38 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// RUN: %clangxx_tsan -O2 %s -o %t && %run %t 2>&1 | FileCheck %s +// RUN: %clangxx_tsan -O3 %s -o %t && %run %t 2>&1 | FileCheck %s +// +// Check that load widening is not tsan-hostile. +#include <pthread.h> +#include <stdio.h> +#include <string.h> + +struct { + int i; + char c1, c2, c3, c4; +} S; + +int G; + +void *Thread1(void *x) { + G = S.c1 + S.c3; + return NULL; +} + +void *Thread2(void *x) { + S.c2 = 1; + return NULL; +} + +int main() { + pthread_t t[2]; + memset(&S, 123, sizeof(S)); + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("PASS\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK: PASS diff --git a/test/tsan/unaligned_norace.cc b/test/tsan/unaligned_norace.cc new file mode 100644 index 000000000000..20cb545f7426 --- /dev/null +++ b/test/tsan/unaligned_norace.cc @@ -0,0 +1,84 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> + +uint64_t objs[8*3*3*2][3]; + +extern "C" { +uint16_t __tsan_unaligned_read2(void *addr); +uint32_t __tsan_unaligned_read4(void *addr); +uint64_t __tsan_unaligned_read8(void *addr); +void __tsan_unaligned_write2(void *addr, uint16_t v); +void __tsan_unaligned_write4(void *addr, uint32_t v); +void __tsan_unaligned_write8(void *addr, uint64_t v); +} + +static void access(char *p, int sz, int rw) { + if (rw) { + switch (sz) { + case 0: __tsan_unaligned_write2(p, 0); break; + case 1: __tsan_unaligned_write4(p, 0); break; + case 2: __tsan_unaligned_write8(p, 0); break; + default: exit(1); + } + } else { + switch (sz) { + case 0: __tsan_unaligned_read2(p); break; + case 1: __tsan_unaligned_read4(p); break; + case 2: __tsan_unaligned_read8(p); break; + default: exit(1); + } + } +} + +static int accesssize(int sz) { + switch (sz) { + case 0: return 2; + case 1: return 4; + case 2: return 8; + } + exit(1); +} + +void Test(bool main) { + uint64_t *obj = objs[0]; + for (int off = 0; off < 8; off++) { + for (int sz1 = 0; sz1 < 3; sz1++) { + for (int sz2 = 0; sz2 < 3; sz2++) { + for (int rw = 0; rw < 2; rw++) { + char *p = (char*)obj + off; + if (main) { + // printf("thr=%d off=%d sz1=%d sz2=%d rw=%d p=%p\n", + // main, off, sz1, sz2, rw, p); + access(p, sz1, true); + } else { + p += accesssize(sz1); + // printf("thr=%d off=%d sz1=%d sz2=%d rw=%d p=%p\n", + // main, off, sz1, sz2, rw, p); + access(p, sz2, rw); + } + obj += 3; + } + } + } + } +} + +void *Thread(void *p) { + (void)p; + Test(false); + return 0; +} + +int main() { + pthread_t th; + pthread_create(&th, 0, Thread, 0); + Test(true); + pthread_join(th, 0); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: +// CHECK: OK diff --git a/test/tsan/unaligned_race.cc b/test/tsan/unaligned_race.cc new file mode 100644 index 000000000000..6e9b5a33f0da --- /dev/null +++ b/test/tsan/unaligned_race.cc @@ -0,0 +1,139 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> + +#define NOINLINE __attribute__((noinline)) + +volatile uint64_t objs[8*2*(2 + 4 + 8)][2]; + +extern "C" { +uint16_t __sanitizer_unaligned_load16(volatile void *addr); +uint32_t __sanitizer_unaligned_load32(volatile void *addr); +uint64_t __sanitizer_unaligned_load64(volatile void *addr); +void __sanitizer_unaligned_store16(volatile void *addr, uint16_t v); +void __sanitizer_unaligned_store32(volatile void *addr, uint32_t v); +void __sanitizer_unaligned_store64(volatile void *addr, uint64_t v); +} + +// All this mess is to generate unique stack for each race, +// otherwise tsan will suppress similar stacks. + +static NOINLINE void access(volatile char *p, int sz, int rw) { + if (rw) { + switch (sz) { + case 0: __sanitizer_unaligned_store16(p, 0); break; + case 1: __sanitizer_unaligned_store32(p, 0); break; + case 2: __sanitizer_unaligned_store64(p, 0); break; + default: exit(1); + } + } else { + switch (sz) { + case 0: __sanitizer_unaligned_load16(p); break; + case 1: __sanitizer_unaligned_load32(p); break; + case 2: __sanitizer_unaligned_load64(p); break; + default: exit(1); + } + } +} + +static int accesssize(int sz) { + switch (sz) { + case 0: return 2; + case 1: return 4; + case 2: return 8; + } + exit(1); +} + +template<int off, int off2> +static NOINLINE void access3(bool main, int sz1, bool rw, volatile char *p) { + p += off; + if (main) { + access(p, sz1, true); + } else { + p += off2; + if (rw) { + *p = 42; + } else { + if (*p == 42) + printf("bingo!\n"); + } + } +} + +template<int off> +static NOINLINE void +access2(bool main, int sz1, int off2, bool rw, volatile char *obj) { + if (off2 == 0) + access3<off, 0>(main, sz1, rw, obj); + else if (off2 == 1) + access3<off, 1>(main, sz1, rw, obj); + else if (off2 == 2) + access3<off, 2>(main, sz1, rw, obj); + else if (off2 == 3) + access3<off, 3>(main, sz1, rw, obj); + else if (off2 == 4) + access3<off, 4>(main, sz1, rw, obj); + else if (off2 == 5) + access3<off, 5>(main, sz1, rw, obj); + else if (off2 == 6) + access3<off, 6>(main, sz1, rw, obj); + else if (off2 == 7) + access3<off, 7>(main, sz1, rw, obj); +} + +static NOINLINE void +access1(bool main, int off, int sz1, int off2, bool rw, char *obj) { + if (off == 0) + access2<0>(main, sz1, off2, rw, obj); + else if (off == 1) + access2<1>(main, sz1, off2, rw, obj); + else if (off == 2) + access2<2>(main, sz1, off2, rw, obj); + else if (off == 3) + access2<3>(main, sz1, off2, rw, obj); + else if (off == 4) + access2<4>(main, sz1, off2, rw, obj); + else if (off == 5) + access2<5>(main, sz1, off2, rw, obj); + else if (off == 6) + access2<6>(main, sz1, off2, rw, obj); + else if (off == 7) + access2<7>(main, sz1, off2, rw, obj); +} + +NOINLINE void Test(bool main) { + volatile uint64_t *obj = objs[0]; + for (int off = 0; off < 8; off++) { + for (int sz1 = 0; sz1 < 3; sz1++) { + for (int off2 = 0; off2 < accesssize(sz1); off2++) { + for (int rw = 0; rw < 2; rw++) { + // printf("thr=%d off=%d sz1=%d off2=%d rw=%d p=%p\n", + // main, off, sz1, off2, rw, obj); + access1(main, off, sz1, off2, rw, (char*)obj); + obj += 2; + } + } + } + } +} + +void *Thread(void *p) { + (void)p; + sleep(1); + Test(false); + return 0; +} + +int main() { + pthread_t th; + pthread_create(&th, 0, Thread, 0); + Test(true); + pthread_join(th, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: ThreadSanitizer: reported 224 warnings diff --git a/test/tsan/vfork.cc b/test/tsan/vfork.cc new file mode 100644 index 000000000000..5ae1dd1ababd --- /dev/null +++ b/test/tsan/vfork.cc @@ -0,0 +1,51 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> + +int fds[2]; +int X; + +void *Thread1(void *x) { + X = 42; + write(fds[1], "a", 1); + return NULL; +} + +void *Thread2(void *x) { + char buf; + while (read(fds[0], &buf, 1) != 1) { + } + X = 43; + return NULL; +} + +int main() { + pipe(fds); + int pid = vfork(); + if (pid < 0) { + printf("FAIL to vfork\n"); + exit(1); + } + if (pid == 0) { // child + // Closing of fds must not affect parent process. + // Strictly saying this is undefined behavior, because vfork child is not + // allowed to call any functions other than exec/exit. But this is what + // openjdk does. + close(fds[0]); + close(fds[1]); + _exit(0); + } + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("DONE\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race +// CHECK-NOT: FAIL to vfork +// CHECK: DONE diff --git a/test/tsan/virtual_inheritance_compile_bug.cc b/test/tsan/virtual_inheritance_compile_bug.cc new file mode 100644 index 000000000000..2a50c2e88d01 --- /dev/null +++ b/test/tsan/virtual_inheritance_compile_bug.cc @@ -0,0 +1,15 @@ +// Regression test for http://code.google.com/p/thread-sanitizer/issues/detail?id=3. +// The C++ variant is much more compact that the LLVM IR equivalent. + +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <stdio.h> +struct AAA { virtual long aaa () { return 0; } }; // NOLINT +struct BBB: virtual AAA { unsigned long bbb; }; // NOLINT +struct CCC: virtual AAA { }; +struct DDD: CCC, BBB { DDD(); }; // NOLINT +DDD::DDD() { } +int main() { + DDD d; + printf("OK\n"); +} +// CHECK: OK diff --git a/test/tsan/vptr_benign_race.cc b/test/tsan/vptr_benign_race.cc new file mode 100644 index 000000000000..92a2b326e717 --- /dev/null +++ b/test/tsan/vptr_benign_race.cc @@ -0,0 +1,51 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <semaphore.h> +#include <stdio.h> + +struct A { + A() { + sem_init(&sem_, 0, 0); + } + virtual void F() { + } + void Done() { + sem_post(&sem_); + } + virtual ~A() { + } + sem_t sem_; +}; + +struct B : A { + virtual void F() { + } + virtual ~B() { + sem_wait(&sem_); + sem_destroy(&sem_); + } +}; + +static A *obj = new B; + +void *Thread1(void *x) { + obj->F(); + obj->Done(); + return NULL; +} + +void *Thread2(void *x) { + delete obj; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + fprintf(stderr, "PASS\n"); +} +// CHECK: PASS +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/test/tsan/vptr_harmful_race.cc b/test/tsan/vptr_harmful_race.cc new file mode 100644 index 000000000000..68e12e8e7e89 --- /dev/null +++ b/test/tsan/vptr_harmful_race.cc @@ -0,0 +1,51 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <semaphore.h> +#include <stdio.h> +#include <unistd.h> + +struct A { + A() { + sem_init(&sem_, 0, 0); + } + virtual void F() { + } + void Done() { + sem_post(&sem_); + } + virtual ~A() { + sem_wait(&sem_); + sem_destroy(&sem_); + } + sem_t sem_; +}; + +struct B : A { + virtual void F() { + } + virtual ~B() { } +}; + +static A *obj = new B; + +void *Thread1(void *x) { + obj->F(); + obj->Done(); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + delete obj; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race on vptr diff --git a/test/tsan/vptr_harmful_race2.cc b/test/tsan/vptr_harmful_race2.cc new file mode 100644 index 000000000000..aa53bbb90fcf --- /dev/null +++ b/test/tsan/vptr_harmful_race2.cc @@ -0,0 +1,51 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <semaphore.h> +#include <stdio.h> +#include <unistd.h> + +struct A { + A() { + sem_init(&sem_, 0, 0); + } + virtual void F() { + } + void Done() { + sem_post(&sem_); + } + virtual ~A() { + sem_wait(&sem_); + sem_destroy(&sem_); + } + sem_t sem_; +}; + +struct B : A { + virtual void F() { + } + virtual ~B() { } +}; + +static A *obj = new B; + +void *Thread1(void *x) { + sleep(1); + obj->F(); + obj->Done(); + return NULL; +} + +void *Thread2(void *x) { + delete obj; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race on vptr diff --git a/test/tsan/vptr_harmful_race3.cc b/test/tsan/vptr_harmful_race3.cc new file mode 100644 index 000000000000..ac6ea94e51eb --- /dev/null +++ b/test/tsan/vptr_harmful_race3.cc @@ -0,0 +1,53 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <semaphore.h> +#include <stdio.h> +#include <unistd.h> + +struct A { + A() { + sem_init(&sem_, 0, 0); + } + virtual void F() { + } + void Done() { + sem_post(&sem_); + } + virtual ~A() { + sem_wait(&sem_); + sem_destroy(&sem_); + } + sem_t sem_; +}; + +struct B : A { + virtual void F() { + } + virtual ~B() { } +}; + +static A *obj = new B; +static void (A::*fn)() = &A::F; + +void *Thread1(void *x) { + sleep(1); + (obj->*fn)(); + obj->Done(); + return NULL; +} + +void *Thread2(void *x) { + delete obj; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race on vptr + diff --git a/test/tsan/vptr_harmful_race4.cc b/test/tsan/vptr_harmful_race4.cc new file mode 100644 index 000000000000..969c9d58a016 --- /dev/null +++ b/test/tsan/vptr_harmful_race4.cc @@ -0,0 +1,34 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +struct A { + virtual void F() { + } + + virtual ~A() { + } +}; + +struct B : A { + virtual void F() { + } +}; + +void *Thread(void *x) { + sleep(1); + ((A*)x)->F(); + return 0; +} + +int main() { + A *obj = new B; + pthread_t t; + pthread_create(&t, 0, Thread, obj); + delete obj; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: heap-use-after-free (virtual call vs free) + diff --git a/test/tsan/write_in_reader_lock.cc b/test/tsan/write_in_reader_lock.cc new file mode 100644 index 000000000000..55882139b153 --- /dev/null +++ b/test/tsan/write_in_reader_lock.cc @@ -0,0 +1,35 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +pthread_rwlock_t rwlock; +int GLOB; + +void *Thread1(void *p) { + (void)p; + pthread_rwlock_rdlock(&rwlock); + // Write under reader lock. + sleep(1); + GLOB++; + pthread_rwlock_unlock(&rwlock); + return 0; +} + +int main(int argc, char *argv[]) { + pthread_rwlock_init(&rwlock, NULL); + pthread_rwlock_rdlock(&rwlock); + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + volatile int x = GLOB; + (void)x; + pthread_rwlock_unlock(&rwlock); + pthread_join(t, 0); + pthread_rwlock_destroy(&rwlock); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at {{.*}} by thread T1{{.*}}: +// CHECK: #0 Thread1(void*) {{.*}}write_in_reader_lock.cc:13 +// CHECK: Previous read of size 4 at {{.*}} by main thread{{.*}}: +// CHECK: #0 main {{.*}}write_in_reader_lock.cc:23 |