//===-- sanitizer_deadlock_detector_test.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 Sanitizer runtime. // Tests for sanitizer_deadlock_detector.h // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_deadlock_detector.h" #include "sanitizer_test_utils.h" #include "gtest/gtest.h" #include #include #include using namespace __sanitizer; using namespace std; typedef BasicBitVector BV1; typedef BasicBitVector<> BV2; typedef TwoLevelBitVector<> BV3; typedef TwoLevelBitVector<3, BasicBitVector > BV4; // Poor man's unique_ptr. template struct ScopedDD { ScopedDD() { dp = new DeadlockDetector; dp->clear(); dtls.clear(); } ~ScopedDD() { delete dp; } DeadlockDetector *dp; DeadlockDetectorTLS dtls; }; template void RunBasicTest() { uptr path[10]; ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; set s; for (size_t i = 0; i < d.size() * 3; i++) { uptr node = d.newNode(0); EXPECT_TRUE(s.insert(node).second); } d.clear(); s.clear(); // Add size() nodes. for (size_t i = 0; i < d.size(); i++) { uptr node = d.newNode(0); EXPECT_TRUE(s.insert(node).second); } // Remove all nodes. for (set::iterator it = s.begin(); it != s.end(); ++it) d.removeNode(*it); // The nodes should be reused. for (size_t i = 0; i < d.size(); i++) { uptr node = d.newNode(0); EXPECT_FALSE(s.insert(node).second); } // Cycle: n1->n2->n1 { d.clear(); dtls.clear(); uptr n1 = d.newNode(1); uptr n2 = d.newNode(2); EXPECT_FALSE(d.onLock(&dtls, n1)); EXPECT_FALSE(d.onLock(&dtls, n2)); d.onUnlock(&dtls, n2); d.onUnlock(&dtls, n1); EXPECT_FALSE(d.onLock(&dtls, n2)); EXPECT_EQ(0U, d.findPathToLock(&dtls, n1, path, 1)); EXPECT_EQ(2U, d.findPathToLock(&dtls, n1, path, 10)); EXPECT_EQ(2U, d.findPathToLock(&dtls, n1, path, 2)); EXPECT_TRUE(d.onLock(&dtls, n1)); EXPECT_EQ(path[0], n1); EXPECT_EQ(path[1], n2); EXPECT_EQ(d.getData(n1), 1U); EXPECT_EQ(d.getData(n2), 2U); d.onUnlock(&dtls, n1); d.onUnlock(&dtls, n2); } // Cycle: n1->n2->n3->n1 { d.clear(); dtls.clear(); uptr n1 = d.newNode(1); uptr n2 = d.newNode(2); uptr n3 = d.newNode(3); EXPECT_FALSE(d.onLock(&dtls, n1)); EXPECT_FALSE(d.onLock(&dtls, n2)); d.onUnlock(&dtls, n2); d.onUnlock(&dtls, n1); EXPECT_FALSE(d.onLock(&dtls, n2)); EXPECT_FALSE(d.onLock(&dtls, n3)); d.onUnlock(&dtls, n3); d.onUnlock(&dtls, n2); EXPECT_FALSE(d.onLock(&dtls, n3)); EXPECT_EQ(0U, d.findPathToLock(&dtls, n1, path, 2)); EXPECT_EQ(3U, d.findPathToLock(&dtls, n1, path, 10)); EXPECT_TRUE(d.onLock(&dtls, n1)); EXPECT_EQ(path[0], n1); EXPECT_EQ(path[1], n2); EXPECT_EQ(path[2], n3); EXPECT_EQ(d.getData(n1), 1U); EXPECT_EQ(d.getData(n2), 2U); EXPECT_EQ(d.getData(n3), 3U); d.onUnlock(&dtls, n1); d.onUnlock(&dtls, n3); } } TEST(DeadlockDetector, BasicTest) { RunBasicTest(); RunBasicTest(); RunBasicTest(); RunBasicTest(); } template void RunRemoveNodeTest() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; uptr l0 = d.newNode(0); uptr l1 = d.newNode(1); uptr l2 = d.newNode(2); uptr l3 = d.newNode(3); uptr l4 = d.newNode(4); uptr l5 = d.newNode(5); // l0=>l1=>l2 d.onLock(&dtls, l0); d.onLock(&dtls, l1); d.onLock(&dtls, l2); d.onUnlock(&dtls, l1); d.onUnlock(&dtls, l0); d.onUnlock(&dtls, l2); // l3=>l4=>l5 d.onLock(&dtls, l3); d.onLock(&dtls, l4); d.onLock(&dtls, l5); d.onUnlock(&dtls, l4); d.onUnlock(&dtls, l3); d.onUnlock(&dtls, l5); set locks; locks.insert(l0); locks.insert(l1); locks.insert(l2); locks.insert(l3); locks.insert(l4); locks.insert(l5); for (uptr i = 6; i < d.size(); i++) { uptr lt = d.newNode(i); locks.insert(lt); d.onLock(&dtls, lt); d.onUnlock(&dtls, lt); d.removeNode(lt); } EXPECT_EQ(locks.size(), d.size()); // l2=>l0 EXPECT_FALSE(d.onLock(&dtls, l2)); EXPECT_TRUE(d.onLock(&dtls, l0)); d.onUnlock(&dtls, l2); d.onUnlock(&dtls, l0); // l4=>l3 EXPECT_FALSE(d.onLock(&dtls, l4)); EXPECT_TRUE(d.onLock(&dtls, l3)); d.onUnlock(&dtls, l4); d.onUnlock(&dtls, l3); EXPECT_EQ(d.size(), d.testOnlyGetEpoch()); d.removeNode(l2); d.removeNode(l3); locks.clear(); // make sure no edges from or to l0,l1,l4,l5 left. for (uptr i = 4; i < d.size(); i++) { uptr lt = d.newNode(i); locks.insert(lt); uptr a, b; // l0 => lt? a = l0; b = lt; EXPECT_FALSE(d.onLock(&dtls, a)); EXPECT_FALSE(d.onLock(&dtls, b)); d.onUnlock(&dtls, a); d.onUnlock(&dtls, b); // l1 => lt? a = l1; b = lt; EXPECT_FALSE(d.onLock(&dtls, a)); EXPECT_FALSE(d.onLock(&dtls, b)); d.onUnlock(&dtls, a); d.onUnlock(&dtls, b); // lt => l4? a = lt; b = l4; EXPECT_FALSE(d.onLock(&dtls, a)); EXPECT_FALSE(d.onLock(&dtls, b)); d.onUnlock(&dtls, a); d.onUnlock(&dtls, b); // lt => l5? a = lt; b = l5; EXPECT_FALSE(d.onLock(&dtls, a)); EXPECT_FALSE(d.onLock(&dtls, b)); d.onUnlock(&dtls, a); d.onUnlock(&dtls, b); d.removeNode(lt); } // Still the same epoch. EXPECT_EQ(d.size(), d.testOnlyGetEpoch()); EXPECT_EQ(locks.size(), d.size() - 4); // l2 and l3 should have ben reused. EXPECT_EQ(locks.count(l2), 1U); EXPECT_EQ(locks.count(l3), 1U); } TEST(DeadlockDetector, RemoveNodeTest) { RunRemoveNodeTest(); RunRemoveNodeTest(); RunRemoveNodeTest(); RunRemoveNodeTest(); } template void RunMultipleEpochsTest() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; set locks; for (uptr i = 0; i < d.size(); i++) { EXPECT_TRUE(locks.insert(d.newNode(i)).second); } EXPECT_EQ(d.testOnlyGetEpoch(), d.size()); for (uptr i = 0; i < d.size(); i++) { EXPECT_TRUE(locks.insert(d.newNode(i)).second); EXPECT_EQ(d.testOnlyGetEpoch(), d.size() * 2); } locks.clear(); uptr l0 = d.newNode(0); uptr l1 = d.newNode(0); d.onLock(&dtls, l0); d.onLock(&dtls, l1); d.onUnlock(&dtls, l0); EXPECT_EQ(d.testOnlyGetEpoch(), 3 * d.size()); for (uptr i = 0; i < d.size(); i++) { EXPECT_TRUE(locks.insert(d.newNode(i)).second); } EXPECT_EQ(d.testOnlyGetEpoch(), 4 * d.size()); #if !SANITIZER_DEBUG // EXPECT_DEATH clones a thread with 4K stack, // which is overflown by tsan memory accesses functions in debug mode. // Can not handle the locks from the previous epoch. // The caller should update the lock id. EXPECT_DEATH(d.onLock(&dtls, l0), "CHECK failed.*current_epoch_"); #endif } TEST(DeadlockDetector, MultipleEpochsTest) { RunMultipleEpochsTest(); RunMultipleEpochsTest(); RunMultipleEpochsTest(); RunMultipleEpochsTest(); } template void RunCorrectEpochFlush() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; vector locks1; for (uptr i = 0; i < d.size(); i++) locks1.push_back(d.newNode(i)); EXPECT_EQ(d.testOnlyGetEpoch(), d.size()); d.onLock(&dtls, locks1[3]); d.onLock(&dtls, locks1[4]); d.onLock(&dtls, locks1[5]); // We have a new epoch, old locks in dtls will have to be forgotten. uptr l0 = d.newNode(0); EXPECT_EQ(d.testOnlyGetEpoch(), d.size() * 2); uptr l1 = d.newNode(0); EXPECT_EQ(d.testOnlyGetEpoch(), d.size() * 2); d.onLock(&dtls, l0); d.onLock(&dtls, l1); EXPECT_TRUE(d.testOnlyHasEdgeRaw(0, 1)); EXPECT_FALSE(d.testOnlyHasEdgeRaw(1, 0)); EXPECT_FALSE(d.testOnlyHasEdgeRaw(3, 0)); EXPECT_FALSE(d.testOnlyHasEdgeRaw(4, 0)); EXPECT_FALSE(d.testOnlyHasEdgeRaw(5, 0)); } TEST(DeadlockDetector, CorrectEpochFlush) { RunCorrectEpochFlush(); RunCorrectEpochFlush(); } template void RunTryLockTest() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; uptr l0 = d.newNode(0); uptr l1 = d.newNode(0); uptr l2 = d.newNode(0); EXPECT_FALSE(d.onLock(&dtls, l0)); EXPECT_FALSE(d.onTryLock(&dtls, l1)); EXPECT_FALSE(d.onLock(&dtls, l2)); EXPECT_TRUE(d.isHeld(&dtls, l0)); EXPECT_TRUE(d.isHeld(&dtls, l1)); EXPECT_TRUE(d.isHeld(&dtls, l2)); EXPECT_FALSE(d.testOnlyHasEdge(l0, l1)); EXPECT_TRUE(d.testOnlyHasEdge(l1, l2)); d.onUnlock(&dtls, l0); d.onUnlock(&dtls, l1); d.onUnlock(&dtls, l2); } TEST(DeadlockDetector, TryLockTest) { RunTryLockTest(); RunTryLockTest(); } template void RunOnFirstLockTest() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; uptr l0 = d.newNode(0); uptr l1 = d.newNode(0); EXPECT_FALSE(d.onFirstLock(&dtls, l0)); // dtls has old epoch. d.onLock(&dtls, l0); d.onUnlock(&dtls, l0); EXPECT_TRUE(d.onFirstLock(&dtls, l0)); // Ok, same ecpoch, first lock. EXPECT_FALSE(d.onFirstLock(&dtls, l1)); // Second lock. d.onLock(&dtls, l1); d.onUnlock(&dtls, l1); d.onUnlock(&dtls, l0); EXPECT_TRUE(d.onFirstLock(&dtls, l0)); // Ok d.onUnlock(&dtls, l0); vector locks1; for (uptr i = 0; i < d.size(); i++) locks1.push_back(d.newNode(i)); EXPECT_TRUE(d.onFirstLock(&dtls, l0)); // Epoch has changed, but not in dtls. uptr l3 = d.newNode(0); d.onLock(&dtls, l3); d.onUnlock(&dtls, l3); EXPECT_FALSE(d.onFirstLock(&dtls, l0)); // Epoch has changed in dtls. } TEST(DeadlockDetector, onFirstLockTest) { RunOnFirstLockTest(); } template void RunRecusriveLockTest() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; uptr l0 = d.newNode(0); uptr l1 = d.newNode(0); uptr l2 = d.newNode(0); uptr l3 = d.newNode(0); EXPECT_FALSE(d.onLock(&dtls, l0)); EXPECT_FALSE(d.onLock(&dtls, l1)); EXPECT_FALSE(d.onLock(&dtls, l0)); // Recurisve. EXPECT_FALSE(d.onLock(&dtls, l2)); d.onUnlock(&dtls, l0); EXPECT_FALSE(d.onLock(&dtls, l3)); d.onUnlock(&dtls, l0); d.onUnlock(&dtls, l1); d.onUnlock(&dtls, l2); d.onUnlock(&dtls, l3); EXPECT_TRUE(d.testOnlyHasEdge(l0, l1)); EXPECT_TRUE(d.testOnlyHasEdge(l0, l2)); EXPECT_TRUE(d.testOnlyHasEdge(l0, l3)); } TEST(DeadlockDetector, RecusriveLockTest) { RunRecusriveLockTest(); } template void RunLockContextTest() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; uptr l0 = d.newNode(0); uptr l1 = d.newNode(0); uptr l2 = d.newNode(0); uptr l3 = d.newNode(0); uptr l4 = d.newNode(0); EXPECT_FALSE(d.onLock(&dtls, l0, 10)); EXPECT_FALSE(d.onLock(&dtls, l1, 11)); EXPECT_FALSE(d.onLock(&dtls, l2, 12)); EXPECT_FALSE(d.onLock(&dtls, l3, 13)); EXPECT_EQ(10U, d.findLockContext(&dtls, l0)); EXPECT_EQ(11U, d.findLockContext(&dtls, l1)); EXPECT_EQ(12U, d.findLockContext(&dtls, l2)); EXPECT_EQ(13U, d.findLockContext(&dtls, l3)); d.onUnlock(&dtls, l0); EXPECT_EQ(0U, d.findLockContext(&dtls, l0)); EXPECT_EQ(11U, d.findLockContext(&dtls, l1)); EXPECT_EQ(12U, d.findLockContext(&dtls, l2)); EXPECT_EQ(13U, d.findLockContext(&dtls, l3)); d.onUnlock(&dtls, l2); EXPECT_EQ(0U, d.findLockContext(&dtls, l0)); EXPECT_EQ(11U, d.findLockContext(&dtls, l1)); EXPECT_EQ(0U, d.findLockContext(&dtls, l2)); EXPECT_EQ(13U, d.findLockContext(&dtls, l3)); EXPECT_FALSE(d.onLock(&dtls, l4, 14)); EXPECT_EQ(14U, d.findLockContext(&dtls, l4)); } TEST(DeadlockDetector, LockContextTest) { RunLockContextTest(); } template void RunRemoveEdgesTest() { ScopedDD sdd; DeadlockDetector &d = *sdd.dp; DeadlockDetectorTLS &dtls = sdd.dtls; vector node(BV::kSize); u32 stk_from = 0, stk_to = 0; int unique_tid = 0; for (size_t i = 0; i < BV::kSize; i++) node[i] = d.newNode(0); for (size_t i = 0; i < BV::kSize; i++) EXPECT_FALSE(d.onLock(&dtls, node[i], i + 1)); for (size_t i = 0; i < BV::kSize; i++) { for (uptr j = i + 1; j < BV::kSize; j++) { EXPECT_TRUE( d.findEdge(node[i], node[j], &stk_from, &stk_to, &unique_tid)); EXPECT_EQ(stk_from, i + 1); EXPECT_EQ(stk_to, j + 1); } } EXPECT_EQ(d.testOnlyGetEpoch(), d.size()); // Remove and re-create half of the nodes. for (uptr i = 1; i < BV::kSize; i += 2) d.removeNode(node[i]); for (uptr i = 1; i < BV::kSize; i += 2) node[i] = d.newNode(0); EXPECT_EQ(d.testOnlyGetEpoch(), d.size()); // The edges from or to the removed nodes should be gone. for (size_t i = 0; i < BV::kSize; i++) { for (uptr j = i + 1; j < BV::kSize; j++) { if ((i % 2) || (j % 2)) EXPECT_FALSE( d.findEdge(node[i], node[j], &stk_from, &stk_to, &unique_tid)); else EXPECT_TRUE( d.findEdge(node[i], node[j], &stk_from, &stk_to, &unique_tid)); } } } TEST(DeadlockDetector, RemoveEdgesTest) { RunRemoveEdgesTest(); }