/* 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: Timur Iskhodzhanov <opensource@google.com> This file contains a set of Windows-specific unit tests for a data race detection tool. */ #include <gtest/gtest.h> #include "test_utils.h" #include "gtest_fixture_injection.h" void DummyWorker() { } void LongWorker() { Sleep(1); volatile int i = 1 << 20; while(i--); } void WriteWorker(int *var) { LongWorker(); *var = 42; } void VeryLongWriteWorker(int *var) { Sleep(1000); *var = 42; } TEST(NegativeTests, WindowsCreateThreadFailureTest) { // {{{1 HANDLE t = ::CreateThread(0, -1, (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0); CHECK(t == 0); } TEST(NegativeTests, DISABLED_WindowsCreateThreadSuspendedTest) { // {{{1 // Hangs under TSan, see // http://code.google.com/p/data-race-test/issues/detail?id=61 int *var = new int; HANDLE t = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)WriteWorker, var, CREATE_SUSPENDED, 0); CHECK(t > 0); EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(t, 200)); *var = 1; EXPECT_EQ(1, ResumeThread(t)); EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE)); EXPECT_EQ(42, *var); delete var; } TEST(NegativeTests, WindowsThreadStackSizeTest) { // {{{1 // Just spawn few threads with different stack sizes. int sizes[3] = {1 << 19, 1 << 21, 1 << 22}; for (int i = 0; i < 3; i++) { HANDLE t = ::CreateThread(0, sizes[i], (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0); CHECK(t > 0); EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE)); CloseHandle(t); } } TEST(NegativeTests, WindowsJoinWithTimeout) { // {{{1 HANDLE t = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)LongWorker, 0, 0, 0); ASSERT_TRUE(t > 0); EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(t, 1)); EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE)); CloseHandle(t); } TEST(NegativeTests, HappensBeforeOnThreadJoin) { // {{{1 int *var = new int; HANDLE t = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)WriteWorker, var, 0, 0); ASSERT_TRUE(t > 0); // Calling WaitForSingleObject two times to make sure the H-B arc // is created on the second call. There was a bug that the thread handle // was deleted even when WaitForSingleObject timed out. EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(t, 1)); EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE)); EXPECT_EQ(*var, 42); CloseHandle(t); delete var; } TEST(NegativeTests, HappensBeforeOnThreadJoinTidReuse) { // {{{1 HANDLE t1 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0); CloseHandle(t1); Sleep(1000); int *var = new int; HANDLE t2 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)WriteWorker, var, 0, 0); printf("t1 = %d, t2 = %d\n"); CHECK(t2 > 0); CHECK(WAIT_OBJECT_0 == ::WaitForSingleObject(t2, INFINITE)); CHECK(*var == 42); delete var; } TEST(NegativeTests, WaitForMultipleObjectsWaitAllTest) { int var1 = 13, var2 = 13; HANDLE t1 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)WriteWorker, &var1, 0, 0), t2 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)WriteWorker, &var2, 0, 0); ASSERT_TRUE(t1 > 0); ASSERT_TRUE(t2 > 0); HANDLE handles[2] = {t1, t2}; // Calling WaitForMultipleObjectsTest two times to make sure the H-B arc // are created on the second call. EXPECT_EQ(WAIT_TIMEOUT, ::WaitForMultipleObjects(2, handles, TRUE, 1)); EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(2, handles, TRUE, INFINITE)); EXPECT_EQ(var1, 42); EXPECT_EQ(var2, 42); CloseHandle(t1); CloseHandle(t2); } TEST(NegativeTests, WaitForMultipleObjectsWaitOneTest) { int var1 = 13, var2 = 13; HANDLE t1 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)VeryLongWriteWorker, &var1, 0, 0), t2 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)WriteWorker, &var2, 0, 0); ASSERT_TRUE(t1 > 0); ASSERT_TRUE(t2 > 0); HANDLE handles[2] = {t1, t2}; // Calling WaitForMultipleObjectsTest two times to make sure the H-B arc // are created on the second call. EXPECT_EQ(WAIT_TIMEOUT, ::WaitForMultipleObjects(2, handles, FALSE, 1)); EXPECT_EQ(WAIT_OBJECT_0 + 1, ::WaitForMultipleObjects(2, handles, FALSE, INFINITE)); EXPECT_EQ(var2, 42); EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(1, handles, FALSE, INFINITE)); EXPECT_EQ(var1, 42); CloseHandle(t1); CloseHandle(t2); } namespace RegisterWaitForSingleObjectTest { // {{{1 StealthNotification *n = NULL; HANDLE monitored_object = NULL; void SignalStealthNotification() { n->wait(); SetEvent(monitored_object); } void foo() { } void CALLBACK DoneWaiting(void *param, BOOLEAN timed_out) { int *i = (int*)param; foo(); // make sure this function has a call. See issue 24. (*i)++; } TEST(NegativeTests, WindowsRegisterWaitForSingleObjectTest) { // {{{1 // These are very tricky false positive found while testing Chromium. // // Report #1: // Everything after UnregisterWaitEx(*, INVALID_HANDLE_VALUE) happens-after // execution of DoneWaiting callback. Currently, we don't catch this h-b. // // Report #2: // The callback thread is re-used between Registet/Unregister/Register calls // so we miss h-b between "int *obj = ..." and DoneWaiting on the second // iteration. for (int i = 0; i < 2; i++) { n = new StealthNotification(); int *obj = new int(0); HANDLE wait_object = NULL; monitored_object = ::CreateEvent(NULL, false, false, NULL); printf("monitored_object = %p\n", monitored_object); MyThread mt(SignalStealthNotification); mt.Start(); ANNOTATE_TRACE_MEMORY(obj); CHECK(0 != ::RegisterWaitForSingleObject(&wait_object, monitored_object, DoneWaiting, obj, INFINITE, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)); printf("wait_object = %p\n", wait_object); n->signal(); mt.Join(); Sleep(1000); CHECK(0 != ::UnregisterWaitEx(wait_object, INVALID_HANDLE_VALUE)); (*obj)++; CHECK(*obj == 2); CloseHandle(monitored_object); delete n; delete obj; } } } namespace QueueUserWorkItemTests { DWORD CALLBACK Callback(void *param) { int *ptr = (int*)param; (*ptr)++; delete ptr; return 0; } TEST(NegativeTests, WindowsQueueUserWorkItemTest) { // False positive: // The callback thread is allocated from a thread pool and can be re-used. // As a result, we may miss h-b between "int *obj = ..." and Callback execution. for (int i = 0; i < 5; i++) { int *obj = new int(0); ANNOTATE_TRACE_MEMORY(obj); CHECK(QueueUserWorkItem(Callback, obj, i % 2 ? WT_EXECUTELONGFUNCTION : 0)); Sleep(500); } } int GLOB = 42; DWORD CALLBACK Callback2(void *param) { StealthNotification *ptr = (StealthNotification*)param; GLOB++; Sleep(100); ptr->signal(); return 0; } TEST(PositiveTests, WindowsQueueUserWorkItemTest) { ANNOTATE_EXPECT_RACE_FOR_TSAN(&GLOB, "PositiveTests.WindowsQueueUserWorkItemTest"); const int N_THREAD = 5; StealthNotification n[N_THREAD]; for (int i = 0; i < N_THREAD; i++) CHECK(QueueUserWorkItem(Callback2, &n[i], i % 2 ? WT_EXECUTELONGFUNCTION : 0)); for (int i = 0; i < N_THREAD; i++) n[i].wait(); } } namespace WindowsCriticalSectionTest { // {{{1 CRITICAL_SECTION cs; TEST(NegativeTests, WindowsCriticalSectionTest) { InitializeCriticalSection(&cs); EnterCriticalSection(&cs); TryEnterCriticalSection(&cs); LeaveCriticalSection(&cs); DeleteCriticalSection(&cs); } } // namespace namespace WindowsSRWLockTest { // {{{1 #if WINVER >= 0x0600 // Vista or Windows Server 2000 SRWLOCK SRWLock; int *obj; void Reader() { AcquireSRWLockShared(&SRWLock); CHECK(*obj <= 2 && *obj >= 0); ReleaseSRWLockShared(&SRWLock); } void Writer() { AcquireSRWLockExclusive(&SRWLock); (*obj)++; ReleaseSRWLockExclusive(&SRWLock); } #if 0 // This doesn't work in older versions of Windows. void TryReader() { if (TryAcquireSRWLockShared(&SRWLock)) { CHECK(*obj <= 2 && *obj >= 0); ReleaseSRWLockShared(&SRWLock); } } void TryWriter() { if (TryAcquireSRWLockExclusive(&SRWLock)) { (*obj)++; ReleaseSRWLockExclusive(&SRWLock); } } #endif TEST(NegativeTests, WindowsSRWLockTest) { InitializeSRWLock(&SRWLock); obj = new int(0); ANNOTATE_TRACE_MEMORY(obj); MyThreadArray t(Reader, Writer, Reader, Writer); t.Start(); t.Join(); AcquireSRWLockShared(&SRWLock); ReleaseSRWLockShared(&SRWLock); CHECK(*obj == 2); delete obj; } TEST(NegativeTests, WindowsSRWLockHackyInitializationTest) { // A similar pattern has been found on Chromium media_unittests InitializeSRWLock(&SRWLock); AcquireSRWLockExclusive(&SRWLock); // Leave the lock acquired // Reset the lock InitializeSRWLock(&SRWLock); obj = new int(0); MyThreadArray t(Reader, Writer, Reader, Writer); t.Start(); t.Join(); delete obj; } #endif // WINVER >= 0x0600 } // namespace namespace WindowsConditionVariableSRWTest { // {{{1 #if WINVER >= 0x0600 // Vista or Windows Server 2000 SRWLOCK SRWLock; CONDITION_VARIABLE cv; bool cond; int *obj; StealthNotification n; void WaiterSRW() { *obj = 1; n.wait(); AcquireSRWLockExclusive(&SRWLock); cond = true; WakeConditionVariable(&cv); ReleaseSRWLockExclusive(&SRWLock); } void WakerSRW() { AcquireSRWLockExclusive(&SRWLock); n.signal(); while (!cond) { SleepConditionVariableSRW(&cv, &SRWLock, 10, 0); } ReleaseSRWLockExclusive(&SRWLock); CHECK(*obj == 1); *obj = 2; } TEST(NegativeTests, WindowsConditionVariableSRWTest) { InitializeSRWLock(&SRWLock); InitializeConditionVariable(&cv); obj = new int(0); cond = false; ANNOTATE_TRACE_MEMORY(obj); MyThreadArray t(WaiterSRW, WakerSRW); t.Start(); t.Join(); CHECK(*obj == 2); delete obj; } #endif // WINVER >= 0x0600 } // namespace namespace WindowsInterlockedListTest { // {{{1 SLIST_HEADER list; struct Item { SLIST_ENTRY entry; int foo; }; void Push() { Item *item = new Item; item->foo = 42; InterlockedPushEntrySList(&list, (PSINGLE_LIST_ENTRY)item); } void Pop() { Item *item; while (0 == (item = (Item*)InterlockedPopEntrySList(&list))) { Sleep(1); } CHECK(item->foo == 42); delete item; } TEST(NegativeTests, WindowsInterlockedListTest) { InitializeSListHead(&list); MyThreadArray t(Push, Pop); t.Start(); t.Join(); } } // namespace namespace FileSystemReports { // {{{1 // This is a test for the flaky report found in // Chromium net_unittests. // // Looks like the test is sensitive to memory allocations / scheduling order, // so you shouldn't run other tests while investigating the issue. // The report is ~50% flaky. HANDLE hDone = NULL; void CreateFileJob() { HANDLE hFile = CreateFileA("ZZZ\\tmpfile", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); CloseHandle(hFile); DWORD attr1 = GetFileAttributes("ZZZ"); // "Concurrent write" is here. } DWORD CALLBACK PrintDirectoryListingJob(void *param) { Sleep(500); WIN32_FIND_DATAA data; // "Current write" is here. HANDLE hFind = FindFirstFileA("ZZZ/*", &data); CHECK(hFind != INVALID_HANDLE_VALUE); CloseHandle(hFind); SetEvent(hDone); return 0; } // This test is not very friendly to bots environment, so you should only // run it manually. TEST(NegativeTests, DISABLED_CreateFileVsFindFirstFileTest) { hDone = ::CreateEvent(NULL, false, false, NULL); ::CreateDirectory("ZZZ", NULL); // Run PrintDirectoryListingJob in a concurrent thread. CHECK(::QueueUserWorkItem(PrintDirectoryListingJob, NULL, WT_EXECUTELONGFUNCTION)); CreateFileJob(); ::WaitForSingleObject(hDone, INFINITE); ::CloseHandle(hDone); CHECK(::DeleteFile("ZZZ\\tmpfile")); CHECK(::RemoveDirectory("ZZZ")); } } //namespace namespace WindowsAtomicsTests { // {{{1 // This test should not give us any reports if compiled with proper MSVS flags. // The Atomic_{Read,Write} functions are ignored in racecheck_unittest.ignore int GLOB = 42; inline int Atomic_Read(volatile const int* ptr) { // MSVS volatile gives us atomicity. int value = *ptr; return value; } inline void Atomic_Write(volatile int* ptr, int value) { // MSVS volatile gives us atomicity. *ptr = value; } void Worker() { int value = Atomic_Read(&GLOB); Atomic_Write(&GLOB, ~value); } TEST(NegativeTests, WindowsAtomicsTests) { MyThreadArray mta(Worker, Worker); mta.Start(); mta.Join(); } } // namespace namespace WindowsSemaphoreTests { void Poster(int *var, HANDLE sem) { *var = 1; ReleaseSemaphore(sem, 1, NULL); } void Waiter(int *var, HANDLE sem) { DWORD ret = ::WaitForSingleObject(sem, INFINITE); ASSERT_EQ(ret, WAIT_OBJECT_0); EXPECT_EQ(*var, 1); } TEST(NegativeTests, SimpleSemaphoreTest) { HANDLE sem = CreateSemaphore(NULL, 0 /* initial count */, 20 /* max count */, NULL); ASSERT_TRUE(sem != NULL); { int VAR = 0; ThreadPool tp(2); tp.StartWorkers(); tp.Add(NewCallback(Waiter, &VAR, sem)); tp.Add(NewCallback(Poster, &VAR, sem)); } CloseHandle(sem); } TEST(NegativeTests, DISABLED_SemaphoreNameReuseTest) { // TODO(timurrrr): Semaphore reuse is not yet understood by TSan. const char NAME[] = "SemaphoreZZZ"; HANDLE h1 = CreateSemaphore(NULL, 0, 10, NAME), h2 = CreateSemaphore(NULL, 0, 15, NAME); ASSERT_TRUE(h1 != NULL); ASSERT_TRUE(h2 != NULL); // h1 and h2 refer to the same semaphore but are not equal. EXPECT_NE(h1, h2); { int VAR = 0; ThreadPool tp(2); tp.StartWorkers(); tp.Add(NewCallback(Waiter, &VAR, h1)); tp.Add(NewCallback(Poster, &VAR, h2)); } CloseHandle(h1); CloseHandle(h2); } } namespace HandleReuseTests { void Waker(int *var, HANDLE h) { *var = 1; SetEvent(h); } void Waiter(int *var, HANDLE h) { DWORD ret = ::WaitForSingleObject(h, INFINITE); ASSERT_EQ(ret, WAIT_OBJECT_0); EXPECT_EQ(*var, 1); } TEST(NegativeTests, DISABLED_EventHandleReuseTest) { // TODO(timurrrr): DuplicateHandle is not yet understood by TSan. HANDLE h1 = CreateEvent(NULL, false, false, NULL); ASSERT_TRUE(h1 != NULL); HANDLE h2 = NULL; DuplicateHandle(GetCurrentProcess(), h1, GetCurrentProcess(), &h2, 0 /* access */, FALSE /* inherit*/, DUPLICATE_SAME_ACCESS); ASSERT_TRUE(h2 != NULL); // h1 and h2 refer to the same Event but are not equal. EXPECT_NE(h1, h2); { int VAR = 0; ThreadPool tp(2); tp.StartWorkers(); tp.Add(NewCallback(Waiter, &VAR, h1)); tp.Add(NewCallback(Waker, &VAR, h2)); } CloseHandle(h1); CloseHandle(h2); } } // End {{{1 // vim:shiftwidth=2:softtabstop=2:expandtab:foldmethod=marker