//===-- 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>();
}