| /* |
| This file is part of Valgrind, a dynamic binary instrumentation |
| framework. |
| |
| Copyright (C) 2008-2008 Google Inc |
| opensource@google.com |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License as |
| published by the Free Software Foundation; either version 2 of the |
| License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307, USA. |
| |
| The GNU General Public License is contained in the file COPYING. |
| */ |
| |
| /* Author: Konstantin Serebryany <opensource@google.com> |
| |
| This file contains a set of unit tests for a data race detection tool. |
| |
| These tests can be compiled with pthreads (default) or |
| with any other library that supports threads, locks, cond vars, etc. |
| |
| */ |
| #ifdef WIN32 |
| #error "Don't build this file on Windows!" |
| #endif |
| |
| #include <fcntl.h> |
| #include <fenv.h> |
| #include <queue> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <string> |
| #include <vector> |
| #include <unistd.h> |
| |
| #ifdef OS_linux |
| # include <sys/epoll.h> |
| #endif // OS_linux |
| |
| #include "test_utils.h" |
| #include <gtest/gtest.h> |
| #include "gtest_fixture_injection.h" |
| |
| static CondVar CV; |
| static int COND = 0; |
| |
| // test11: FP. Synchronization via CondVar, 2 workers. {{{1 |
| // This test is properly synchronized, but currently (Dec 2007) |
| // helgrind reports a false positive. |
| // |
| // Parent: Worker1, Worker2: |
| // 1. Start(workers) a. read(GLOB) |
| // 2. MU.Lock() b. MU.Lock() |
| // 3. while(COND != 2) /-------- c. CV.Signal() |
| // CV.Wait(&MU) <-------/ d. MU.Unlock() |
| // 4. MU.Unlock() |
| // 5. write(GLOB) |
| // |
| namespace test11 { |
| int GLOB = 0; |
| Mutex MU; |
| void Worker() { |
| usleep(200000); |
| CHECK(GLOB != 777); |
| |
| MU.Lock(); |
| COND++; |
| CV.Signal(); |
| MU.Unlock(); |
| } |
| |
| void Parent() { |
| COND = 0; |
| |
| MyThreadArray t(Worker, Worker); |
| t.Start(); |
| |
| MU.Lock(); |
| while(COND != 2) { |
| CV.Wait(&MU); |
| } |
| MU.Unlock(); |
| |
| GLOB = 2; |
| |
| t.Join(); |
| } |
| |
| TEST(NegativeTests, test11) { |
| // ANNOTATE_EXPECT_RACE(&GLOB, "test11. FP. Fixed by MSMProp1."); |
| printf("test11: negative\n"); |
| Parent(); |
| printf("\tGLOB=%d\n", GLOB); |
| } |
| } // namespace test11 |
| |
| |
| // test75: TN. Test for sem_post, sem_wait, sem_trywait. {{{1 |
| namespace test75 { |
| int GLOB = 0; |
| sem_t sem[2]; |
| |
| void Poster() { |
| GLOB = 1; |
| sem_post(&sem[0]); |
| sem_post(&sem[1]); |
| } |
| |
| void Waiter() { |
| sem_wait(&sem[0]); |
| CHECK(GLOB==1); |
| } |
| void TryWaiter() { |
| usleep(500000); |
| sem_trywait(&sem[1]); |
| CHECK(GLOB==1); |
| } |
| |
| TEST(NegativeTests, test75) { |
| #ifndef NO_UNNAMED_SEM |
| sem_init(&sem[0], 0, 0); |
| sem_init(&sem[1], 0, 0); |
| |
| printf("test75: negative\n"); |
| { |
| MyThreadArray t(Poster, Waiter); |
| t.Start(); |
| t.Join(); |
| } |
| GLOB = 2; |
| { |
| MyThreadArray t(Poster, TryWaiter); |
| t.Start(); |
| t.Join(); |
| } |
| printf("\tGLOB=%d\n", GLOB); |
| |
| sem_destroy(&sem[0]); |
| sem_destroy(&sem[1]); |
| #endif // NO_UNNAMED_SEM |
| } |
| } // namespace test75 |
| |
| |
| // test98: Synchronization via read/write (or send/recv). {{{1 |
| namespace test98 { |
| // The synchronization here is done by a pair of read/write calls |
| // that create a happens-before arc. Same may be done with send/recv. |
| // Such synchronization is quite unusual in real programs |
| // (why would one synchronizae via a file or socket?), but |
| // quite possible in unittests where one threads runs for producer |
| // and one for consumer. |
| // |
| // A race detector has to create a happens-before arcs for |
| // {read,send}->{write,recv} even if the file descriptors are different. |
| // |
| int GLOB = 0; |
| int fd_out = -1; |
| int fd_in = -1; |
| |
| void Writer() { |
| usleep(1000); |
| GLOB = 1; |
| const char *str = "Hey there!\n"; |
| const int size = strlen(str) + 1; |
| CHECK(size == write(fd_out, str, size)); |
| } |
| |
| void Reader() { |
| char buff[100]; |
| while (read(fd_in, buff, 100) == 0) |
| sleep(1); |
| printf("read: %s\n", buff); |
| GLOB = 2; |
| } |
| |
| #ifndef __APPLE__ |
| // Tsan for Mac OS is missing the unlink() syscall handler. |
| // TODO(glider): add the syscall handler to Valgrind. |
| TEST(NegativeTests, test98) { |
| printf("test98: negative, synchronization via I/O\n"); |
| char in_name[100]; |
| char out_name[100]; |
| // we open two files, on for reading and one for writing, |
| // but the files are actually the same (symlinked). |
| sprintf(in_name, "/tmp/racecheck_unittest_in.%d", getpid()); |
| sprintf(out_name, "/tmp/racecheck_unittest_out.%d", getpid()); |
| fd_out = creat(out_name, O_WRONLY | S_IRWXU); |
| CHECK(0 == symlink(out_name, in_name)); |
| fd_in = open(in_name, 0, O_RDONLY); |
| CHECK(fd_out >= 0); |
| CHECK(fd_in >= 0); |
| MyThreadArray t(Writer, Reader); |
| t.Start(); |
| t.Join(); |
| printf("\tGLOB=%d\n", GLOB); |
| // cleanup |
| close(fd_in); |
| close(fd_out); |
| unlink(in_name); |
| unlink(out_name); |
| } |
| #endif // __APPLE__ |
| } // namespace test98 |
| |
| |
| namespace NegativeTests_PthreadOnce { // {{{1 |
| int *GLOB = NULL; |
| static pthread_once_t once = PTHREAD_ONCE_INIT; |
| void Init() { |
| GLOB = new int; |
| ANNOTATE_TRACE_MEMORY(GLOB); |
| *GLOB = 777; |
| } |
| |
| void Worker0() { |
| pthread_once(&once, Init); |
| } |
| void Worker1() { |
| usleep(100000); |
| pthread_once(&once, Init); |
| CHECK(*GLOB == 777); |
| } |
| |
| |
| TEST(NegativeTests, PthreadOnceTest) { |
| MyThreadArray t(Worker0, Worker1, Worker1, Worker1); |
| t.Start(); |
| t.Join(); |
| printf("\tGLOB=%d\n", *GLOB); |
| } |
| } // namespace |
| |
| |
| // test110: TP. Simple races with stack, global and heap objects. {{{1 |
| namespace test110 { |
| int GLOB = 0; |
| static int STATIC; |
| |
| char *STACK = 0; |
| |
| int *MALLOC; |
| int *CALLOC; |
| int *REALLOC; |
| int *VALLOC; |
| int *PVALLOC; |
| int *MEMALIGN; |
| int *POSIX_MEMALIGN; |
| int *MMAP; |
| |
| int *NEW; |
| int *NEW_ARR; |
| |
| void Worker() { |
| GLOB++; |
| STATIC++; |
| |
| (*STACK)++; |
| |
| (*MALLOC)++; |
| (*CALLOC)++; |
| (*REALLOC)++; |
| (*VALLOC)++; |
| (*PVALLOC)++; |
| (*MEMALIGN)++; |
| (*POSIX_MEMALIGN)++; |
| (*MMAP)++; |
| |
| (*NEW)++; |
| (*NEW_ARR)++; |
| } |
| TEST(PositiveTests, test110) { |
| printf("test110: positive (race on a stack object)\n"); |
| |
| char x = 0; |
| STACK = &x; |
| |
| MALLOC = (int*)malloc(sizeof(int)); |
| CALLOC = (int*)calloc(1, sizeof(int)); |
| REALLOC = (int*)realloc(NULL, sizeof(int)); |
| VALLOC = (int*)valloc(sizeof(int)); |
| PVALLOC = (int*)valloc(sizeof(int)); // TODO: pvalloc breaks helgrind. |
| MEMALIGN = (int*)memalign(64, sizeof(int)); |
| CHECK(0 == posix_memalign((void**)&POSIX_MEMALIGN, 64, sizeof(int))); |
| MMAP = (int*)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANON, -1, 0); |
| |
| NEW = new int; |
| NEW_ARR = new int[10]; |
| |
| |
| ANNOTATE_EXPECT_RACE(STACK, "real race on stack object"); |
| ANNOTATE_EXPECT_RACE(&GLOB, "real race on global object"); |
| ANNOTATE_EXPECT_RACE(&STATIC, "real race on a static global object"); |
| ANNOTATE_EXPECT_RACE(MALLOC, "real race on a malloc-ed object"); |
| ANNOTATE_EXPECT_RACE(CALLOC, "real race on a calloc-ed object"); |
| ANNOTATE_EXPECT_RACE(REALLOC, "real race on a realloc-ed object"); |
| ANNOTATE_EXPECT_RACE(VALLOC, "real race on a valloc-ed object"); |
| ANNOTATE_EXPECT_RACE(PVALLOC, "real race on a pvalloc-ed object"); |
| ANNOTATE_EXPECT_RACE(MEMALIGN, "real race on a memalign-ed object"); |
| ANNOTATE_EXPECT_RACE(POSIX_MEMALIGN, "real race on a posix_memalign-ed object"); |
| ANNOTATE_EXPECT_RACE(MMAP, "real race on a mmap-ed object"); |
| |
| ANNOTATE_EXPECT_RACE(NEW, "real race on a new-ed object"); |
| ANNOTATE_EXPECT_RACE(NEW_ARR, "real race on a new[]-ed object"); |
| |
| MyThreadArray t(Worker, Worker, Worker); |
| t.Start(); |
| t.Join(); |
| printf("\tSTACK=%d\n", *STACK); |
| CHECK(GLOB <= 3); |
| CHECK(STATIC <= 3); |
| |
| free(MALLOC); |
| free(CALLOC); |
| free(REALLOC); |
| free(VALLOC); |
| free(PVALLOC); |
| free(MEMALIGN); |
| free(POSIX_MEMALIGN); |
| munmap(MMAP, sizeof(int)); |
| delete NEW; |
| delete [] NEW_ARR; |
| } |
| } // namespace test110 |
| |
| |
| // test115: TN. sem_open. {{{1 |
| namespace test115 { |
| int tid = 0; |
| Mutex mu; |
| const char *kSemName = "drt-test-sem"; |
| |
| int GLOB = 0; |
| |
| sem_t *DoSemOpen() { |
| // TODO: there is some race report inside sem_open |
| // for which suppressions do not work... (???) |
| ANNOTATE_IGNORE_WRITES_BEGIN(); |
| sem_t *sem = sem_open(kSemName, O_CREAT, 0600, 3); |
| ANNOTATE_IGNORE_WRITES_END(); |
| return sem; |
| } |
| |
| void Worker() { |
| mu.Lock(); |
| int my_tid = tid++; |
| mu.Unlock(); |
| |
| if (my_tid == 0) { |
| GLOB = 1; |
| } |
| |
| // if the detector observes a happens-before arc between |
| // sem_open and sem_wait, it will be silent. |
| sem_t *sem = DoSemOpen(); |
| usleep(100000); |
| CHECK(sem != SEM_FAILED); |
| CHECK(sem_wait(sem) == 0); |
| |
| if (my_tid > 0) { |
| CHECK(GLOB == 1); |
| } |
| } |
| |
| #ifndef __APPLE__ |
| /* This test is disabled for Darwin because of the tricky implementation of |
| * sem_open on that platform: subsequent attempts to open an existing semafore |
| * create new ones. */ |
| TEST(NegativeTests, test115) { |
| printf("test115: stab (sem_open())\n"); |
| |
| // just check that sem_open is not completely broken |
| sem_unlink(kSemName); |
| sem_t* sem = DoSemOpen(); |
| CHECK(sem != SEM_FAILED); |
| CHECK(sem_wait(sem) == 0); |
| sem_unlink(kSemName); |
| |
| // check that sem_open and sem_wait create a happens-before arc. |
| MyThreadArray t(Worker, Worker, Worker); |
| t.Start(); |
| t.Join(); |
| // clean up |
| sem_unlink(kSemName); |
| } |
| #endif // __APPLE__ |
| } // namespace test115 |
| |
| |
| // test122 TP: Simple test with RWLock {{{1 |
| namespace test122 { |
| int VAR1 = 0; |
| int VAR2 = 0; |
| RWLock mu; |
| |
| void WriteWhileHoldingReaderLock(int *p) { |
| usleep(100000); |
| ReaderLockScoped lock(&mu); // Reader lock for writing. -- bug. |
| (*p)++; |
| } |
| |
| void CorrectWrite(int *p) { |
| WriterLockScoped lock(&mu); |
| (*p)++; |
| } |
| |
| void Thread1() { WriteWhileHoldingReaderLock(&VAR1); } |
| void Thread2() { CorrectWrite(&VAR1); } |
| void Thread3() { CorrectWrite(&VAR2); } |
| void Thread4() { WriteWhileHoldingReaderLock(&VAR2); } |
| |
| |
| TEST(PositiveTests, test122) { |
| printf("test122: positive (rw-lock)\n"); |
| VAR1 = 0; |
| VAR2 = 0; |
| ANNOTATE_TRACE_MEMORY(&VAR1); |
| ANNOTATE_TRACE_MEMORY(&VAR2); |
| if (!Tsan_PureHappensBefore()) { |
| ANNOTATE_EXPECT_RACE_FOR_TSAN(&VAR1, "test122. TP. ReaderLock-ed while writing"); |
| ANNOTATE_EXPECT_RACE_FOR_TSAN(&VAR2, "test122. TP. ReaderLock-ed while writing"); |
| } |
| MyThreadArray t(Thread1, Thread2, Thread3, Thread4); |
| t.Start(); |
| t.Join(); |
| } |
| } // namespace test122 |
| |
| |
| // test125 TN: Backwards lock (annotated). {{{1 |
| namespace test125 { |
| // This test uses "Backwards mutex" locking protocol. |
| // We take a *reader* lock when writing to a per-thread data |
| // (GLOB[thread_num]) and we take a *writer* lock when we |
| // are reading from the entire array at once. |
| // |
| // Such locking protocol is not understood by ThreadSanitizer's |
| // hybrid state machine. So, you either have to use a pure-happens-before |
| // detector ("tsan --pure-happens-before") or apply pure happens-before mode |
| // to this particular lock by using ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(&mu). |
| |
| const int n_threads = 3; |
| RWLock mu; |
| int GLOB[n_threads]; |
| |
| int adder_num; // updated atomically. |
| |
| void Adder() { |
| int my_num = AtomicIncrement(&adder_num, 1) - 1; |
| CHECK(my_num >= 0); |
| CHECK(my_num < n_threads); |
| |
| ReaderLockScoped lock(&mu); |
| GLOB[my_num]++; |
| } |
| |
| void Aggregator() { |
| int sum = 0; |
| { |
| WriterLockScoped lock(&mu); |
| for (int i = 0; i < n_threads; i++) { |
| sum += GLOB[i]; |
| } |
| } |
| printf("sum=%d\n", sum); |
| } |
| |
| TEST(NegativeTests, test125) { |
| printf("test125: negative\n"); |
| |
| ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(&mu); |
| |
| // run Adders, then Aggregator |
| adder_num = 0; |
| { |
| MyThreadArray t(Adder, Adder, Adder, Aggregator); |
| t.Start(); |
| t.Join(); |
| } |
| |
| // Run Aggregator first. |
| adder_num = 0; |
| { |
| MyThreadArray t(Aggregator, Adder, Adder, Adder); |
| t.Start(); |
| t.Join(); |
| } |
| |
| } |
| } // namespace test125 |
| |
| |
| namespace MmapTest { // {{{1 |
| |
| const int kMmapSize = 65536; |
| |
| void SubWorker() { |
| for (int i = 0; i < 500; i++) { |
| int *ptr = (int*)mmap(NULL, kMmapSize, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANON, -1, 0); |
| *ptr = 42; |
| munmap(ptr, kMmapSize); |
| } |
| } |
| |
| void Worker1() { |
| MyThreadArray t(SubWorker, SubWorker, SubWorker, SubWorker); |
| t.Start(); |
| t.Join(); |
| } |
| void Worker() { |
| MyThreadArray t(Worker1, Worker1, Worker1, Worker1); |
| t.Start(); |
| t.Join(); |
| } |
| |
| TEST(NegativeTests, MmapTest) { |
| MyThreadArray t(Worker, Worker, Worker, Worker); |
| t.Start(); |
| t.Join(); |
| } |
| } // namespace |
| |
| |
| // A regression test for mmap/munmap handling in Pin. |
| // If the tool misses munmap() calls it may report a false positive if two |
| // threads map the same memory region. |
| namespace MmapRegressionTest { // {{{1 |
| |
| const int kMmapSize = 65536; |
| const uintptr_t kStartAddress = 0x10000; |
| |
| StealthNotification n1; |
| |
| void Worker() { |
| int *ptr = (int*)mmap((void*)kStartAddress, kMmapSize, |
| PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANON, -1, 0); |
| *ptr = 42; |
| munmap(ptr, kMmapSize); |
| } |
| |
| TEST(NegativeTests, MmapRegressionTest) { |
| MyThreadArray t(Worker, Worker); |
| t.Start(); |
| t.Join(); |
| } |
| |
| } // namespace |
| |
| // test136. Unlock twice. {{{1 |
| namespace test136 { |
| TEST(LockTests, UnlockTwice) { |
| pthread_mutexattr_t attr; |
| CHECK(0 == pthread_mutexattr_init(&attr)); |
| CHECK(0 == pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)); |
| |
| pthread_mutex_t mu; |
| CHECK(0 == pthread_mutex_init(&mu, &attr)); |
| CHECK(0 == pthread_mutex_lock(&mu)); |
| CHECK(0 == pthread_mutex_unlock(&mu)); |
| int ret_unlock = pthread_mutex_unlock(&mu); // unlocking twice. |
| int ret_destroy = pthread_mutex_destroy(&mu); |
| printf(" pthread_mutex_unlock returned %d\n", ret_unlock); |
| printf(" pthread_mutex_destroy returned %d\n", ret_destroy); |
| } |
| } // namespace test136 |
| |
| |
| // test141 FP. unlink/fopen, rmdir/opendir. {{{1 |
| namespace test141 { |
| int GLOB1 = 0, |
| GLOB2 = 0; |
| char *dir_name = NULL, |
| *filename = NULL; |
| |
| void Waker1() { |
| usleep(100000); |
| GLOB1 = 1; // Write |
| // unlink deletes a file 'filename' |
| // which exits spin-loop in Waiter1(). |
| printf(" Deleting file...\n"); |
| CHECK(unlink(filename) == 0); |
| } |
| |
| void Waiter1() { |
| FILE *tmp; |
| while ((tmp = fopen(filename, "r")) != NULL) { |
| fclose(tmp); |
| usleep(10000); |
| } |
| printf(" ...file has been deleted\n"); |
| GLOB1 = 2; // Write |
| } |
| |
| void Waker2() { |
| usleep(100000); |
| GLOB2 = 1; // Write |
| // rmdir deletes a directory 'dir_name' |
| // which exit spin-loop in Waker(). |
| printf(" Deleting directory...\n"); |
| CHECK(rmdir(dir_name) == 0); |
| } |
| |
| void Waiter2() { |
| DIR *tmp; |
| while ((tmp = opendir(dir_name)) != NULL) { |
| closedir(tmp); |
| usleep(10000); |
| } |
| printf(" ...directory has been deleted\n"); |
| GLOB2 = 2; |
| } |
| |
| TEST(NegativeTests, test141) { |
| printf("test141: FP. unlink/fopen, rmdir/opendir.\n"); |
| |
| dir_name = strdup("/tmp/tsan-XXXXXX"); |
| CHECK(mkdtemp(dir_name) != 0); |
| |
| filename = strdup((std::string() + dir_name + "/XXXXXX").c_str()); |
| const int fd = mkstemp(filename); |
| CHECK(fd >= 0); |
| close(fd); |
| |
| MyThreadArray mta1(Waker1, Waiter1); |
| mta1.Start(); |
| mta1.Join(); |
| |
| MyThreadArray mta2(Waker2, Waiter2); |
| mta2.Start(); |
| mta2.Join(); |
| |
| free(filename); |
| filename = 0; |
| free(dir_name); |
| dir_name = 0; |
| } |
| } // namespace test141 |
| |
| |
| // test146: TP. Unit test for RWLock::TryLock and RWLock::ReaderTryLock. {{{1 |
| namespace test146 { |
| // Worker1 locks the globals for writing for a long time. |
| // Worker2 tries to write to globals twice: without a writer lock and with it. |
| // Worker3 tries to read from globals twice: without a reader lock and with it. |
| int GLOB1 = 0; |
| char padding1[64]; |
| int GLOB2 = 0; |
| char padding2[64]; |
| int GLOB3 = 0; |
| char padding3[64]; |
| int GLOB4 = 0; |
| RWLock MU; |
| StealthNotification n1, n2, n3, n4, n5; |
| |
| void Worker1() { |
| MU.Lock(); |
| GLOB1 = 1; |
| GLOB2 = 1; |
| GLOB3 = 1; |
| GLOB4 = 1; |
| n1.signal(); |
| n2.wait(); |
| n3.wait(); |
| MU.Unlock(); |
| n4.signal(); |
| } |
| |
| void Worker2() { |
| n1.wait(); |
| if (MU.TryLock()) CHECK(0); |
| else |
| GLOB1 = 2; |
| n2.signal(); |
| n5.wait(); |
| if (MU.TryLock()) { |
| GLOB2 = 2; |
| MU.Unlock(); |
| } else { |
| CHECK(0); |
| } |
| } |
| |
| void Worker3() { |
| n1.wait(); |
| if (MU.ReaderTryLock()) CHECK(0); |
| else |
| printf("\treading GLOB3: %d\n", GLOB3); |
| n3.signal(); |
| n4.wait(); |
| if (MU.ReaderTryLock()) { |
| printf("\treading GLOB4: %d\n", GLOB4); |
| MU.ReaderUnlock(); |
| } else { |
| CHECK(0); |
| } |
| n5.signal(); |
| } |
| |
| TEST(PositiveTests, test146) { |
| ANNOTATE_EXPECT_RACE_FOR_TSAN(&GLOB1, "test146. TP: a data race on GLOB1."); |
| ANNOTATE_EXPECT_RACE_FOR_TSAN(&GLOB3, "test146. TP: a data race on GLOB3."); |
| ANNOTATE_TRACE_MEMORY(&GLOB1); |
| ANNOTATE_TRACE_MEMORY(&GLOB2); |
| ANNOTATE_TRACE_MEMORY(&GLOB3); |
| ANNOTATE_TRACE_MEMORY(&GLOB4); |
| printf("test146: positive\n"); |
| MyThreadArray t(Worker1, Worker2, Worker3); |
| t.Start(); |
| t.Join(); |
| printf("\tGLOB1=%d\n", GLOB1); |
| printf("\tGLOB2=%d\n", GLOB2); |
| printf("\tGLOB3=%d\n", GLOB3); |
| printf("\tGLOB4=%d\n", GLOB4); |
| } |
| } // namespace test146 |
| |
| namespace PositiveTests_CyclicBarrierTest { // {{{1 |
| #ifndef NO_BARRIER |
| // regression test for correct support of cyclic barrier. |
| // This test was suggested by Julian Seward. |
| // There is a race on L here between a1 and b1, |
| // but a naive support of barrier makes us miss this race. |
| pthread_barrier_t B; |
| int L; |
| |
| // A1/A2: write L, then wait for barrier, then sleep |
| void a1() { |
| L = 1; |
| pthread_barrier_wait(&B); |
| sleep(1); |
| } |
| void a2() { |
| pthread_barrier_wait(&B); |
| sleep(1); |
| } |
| |
| // B1/B2: sleep, wait for barrier, then write L |
| void b1() { |
| sleep(1); |
| pthread_barrier_wait(&B); |
| L = 1; |
| } |
| void b2() { |
| sleep(1); |
| pthread_barrier_wait(&B); |
| } |
| |
| TEST(PositiveTests, CyclicBarrierTest) { |
| ANNOTATE_EXPECT_RACE_FOR_TSAN(&L, "real race, may be hidden" |
| " by incorrect implementation of barrier"); |
| pthread_barrier_init(&B, NULL, 3); |
| MyThreadArray t1(a1, a2, a2), |
| t2(b1, b2, b2); |
| t1.Start(); |
| t2.Start(); |
| t1.Join(); |
| t2.Join(); |
| } |
| |
| |
| int *G = NULL; |
| |
| void Worker() { |
| pthread_barrier_wait(&B); |
| (*G) = 1; |
| pthread_barrier_wait(&B); |
| } |
| |
| TEST(PositiveTests, CyclicBarrierTwoCallsTest) { |
| pthread_barrier_init(&B, NULL, 2); |
| G = new int(0); |
| ANNOTATE_TRACE_MEMORY(G); |
| ANNOTATE_EXPECT_RACE_FOR_TSAN(G, "real race, may be hidden" |
| " by incorrect implementation of barrier"); |
| MyThreadArray t1(Worker, Worker); |
| t1.Start(); |
| t1.Join(); |
| CHECK(*G == 1); |
| delete G; |
| } |
| |
| |
| |
| #endif // NO_BARRIER |
| } // namespace |
| |
| TEST(NegativeTests, Mmap84GTest) { // {{{1 |
| #ifdef ARCH_amd64 |
| #ifdef OS_linux |
| // test that we can mmap 84G and can do it fast. |
| size_t size = (1ULL << 32) * 21; // 21 * 4G |
| void *mem_ptr = mmap((void *) 0, |
| size, |
| PROT_EXEC | PROT_READ | PROT_WRITE, |
| MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, |
| -1, |
| (off_t) 0); |
| printf("res=%p\n", mem_ptr); |
| #endif |
| #endif |
| } |
| |
| namespace NegativeTests_PthreadCreateFailureTest { // {{{1 |
| #ifdef OS_linux |
| void* ThreadRoutine(void *) { |
| return NULL; |
| } |
| |
| TEST(NegativeTests, PthreadCreateFailureTest) { |
| pthread_attr_t attributes; |
| pthread_attr_init(&attributes); |
| pthread_attr_setstacksize(&attributes, -1); |
| pthread_t handle; |
| CHECK(pthread_create(&handle, &attributes, ThreadRoutine, NULL) != 0); |
| pthread_attr_destroy(&attributes); |
| } |
| #endif // OS_linux |
| } // namespace NegativeTests_PthreadCreateFailureTest |
| |
| namespace Signals { // {{{1 |
| |
| typedef void (*Sigaction)(int, siginfo_t *, void *); |
| |
| int GLOB = 0; |
| |
| static void EnableSigprof(Sigaction SignalHandler) { |
| struct sigaction sa; |
| sa.sa_sigaction = SignalHandler; |
| sa.sa_flags = SA_RESTART | SA_SIGINFO; |
| sigemptyset(&sa.sa_mask); |
| if (sigaction(SIGPROF, &sa, NULL) != 0) { |
| perror("sigaction"); |
| abort(); |
| } |
| struct itimerval timer; |
| timer.it_interval.tv_sec = 0; |
| timer.it_interval.tv_usec = 1000000 / 10000; |
| timer.it_value = timer.it_interval; |
| if (setitimer(ITIMER_PROF, &timer, 0) != 0) { |
| perror("setitimer"); |
| abort(); |
| } |
| } |
| |
| static void DisableSigprof() { |
| // Disable the profiling timer. |
| struct itimerval timer; |
| timer.it_interval.tv_sec = 0; |
| timer.it_interval.tv_usec = 0; |
| timer.it_value = timer.it_interval; |
| if (setitimer(ITIMER_PROF, &timer, 0) != 0) { |
| perror("setitimer"); |
| abort(); |
| } |
| // Ignore SIGPROFs from now on. |
| struct sigaction sa; |
| sa.sa_handler = SIG_IGN; |
| sa.sa_flags = SA_RESTART | SA_SIGINFO; |
| sigemptyset(&sa.sa_mask); |
| if (sigaction(SIGPROF, &sa, NULL) != 0) { |
| perror("sigaction"); |
| abort(); |
| } |
| } |
| |
| void MallocTestWorker() { |
| for (int i = 0; i < 100000; i++) { |
| void *x = malloc((i % 64) + 1); |
| free (x); |
| } |
| } |
| |
| // Regression test for |
| // http://code.google.com/p/data-race-test/issues/detail?id=13 . |
| // Make sure that locking events are handled in signal handlers. |
| // |
| // For some reason, invoking the signal handlers causes deadlocks on Mac OS. |
| #ifndef __APPLE__ |
| Mutex mu; |
| |
| void SignalHandlerWithMutex(int, siginfo_t*, void*) { |
| mu.Lock(); |
| GLOB++; |
| mu.Unlock(); |
| } |
| |
| TEST(Signals, SignalsAndMallocTestWithMutex) { |
| EnableSigprof(SignalHandlerWithMutex); |
| MyThreadArray t(MallocTestWorker, MallocTestWorker, MallocTestWorker); |
| t.Start(); |
| t.Join(); |
| printf("\tGLOB=%d\n", GLOB); |
| DisableSigprof(); |
| } |
| #endif |
| |
| // Another regression test for |
| // http://code.google.com/p/data-race-test/issues/detail?id=13 . |
| // Make sure that locking events are handled in signal handlers. |
| SpinLock sl; |
| |
| void SignalHandlerWithSpinlock(int, siginfo_t*, void*) { |
| sl.Lock(); |
| GLOB++; |
| sl.Unlock(); |
| } |
| |
| TEST(Signals, DISABLED_SignalsAndMallocTestWithSpinlock) { |
| EnableSigprof(SignalHandlerWithSpinlock); |
| MyThreadArray t(MallocTestWorker, MallocTestWorker, MallocTestWorker); |
| t.Start(); |
| t.Join(); |
| printf("\tGLOB=%d\n", GLOB); |
| DisableSigprof(); |
| } |
| |
| // Regression test for |
| // http://code.google.com/p/data-race-test/issues/detail?id=14. |
| static void WaitTestSignalHandler(int, siginfo_t*, void*) { |
| ANNOTATE_HAPPENS_AFTER((void*)0x1234); |
| } |
| |
| void WaitTestWorker() { |
| for (int i = 0; i < 1000000; i++) { |
| ANNOTATE_HAPPENS_AFTER((void*)0x1234); |
| } |
| } |
| |
| TEST(Signals, SignalsAndWaitTest) { |
| EnableSigprof(WaitTestSignalHandler); |
| MyThreadArray t(WaitTestWorker, WaitTestWorker, WaitTestWorker); |
| t.Start(); |
| t.Join(); |
| DisableSigprof(); |
| } |
| |
| #ifndef __APPLE__ |
| // this test crashes on Mac in debug TSan build, see |
| // http://code.google.com/p/data-race-test/issues/detail?id=47 |
| pid_t child_pid = 0; |
| |
| void child_handler(int signum) { |
| if (signum == SIGCHLD && child_pid == 0) { |
| printf("Whoops, PID shouldn't be 0!\n"); |
| } |
| } |
| |
| TEST(Signals, PositiveTests_RaceInSignal) { |
| // Currently the data race on child_pid can't be found, |
| // see http://code.google.com/p/data-race-test/issues/detail?id=46 |
| //ANNOTATE_EXPECT_RACE(&child_pid, "Race on pid: fork vs signal handler"); |
| signal(SIGCHLD, child_handler); |
| child_pid = fork(); |
| if (child_pid == 0) { |
| // in child |
| exit(0); |
| } |
| wait(NULL); |
| } |
| #endif // __APPLE__ |
| |
| } // namespace; |
| |
| TEST(WeirdSizesTests, FegetenvTest) { |
| // http://code.google.com/p/data-race-test/issues/detail?id=36 |
| fenv_t tmp; |
| if (fegetenv(&tmp) != 0) |
| FAIL() << "fegetenv failed"; |
| } |
| |
| namespace NegativeTests_epoll { // {{{1 |
| #ifdef OS_linux |
| int GLOB; |
| |
| // Currently, ThreadSanitizer should create hb arcs between |
| // epoll_ctl and epoll_wait regardless of the parameters. Check that. |
| |
| void Worker1() { |
| GLOB++; |
| struct epoll_event event; |
| epoll_ctl(0, 0, 0, &event); |
| } |
| void Worker2() { |
| struct epoll_event event; |
| epoll_wait(0, &event, 0, 0); |
| GLOB++; |
| } |
| |
| TEST(NegativeTests,epollTest) { |
| MyThreadArray mta(Worker1, Worker2); |
| mta.Start(); |
| mta.Join(); |
| } |
| #endif // OS_linux |
| } |
| namespace NegativeTests_LockfTest { // {{{1 |
| |
| class ShmMutex { |
| public: |
| ShmMutex() : fd_(-1) { } |
| void set_fd(int fd) { |
| CHECK(fd_ == -1); |
| fd_ = fd; |
| } |
| void Lock() { |
| LockOrUnlockInternal(true); |
| } |
| void Unlock() { |
| LockOrUnlockInternal(false); |
| } |
| private: |
| void LockOrUnlockInternal(bool lock) { |
| CHECK(fd_ >= 0); |
| while (lockf(fd_, lock ? F_LOCK : F_ULOCK, 0) < 0) { |
| if (errno == EINTR) { |
| continue; |
| } else if (errno == ENOLCK) { |
| usleep(5000); |
| continue; |
| } |
| CHECK(0); |
| } |
| |
| } |
| |
| int fd_; |
| } mu; |
| |
| int GLOB; |
| |
| void Worker() { |
| mu.Lock(); |
| GLOB++; |
| mu.Unlock(); |
| } |
| |
| TEST(NegativeTests,DISABLED_LockfTest) { |
| mu.set_fd(1 /* stdout */); |
| MyThreadArray mta(Worker, Worker); |
| mta.Start(); |
| mta.Join(); |
| } |
| |
| } |
| namespace PositiveTests_LockThenNoLock { // {{{1 |
| // Regression test for a bug fixed by r2312 |
| int GLOB; |
| pthread_mutex_t mu; |
| StealthNotification n1, n2; |
| |
| void Worker1() { |
| pthread_mutex_lock(&mu); |
| GLOB = 1; |
| pthread_mutex_unlock(&mu); |
| n1.signal(); |
| n2.wait(); |
| GLOB = 2; |
| } |
| |
| void Worker2() { |
| pthread_mutex_lock(&mu); |
| GLOB = 3; |
| pthread_mutex_unlock(&mu); |
| n2.signal(); |
| n1.wait(); |
| GLOB = 4; |
| } |
| |
| TEST(PositiveTests, LockThenNoLock) { |
| pthread_mutex_init(&mu, NULL); |
| ANNOTATE_TRACE_MEMORY(&GLOB); |
| ANNOTATE_EXPECT_RACE(&GLOB, "race"); |
| ANNOTATE_NOT_HAPPENS_BEFORE_MUTEX(&mu); |
| MyThreadArray t(Worker1, Worker2); |
| t.Start(); |
| t.Join(); |
| pthread_mutex_destroy(&mu); |
| } |
| } // namespace |
| |
| #ifdef __APPLE__ |
| namespace NegativeTests_PthreadCondWaitRelativeNp { // {{{1 |
| int GLOB = 0; |
| pthread_mutex_t mu; |
| pthread_cond_t cv; |
| |
| void Waiter() { |
| struct timespec tv = {1000, 1000}; |
| pthread_mutex_lock(&mu); |
| pthread_cond_timedwait_relative_np(&cv, &mu, &tv); |
| GLOB = 2; |
| pthread_mutex_unlock(&mu); |
| } |
| |
| void Waker() { |
| pthread_mutex_lock(&mu); |
| GLOB = 1; |
| pthread_cond_signal(&cv); |
| pthread_mutex_unlock(&mu); |
| } |
| |
| TEST(NegativeTests, PthreadCondWaitRelativeNpTest) { |
| pthread_mutex_init(&mu, NULL); |
| pthread_cond_init(&cv, NULL); |
| MyThreadArray t(Waiter, Waker); |
| t.Start(); |
| t.Join(); |
| pthread_mutex_destroy(&mu); |
| pthread_cond_destroy(&cv); |
| } |
| } // namespace |
| #endif // __APPLE__ |
| |
| namespace PositiveTests_RWLockVsRWLockTest { // {{{1 |
| // Test that reader lock/unlock do not create a hb-arc. |
| RWLock mu; |
| int GLOB; |
| StealthNotification n; |
| |
| void Thread1() { |
| GLOB = 1; |
| mu.ReaderLock(); |
| mu.ReaderUnlock(); |
| n.signal(); |
| } |
| |
| void Thread2() { |
| n.wait(); |
| mu.ReaderLock(); |
| mu.ReaderUnlock(); |
| GLOB = 1; |
| } |
| |
| TEST(PositiveTests, RWLockVsRWLockTest) { |
| ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX(&mu); |
| ANNOTATE_EXPECT_RACE(&GLOB, "rwunlock/rwlock is not a hb-arc"); |
| MyThreadArray t(Thread1, Thread2); |
| t.Start(); |
| t.Join(); |
| } |
| |
| } // namespace |
| |
| namespace TSDTests { |
| // Test the support for libpthread TSD destructors. |
| pthread_key_t key; |
| const int kInitialValue = 0xfeedface; |
| int tsd_array[2]; |
| |
| void Destructor(void *ptr) { |
| int *write = (int*) ptr; |
| *write = kInitialValue; |
| } |
| |
| void DoWork(int index) { |
| int *value = &(tsd_array[index]); |
| *value = 42; |
| pthread_setspecific(key, value); |
| int *read = (int*) pthread_getspecific(key); |
| CHECK(read == value); |
| } |
| |
| void Worker0() { DoWork(0); } |
| void Worker1() { DoWork(1); } |
| |
| TEST(TSDTests, TSDDestructorTest) { |
| pthread_key_create(&key, Destructor); |
| MyThreadArray t(Worker0, Worker1); |
| t.Start(); |
| t.Join(); |
| for (int i = 0; i < 2; ++i) { |
| CHECK(tsd_array[i] == kInitialValue); |
| } |
| } |
| |
| } |
| |
| // End {{{1 |
| // vim:shiftwidth=2:softtabstop=2:expandtab:foldmethod=marker |