1 //===-- sanitizer_stackdepot_test.cpp -------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file is a part of ThreadSanitizer/AddressSanitizer runtime. 10 // 11 //===----------------------------------------------------------------------===// 12 #include "sanitizer_common/sanitizer_stackdepot.h" 13 14 #include <atomic> 15 #include <numeric> 16 #include <regex> 17 #include <sstream> 18 #include <string> 19 #include <thread> 20 21 #include "gtest/gtest.h" 22 #include "sanitizer_common/sanitizer_internal_defs.h" 23 #include "sanitizer_common/sanitizer_libc.h" 24 25 namespace __sanitizer { 26 27 class StackDepotTest : public testing::Test { 28 protected: 29 void SetUp() override { StackDepotTestOnlyUnmap(); } 30 void TearDown() override { 31 StackDepotStats stack_depot_stats = StackDepotGetStats(); 32 Printf("StackDepot: %zd ids; %zdM allocated\n", 33 stack_depot_stats.n_uniq_ids, stack_depot_stats.allocated >> 20); 34 StackDepotTestOnlyUnmap(); 35 } 36 }; 37 38 TEST_F(StackDepotTest, Basic) { 39 uptr array[] = {1, 2, 3, 4, 5}; 40 StackTrace s1(array, ARRAY_SIZE(array)); 41 u32 i1 = StackDepotPut(s1); 42 StackTrace stack = StackDepotGet(i1); 43 EXPECT_NE(stack.trace, (uptr*)0); 44 EXPECT_EQ(ARRAY_SIZE(array), stack.size); 45 EXPECT_EQ(0, internal_memcmp(stack.trace, array, sizeof(array))); 46 } 47 48 TEST_F(StackDepotTest, Absent) { 49 StackTrace stack = StackDepotGet((1 << 30) - 1); 50 EXPECT_EQ((uptr*)0, stack.trace); 51 } 52 53 TEST_F(StackDepotTest, EmptyStack) { 54 u32 i1 = StackDepotPut(StackTrace()); 55 StackTrace stack = StackDepotGet(i1); 56 EXPECT_EQ((uptr*)0, stack.trace); 57 } 58 59 TEST_F(StackDepotTest, ZeroId) { 60 StackTrace stack = StackDepotGet(0); 61 EXPECT_EQ((uptr*)0, stack.trace); 62 } 63 64 TEST_F(StackDepotTest, Same) { 65 uptr array[] = {1, 2, 3, 4, 6}; 66 StackTrace s1(array, ARRAY_SIZE(array)); 67 u32 i1 = StackDepotPut(s1); 68 u32 i2 = StackDepotPut(s1); 69 EXPECT_EQ(i1, i2); 70 StackTrace stack = StackDepotGet(i1); 71 EXPECT_NE(stack.trace, (uptr*)0); 72 EXPECT_EQ(ARRAY_SIZE(array), stack.size); 73 EXPECT_EQ(0, internal_memcmp(stack.trace, array, sizeof(array))); 74 } 75 76 TEST_F(StackDepotTest, Several) { 77 uptr array1[] = {1, 2, 3, 4, 7}; 78 StackTrace s1(array1, ARRAY_SIZE(array1)); 79 u32 i1 = StackDepotPut(s1); 80 uptr array2[] = {1, 2, 3, 4, 8, 9}; 81 StackTrace s2(array2, ARRAY_SIZE(array2)); 82 u32 i2 = StackDepotPut(s2); 83 EXPECT_NE(i1, i2); 84 } 85 86 TEST_F(StackDepotTest, Print) { 87 uptr array1[] = {0x111, 0x222, 0x333, 0x444, 0x777}; 88 StackTrace s1(array1, ARRAY_SIZE(array1)); 89 u32 i1 = StackDepotPut(s1); 90 uptr array2[] = {0x1111, 0x2222, 0x3333, 0x4444, 0x8888, 0x9999}; 91 StackTrace s2(array2, ARRAY_SIZE(array2)); 92 u32 i2 = StackDepotPut(s2); 93 EXPECT_NE(i1, i2); 94 95 auto fix_regex = [](const std::string& s) -> std::string { 96 if (!SANITIZER_WINDOWS) 97 return s; 98 return std::regex_replace(s, std::regex("\\.\\*"), ".*\\n.*"); 99 }; 100 EXPECT_EXIT( 101 (StackDepotPrintAll(), exit(0)), ::testing::ExitedWithCode(0), 102 fix_regex("Stack for id .*#0 0x1.*#1 0x2.*#2 0x3.*#3 0x4.*#4 0x7.*")); 103 EXPECT_EXIT( 104 (StackDepotPrintAll(), exit(0)), ::testing::ExitedWithCode(0), 105 fix_regex( 106 "Stack for id .*#0 0x1.*#1 0x2.*#2 0x3.*#3 0x4.*#4 0x8.*#5 0x9.*")); 107 } 108 109 TEST_F(StackDepotTest, PrintNoLock) { 110 u32 n = 2000; 111 std::vector<u32> idx2id(n); 112 for (u32 i = 0; i < n; ++i) { 113 uptr array[] = {0x111, 0x222, i, 0x444, 0x777}; 114 StackTrace s(array, ARRAY_SIZE(array)); 115 idx2id[i] = StackDepotPut(s); 116 } 117 StackDepotPrintAll(); 118 for (u32 i = 0; i < n; ++i) { 119 uptr array[] = {0x111, 0x222, i, 0x444, 0x777}; 120 StackTrace s(array, ARRAY_SIZE(array)); 121 CHECK_EQ(idx2id[i], StackDepotPut(s)); 122 } 123 } 124 125 static struct StackDepotBenchmarkParams { 126 int UniqueStacksPerThread; 127 int RepeatPerThread; 128 int Threads; 129 bool UniqueThreads; 130 bool UseCount; 131 } params[] = { 132 // All traces are unique, very unusual. 133 {10000000, 1, 1, false, false}, 134 {8000000, 1, 4, false, false}, 135 {8000000, 1, 16, false, false}, 136 // Probably most realistic sets. 137 {3000000, 10, 1, false, false}, 138 {3000000, 10, 4, false, false}, 139 {3000000, 10, 16, false, false}, 140 // Update use count as msan/dfsan. 141 {3000000, 10, 1, false, true}, 142 {3000000, 10, 4, false, true}, 143 {3000000, 10, 16, false, true}, 144 // Unrealistic, as above, but traces are unique inside of thread. 145 {4000000, 1, 4, true, false}, 146 {2000000, 1, 16, true, false}, 147 {2000000, 10, 4, true, false}, 148 {500000, 10, 16, true, false}, 149 {1500000, 10, 4, true, true}, 150 {800000, 10, 16, true, true}, 151 }; 152 153 static std::string PrintStackDepotBenchmarkParams( 154 const testing::TestParamInfo<StackDepotBenchmarkParams>& info) { 155 std::stringstream name; 156 name << info.param.UniqueStacksPerThread << "_" << info.param.RepeatPerThread 157 << "_" << info.param.Threads << (info.param.UseCount ? "_UseCount" : "") 158 << (info.param.UniqueThreads ? "_UniqueThreads" : ""); 159 return name.str(); 160 } 161 162 class StackDepotBenchmark 163 : public StackDepotTest, 164 public testing::WithParamInterface<StackDepotBenchmarkParams> {}; 165 166 // Test which can be used as a simple benchmark. It's disabled to avoid slowing 167 // down check-sanitizer. 168 // Usage: Sanitizer-<ARCH>-Test --gtest_also_run_disabled_tests \ 169 // '--gtest_filter=*Benchmark*' 170 TEST_P(StackDepotBenchmark, DISABLED_Benchmark) { 171 auto Param = GetParam(); 172 std::atomic<unsigned int> here = {}; 173 174 auto thread = [&](int idx) { 175 here++; 176 while (here < Param.UniqueThreads) std::this_thread::yield(); 177 178 std::vector<uptr> frames(64); 179 for (int r = 0; r < Param.RepeatPerThread; ++r) { 180 std::iota(frames.begin(), frames.end(), idx + 1); 181 for (int i = 0; i < Param.UniqueStacksPerThread; ++i) { 182 StackTrace s(frames.data(), frames.size()); 183 auto h = StackDepotPut_WithHandle(s); 184 if (Param.UseCount) 185 h.inc_use_count_unsafe(); 186 std::next_permutation(frames.begin(), frames.end()); 187 }; 188 } 189 }; 190 191 std::vector<std::thread> threads; 192 for (int i = 0; i < Param.Threads; ++i) 193 threads.emplace_back(thread, Param.UniqueThreads * i); 194 for (auto& t : threads) t.join(); 195 } 196 197 INSTANTIATE_TEST_SUITE_P(StackDepotBenchmarkSuite, StackDepotBenchmark, 198 testing::ValuesIn(params), 199 PrintStackDepotBenchmarkParams); 200 201 } // namespace __sanitizer 202