123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- //===-- tsan_rtl_mutex.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.
- //
- //===----------------------------------------------------------------------===//
- #include <sanitizer_common/sanitizer_deadlock_detector_interface.h>
- #include <sanitizer_common/sanitizer_stackdepot.h>
- #include "tsan_rtl.h"
- #include "tsan_flags.h"
- #include "tsan_sync.h"
- #include "tsan_report.h"
- #include "tsan_symbolize.h"
- #include "tsan_platform.h"
- namespace __tsan {
- void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r);
- void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
- FastState last_lock, StackID creation_stack_id);
- struct Callback final : public DDCallback {
- ThreadState *thr;
- uptr pc;
- Callback(ThreadState *thr, uptr pc)
- : thr(thr)
- , pc(pc) {
- DDCallback::pt = thr->proc()->dd_pt;
- DDCallback::lt = thr->dd_lt;
- }
- StackID Unwind() override { return CurrentStackId(thr, pc); }
- int UniqueTid() override { return thr->tid; }
- };
- void DDMutexInit(ThreadState *thr, uptr pc, SyncVar *s) {
- Callback cb(thr, pc);
- ctx->dd->MutexInit(&cb, &s->dd);
- s->dd.ctx = s->addr;
- }
- static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ,
- uptr addr, StackID creation_stack_id) {
- // In Go, these misuses are either impossible, or detected by std lib,
- // or false positives (e.g. unlock in a different thread).
- if (SANITIZER_GO)
- return;
- if (!ShouldReport(thr, typ))
- return;
- ThreadRegistryLock l(&ctx->thread_registry);
- ScopedReport rep(typ);
- rep.AddMutex(addr, creation_stack_id);
- VarSizeStackTrace trace;
- ObtainCurrentStack(thr, pc, &trace);
- rep.AddStack(trace, true);
- rep.AddLocation(addr, 1);
- OutputReport(thr, rep);
- }
- static void RecordMutexLock(ThreadState *thr, uptr pc, uptr addr,
- StackID stack_id, bool write) {
- auto typ = write ? EventType::kLock : EventType::kRLock;
- // Note: it's important to trace before modifying mutex set
- // because tracing can switch trace part and we write the current
- // mutex set in the beginning of each part.
- // If we do it in the opposite order, we will write already reduced
- // mutex set in the beginning of the part and then trace unlock again.
- TraceMutexLock(thr, typ, pc, addr, stack_id);
- thr->mset.AddAddr(addr, stack_id, write);
- }
- static void RecordMutexUnlock(ThreadState *thr, uptr addr) {
- // See the comment in RecordMutexLock re order of operations.
- TraceMutexUnlock(thr, addr);
- thr->mset.DelAddr(addr);
- }
- void MutexCreate(ThreadState *thr, uptr pc, uptr addr, u32 flagz) {
- DPrintf("#%d: MutexCreate %zx flagz=0x%x\n", thr->tid, addr, flagz);
- if (!(flagz & MutexFlagLinkerInit) && pc && IsAppMem(addr))
- MemoryAccess(thr, pc, addr, 1, kAccessWrite);
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- s->SetFlags(flagz & MutexCreationFlagMask);
- // Save stack in the case the sync object was created before as atomic.
- if (!SANITIZER_GO && s->creation_stack_id == kInvalidStackID)
- s->creation_stack_id = CurrentStackId(thr, pc);
- }
- void MutexDestroy(ThreadState *thr, uptr pc, uptr addr, u32 flagz) {
- DPrintf("#%d: MutexDestroy %zx\n", thr->tid, addr);
- bool unlock_locked = false;
- StackID creation_stack_id;
- FastState last_lock;
- {
- auto s = ctx->metamap.GetSyncIfExists(addr);
- if (!s)
- return;
- SlotLocker locker(thr);
- {
- Lock lock(&s->mtx);
- creation_stack_id = s->creation_stack_id;
- last_lock = s->last_lock;
- if ((flagz & MutexFlagLinkerInit) || s->IsFlagSet(MutexFlagLinkerInit) ||
- ((flagz & MutexFlagNotStatic) && !s->IsFlagSet(MutexFlagNotStatic))) {
- // Destroy is no-op for linker-initialized mutexes.
- return;
- }
- if (common_flags()->detect_deadlocks) {
- Callback cb(thr, pc);
- ctx->dd->MutexDestroy(&cb, &s->dd);
- ctx->dd->MutexInit(&cb, &s->dd);
- }
- if (flags()->report_destroy_locked && s->owner_tid != kInvalidTid &&
- !s->IsFlagSet(MutexFlagBroken)) {
- s->SetFlags(MutexFlagBroken);
- unlock_locked = true;
- }
- s->Reset();
- }
- // Imitate a memory write to catch unlock-destroy races.
- if (pc && IsAppMem(addr))
- MemoryAccess(thr, pc, addr, 1, kAccessWrite | kAccessFree);
- }
- if (unlock_locked && ShouldReport(thr, ReportTypeMutexDestroyLocked))
- ReportDestroyLocked(thr, pc, addr, last_lock, creation_stack_id);
- thr->mset.DelAddr(addr, true);
- // s will be destroyed and freed in MetaMap::FreeBlock.
- }
- void MutexPreLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) {
- DPrintf("#%d: MutexPreLock %zx flagz=0x%x\n", thr->tid, addr, flagz);
- if (flagz & MutexFlagTryLock)
- return;
- if (!common_flags()->detect_deadlocks)
- return;
- Callback cb(thr, pc);
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- ReadLock lock(&s->mtx);
- s->UpdateFlags(flagz);
- if (s->owner_tid != thr->tid)
- ctx->dd->MutexBeforeLock(&cb, &s->dd, true);
- }
- ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb));
- }
- void MutexPostLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz, int rec) {
- DPrintf("#%d: MutexPostLock %zx flag=0x%x rec=%d\n",
- thr->tid, addr, flagz, rec);
- if (flagz & MutexFlagRecursiveLock)
- CHECK_GT(rec, 0);
- else
- rec = 1;
- if (pc && IsAppMem(addr))
- MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic);
- bool report_double_lock = false;
- bool pre_lock = false;
- bool first = false;
- StackID creation_stack_id = kInvalidStackID;
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- creation_stack_id = s->creation_stack_id;
- RecordMutexLock(thr, pc, addr, creation_stack_id, true);
- {
- Lock lock(&s->mtx);
- first = s->recursion == 0;
- s->UpdateFlags(flagz);
- if (s->owner_tid == kInvalidTid) {
- CHECK_EQ(s->recursion, 0);
- s->owner_tid = thr->tid;
- s->last_lock = thr->fast_state;
- } else if (s->owner_tid == thr->tid) {
- CHECK_GT(s->recursion, 0);
- } else if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) {
- s->SetFlags(MutexFlagBroken);
- report_double_lock = true;
- }
- s->recursion += rec;
- if (first) {
- if (!thr->ignore_sync) {
- thr->clock.Acquire(s->clock);
- thr->clock.Acquire(s->read_clock);
- }
- }
- if (first && common_flags()->detect_deadlocks) {
- pre_lock = (flagz & MutexFlagDoPreLockOnPostLock) &&
- !(flagz & MutexFlagTryLock);
- Callback cb(thr, pc);
- if (pre_lock)
- ctx->dd->MutexBeforeLock(&cb, &s->dd, true);
- ctx->dd->MutexAfterLock(&cb, &s->dd, true, flagz & MutexFlagTryLock);
- }
- }
- }
- if (report_double_lock)
- ReportMutexMisuse(thr, pc, ReportTypeMutexDoubleLock, addr,
- creation_stack_id);
- if (first && pre_lock && common_flags()->detect_deadlocks) {
- Callback cb(thr, pc);
- ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb));
- }
- }
- int MutexUnlock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) {
- DPrintf("#%d: MutexUnlock %zx flagz=0x%x\n", thr->tid, addr, flagz);
- if (pc && IsAppMem(addr))
- MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic);
- StackID creation_stack_id;
- RecordMutexUnlock(thr, addr);
- bool report_bad_unlock = false;
- int rec = 0;
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- bool released = false;
- {
- Lock lock(&s->mtx);
- creation_stack_id = s->creation_stack_id;
- if (!SANITIZER_GO && (s->recursion == 0 || s->owner_tid != thr->tid)) {
- if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) {
- s->SetFlags(MutexFlagBroken);
- report_bad_unlock = true;
- }
- } else {
- rec = (flagz & MutexFlagRecursiveUnlock) ? s->recursion : 1;
- s->recursion -= rec;
- if (s->recursion == 0) {
- s->owner_tid = kInvalidTid;
- if (!thr->ignore_sync) {
- thr->clock.ReleaseStore(&s->clock);
- released = true;
- }
- }
- }
- if (common_flags()->detect_deadlocks && s->recursion == 0 &&
- !report_bad_unlock) {
- Callback cb(thr, pc);
- ctx->dd->MutexBeforeUnlock(&cb, &s->dd, true);
- }
- }
- if (released)
- IncrementEpoch(thr);
- }
- if (report_bad_unlock)
- ReportMutexMisuse(thr, pc, ReportTypeMutexBadUnlock, addr,
- creation_stack_id);
- if (common_flags()->detect_deadlocks && !report_bad_unlock) {
- Callback cb(thr, pc);
- ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb));
- }
- return rec;
- }
- void MutexPreReadLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) {
- DPrintf("#%d: MutexPreReadLock %zx flagz=0x%x\n", thr->tid, addr, flagz);
- if ((flagz & MutexFlagTryLock) || !common_flags()->detect_deadlocks)
- return;
- Callback cb(thr, pc);
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- ReadLock lock(&s->mtx);
- s->UpdateFlags(flagz);
- ctx->dd->MutexBeforeLock(&cb, &s->dd, false);
- }
- ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb));
- }
- void MutexPostReadLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) {
- DPrintf("#%d: MutexPostReadLock %zx flagz=0x%x\n", thr->tid, addr, flagz);
- if (pc && IsAppMem(addr))
- MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic);
- bool report_bad_lock = false;
- bool pre_lock = false;
- StackID creation_stack_id = kInvalidStackID;
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- creation_stack_id = s->creation_stack_id;
- RecordMutexLock(thr, pc, addr, creation_stack_id, false);
- {
- ReadLock lock(&s->mtx);
- s->UpdateFlags(flagz);
- if (s->owner_tid != kInvalidTid) {
- if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) {
- s->SetFlags(MutexFlagBroken);
- report_bad_lock = true;
- }
- }
- if (!thr->ignore_sync)
- thr->clock.Acquire(s->clock);
- s->last_lock = thr->fast_state;
- if (common_flags()->detect_deadlocks) {
- pre_lock = (flagz & MutexFlagDoPreLockOnPostLock) &&
- !(flagz & MutexFlagTryLock);
- Callback cb(thr, pc);
- if (pre_lock)
- ctx->dd->MutexBeforeLock(&cb, &s->dd, false);
- ctx->dd->MutexAfterLock(&cb, &s->dd, false, flagz & MutexFlagTryLock);
- }
- }
- }
- if (report_bad_lock)
- ReportMutexMisuse(thr, pc, ReportTypeMutexBadReadLock, addr,
- creation_stack_id);
- if (pre_lock && common_flags()->detect_deadlocks) {
- Callback cb(thr, pc);
- ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb));
- }
- }
- void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: MutexReadUnlock %zx\n", thr->tid, addr);
- if (pc && IsAppMem(addr))
- MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic);
- RecordMutexUnlock(thr, addr);
- StackID creation_stack_id;
- bool report_bad_unlock = false;
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- bool released = false;
- {
- Lock lock(&s->mtx);
- creation_stack_id = s->creation_stack_id;
- if (s->owner_tid != kInvalidTid) {
- if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) {
- s->SetFlags(MutexFlagBroken);
- report_bad_unlock = true;
- }
- }
- if (!thr->ignore_sync) {
- thr->clock.Release(&s->read_clock);
- released = true;
- }
- if (common_flags()->detect_deadlocks && s->recursion == 0) {
- Callback cb(thr, pc);
- ctx->dd->MutexBeforeUnlock(&cb, &s->dd, false);
- }
- }
- if (released)
- IncrementEpoch(thr);
- }
- if (report_bad_unlock)
- ReportMutexMisuse(thr, pc, ReportTypeMutexBadReadUnlock, addr,
- creation_stack_id);
- if (common_flags()->detect_deadlocks) {
- Callback cb(thr, pc);
- ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb));
- }
- }
- void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr);
- if (pc && IsAppMem(addr))
- MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic);
- RecordMutexUnlock(thr, addr);
- StackID creation_stack_id;
- bool report_bad_unlock = false;
- bool write = true;
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- bool released = false;
- {
- Lock lock(&s->mtx);
- creation_stack_id = s->creation_stack_id;
- if (s->owner_tid == kInvalidTid) {
- // Seems to be read unlock.
- write = false;
- if (!thr->ignore_sync) {
- thr->clock.Release(&s->read_clock);
- released = true;
- }
- } else if (s->owner_tid == thr->tid) {
- // Seems to be write unlock.
- CHECK_GT(s->recursion, 0);
- s->recursion--;
- if (s->recursion == 0) {
- s->owner_tid = kInvalidTid;
- if (!thr->ignore_sync) {
- thr->clock.ReleaseStore(&s->clock);
- released = true;
- }
- }
- } else if (!s->IsFlagSet(MutexFlagBroken)) {
- s->SetFlags(MutexFlagBroken);
- report_bad_unlock = true;
- }
- if (common_flags()->detect_deadlocks && s->recursion == 0) {
- Callback cb(thr, pc);
- ctx->dd->MutexBeforeUnlock(&cb, &s->dd, write);
- }
- }
- if (released)
- IncrementEpoch(thr);
- }
- if (report_bad_unlock)
- ReportMutexMisuse(thr, pc, ReportTypeMutexBadUnlock, addr,
- creation_stack_id);
- if (common_flags()->detect_deadlocks) {
- Callback cb(thr, pc);
- ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb));
- }
- }
- void MutexRepair(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: MutexRepair %zx\n", thr->tid, addr);
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- Lock lock(&s->mtx);
- s->owner_tid = kInvalidTid;
- s->recursion = 0;
- }
- void MutexInvalidAccess(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: MutexInvalidAccess %zx\n", thr->tid, addr);
- StackID creation_stack_id = kInvalidStackID;
- {
- SlotLocker locker(thr);
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);
- if (s)
- creation_stack_id = s->creation_stack_id;
- }
- ReportMutexMisuse(thr, pc, ReportTypeMutexInvalidAccess, addr,
- creation_stack_id);
- }
- void Acquire(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: Acquire %zx\n", thr->tid, addr);
- if (thr->ignore_sync)
- return;
- auto s = ctx->metamap.GetSyncIfExists(addr);
- if (!s)
- return;
- SlotLocker locker(thr);
- if (!s->clock)
- return;
- ReadLock lock(&s->mtx);
- thr->clock.Acquire(s->clock);
- }
- void AcquireGlobal(ThreadState *thr) {
- DPrintf("#%d: AcquireGlobal\n", thr->tid);
- if (thr->ignore_sync)
- return;
- SlotLocker locker(thr);
- for (auto &slot : ctx->slots) thr->clock.Set(slot.sid, slot.epoch());
- }
- void Release(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: Release %zx\n", thr->tid, addr);
- if (thr->ignore_sync)
- return;
- SlotLocker locker(thr);
- {
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, false);
- Lock lock(&s->mtx);
- thr->clock.Release(&s->clock);
- }
- IncrementEpoch(thr);
- }
- void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr);
- if (thr->ignore_sync)
- return;
- SlotLocker locker(thr);
- {
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, false);
- Lock lock(&s->mtx);
- thr->clock.ReleaseStore(&s->clock);
- }
- IncrementEpoch(thr);
- }
- void ReleaseStoreAcquire(ThreadState *thr, uptr pc, uptr addr) {
- DPrintf("#%d: ReleaseStoreAcquire %zx\n", thr->tid, addr);
- if (thr->ignore_sync)
- return;
- SlotLocker locker(thr);
- {
- auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, false);
- Lock lock(&s->mtx);
- thr->clock.ReleaseStoreAcquire(&s->clock);
- }
- IncrementEpoch(thr);
- }
- void IncrementEpoch(ThreadState *thr) {
- DCHECK(!thr->ignore_sync);
- DCHECK(thr->slot_locked);
- Epoch epoch = EpochInc(thr->fast_state.epoch());
- if (!EpochOverflow(epoch)) {
- Sid sid = thr->fast_state.sid();
- thr->clock.Set(sid, epoch);
- thr->fast_state.SetEpoch(epoch);
- thr->slot->SetEpoch(epoch);
- TraceTime(thr);
- }
- }
- #if !SANITIZER_GO
- void AfterSleep(ThreadState *thr, uptr pc) {
- DPrintf("#%d: AfterSleep\n", thr->tid);
- if (thr->ignore_sync)
- return;
- thr->last_sleep_stack_id = CurrentStackId(thr, pc);
- thr->last_sleep_clock.Reset();
- SlotLocker locker(thr);
- for (auto &slot : ctx->slots)
- thr->last_sleep_clock.Set(slot.sid, slot.epoch());
- }
- #endif
- void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
- if (r == 0 || !ShouldReport(thr, ReportTypeDeadlock))
- return;
- ThreadRegistryLock l(&ctx->thread_registry);
- ScopedReport rep(ReportTypeDeadlock);
- for (int i = 0; i < r->n; i++) {
- rep.AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
- rep.AddUniqueTid((int)r->loop[i].thr_ctx);
- rep.AddThread((int)r->loop[i].thr_ctx);
- }
- uptr dummy_pc = 0x42;
- for (int i = 0; i < r->n; i++) {
- for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) {
- u32 stk = r->loop[i].stk[j];
- if (stk && stk != kInvalidStackID) {
- rep.AddStack(StackDepotGet(stk), true);
- } else {
- // Sometimes we fail to extract the stack trace (FIXME: investigate),
- // but we should still produce some stack trace in the report.
- rep.AddStack(StackTrace(&dummy_pc, 1), true);
- }
- }
- }
- OutputReport(thr, rep);
- }
- void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
- FastState last_lock, StackID creation_stack_id) {
- // We need to lock the slot during RestoreStack because it protects
- // the slot journal.
- Lock slot_lock(&ctx->slots[static_cast<uptr>(last_lock.sid())].mtx);
- ThreadRegistryLock l0(&ctx->thread_registry);
- Lock slots_lock(&ctx->slot_mtx);
- ScopedReport rep(ReportTypeMutexDestroyLocked);
- rep.AddMutex(addr, creation_stack_id);
- VarSizeStackTrace trace;
- ObtainCurrentStack(thr, pc, &trace);
- rep.AddStack(trace, true);
- Tid tid;
- DynamicMutexSet mset;
- uptr tag;
- if (!RestoreStack(EventType::kLock, last_lock.sid(), last_lock.epoch(), addr,
- 0, kAccessWrite, &tid, &trace, mset, &tag))
- return;
- rep.AddStack(trace, true);
- rep.AddLocation(addr, 1);
- OutputReport(thr, rep);
- }
- } // namespace __tsan
|