Index: compiler-rt/trunk/lib/tsan/CMakeLists.txt =================================================================== --- compiler-rt/trunk/lib/tsan/CMakeLists.txt +++ compiler-rt/trunk/lib/tsan/CMakeLists.txt @@ -25,6 +25,7 @@ set(TSAN_SOURCES rtl/tsan_clock.cc rtl/tsan_debugging.cc + rtl/tsan_external.cc rtl/tsan_fd.cc rtl/tsan_flags.cc rtl/tsan_ignoreset.cc Index: compiler-rt/trunk/lib/tsan/rtl/tsan_debugging.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_debugging.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_debugging.cc @@ -24,6 +24,7 @@ if (typ == ReportTypeVptrRace) return "data-race-vptr"; if (typ == ReportTypeUseAfterFree) return "heap-use-after-free"; if (typ == ReportTypeVptrUseAfterFree) return "heap-use-after-free-vptr"; + if (typ == ReportTypeExternalRace) return "external-race"; if (typ == ReportTypeThreadLeak) return "thread-leak"; if (typ == ReportTypeMutexDestroyLocked) return "locked-mutex-destroy"; if (typ == ReportTypeMutexDoubleLock) return "mutex-double-lock"; Index: compiler-rt/trunk/lib/tsan/rtl/tsan_defs.h =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_defs.h +++ compiler-rt/trunk/lib/tsan/rtl/tsan_defs.h @@ -149,7 +149,8 @@ // Descriptor of user's memory block. struct MBlock { - u64 siz; + u64 siz : 48; + u64 tag : 16; u32 stk; u16 tid; }; Index: compiler-rt/trunk/lib/tsan/rtl/tsan_external.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_external.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_external.cc @@ -0,0 +1,78 @@ +//===-- tsan_external.cc --------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_rtl.h" + +namespace __tsan { + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +const uptr kMaxTag = 128; // Limited to 65,536, since MBlock only stores tags + // as 16-bit values, see tsan_defs.h. + +const char *registered_tags[kMaxTag]; +static atomic_uint32_t used_tags{1}; // Tag 0 means "no tag". NOLINT + +const char *GetObjectTypeFromTag(uptr tag) { + if (tag == 0) return nullptr; + // Invalid/corrupted tag? Better return NULL and let the caller deal with it. + if (tag >= atomic_load(&used_tags, memory_order_relaxed)) return nullptr; + return registered_tags[tag]; +} + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +void *__tsan_external_register_tag(const char *object_type) { + uptr new_tag = atomic_fetch_add(&used_tags, 1, memory_order_relaxed); + CHECK_LT(new_tag, kMaxTag); + registered_tags[new_tag] = internal_strdup(object_type); + return (void *)new_tag; +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_assign_tag(void *addr, void *tag) { + CHECK_LT(tag, atomic_load(&used_tags, memory_order_relaxed)); + Allocator *a = allocator(); + MBlock *b = nullptr; + if (a->PointerIsMine((void *)addr)) { + void *block_begin = a->GetBlockBegin((void *)addr); + if (block_begin) b = ctx->metamap.GetBlock((uptr)block_begin); + } + if (b) { + b->tag = (uptr)tag; + } +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_read(void *addr, void *caller_pc, void *tag) { + CHECK_LT(tag, atomic_load(&used_tags, memory_order_relaxed)); + ThreadState *thr = cur_thread(); + thr->external_tag = (uptr)tag; + FuncEntry(thr, (uptr)caller_pc); + MemoryRead(thr, CALLERPC, (uptr)addr, kSizeLog8); + FuncExit(thr); + thr->external_tag = 0; +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_write(void *addr, void *caller_pc, void *tag) { + CHECK_LT(tag, atomic_load(&used_tags, memory_order_relaxed)); + ThreadState *thr = cur_thread(); + thr->external_tag = (uptr)tag; + FuncEntry(thr, (uptr)caller_pc); + MemoryWrite(thr, CALLERPC, (uptr)addr, kSizeLog8); + FuncExit(thr); + thr->external_tag = 0; +} +} // extern "C" + +} // namespace __tsan Index: compiler-rt/trunk/lib/tsan/rtl/tsan_interface.h =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_interface.h +++ compiler-rt/trunk/lib/tsan/rtl/tsan_interface.h @@ -79,6 +79,15 @@ SANITIZER_INTERFACE_ATTRIBUTE void __tsan_ignore_thread_end(); SANITIZER_INTERFACE_ATTRIBUTE +void *__tsan_external_register_tag(const char *object_type); +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_assign_tag(void *addr, void *tag); +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_read(void *addr, void *caller_pc, void *tag); +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_write(void *addr, void *caller_pc, void *tag); + +SANITIZER_INTERFACE_ATTRIBUTE void __tsan_read_range(void *addr, unsigned long size); // NOLINT SANITIZER_INTERFACE_ATTRIBUTE void __tsan_write_range(void *addr, unsigned long size); // NOLINT Index: compiler-rt/trunk/lib/tsan/rtl/tsan_report.h =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_report.h +++ compiler-rt/trunk/lib/tsan/rtl/tsan_report.h @@ -24,6 +24,7 @@ ReportTypeVptrRace, ReportTypeUseAfterFree, ReportTypeVptrUseAfterFree, + ReportTypeExternalRace, ReportTypeThreadLeak, ReportTypeMutexDestroyLocked, ReportTypeMutexDoubleLock, @@ -56,6 +57,7 @@ int size; bool write; bool atomic; + uptr external_tag; Vector mset; ReportStack *stack; @@ -75,6 +77,7 @@ DataInfo global; uptr heap_chunk_start; uptr heap_chunk_size; + uptr external_tag; int tid; int fd; bool suppressable; Index: compiler-rt/trunk/lib/tsan/rtl/tsan_report.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_report.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_report.cc @@ -90,6 +90,8 @@ return "heap-use-after-free"; if (typ == ReportTypeVptrUseAfterFree) return "heap-use-after-free (virtual call vs free)"; + if (typ == ReportTypeExternalRace) + return "race on a library object"; if (typ == ReportTypeThreadLeak) return "thread leak"; if (typ == ReportTypeMutexDestroyLocked) @@ -152,14 +154,25 @@ : (write ? "Previous write" : "Previous read")); } +static const char *ExternalMopDesc(bool first, bool write) { + return first ? (write ? "Mutating" : "Read-only") + : (write ? "Previous mutating" : "Previous read-only"); +} + static void PrintMop(const ReportMop *mop, bool first) { Decorator d; char thrbuf[kThreadBufSize]; Printf("%s", d.Access()); - Printf(" %s of size %d at %p by %s", - MopDesc(first, mop->write, mop->atomic), - mop->size, (void*)mop->addr, - thread_name(thrbuf, mop->tid)); + const char *object_type = GetObjectTypeFromTag(mop->external_tag); + if (!object_type) { + Printf(" %s of size %d at %p by %s", + MopDesc(first, mop->write, mop->atomic), mop->size, + (void *)mop->addr, thread_name(thrbuf, mop->tid)); + } else { + Printf(" %s access of object %s at %p by %s", + ExternalMopDesc(first, mop->write), object_type, + (void *)mop->addr, thread_name(thrbuf, mop->tid)); + } PrintMutexSet(mop->mset); Printf(":\n"); Printf("%s", d.EndAccess()); @@ -183,9 +196,16 @@ global.module_offset); } else if (loc->type == ReportLocationHeap) { char thrbuf[kThreadBufSize]; - Printf(" Location is heap block of size %zu at %p allocated by %s:\n", - loc->heap_chunk_size, loc->heap_chunk_start, - thread_name(thrbuf, loc->tid)); + const char *object_type = GetObjectTypeFromTag(loc->external_tag); + if (!object_type) { + Printf(" Location is heap block of size %zu at %p allocated by %s:\n", + loc->heap_chunk_size, loc->heap_chunk_start, + thread_name(thrbuf, loc->tid)); + } else { + Printf(" Location is %s object of size %zu at %p allocated by %s:\n", + object_type, loc->heap_chunk_size, loc->heap_chunk_start, + thread_name(thrbuf, loc->tid)); + } print_stack = true; } else if (loc->type == ReportLocationStack) { Printf(" Location is stack of %s.\n\n", thread_name(thrbuf, loc->tid)); Index: compiler-rt/trunk/lib/tsan/rtl/tsan_rtl.h =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_rtl.h +++ compiler-rt/trunk/lib/tsan/rtl/tsan_rtl.h @@ -410,6 +410,7 @@ bool is_dead; bool is_freeing; bool is_vptr_access; + uptr external_tag; const uptr stk_addr; const uptr stk_size; const uptr tls_addr; @@ -564,7 +565,7 @@ explicit ScopedReport(ReportType typ); ~ScopedReport(); - void AddMemoryAccess(uptr addr, Shadow s, StackTrace stack, + void AddMemoryAccess(uptr addr, uptr external_tag, Shadow s, StackTrace stack, const MutexSet *mset); void AddStack(StackTrace stack, bool suppressable = false); void AddThread(const ThreadContext *tctx, bool suppressable = false); @@ -640,6 +641,8 @@ bool IsExpectedReport(uptr addr, uptr size); void PrintMatchedBenignRaces(); +const char *GetObjectTypeFromTag(uptr tag); + #if defined(TSAN_DEBUG_OUTPUT) && TSAN_DEBUG_OUTPUT >= 1 # define DPrintf Printf #else Index: compiler-rt/trunk/lib/tsan/rtl/tsan_rtl_report.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_rtl_report.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_rtl_report.cc @@ -164,8 +164,8 @@ (*rs)->suppressable = suppressable; } -void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, StackTrace stack, - const MutexSet *mset) { +void ScopedReport::AddMemoryAccess(uptr addr, uptr external_tag, Shadow s, + StackTrace stack, const MutexSet *mset) { void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop)); ReportMop *mop = new(mem) ReportMop; rep_->mops.PushBack(mop); @@ -175,6 +175,7 @@ mop->write = s.IsWrite(); mop->atomic = s.IsAtomic(); mop->stack = SymbolizeStack(stack); + mop->external_tag = external_tag; if (mop->stack) mop->stack->suppressable = true; for (uptr i = 0; i < mset->Size(); i++) { @@ -337,6 +338,7 @@ ReportLocation *loc = ReportLocation::New(ReportLocationHeap); loc->heap_chunk_start = (uptr)allocator()->GetBlockBegin((void *)addr); loc->heap_chunk_size = b->siz; + loc->external_tag = b->tag; loc->tid = tctx ? tctx->tid : b->tid; loc->stack = SymbolizeStackId(b->stk); rep_->locs.PushBack(loc); @@ -623,6 +625,8 @@ typ = ReportTypeVptrRace; else if (freed) typ = ReportTypeUseAfterFree; + else if (thr->external_tag > 0) + typ = ReportTypeExternalRace; if (IsFiredSuppression(ctx, typ, addr)) return; @@ -651,7 +655,8 @@ ScopedReport rep(typ); for (uptr i = 0; i < kMop; i++) { Shadow s(thr->racy_state[i]); - rep.AddMemoryAccess(addr, s, traces[i], i == 0 ? &thr->mset : mset2); + rep.AddMemoryAccess(addr, thr->external_tag, s, traces[i], + i == 0 ? &thr->mset : mset2); } for (uptr i = 0; i < kMop; i++) { Index: compiler-rt/trunk/lib/tsan/rtl/tsan_suppressions.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_suppressions.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_suppressions.cc @@ -74,6 +74,8 @@ return kSuppressionRace; else if (typ == ReportTypeVptrUseAfterFree) return kSuppressionRace; + else if (typ == ReportTypeExternalRace) + return kSuppressionRace; else if (typ == ReportTypeThreadLeak) return kSuppressionThread; else if (typ == ReportTypeMutexDestroyLocked) Index: compiler-rt/trunk/lib/tsan/rtl/tsan_sync.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_sync.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_sync.cc @@ -64,6 +64,7 @@ u32 idx = block_alloc_.Alloc(&thr->proc()->block_cache); MBlock *b = block_alloc_.Map(idx); b->siz = sz; + b->tag = 0; b->tid = thr->tid; b->stk = CurrentStackId(thr, pc); u32 *meta = MemToMeta(p); Index: compiler-rt/trunk/test/tsan/Darwin/external.cc =================================================================== --- compiler-rt/trunk/test/tsan/Darwin/external.cc +++ compiler-rt/trunk/test/tsan/Darwin/external.cc @@ -0,0 +1,153 @@ +// RUN: %clangxx_tsan %s -o %t-lib-instrumented.dylib -shared -DSHARED_LIB +// RUN: %clangxx_tsan %s -o %t-lib-noninstrumented.dylib -shared -DSHARED_LIB -fno-sanitize=thread +// RUN: %clangxx_tsan %s -o %t-lib-noninstrumented-callbacks.dylib -shared -DSHARED_LIB -fno-sanitize=thread -DUSE_TSAN_CALLBACKS +// RUN: %clangxx_tsan %s %t-lib-instrumented.dylib -o %t-lib-instrumented +// RUN: %clangxx_tsan %s %t-lib-noninstrumented.dylib -o %t-lib-noninstrumented +// RUN: %clangxx_tsan %s %t-lib-noninstrumented-callbacks.dylib -o %t-lib-noninstrumented-callbacks + +// RUN: %deflake %run %t-lib-instrumented 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=TEST1 +// RUN: %run %t-lib-noninstrumented 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=TEST2 +// RUN: %deflake %run %t-lib-noninstrumented-callbacks 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=TEST3 + +#include + +#include +#include +#include + +struct MyObject; +typedef MyObject *MyObjectRef; +extern "C" { + void InitializeLibrary(); + MyObject *ObjectCreate(); + long ObjectRead(MyObject *); + void ObjectWrite(MyObject *, long); + void ObjectWriteAnother(MyObject *, long); +} + +#if defined(SHARED_LIB) + +struct MyObject { + long _val; + long _another; +}; + +#if defined(USE_TSAN_CALLBACKS) +static void *tag; +void *(*callback_register_tag)(const char *object_type); +void *(*callback_assign_tag)(void *addr, void *tag); +void (*callback_read)(void *addr, void *caller_pc, void *tag); +void (*callback_write)(void *addr, void *caller_pc, void *tag); +#endif + +void InitializeLibrary() { +#if defined(USE_TSAN_CALLBACKS) + callback_register_tag = (decltype(callback_register_tag))dlsym(RTLD_DEFAULT, "__tsan_external_register_tag"); + callback_assign_tag = (decltype(callback_assign_tag))dlsym(RTLD_DEFAULT, "__tsan_external_assign_tag"); + callback_read = (decltype(callback_read))dlsym(RTLD_DEFAULT, "__tsan_external_read"); + callback_write = (decltype(callback_write))dlsym(RTLD_DEFAULT, "__tsan_external_write"); + tag = callback_register_tag("MyLibrary::MyObject"); +#endif +} + +MyObject *ObjectCreate() { + MyObject *ref = (MyObject *)malloc(sizeof(MyObject)); +#if defined(USE_TSAN_CALLBACKS) + callback_assign_tag(ref, tag); +#endif + return ref; +} + +long ObjectRead(MyObject *ref) { +#if defined(USE_TSAN_CALLBACKS) + callback_read(ref, __builtin_return_address(0), tag); +#endif + return ref->_val; +} + +void ObjectWrite(MyObject *ref, long val) { +#if defined(USE_TSAN_CALLBACKS) + callback_write(ref, __builtin_return_address(0), tag); +#endif + ref->_val = val; +} + +void ObjectWriteAnother(MyObject *ref, long val) { +#if defined(USE_TSAN_CALLBACKS) + callback_write(ref, __builtin_return_address(0), tag); +#endif + ref->_another = val; +} + +#else // defined(SHARED_LIB) + +int main(int argc, char *argv[]) { + InitializeLibrary(); + + { + MyObjectRef ref = ObjectCreate(); + std::thread t1([ref]{ ObjectRead(ref); }); + std::thread t2([ref]{ ObjectRead(ref); }); + t1.join(); + t2.join(); + } + + // CHECK-NOT: WARNING: ThreadSanitizer + + fprintf(stderr, "RR test done\n"); + // CHECK: RR test done + + { + MyObjectRef ref = ObjectCreate(); + std::thread t1([ref]{ ObjectRead(ref); }); + std::thread t2([ref]{ ObjectWrite(ref, 66); }); + t1.join(); + t2.join(); + } + + // TEST1: WARNING: ThreadSanitizer: data race + // TEST1: {{Write|Read}} of size 8 at + // TEST1: Previous {{write|read}} of size 8 at + // TEST1: Location is heap block of size 16 at + + // TEST2-NOT: WARNING: ThreadSanitizer + + // TEST3: WARNING: ThreadSanitizer: race on a library object + // TEST3: {{Mutating|read-only}} access of object MyLibrary::MyObject at + // TEST3: {{ObjectWrite|ObjectRead}} + // TEST3: Previous {{mutating|read-only}} access of object MyLibrary::MyObject at + // TEST3: {{ObjectWrite|ObjectRead}} + // TEST3: Location is MyLibrary::MyObject object of size 16 at + // TEST3: {{ObjectCreate}} + + fprintf(stderr, "RW test done\n"); + // CHECK: RW test done + + { + MyObjectRef ref = ObjectCreate(); + std::thread t1([ref]{ ObjectWrite(ref, 76); }); + std::thread t2([ref]{ ObjectWriteAnother(ref, 77); }); + t1.join(); + t2.join(); + } + + // TEST1-NOT: WARNING: ThreadSanitizer: data race + + // TEST2-NOT: WARNING: ThreadSanitizer + + // TEST3: WARNING: ThreadSanitizer: race on a library object + // TEST3: Mutating access of object MyLibrary::MyObject at + // TEST3: {{ObjectWrite|ObjectWriteAnother}} + // TEST3: Previous mutating access of object MyLibrary::MyObject at + // TEST3: {{ObjectWrite|ObjectWriteAnother}} + // TEST3: Location is MyLibrary::MyObject object of size 16 at + // TEST3: {{ObjectCreate}} + + fprintf(stderr, "WW test done\n"); + // CHECK: WW test done +} + +#endif // defined(SHARED_LIB)