This patch splits the handling of racy address and racy stack into separate functions. If a race was already reported for the address, we can avoid the cost for collecting the involved stacks. This patch also removes the race condition in storing the racy address / racy stack. This race condition allowed all threads to report the race. This patch changes the transitive suppression of reports. Previously suppression could transitively chain memory location and racy stacks. Now racy memory and racy stack are separate suppressions. Commit again, now with fixed tests. Reviewed by: dvyukov Differential Revision: https://reviews.llvm.org/D83625
500 lines
13 KiB
C++
500 lines
13 KiB
C++
//===-- tsan_test_util_posix.cpp ------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file is a part of ThreadSanitizer (TSan), a race detector.
|
|
//
|
|
// Test utils, Linux, FreeBSD, NetBSD and Darwin implementation.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "sanitizer_common/sanitizer_atomic.h"
|
|
#include "tsan_interface.h"
|
|
#include "tsan_posix_util.h"
|
|
#include "tsan_test_util.h"
|
|
#include "tsan_report.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <assert.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#define CALLERPC (__builtin_return_address(0))
|
|
|
|
using namespace __tsan;
|
|
|
|
static __thread bool expect_report;
|
|
static __thread bool expect_report_reported;
|
|
static __thread ReportType expect_report_type;
|
|
|
|
static void *BeforeInitThread(void *param) {
|
|
(void)param;
|
|
return 0;
|
|
}
|
|
|
|
static void AtExit() {
|
|
}
|
|
|
|
void TestMutexBeforeInit() {
|
|
// Mutexes must be usable before __tsan_init();
|
|
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
|
|
__interceptor_pthread_mutex_lock(&mtx);
|
|
__interceptor_pthread_mutex_unlock(&mtx);
|
|
__interceptor_pthread_mutex_destroy(&mtx);
|
|
pthread_t thr;
|
|
__interceptor_pthread_create(&thr, 0, BeforeInitThread, 0);
|
|
__interceptor_pthread_join(thr, 0);
|
|
atexit(AtExit);
|
|
}
|
|
|
|
namespace __tsan {
|
|
bool OnReport(const ReportDesc *rep, bool suppressed) {
|
|
if (expect_report) {
|
|
if (rep->typ != expect_report_type) {
|
|
printf("Expected report of type %d, got type %d\n",
|
|
(int)expect_report_type, (int)rep->typ);
|
|
EXPECT_TRUE(false) << "Wrong report type";
|
|
return false;
|
|
}
|
|
} else {
|
|
EXPECT_TRUE(false) << "Unexpected report";
|
|
return false;
|
|
}
|
|
expect_report_reported = true;
|
|
return true;
|
|
}
|
|
} // namespace __tsan
|
|
|
|
static void* allocate_addr(int size, int offset_from_aligned = 0) {
|
|
static uintptr_t foo;
|
|
static atomic_uintptr_t uniq = {(uintptr_t)&foo}; // Some real address.
|
|
const int kAlign = 16;
|
|
CHECK(offset_from_aligned < kAlign);
|
|
size = (size + 2 * kAlign) & ~(kAlign - 1);
|
|
uintptr_t addr = atomic_fetch_add(&uniq, size, memory_order_relaxed);
|
|
return (void*)(addr + offset_from_aligned);
|
|
}
|
|
|
|
MemLoc::MemLoc(int offset_from_aligned)
|
|
: loc_(allocate_addr(16, offset_from_aligned)) {
|
|
}
|
|
|
|
MemLoc::~MemLoc() {
|
|
}
|
|
|
|
Mutex::Mutex(Type type)
|
|
: alive_()
|
|
, type_(type) {
|
|
}
|
|
|
|
Mutex::~Mutex() {
|
|
CHECK(!alive_);
|
|
}
|
|
|
|
void Mutex::Init() {
|
|
CHECK(!alive_);
|
|
alive_ = true;
|
|
if (type_ == Normal)
|
|
CHECK_EQ(__interceptor_pthread_mutex_init((pthread_mutex_t*)mtx_, 0), 0);
|
|
#ifndef __APPLE__
|
|
else if (type_ == Spin)
|
|
CHECK_EQ(pthread_spin_init((pthread_spinlock_t*)mtx_, 0), 0);
|
|
#endif
|
|
else if (type_ == RW)
|
|
CHECK_EQ(__interceptor_pthread_rwlock_init((pthread_rwlock_t*)mtx_, 0), 0);
|
|
else
|
|
CHECK(0);
|
|
}
|
|
|
|
void Mutex::StaticInit() {
|
|
CHECK(!alive_);
|
|
CHECK(type_ == Normal);
|
|
alive_ = true;
|
|
pthread_mutex_t tmp = PTHREAD_MUTEX_INITIALIZER;
|
|
memcpy(mtx_, &tmp, sizeof(tmp));
|
|
}
|
|
|
|
void Mutex::Destroy() {
|
|
CHECK(alive_);
|
|
alive_ = false;
|
|
if (type_ == Normal)
|
|
CHECK_EQ(__interceptor_pthread_mutex_destroy((pthread_mutex_t*)mtx_), 0);
|
|
#ifndef __APPLE__
|
|
else if (type_ == Spin)
|
|
CHECK_EQ(pthread_spin_destroy((pthread_spinlock_t*)mtx_), 0);
|
|
#endif
|
|
else if (type_ == RW)
|
|
CHECK_EQ(__interceptor_pthread_rwlock_destroy((pthread_rwlock_t*)mtx_), 0);
|
|
}
|
|
|
|
void Mutex::Lock() {
|
|
CHECK(alive_);
|
|
if (type_ == Normal)
|
|
CHECK_EQ(__interceptor_pthread_mutex_lock((pthread_mutex_t*)mtx_), 0);
|
|
#ifndef __APPLE__
|
|
else if (type_ == Spin)
|
|
CHECK_EQ(pthread_spin_lock((pthread_spinlock_t*)mtx_), 0);
|
|
#endif
|
|
else if (type_ == RW)
|
|
CHECK_EQ(__interceptor_pthread_rwlock_wrlock((pthread_rwlock_t*)mtx_), 0);
|
|
}
|
|
|
|
bool Mutex::TryLock() {
|
|
CHECK(alive_);
|
|
if (type_ == Normal)
|
|
return __interceptor_pthread_mutex_trylock((pthread_mutex_t*)mtx_) == 0;
|
|
#ifndef __APPLE__
|
|
else if (type_ == Spin)
|
|
return pthread_spin_trylock((pthread_spinlock_t*)mtx_) == 0;
|
|
#endif
|
|
else if (type_ == RW)
|
|
return __interceptor_pthread_rwlock_trywrlock((pthread_rwlock_t*)mtx_) == 0;
|
|
return false;
|
|
}
|
|
|
|
void Mutex::Unlock() {
|
|
CHECK(alive_);
|
|
if (type_ == Normal)
|
|
CHECK_EQ(__interceptor_pthread_mutex_unlock((pthread_mutex_t*)mtx_), 0);
|
|
#ifndef __APPLE__
|
|
else if (type_ == Spin)
|
|
CHECK_EQ(pthread_spin_unlock((pthread_spinlock_t*)mtx_), 0);
|
|
#endif
|
|
else if (type_ == RW)
|
|
CHECK_EQ(__interceptor_pthread_rwlock_unlock((pthread_rwlock_t*)mtx_), 0);
|
|
}
|
|
|
|
void Mutex::ReadLock() {
|
|
CHECK(alive_);
|
|
CHECK(type_ == RW);
|
|
CHECK_EQ(__interceptor_pthread_rwlock_rdlock((pthread_rwlock_t*)mtx_), 0);
|
|
}
|
|
|
|
bool Mutex::TryReadLock() {
|
|
CHECK(alive_);
|
|
CHECK(type_ == RW);
|
|
return __interceptor_pthread_rwlock_tryrdlock((pthread_rwlock_t*)mtx_) == 0;
|
|
}
|
|
|
|
void Mutex::ReadUnlock() {
|
|
CHECK(alive_);
|
|
CHECK(type_ == RW);
|
|
CHECK_EQ(__interceptor_pthread_rwlock_unlock((pthread_rwlock_t*)mtx_), 0);
|
|
}
|
|
|
|
struct Event {
|
|
enum Type {
|
|
SHUTDOWN,
|
|
READ,
|
|
WRITE,
|
|
VPTR_UPDATE,
|
|
CALL,
|
|
RETURN,
|
|
MUTEX_CREATE,
|
|
MUTEX_DESTROY,
|
|
MUTEX_LOCK,
|
|
MUTEX_TRYLOCK,
|
|
MUTEX_UNLOCK,
|
|
MUTEX_READLOCK,
|
|
MUTEX_TRYREADLOCK,
|
|
MUTEX_READUNLOCK,
|
|
MEMCPY,
|
|
MEMSET
|
|
};
|
|
Type type;
|
|
void *ptr;
|
|
uptr arg;
|
|
uptr arg2;
|
|
bool res;
|
|
bool expect_report;
|
|
ReportType report_type;
|
|
|
|
explicit Event(Type type, const void *ptr = 0, uptr arg = 0, uptr arg2 = 0)
|
|
: type(type),
|
|
ptr(const_cast<void *>(ptr)),
|
|
arg(arg),
|
|
arg2(arg2),
|
|
res(),
|
|
expect_report(),
|
|
report_type() {}
|
|
|
|
void ExpectReport(ReportType type) {
|
|
expect_report = true;
|
|
report_type = type;
|
|
}
|
|
};
|
|
|
|
struct ScopedThread::Impl {
|
|
pthread_t thread;
|
|
bool main;
|
|
bool detached;
|
|
atomic_uintptr_t event; // Event*
|
|
|
|
static void *ScopedThreadCallback(void *arg);
|
|
void send(Event *ev);
|
|
void HandleEvent(Event *ev);
|
|
};
|
|
|
|
void ScopedThread::Impl::HandleEvent(Event *ev) {
|
|
CHECK_EQ(expect_report, false);
|
|
expect_report = ev->expect_report;
|
|
expect_report_reported = false;
|
|
expect_report_type = ev->report_type;
|
|
switch (ev->type) {
|
|
case Event::READ:
|
|
case Event::WRITE: {
|
|
void (*tsan_mop)(void *addr, void *pc) = 0;
|
|
if (ev->type == Event::READ) {
|
|
switch (ev->arg /*size*/) {
|
|
case 1:
|
|
tsan_mop = __tsan_read1_pc;
|
|
break;
|
|
case 2:
|
|
tsan_mop = __tsan_read2_pc;
|
|
break;
|
|
case 4:
|
|
tsan_mop = __tsan_read4_pc;
|
|
break;
|
|
case 8:
|
|
tsan_mop = __tsan_read8_pc;
|
|
break;
|
|
case 16:
|
|
tsan_mop = __tsan_read16_pc;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (ev->arg /*size*/) {
|
|
case 1:
|
|
tsan_mop = __tsan_write1_pc;
|
|
break;
|
|
case 2:
|
|
tsan_mop = __tsan_write2_pc;
|
|
break;
|
|
case 4:
|
|
tsan_mop = __tsan_write4_pc;
|
|
break;
|
|
case 8:
|
|
tsan_mop = __tsan_write8_pc;
|
|
break;
|
|
case 16:
|
|
tsan_mop = __tsan_write16_pc;
|
|
break;
|
|
}
|
|
}
|
|
CHECK_NE(tsan_mop, 0);
|
|
#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__NetBSD__)
|
|
const int ErrCode = ESOCKTNOSUPPORT;
|
|
#else
|
|
const int ErrCode = ECHRNG;
|
|
#endif
|
|
errno = ErrCode;
|
|
tsan_mop(ev->ptr, (void *)ev->arg2);
|
|
CHECK_EQ(ErrCode, errno); // In no case must errno be changed.
|
|
break;
|
|
}
|
|
case Event::VPTR_UPDATE:
|
|
__tsan_vptr_update((void**)ev->ptr, (void*)ev->arg);
|
|
break;
|
|
case Event::CALL:
|
|
__tsan_func_entry((void*)((uptr)ev->ptr));
|
|
break;
|
|
case Event::RETURN:
|
|
__tsan_func_exit();
|
|
break;
|
|
case Event::MUTEX_CREATE:
|
|
static_cast<Mutex*>(ev->ptr)->Init();
|
|
break;
|
|
case Event::MUTEX_DESTROY:
|
|
static_cast<Mutex*>(ev->ptr)->Destroy();
|
|
break;
|
|
case Event::MUTEX_LOCK:
|
|
static_cast<Mutex*>(ev->ptr)->Lock();
|
|
break;
|
|
case Event::MUTEX_TRYLOCK:
|
|
ev->res = static_cast<Mutex*>(ev->ptr)->TryLock();
|
|
break;
|
|
case Event::MUTEX_UNLOCK:
|
|
static_cast<Mutex*>(ev->ptr)->Unlock();
|
|
break;
|
|
case Event::MUTEX_READLOCK:
|
|
static_cast<Mutex*>(ev->ptr)->ReadLock();
|
|
break;
|
|
case Event::MUTEX_TRYREADLOCK:
|
|
ev->res = static_cast<Mutex*>(ev->ptr)->TryReadLock();
|
|
break;
|
|
case Event::MUTEX_READUNLOCK:
|
|
static_cast<Mutex*>(ev->ptr)->ReadUnlock();
|
|
break;
|
|
case Event::MEMCPY:
|
|
__interceptor_memcpy(ev->ptr, (void*)ev->arg, ev->arg2);
|
|
break;
|
|
case Event::MEMSET:
|
|
__interceptor_memset(ev->ptr, ev->arg, ev->arg2);
|
|
break;
|
|
default: CHECK(0);
|
|
}
|
|
if (expect_report && !expect_report_reported) {
|
|
printf("Missed expected report of type %d\n", (int)ev->report_type);
|
|
EXPECT_TRUE(false) << "Missed expected race";
|
|
}
|
|
expect_report = false;
|
|
}
|
|
|
|
void *ScopedThread::Impl::ScopedThreadCallback(void *arg) {
|
|
__tsan_func_entry(CALLERPC);
|
|
Impl *impl = (Impl*)arg;
|
|
for (;;) {
|
|
Event* ev = (Event*)atomic_load(&impl->event, memory_order_acquire);
|
|
if (ev == 0) {
|
|
sched_yield();
|
|
continue;
|
|
}
|
|
if (ev->type == Event::SHUTDOWN) {
|
|
atomic_store(&impl->event, 0, memory_order_release);
|
|
break;
|
|
}
|
|
impl->HandleEvent(ev);
|
|
atomic_store(&impl->event, 0, memory_order_release);
|
|
}
|
|
__tsan_func_exit();
|
|
return 0;
|
|
}
|
|
|
|
void ScopedThread::Impl::send(Event *e) {
|
|
if (main) {
|
|
HandleEvent(e);
|
|
} else {
|
|
CHECK_EQ(atomic_load(&event, memory_order_relaxed), 0);
|
|
atomic_store(&event, (uintptr_t)e, memory_order_release);
|
|
while (atomic_load(&event, memory_order_acquire) != 0)
|
|
sched_yield();
|
|
}
|
|
}
|
|
|
|
ScopedThread::ScopedThread(bool detached, bool main) {
|
|
impl_ = new Impl;
|
|
impl_->main = main;
|
|
impl_->detached = detached;
|
|
atomic_store(&impl_->event, 0, memory_order_relaxed);
|
|
if (!main) {
|
|
pthread_attr_t attr;
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setdetachstate(
|
|
&attr, detached ? PTHREAD_CREATE_DETACHED : PTHREAD_CREATE_JOINABLE);
|
|
pthread_attr_setstacksize(&attr, 64*1024);
|
|
__interceptor_pthread_create(&impl_->thread, &attr,
|
|
ScopedThread::Impl::ScopedThreadCallback, impl_);
|
|
}
|
|
}
|
|
|
|
ScopedThread::~ScopedThread() {
|
|
if (!impl_->main) {
|
|
Event event(Event::SHUTDOWN);
|
|
impl_->send(&event);
|
|
if (!impl_->detached)
|
|
__interceptor_pthread_join(impl_->thread, 0);
|
|
}
|
|
delete impl_;
|
|
}
|
|
|
|
void ScopedThread::Detach() {
|
|
CHECK(!impl_->main);
|
|
CHECK(!impl_->detached);
|
|
impl_->detached = true;
|
|
__interceptor_pthread_detach(impl_->thread);
|
|
}
|
|
|
|
void ScopedThread::Access(void *addr, bool is_write,
|
|
int size, bool expect_race) {
|
|
Event event(is_write ? Event::WRITE : Event::READ, addr, size,
|
|
(uptr)CALLERPC);
|
|
if (expect_race)
|
|
event.ExpectReport(ReportTypeRace);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::VptrUpdate(const MemLoc &vptr,
|
|
const MemLoc &new_val,
|
|
bool expect_race) {
|
|
Event event(Event::VPTR_UPDATE, vptr.loc(), (uptr)new_val.loc());
|
|
if (expect_race)
|
|
event.ExpectReport(ReportTypeRace);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::Call(void(*pc)()) {
|
|
Event event(Event::CALL, (void*)((uintptr_t)pc));
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::Return() {
|
|
Event event(Event::RETURN);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::Create(const Mutex &m) {
|
|
Event event(Event::MUTEX_CREATE, &m);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::Destroy(const Mutex &m) {
|
|
Event event(Event::MUTEX_DESTROY, &m);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::Lock(const Mutex &m) {
|
|
Event event(Event::MUTEX_LOCK, &m);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
bool ScopedThread::TryLock(const Mutex &m) {
|
|
Event event(Event::MUTEX_TRYLOCK, &m);
|
|
impl_->send(&event);
|
|
return event.res;
|
|
}
|
|
|
|
void ScopedThread::Unlock(const Mutex &m) {
|
|
Event event(Event::MUTEX_UNLOCK, &m);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::ReadLock(const Mutex &m) {
|
|
Event event(Event::MUTEX_READLOCK, &m);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
bool ScopedThread::TryReadLock(const Mutex &m) {
|
|
Event event(Event::MUTEX_TRYREADLOCK, &m);
|
|
impl_->send(&event);
|
|
return event.res;
|
|
}
|
|
|
|
void ScopedThread::ReadUnlock(const Mutex &m) {
|
|
Event event(Event::MUTEX_READUNLOCK, &m);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::Memcpy(void *dst, const void *src, int size,
|
|
bool expect_race) {
|
|
Event event(Event::MEMCPY, dst, (uptr)src, size);
|
|
if (expect_race)
|
|
event.ExpectReport(ReportTypeRace);
|
|
impl_->send(&event);
|
|
}
|
|
|
|
void ScopedThread::Memset(void *dst, int val, int size,
|
|
bool expect_race) {
|
|
Event event(Event::MEMSET, dst, val, size);
|
|
if (expect_race)
|
|
event.ExpectReport(ReportTypeRace);
|
|
impl_->send(&event);
|
|
}
|