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 #if !SCUDO_FUCHSIA
60 testSecondaryBasic<scudo::MapAllocator<scudo::MapAllocatorCache<>>>();
61 testSecondaryBasic<
62 scudo::MapAllocator<scudo::MapAllocatorCache<128U, 64U, 1UL << 20>>>();
63 #endif
64 }
65
66 #if SCUDO_FUCHSIA
67 using LargeAllocator = scudo::MapAllocator<scudo::MapAllocatorNoCache>;
68 #else
69 using LargeAllocator = scudo::MapAllocator<scudo::MapAllocatorCache<>>;
70 #endif
71
72 // This exercises a variety of combinations of size and alignment for the
73 // MapAllocator. The size computation done here mimic the ones done by the
74 // combined allocator.
TEST(ScudoSecondaryTest,SecondaryCombinations)75 TEST(ScudoSecondaryTest, SecondaryCombinations) {
76 constexpr scudo::uptr MinAlign = FIRST_32_SECOND_64(8, 16);
77 constexpr scudo::uptr HeaderSize = scudo::roundUpTo(8, MinAlign);
78 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
79 L->init(nullptr);
80 for (scudo::uptr SizeLog = 0; SizeLog <= 20; SizeLog++) {
81 for (scudo::uptr AlignLog = FIRST_32_SECOND_64(3, 4); AlignLog <= 16;
82 AlignLog++) {
83 const scudo::uptr Align = 1U << AlignLog;
84 for (scudo::sptr Delta = -128; Delta <= 128; Delta += 8) {
85 if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
86 continue;
87 const scudo::uptr UserSize =
88 scudo::roundUpTo((1U << SizeLog) + Delta, MinAlign);
89 const scudo::uptr Size =
90 HeaderSize + UserSize + (Align > MinAlign ? Align - HeaderSize : 0);
91 void *P = L->allocate(Size, Align);
92 EXPECT_NE(P, nullptr);
93 void *AlignedP = reinterpret_cast<void *>(
94 scudo::roundUpTo(reinterpret_cast<scudo::uptr>(P), Align));
95 memset(AlignedP, 0xff, UserSize);
96 L->deallocate(P);
97 }
98 }
99 }
100 scudo::ScopedString Str(1024);
101 L->getStats(&Str);
102 Str.output();
103 }
104
TEST(ScudoSecondaryTest,SecondaryIterate)105 TEST(ScudoSecondaryTest, SecondaryIterate) {
106 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
107 L->init(nullptr);
108 std::vector<void *> V;
109 const scudo::uptr PageSize = scudo::getPageSizeCached();
110 for (scudo::uptr I = 0; I < 32U; I++)
111 V.push_back(L->allocate((std::rand() % 16) * PageSize));
112 auto Lambda = [V](scudo::uptr Block) {
113 EXPECT_NE(std::find(V.begin(), V.end(), reinterpret_cast<void *>(Block)),
114 V.end());
115 };
116 L->disable();
117 L->iterateOverBlocks(Lambda);
118 L->enable();
119 while (!V.empty()) {
120 L->deallocate(V.back());
121 V.pop_back();
122 }
123 scudo::ScopedString Str(1024);
124 L->getStats(&Str);
125 Str.output();
126 }
127
TEST(ScudoSecondaryTest,SecondaryOptions)128 TEST(ScudoSecondaryTest, SecondaryOptions) {
129 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
130 L->init(nullptr);
131 // Attempt to set a maximum number of entries higher than the array size.
132 EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4096U));
133 // A negative number will be cast to a scudo::u32, and fail.
134 EXPECT_FALSE(L->setOption(scudo::Option::MaxCacheEntriesCount, -1));
135 if (L->canCache(0U)) {
136 // Various valid combinations.
137 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
138 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
139 EXPECT_TRUE(L->canCache(1UL << 18));
140 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 17));
141 EXPECT_FALSE(L->canCache(1UL << 18));
142 EXPECT_TRUE(L->canCache(1UL << 16));
143 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 0U));
144 EXPECT_FALSE(L->canCache(1UL << 16));
145 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntriesCount, 4U));
146 EXPECT_TRUE(L->setOption(scudo::Option::MaxCacheEntrySize, 1UL << 20));
147 EXPECT_TRUE(L->canCache(1UL << 16));
148 }
149 }
150
151 static std::mutex Mutex;
152 static std::condition_variable Cv;
153 static bool Ready;
154
performAllocations(LargeAllocator * L)155 static void performAllocations(LargeAllocator *L) {
156 std::vector<void *> V;
157 const scudo::uptr PageSize = scudo::getPageSizeCached();
158 {
159 std::unique_lock<std::mutex> Lock(Mutex);
160 while (!Ready)
161 Cv.wait(Lock);
162 }
163 for (scudo::uptr I = 0; I < 128U; I++) {
164 // Deallocate 75% of the blocks.
165 const bool Deallocate = (rand() & 3) != 0;
166 void *P = L->allocate((std::rand() % 16) * PageSize);
167 if (Deallocate)
168 L->deallocate(P);
169 else
170 V.push_back(P);
171 }
172 while (!V.empty()) {
173 L->deallocate(V.back());
174 V.pop_back();
175 }
176 }
177
TEST(ScudoSecondaryTest,SecondaryThreadsRace)178 TEST(ScudoSecondaryTest, SecondaryThreadsRace) {
179 Ready = false;
180 std::unique_ptr<LargeAllocator> L(new LargeAllocator);
181 L->init(nullptr, /*ReleaseToOsInterval=*/0);
182 std::thread Threads[16];
183 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
184 Threads[I] = std::thread(performAllocations, L.get());
185 {
186 std::unique_lock<std::mutex> Lock(Mutex);
187 Ready = true;
188 Cv.notify_all();
189 }
190 for (auto &T : Threads)
191 T.join();
192 scudo::ScopedString Str(1024);
193 L->getStats(&Str);
194 Str.output();
195 }
196