/*
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