Index: include/sanitizer/tsan_interface.h =================================================================== --- include/sanitizer/tsan_interface.h +++ include/sanitizer/tsan_interface.h @@ -44,6 +44,11 @@ const unsigned __tsan_mutex_write_reentrant = 1 << 1; // Mutex is read reentrant. const unsigned __tsan_mutex_read_reentrant = 1 << 2; +// Mutex does not have static storage duration, and must not be used after +// its destructor runs. The opposite of __tsan_mutex_linker_init. +// If this flag is passed to __tsan_mutex_destroy, then the destruction +// is ignored unless this flag was previously set on the mutex. +const unsigned __tsan_mutex_not_static = 1 << 8; // Mutex operation flags: @@ -70,6 +75,7 @@ // Annotate destruction of a mutex. // Supported flags: // - __tsan_mutex_linker_init +// - __tsan_mutex_not_static void __tsan_mutex_destroy(void *addr, unsigned flags); // Annotate start of lock operation. Index: lib/tsan/rtl/tsan_rtl_mutex.cc =================================================================== --- lib/tsan/rtl/tsan_rtl_mutex.cc +++ lib/tsan/rtl/tsan_rtl_mutex.cc @@ -84,7 +84,9 @@ SyncVar *s = ctx->metamap.GetIfExistsAndLock(addr, true); if (s == 0) return; - if ((flagz & MutexFlagLinkerInit) || s->IsFlagSet(MutexFlagLinkerInit)) { + if ((flagz & MutexFlagLinkerInit) + || s->IsFlagSet(MutexFlagLinkerInit) + || ((flagz & MutexFlagNotStatic) && !s->IsFlagSet(MutexFlagNotStatic))) { // Destroy is no-op for linker-initialized mutexes. s->mtx.Unlock(); return; Index: lib/tsan/rtl/tsan_sync.h =================================================================== --- lib/tsan/rtl/tsan_sync.h +++ lib/tsan/rtl/tsan_sync.h @@ -34,6 +34,7 @@ MutexFlagTryLockFailed = 1 << 5, // __tsan_mutex_try_lock_failed MutexFlagRecursiveLock = 1 << 6, // __tsan_mutex_recursive_lock MutexFlagRecursiveUnlock = 1 << 7, // __tsan_mutex_recursive_unlock + MutexFlagNotStatic = 1 << 8, // __tsan_mutex_not_static // The following flags are runtime private. // Mutex API misuse was detected, so don't report any more. @@ -43,7 +44,8 @@ // Must list all mutex creation flags. MutexCreationFlagMask = MutexFlagLinkerInit | MutexFlagWriteReentrant | - MutexFlagReadReentrant, + MutexFlagReadReentrant | + MutexFlagNotStatic, }; struct SyncVar { Index: test/tsan/custom_mutex.h =================================================================== --- test/tsan/custom_mutex.h +++ test/tsan/custom_mutex.h @@ -6,15 +6,16 @@ // A very primitive mutex annotated with tsan annotations. class Mutex { public: - Mutex(bool prof, unsigned flags) + Mutex(bool prof, unsigned create_flags, unsigned destroy_flags=0) : prof_(prof) , locked_(false) - , seq_(0) { - __tsan_mutex_create(this, flags); + , seq_(0) + , destroy_flags_(destroy_flags) { + __tsan_mutex_create(this, create_flags); } ~Mutex() { - __tsan_mutex_destroy(this, 0); + __tsan_mutex_destroy(this, destroy_flags_); } void Lock() { @@ -57,6 +58,7 @@ const bool prof_; std::atomic locked_; int seq_; + unsigned destroy_flags_; // This models mutex profiling subsystem. static Mutex prof_mu_; Index: test/tsan/custom_mutex4.cc =================================================================== --- test/tsan/custom_mutex4.cc +++ test/tsan/custom_mutex4.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 --std=c++11 %s -o %t && %run %t 2>&1 | FileCheck %s +#include "custom_mutex.h" + +#include + +// Test that the destruction events of a mutex are ignored when the +// annotations request this. +// +// Use after destruction is UB, but __tsan_mutex_linker_init and +// __tsan_mutex_not_static exist to support global variables of mutex type, +// which might be accessed during program shutdown after the class's destructor +// has run. + +int main() { + std::aligned_storage::type mu1_store; + Mutex* mu1 = reinterpret_cast(&mu1_store); + new (&mu1_store) Mutex(false, __tsan_mutex_linker_init); + mu1->Lock(); + mu1->~Mutex(); + mu1->Unlock(); + + std::aligned_storage::type mu2_store; + Mutex* mu2 = reinterpret_cast(&mu2_store); + new (&mu2_store) Mutex(false, 0, __tsan_mutex_not_static); + mu2->Lock(); + mu2->~Mutex(); + mu2->Unlock(); + + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK: DONE Index: test/tsan/custom_mutex5.cc =================================================================== --- test/tsan/custom_mutex5.cc +++ test/tsan/custom_mutex5.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 --std=c++11 %s -o %t && %deflake %run %t 2>&1 | FileCheck %s +#include "custom_mutex.h" + +#include + +// Test that we detect the destruction of an in-use mutex when the +// thread annotations don't otherwise disable the check. + +int main() { + std::aligned_storage::type mu1_store; + Mutex* mu1 = reinterpret_cast(&mu1_store); + new(&mu1_store) Mutex(false, 0); + mu1->Lock(); + mu1->~Mutex(); + mu1->Unlock(); + + std::aligned_storage::type mu2_store; + Mutex* mu2 = reinterpret_cast(&mu2_store); + new(&mu2_store) + Mutex(false, __tsan_mutex_not_static, __tsan_mutex_not_static); + mu2->Lock(); + mu2->~Mutex(); + mu2->Unlock(); + + fprintf(stderr, "DONE\n"); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex +// CHECK: main {{.*}}custom_mutex5.cc:14 +// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex +// CHECK: main {{.*}}custom_mutex5.cc:22 +// CHECK: DONE