//===-- sanitizer_stack_store_test.cpp --------------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_stack_store.h" #include #include #include #include "gtest/gtest.h" #include "sanitizer_atomic.h" #include "sanitizer_hash.h" #include "sanitizer_stacktrace.h" namespace __sanitizer { class StackStoreTest : public testing::Test { protected: void SetUp() override {} void TearDown() override { store_.TestOnlyUnmap(); } template void ForEachTrace(Fn fn, uptr n = 1000000) { std::vector frames(kStackTraceMax); std::iota(frames.begin(), frames.end(), 0x100000); MurMur2HashBuilder h(0); for (uptr i = 0; i < n; ++i) { h.add(i); u32 size = h.get() % kStackTraceMax; h.add(i); uptr tag = h.get() % 256; StackTrace s(frames.data(), size, tag); if (!s.size && !s.tag) continue; fn(s); if (HasFailure()) return; std::next_permutation(frames.begin(), frames.end()); }; } using BlockInfo = StackStore::BlockInfo; uptr GetTotalFramesCount() const { return atomic_load_relaxed(&store_.total_frames_); } uptr CountReadyToPackBlocks() { uptr res = 0; for (BlockInfo& b : store_.blocks_) res += b.Stored(0); return res; } uptr CountPackedBlocks() const { uptr res = 0; for (const BlockInfo& b : store_.blocks_) res += b.IsPacked(); return res; } uptr IdToOffset(StackStore::Id id) const { return store_.IdToOffset(id); } static constexpr uptr kBlockSizeFrames = StackStore::kBlockSizeFrames; static constexpr uptr kBlockSizeBytes = StackStore::kBlockSizeBytes; StackStore store_ = {}; }; TEST_F(StackStoreTest, Empty) { uptr before = store_.Allocated(); uptr pack = 0; EXPECT_EQ(0u, store_.Store({}, &pack)); uptr after = store_.Allocated(); EXPECT_EQ(before, after); } TEST_F(StackStoreTest, Basic) { std::vector ids; ForEachTrace([&](const StackTrace& s) { uptr pack = 0; ids.push_back(store_.Store(s, &pack)); }); auto id = ids.begin(); ForEachTrace([&](const StackTrace& s) { StackTrace trace = store_.Load(*(id++)); EXPECT_EQ(s.size, trace.size); EXPECT_EQ(s.tag, trace.tag); EXPECT_EQ(std::vector(s.trace, s.trace + s.size), std::vector(trace.trace, trace.trace + trace.size)); }); } TEST_F(StackStoreTest, Allocated) { EXPECT_LE(store_.Allocated(), 0x100000u); std::vector ids; ForEachTrace([&](const StackTrace& s) { uptr pack = 0; ids.push_back(store_.Store(s, &pack)); }); EXPECT_NEAR(store_.Allocated(), FIRST_32_SECOND_64(500000000u, 1000000000u), FIRST_32_SECOND_64(50000000u, 100000000u)); store_.TestOnlyUnmap(); EXPECT_LE(store_.Allocated(), 0x100000u); } TEST_F(StackStoreTest, ReadyToPack) { uptr next_pack = kBlockSizeFrames; uptr total_ready = 0; ForEachTrace( [&](const StackTrace& s) { uptr pack = 0; StackStore::Id id = store_.Store(s, &pack); uptr end_idx = IdToOffset(id) + 1 + s.size; if (end_idx >= next_pack) { EXPECT_EQ(1u, pack); next_pack += kBlockSizeFrames; } else { EXPECT_EQ(0u, pack); } total_ready += pack; EXPECT_EQ(CountReadyToPackBlocks(), total_ready); }, 100000); EXPECT_EQ(GetTotalFramesCount() / kBlockSizeFrames, total_ready); } struct StackStorePackTest : public StackStoreTest, public ::testing::WithParamInterface< std::pair> {}; INSTANTIATE_TEST_SUITE_P( PackUnpacks, StackStorePackTest, ::testing::ValuesIn({ StackStorePackTest::ParamType(StackStore::Compression::Delta, FIRST_32_SECOND_64(2, 6)), StackStorePackTest::ParamType(StackStore::Compression::LZW, FIRST_32_SECOND_64(60, 130)), })); TEST_P(StackStorePackTest, PackUnpack) { std::vector ids; StackStore::Compression type = GetParam().first; uptr expected_ratio = GetParam().second; ForEachTrace([&](const StackTrace& s) { uptr pack = 0; ids.push_back(store_.Store(s, &pack)); if (pack) { uptr before = store_.Allocated(); uptr diff = store_.Pack(type); uptr after = store_.Allocated(); EXPECT_EQ(before - after, diff); EXPECT_LT(after, before); EXPECT_GE(kBlockSizeBytes / (kBlockSizeBytes - (before - after)), expected_ratio); } }); uptr packed_blocks = CountPackedBlocks(); // Unpack random block. store_.Load(kBlockSizeFrames * 7 + 123); EXPECT_EQ(packed_blocks - 1, CountPackedBlocks()); // Unpack all blocks. auto id = ids.begin(); ForEachTrace([&](const StackTrace& s) { StackTrace trace = store_.Load(*(id++)); EXPECT_EQ(s.size, trace.size); EXPECT_EQ(s.tag, trace.tag); EXPECT_EQ(std::vector(s.trace, s.trace + s.size), std::vector(trace.trace, trace.trace + trace.size)); }); EXPECT_EQ(0u, CountPackedBlocks()); EXPECT_EQ(0u, store_.Pack(type)); EXPECT_EQ(0u, CountPackedBlocks()); } TEST_P(StackStorePackTest, Failed) { MurMur2Hash64Builder h(0); StackStore::Compression type = GetParam().first; std::vector frames(200); for (uptr i = 0; i < kBlockSizeFrames * 4 / frames.size(); ++i) { for (uptr& f : frames) { h.add(1); // Make it difficult to pack. f = h.get(); } uptr pack = 0; store_.Store(StackTrace(frames.data(), frames.size()), &pack); if (pack) EXPECT_EQ(0u, store_.Pack(type)); } EXPECT_EQ(0u, CountPackedBlocks()); } } // namespace __sanitizer