#include "test_helpers.h" #include "xray_segmented_array.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include #include namespace __xray { namespace { using ::testing::SizeIs; struct TestData { s64 First; s64 Second; // Need a constructor for emplace operations. TestData(s64 F, s64 S) : First(F), Second(S) {} }; void PrintTo(const TestData &D, std::ostream *OS) { *OS << "{ " << D.First << ", " << D.Second << " }"; } TEST(SegmentedArrayTest, ConstructWithAllocators) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 4); Array Data(A); (void)Data; } TEST(SegmentedArrayTest, ConstructAndPopulate) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 4); Array data(A); ASSERT_NE(data.Append(TestData{0, 0}), nullptr); ASSERT_NE(data.Append(TestData{1, 1}), nullptr); ASSERT_EQ(data.size(), 2u); } TEST(SegmentedArrayTest, ConstructPopulateAndLookup) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 4); Array data(A); ASSERT_NE(data.Append(TestData{0, 1}), nullptr); ASSERT_EQ(data.size(), 1u); ASSERT_EQ(data[0].First, 0); ASSERT_EQ(data[0].Second, 1); } TEST(SegmentedArrayTest, PopulateWithMoreElements) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 24); Array data(A); static const auto kMaxElements = 100u; for (auto I = 0u; I < kMaxElements; ++I) { ASSERT_NE(data.Append(TestData{I, I + 1}), nullptr); } ASSERT_EQ(data.size(), kMaxElements); for (auto I = 0u; I < kMaxElements; ++I) { ASSERT_EQ(data[I].First, I); ASSERT_EQ(data[I].Second, I + 1); } } TEST(SegmentedArrayTest, AppendEmplace) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 4); Array data(A); ASSERT_NE(data.AppendEmplace(1, 1), nullptr); ASSERT_EQ(data[0].First, 1); ASSERT_EQ(data[0].Second, 1); } TEST(SegmentedArrayTest, AppendAndTrim) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 4); Array data(A); ASSERT_NE(data.AppendEmplace(1, 1), nullptr); ASSERT_EQ(data.size(), 1u); data.trim(1); ASSERT_EQ(data.size(), 0u); ASSERT_TRUE(data.empty()); } TEST(SegmentedArrayTest, IteratorAdvance) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 4); Array data(A); ASSERT_TRUE(data.empty()); ASSERT_EQ(data.begin(), data.end()); auto I0 = data.begin(); ASSERT_EQ(I0++, data.begin()); ASSERT_NE(I0, data.begin()); for (const auto &D : data) { (void)D; FAIL(); } ASSERT_NE(data.AppendEmplace(1, 1), nullptr); ASSERT_EQ(data.size(), 1u); ASSERT_NE(data.begin(), data.end()); auto &D0 = *data.begin(); ASSERT_EQ(D0.First, 1); ASSERT_EQ(D0.Second, 1); } TEST(SegmentedArrayTest, IteratorRetreat) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 4); Array data(A); ASSERT_TRUE(data.empty()); ASSERT_EQ(data.begin(), data.end()); ASSERT_NE(data.AppendEmplace(1, 1), nullptr); ASSERT_EQ(data.size(), 1u); ASSERT_NE(data.begin(), data.end()); auto &D0 = *data.begin(); ASSERT_EQ(D0.First, 1); ASSERT_EQ(D0.Second, 1); auto I0 = data.end(); ASSERT_EQ(I0--, data.end()); ASSERT_NE(I0, data.end()); ASSERT_EQ(I0, data.begin()); ASSERT_EQ(I0->First, 1); ASSERT_EQ(I0->Second, 1); } TEST(SegmentedArrayTest, IteratorTrimBehaviour) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 20); Array Data(A); ASSERT_TRUE(Data.empty()); auto I0Begin = Data.begin(), I0End = Data.end(); // Add enough elements in Data to have more than one chunk. constexpr auto Segment = Array::SegmentSize; constexpr auto SegmentX2 = Segment * 2; for (auto i = SegmentX2; i > 0u; --i) { Data.AppendEmplace(static_cast(i), static_cast(i)); } ASSERT_EQ(Data.size(), SegmentX2); { auto &Back = Data.back(); ASSERT_EQ(Back.First, 1); ASSERT_EQ(Back.Second, 1); } // Trim one chunk's elements worth. Data.trim(Segment); ASSERT_EQ(Data.size(), Segment); // Check that we are still able to access 'back' properly. { auto &Back = Data.back(); ASSERT_EQ(Back.First, static_cast(Segment + 1)); ASSERT_EQ(Back.Second, static_cast(Segment + 1)); } // Then trim until it's empty. Data.trim(Segment); ASSERT_TRUE(Data.empty()); // Here our iterators should be the same. auto I1Begin = Data.begin(), I1End = Data.end(); EXPECT_EQ(I0Begin, I1Begin); EXPECT_EQ(I0End, I1End); // Then we ensure that adding elements back works just fine. for (auto i = SegmentX2; i > 0u; --i) { Data.AppendEmplace(static_cast(i), static_cast(i)); } EXPECT_EQ(Data.size(), SegmentX2); } TEST(SegmentedArrayTest, HandleExhaustedAllocator) { using AllocatorType = typename Array::AllocatorType; constexpr auto Segment = Array::SegmentSize; constexpr auto MaxElements = Array::ElementsPerSegment; AllocatorType A(Segment); Array Data(A); for (auto i = MaxElements; i > 0u; --i) EXPECT_NE(Data.AppendEmplace(static_cast(i), static_cast(i)), nullptr); EXPECT_EQ(Data.AppendEmplace(0, 0), nullptr); EXPECT_THAT(Data, SizeIs(MaxElements)); // Trimming more elements than there are in the container should be fine. Data.trim(MaxElements + 1); EXPECT_THAT(Data, SizeIs(0u)); } struct ShadowStackEntry { uint64_t EntryTSC = 0; uint64_t *NodePtr = nullptr; ShadowStackEntry(uint64_t T, uint64_t *N) : EntryTSC(T), NodePtr(N) {} }; TEST(SegmentedArrayTest, SimulateStackBehaviour) { using AllocatorType = typename Array::AllocatorType; AllocatorType A(1 << 10); Array Data(A); static uint64_t Dummy = 0; constexpr uint64_t Max = 9; for (uint64_t i = 0; i < Max; ++i) { auto P = Data.Append({i, &Dummy}); ASSERT_NE(P, nullptr); ASSERT_EQ(P->NodePtr, &Dummy); auto &Back = Data.back(); ASSERT_EQ(Back.NodePtr, &Dummy); ASSERT_EQ(Back.EntryTSC, i); } // Simulate a stack by checking the data from the end as we're trimming. auto Counter = Max; ASSERT_EQ(Data.size(), size_t(Max)); while (!Data.empty()) { const auto &Top = Data.back(); uint64_t *TopNode = Top.NodePtr; EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter; Data.trim(1); --Counter; ASSERT_EQ(Data.size(), size_t(Counter)); } } TEST(SegmentedArrayTest, PlacementNewOnAlignedStorage) { using AllocatorType = typename Array::AllocatorType; typename std::aligned_storage::type AllocatorStorage; new (&AllocatorStorage) AllocatorType(1 << 10); auto *A = reinterpret_cast(&AllocatorStorage); typename std::aligned_storage), alignof(Array)>::type ArrayStorage; new (&ArrayStorage) Array(*A); auto *Data = reinterpret_cast *>(&ArrayStorage); static uint64_t Dummy = 0; constexpr uint64_t Max = 9; for (uint64_t i = 0; i < Max; ++i) { auto P = Data->Append({i, &Dummy}); ASSERT_NE(P, nullptr); ASSERT_EQ(P->NodePtr, &Dummy); auto &Back = Data->back(); ASSERT_EQ(Back.NodePtr, &Dummy); ASSERT_EQ(Back.EntryTSC, i); } // Simulate a stack by checking the data from the end as we're trimming. auto Counter = Max; ASSERT_EQ(Data->size(), size_t(Max)); while (!Data->empty()) { const auto &Top = Data->back(); uint64_t *TopNode = Top.NodePtr; EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter; Data->trim(1); --Counter; ASSERT_EQ(Data->size(), size_t(Counter)); } // Once the stack is exhausted, we re-use the storage. for (uint64_t i = 0; i < Max; ++i) { auto P = Data->Append({i, &Dummy}); ASSERT_NE(P, nullptr); ASSERT_EQ(P->NodePtr, &Dummy); auto &Back = Data->back(); ASSERT_EQ(Back.NodePtr, &Dummy); ASSERT_EQ(Back.EntryTSC, i); } // We re-initialize the storage, by calling the destructor and // placement-new'ing again. Data->~Array(); A->~AllocatorType(); new (A) AllocatorType(1 << 10); new (Data) Array(*A); // Then re-do the test. for (uint64_t i = 0; i < Max; ++i) { auto P = Data->Append({i, &Dummy}); ASSERT_NE(P, nullptr); ASSERT_EQ(P->NodePtr, &Dummy); auto &Back = Data->back(); ASSERT_EQ(Back.NodePtr, &Dummy); ASSERT_EQ(Back.EntryTSC, i); } // Simulate a stack by checking the data from the end as we're trimming. Counter = Max; ASSERT_EQ(Data->size(), size_t(Max)); while (!Data->empty()) { const auto &Top = Data->back(); uint64_t *TopNode = Top.NodePtr; EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter; Data->trim(1); --Counter; ASSERT_EQ(Data->size(), size_t(Counter)); } // Once the stack is exhausted, we re-use the storage. for (uint64_t i = 0; i < Max; ++i) { auto P = Data->Append({i, &Dummy}); ASSERT_NE(P, nullptr); ASSERT_EQ(P->NodePtr, &Dummy); auto &Back = Data->back(); ASSERT_EQ(Back.NodePtr, &Dummy); ASSERT_EQ(Back.EntryTSC, i); } } TEST(SegmentedArrayTest, ArrayOfPointersIteratorAccess) { using PtrArray = Array; PtrArray::AllocatorType Alloc(16384); Array A(Alloc); static constexpr size_t Count = 100; std::vector Integers(Count); std::iota(Integers.begin(), Integers.end(), 0); for (auto &I : Integers) ASSERT_NE(A.Append(&I), nullptr); int V = 0; ASSERT_EQ(A.size(), Count); for (auto P : A) { ASSERT_NE(P, nullptr); ASSERT_EQ(*P, V++); } } TEST(SegmentedArrayTest, ArrayOfPointersIteratorAccessExhaustion) { using PtrArray = Array; PtrArray::AllocatorType Alloc(4096); Array A(Alloc); static constexpr size_t Count = 1000; std::vector Integers(Count); std::iota(Integers.begin(), Integers.end(), 0); for (auto &I : Integers) if (A.Append(&I) == nullptr) break; int V = 0; ASSERT_LT(A.size(), Count); for (auto P : A) { ASSERT_NE(P, nullptr); ASSERT_EQ(*P, V++); } } } // namespace } // namespace __xray