#include "../RootAutoDetector.h" #include "sanitizer_common/sanitizer_array_ref.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using namespace __ctx_profile; using ::testing::IsEmpty; using ::testing::Not; using ::testing::SizeIs; // Utility for describing a preorder traversal. By default it captures the // address and count at a callsite node. Implicitly nodes are expected to have 1 // child. If they have none, we place a Marker::term and if they have more than // one, we place a Marker::split(nr_of_children) For example, using a list // notation, and letters to denote a pair of address and count: // (A (B C) (D (E F))) is a list of markers: A, split(2), B, term, C, // term, D, split(2), E, term, F, term class Marker { enum class Kind { End, Value, Split }; const uptr Value; const uptr Count; const Kind K; Marker(uptr V, uptr C, Kind S) : Value(V), Count(C), K(S) {} public: Marker(uptr V, uptr C) : Marker(V, C, Kind::Value) {} static Marker split(uptr V) { return Marker(V, 0, Kind::Split); } static Marker term() { return Marker(0, 0, Kind::End); } bool isSplit() const { return K == Kind::Split; } bool isTerm() const { return K == Kind::End; } bool isVal() const { return K == Kind::Value; } bool operator==(const Marker &M) const { return Value == M.Value && Count == M.Count && K == M.K; } }; class MockCallsiteTrie final : public PerThreadCallsiteTrie { // Return the first multiple of 100. uptr getFctStartAddr(uptr CallsiteAddress) const override { return (CallsiteAddress / 100) * 100; } static void popAndCheck(ArrayRef &Preorder, Marker M) { ASSERT_THAT(Preorder, Not(IsEmpty())); ASSERT_EQ(Preorder[0], M); Preorder = Preorder.drop_front(); } static void checkSameImpl(const Trie &T, ArrayRef &Preorder) { popAndCheck(Preorder, {T.CallsiteAddress, T.Count}); if (T.Children.empty()) { popAndCheck(Preorder, Marker::term()); return; } if (T.Children.size() > 1) popAndCheck(Preorder, Marker::split(T.Children.size())); T.Children.forEach([&](const auto &KVP) { checkSameImpl(KVP.second, Preorder); return true; }); } public: void checkSame(ArrayRef Preorder) const { checkSameImpl(TheTrie, Preorder); ASSERT_THAT(Preorder, IsEmpty()); } }; TEST(PerThreadCallsiteTrieTest, Insert) { MockCallsiteTrie R; uptr Stack1[]{4, 3, 2, 1}; R.insertStack(StackTrace(Stack1, 4)); R.checkSame(ArrayRef( {{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, Marker::term()})); uptr Stack2[]{5, 4, 3, 2, 1}; R.insertStack(StackTrace(Stack2, 5)); R.checkSame(ArrayRef( {{0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}, {5, 1}, Marker::term()})); uptr Stack3[]{6, 3, 2, 1}; R.insertStack(StackTrace(Stack3, 4)); R.checkSame(ArrayRef({{0, 3}, {1, 3}, {2, 3}, {3, 3}, Marker::split(2), {4, 2}, {5, 1}, Marker::term(), {6, 1}, Marker::term()})); uptr Stack4[]{7, 2, 1}; R.insertStack(StackTrace(Stack4, 3)); R.checkSame(ArrayRef({{0, 4}, {1, 4}, {2, 4}, Marker::split(2), {7, 1}, Marker::term(), {3, 3}, Marker::split(2), {4, 2}, {5, 1}, Marker::term(), {6, 1}, Marker::term()})); } TEST(PerThreadCallsiteTrieTest, DetectRoots) { MockCallsiteTrie T; uptr Stack1[]{501, 302, 202, 102}; uptr Stack2[]{601, 402, 203, 102}; T.insertStack({Stack1, 4}); T.insertStack({Stack2, 4}); auto R = T.determineRoots(); EXPECT_THAT(R, SizeIs(2U)); EXPECT_TRUE(R.contains(300)); EXPECT_TRUE(R.contains(400)); } TEST(PerThreadCallsiteTrieTest, DetectRootsNoBranches) { MockCallsiteTrie T; uptr Stack1[]{501, 302, 202, 102}; T.insertStack({Stack1, 4}); auto R = T.determineRoots(); EXPECT_THAT(R, IsEmpty()); } TEST(PerThreadCallsiteTrieTest, DetectRootsUnknownFct) { MockCallsiteTrie T; uptr Stack1[]{501, 302, 202, 102}; // The MockCallsiteTree address resolver resolves addresses over 100, so 40 // will be mapped to 0. uptr Stack2[]{601, 40, 203, 102}; T.insertStack({Stack1, 4}); T.insertStack({Stack2, 4}); auto R = T.determineRoots(); ASSERT_THAT(R, SizeIs(2U)); EXPECT_TRUE(R.contains(300)); EXPECT_TRUE(R.contains(0)); }