| //===-- sanitizer_deadlock_detector_test.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 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 <algorithm> |
| #include <vector> |
| #include <set> |
| |
| using namespace __sanitizer; |
| using namespace std; |
| |
| typedef BasicBitVector<u8> BV1; |
| typedef BasicBitVector<> BV2; |
| typedef TwoLevelBitVector<> BV3; |
| typedef TwoLevelBitVector<3, BasicBitVector<u8> > BV4; |
| |
| // Poor man's unique_ptr. |
| template<class BV> |
| struct ScopedDD { |
| ScopedDD() { |
| dp = new DeadlockDetector<BV>; |
| dp->clear(); |
| dtls.clear(); |
| } |
| ~ScopedDD() { delete dp; } |
| DeadlockDetector<BV> *dp; |
| DeadlockDetectorTLS<BV> dtls; |
| }; |
| |
| template <class BV> |
| void RunBasicTest() { |
| uptr path[10]; |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &dtls = sdd.dtls; |
| set<uptr> 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<uptr>::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<BV1>(); |
| RunBasicTest<BV2>(); |
| RunBasicTest<BV3>(); |
| RunBasicTest<BV4>(); |
| } |
| |
| template <class BV> |
| void RunRemoveNodeTest() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &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<uptr> 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<BV1>(); |
| RunRemoveNodeTest<BV2>(); |
| RunRemoveNodeTest<BV3>(); |
| RunRemoveNodeTest<BV4>(); |
| } |
| |
| template <class BV> |
| void RunMultipleEpochsTest() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &dtls = sdd.dtls; |
| |
| set<uptr> 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<BV1>(); |
| RunMultipleEpochsTest<BV2>(); |
| RunMultipleEpochsTest<BV3>(); |
| RunMultipleEpochsTest<BV4>(); |
| } |
| |
| template <class BV> |
| void RunCorrectEpochFlush() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &dtls = sdd.dtls; |
| vector<uptr> 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<BV1>(); |
| RunCorrectEpochFlush<BV2>(); |
| } |
| |
| template <class BV> |
| void RunTryLockTest() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &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<BV1>(); |
| RunTryLockTest<BV2>(); |
| } |
| |
| template <class BV> |
| void RunOnFirstLockTest() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &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<uptr> 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<BV2>(); |
| } |
| |
| template <class BV> |
| void RunRecusriveLockTest() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &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<BV2>(); |
| } |
| |
| template <class BV> |
| void RunLockContextTest() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &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<BV2>(); |
| } |
| |
| template <class BV> |
| void RunRemoveEdgesTest() { |
| ScopedDD<BV> sdd; |
| DeadlockDetector<BV> &d = *sdd.dp; |
| DeadlockDetectorTLS<BV> &dtls = sdd.dtls; |
| vector<uptr> 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<BV1>(); |
| } |