1 //===-- secondary_test.cpp --------------------------------------*- C++ -*-===//
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 #include "tests/scudo_unit_test.h"
10
11 #include "secondary.h"
12
13 #include <stdio.h>
14
15 #include <condition_variable>
16 #include <mutex>
17 #include <random>
18 #include <thread>
19 #include <vector>
20
testSecondaryBasic(void)21 template <class SecondaryT> static void testSecondaryBasic(void) {
22 scudo::GlobalStats S;
23 S.init();
24 std::unique_ptr<SecondaryT> L(new SecondaryT);
25 L->init(&S);
26 const scudo::uptr Size = 1U << 16;
27 void *P = L->allocate(Size);
28 EXPECT_NE(P, nullptr);
29 memset(P, 'A', Size);
30 EXPECT_GE(SecondaryT::getBlockSize(P), Size);
31 L->deallocate(P);
32 // If the Secondary can't cache that pointer, it will be unmapped.
33 if (!L->canCache(Size))
34 EXPECT_DEATH(memset(P, 'A', Size), "");
35
36 const scudo::uptr Align = 1U << 16;
37 P = L->allocate(Size + Align, Align);
38 EXPECT_NE(P, nullptr);
39 void *AlignedP = reinterpret_cast<void *>(
40 scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
41 memset(AlignedP, 'A', Size);
42 L->deallocate(P);
43
44 std::vector<void *> V;
45 for (scudo::uptr I = 0; I < 32U; I++)
46 V.push_back(L->allocate(Size));
47 std::shuffle(V.begin(), V.end(), std::mt19937(std::random_device()()));
48 while (!V.empty()) {
49 L->deallocate(V.back());
50 V.pop_back();
51 }
52 scudo::ScopedString Str(1024);
53 L->getStats(&Str);
54 Str.output();
55 }
56
TEST(ScudoSecondaryTest,SecondaryBasic)57 TEST(ScudoSecondaryTest, SecondaryBasic) {
58 testSecondaryBasic<scudo::MapAllocator<scudo::MapAllocatorNoCache>>();
59 testSecondaryBasic<scudo::MapAllocator<scudo::MapAllocatorCache<>>>();
60 testSecondaryBasic<
61 scudo::MapAllocator<scudo::MapAllocatorCache<128U, 64U, 1UL << 20>>>();
62 }
63
64 using LargeAllocator = scudo::MapAllocator<scudo::MapAllocatorCache<>>;
65
66 // This exercises a variety of combinations of size and alignment for the
67 // MapAllocator. The size computation done here mimic the ones done by the
68 // combined allocator.
TEST(ScudoSecondaryTest,SecondaryCombinations)69 TEST(ScudoSecondaryTest, SecondaryCombinations) {
70 constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
71 constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
72 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
73 L->init(nullptr);
74 for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
75 for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
76 AlignLog++) {
77 const scudo::uptr Align = 1U << AlignLog;
78 for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
79 if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
80 continue;
81 const scudo::uptr UserSize =
82 scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
83 const scudo::uptr Size =
84 HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
85 void *P = L->allocate(Size, Align);
86 EXPECT_NE(P, nullptr);
87 void *AlignedP = reinterpret_cast<void *>(
88 scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
89 memset(AlignedP, 0xff, UserSize);
90 L->deallocate(P);
91 }
92 }
93 }
94 scudo::ScopedString Str(1024);
95 L->getStats(&Str);
96 Str.output();
97 }
98
TEST(ScudoSecondaryTest,SecondaryIterate)99 TEST(ScudoSecondaryTest, SecondaryIterate) {
100 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
101 L->init(nullptr);
102 std::vector<void *> V;
103 const scudo::uptr PageSize = scudo::getPageSizeCached();
104 for (scudo::uptr I = 0; I < 32U; I++)
105 V.push_back(L->allocate((std::rand() % 16) * PageSize));
106 auto Lambda = [V](scudo::uptr Block) {
107 EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
108 V.end());
109 };
110 L->disable();
111 L->iterateOverBlocks(Lambda);
112 L->enable();
113 while (!V.empty()) {
114 L->deallocate(V.back());
115 V.pop_back();
116 }
117 scudo::ScopedString Str(1024);
118 L->getStats(&Str);
119 Str.output();
120 }
121
TEST(ScudoSecondaryTest,SecondaryOptions)122 TEST(ScudoSecondaryTest, SecondaryOptions) {
123 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
124 L->init(nullptr);
125 // Attempt to set a maximum number of entries higher than the array size.
126 EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
127 // A negative number will be cast to a scudo::u32, and fail.
128 EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, -1));
129 if (L->canCache(0U)) {
130 // Various valid combinations.
131 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
132 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
133 EXPECT_TRUE(L->canCache(1UL << 18));
134 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
135 EXPECT_FALSE(L->canCache(1UL << 18));
136 EXPECT_TRUE(L->canCache(1UL << 16));
137 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
138 EXPECT_FALSE(L->canCache(1UL << 16));
139 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
140 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
141 EXPECT_TRUE(L->canCache(1UL << 16));
142 }
143 }
144
145 static std::mutex Mutex;
146 static std::condition_variable Cv;
147 static bool Ready;
148
performAllocations(LargeAllocator * L)149 static void performAllocations(LargeAllocator *L) {
150 std::vector<void *> V;
151 const scudo::uptr PageSize = scudo::getPageSizeCached();
152 {
153 std::unique_lock<std::mutex> Lock(Mutex);
154 while (!Ready)
155 Cv.wait(Lock);
156 }
157 for (scudo::uptr I = 0; I < 128U; I++) {
158 // Deallocate 75% of the blocks.
159 const bool Deallocate = (rand() & 3) != 0;
160 void *P = L->allocate((std::rand() % 16) * PageSize);
161 if (Deallocate)
162 L->deallocate(P);
163 else
164 V.push_back(P);
165 }
166 while (!V.empty()) {
167 L->deallocate(V.back());
168 V.pop_back();
169 }
170 }
171
TEST(ScudoSecondaryTest,SecondaryThreadsRace)172 TEST(ScudoSecondaryTest, SecondaryThreadsRace) {
173 Ready = false;
174 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
175 L->init(nullptr, /*ReleaseToOsInterval=*/0);
176 std::thread Threads[16];
177 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
178 Threads[I] = std::thread(performAllocations, L.get());
179 {
180 std::unique_lock<std::mutex> Lock(Mutex);
181 Ready = true;
182 Cv.notify_all();
183 }
184 for (auto &T : Threads)
185 T.join();
186 scudo::ScopedString Str(1024);
187 L->getStats(&Str);
188 Str.output();
189 }
190