1 //===-- quarantine_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 "quarantine.h"
12
13 #include <pthread.h>
14 #include <stdlib.h>
15
16 static void *FakePtr = reinterpret_cast<void *>(0xFA83FA83);
17 static const scudo::uptr BlockSize = 8UL;
18 static const scudo::uptr LargeBlockSize = 16384UL;
19
20 struct QuarantineCallback {
recycleQuarantineCallback21 void recycle(void *P) { EXPECT_EQ(P, FakePtr); }
allocateQuarantineCallback22 void *allocate(scudo::uptr Size) { return malloc(Size); }
deallocateQuarantineCallback23 void deallocate(void *P) { free(P); }
24 };
25
26 typedef scudo::GlobalQuarantine<QuarantineCallback, void> QuarantineT;
27 typedef typename QuarantineT::CacheT CacheT;
28
29 static QuarantineCallback Cb;
30
deallocateCache(CacheT * Cache)31 static void deallocateCache(CacheT *Cache) {
32 while (scudo::QuarantineBatch *Batch = Cache->dequeueBatch())
33 Cb.deallocate(Batch);
34 }
35
TEST(ScudoQuarantineTest,QuarantineBatchMerge)36 TEST(ScudoQuarantineTest, QuarantineBatchMerge) {
37 // Verify the trivial case.
38 scudo::QuarantineBatch Into;
39 Into.init(FakePtr, 4UL);
40 scudo::QuarantineBatch From;
41 From.init(FakePtr, 8UL);
42
43 Into.merge(&From);
44
45 EXPECT_EQ(Into.Count, 2UL);
46 EXPECT_EQ(Into.Batch[0], FakePtr);
47 EXPECT_EQ(Into.Batch[1], FakePtr);
48 EXPECT_EQ(Into.Size, 12UL + sizeof(scudo::QuarantineBatch));
49 EXPECT_EQ(Into.getQuarantinedSize(), 12UL);
50
51 EXPECT_EQ(From.Count, 0UL);
52 EXPECT_EQ(From.Size, sizeof(scudo::QuarantineBatch));
53 EXPECT_EQ(From.getQuarantinedSize(), 0UL);
54
55 // Merge the batch to the limit.
56 for (scudo::uptr I = 2; I < scudo::QuarantineBatch::MaxCount; ++I)
57 From.push_back(FakePtr, 8UL);
58 EXPECT_TRUE(Into.Count + From.Count == scudo::QuarantineBatch::MaxCount);
59 EXPECT_TRUE(Into.canMerge(&From));
60
61 Into.merge(&From);
62 EXPECT_TRUE(Into.Count == scudo::QuarantineBatch::MaxCount);
63
64 // No more space, not even for one element.
65 From.init(FakePtr, 8UL);
66
67 EXPECT_FALSE(Into.canMerge(&From));
68 }
69
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesEmpty)70 TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesEmpty) {
71 CacheT Cache;
72 CacheT ToDeallocate;
73 Cache.init();
74 ToDeallocate.init();
75 Cache.mergeBatches(&ToDeallocate);
76
77 EXPECT_EQ(ToDeallocate.getSize(), 0UL);
78 EXPECT_EQ(ToDeallocate.dequeueBatch(), nullptr);
79 }
80
TEST(SanitizerCommon,QuarantineCacheMergeBatchesOneBatch)81 TEST(SanitizerCommon, QuarantineCacheMergeBatchesOneBatch) {
82 CacheT Cache;
83 Cache.init();
84 Cache.enqueue(Cb, FakePtr, BlockSize);
85 EXPECT_EQ(BlockSize + sizeof(scudo::QuarantineBatch), Cache.getSize());
86
87 CacheT ToDeallocate;
88 ToDeallocate.init();
89 Cache.mergeBatches(&ToDeallocate);
90
91 // Nothing to merge, nothing to deallocate.
92 EXPECT_EQ(BlockSize + sizeof(scudo::QuarantineBatch), Cache.getSize());
93
94 EXPECT_EQ(ToDeallocate.getSize(), 0UL);
95 EXPECT_EQ(ToDeallocate.dequeueBatch(), nullptr);
96
97 deallocateCache(&Cache);
98 }
99
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesSmallBatches)100 TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesSmallBatches) {
101 // Make a Cache with two batches small enough to merge.
102 CacheT From;
103 From.init();
104 From.enqueue(Cb, FakePtr, BlockSize);
105 CacheT Cache;
106 Cache.init();
107 Cache.enqueue(Cb, FakePtr, BlockSize);
108
109 Cache.transfer(&From);
110 EXPECT_EQ(BlockSize * 2 + sizeof(scudo::QuarantineBatch) * 2,
111 Cache.getSize());
112
113 CacheT ToDeallocate;
114 ToDeallocate.init();
115 Cache.mergeBatches(&ToDeallocate);
116
117 // Batches merged, one batch to deallocate.
118 EXPECT_EQ(BlockSize * 2 + sizeof(scudo::QuarantineBatch), Cache.getSize());
119 EXPECT_EQ(ToDeallocate.getSize(), sizeof(scudo::QuarantineBatch));
120
121 deallocateCache(&Cache);
122 deallocateCache(&ToDeallocate);
123 }
124
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesTooBigToMerge)125 TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesTooBigToMerge) {
126 const scudo::uptr NumBlocks = scudo::QuarantineBatch::MaxCount - 1;
127
128 // Make a Cache with two batches small enough to merge.
129 CacheT From;
130 CacheT Cache;
131 From.init();
132 Cache.init();
133 for (scudo::uptr I = 0; I < NumBlocks; ++I) {
134 From.enqueue(Cb, FakePtr, BlockSize);
135 Cache.enqueue(Cb, FakePtr, BlockSize);
136 }
137 Cache.transfer(&From);
138 EXPECT_EQ(BlockSize * NumBlocks * 2 + sizeof(scudo::QuarantineBatch) * 2,
139 Cache.getSize());
140
141 CacheT ToDeallocate;
142 ToDeallocate.init();
143 Cache.mergeBatches(&ToDeallocate);
144
145 // Batches cannot be merged.
146 EXPECT_EQ(BlockSize * NumBlocks * 2 + sizeof(scudo::QuarantineBatch) * 2,
147 Cache.getSize());
148 EXPECT_EQ(ToDeallocate.getSize(), 0UL);
149
150 deallocateCache(&Cache);
151 }
152
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesALotOfBatches)153 TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesALotOfBatches) {
154 const scudo::uptr NumBatchesAfterMerge = 3;
155 const scudo::uptr NumBlocks =
156 scudo::QuarantineBatch::MaxCount * NumBatchesAfterMerge;
157 const scudo::uptr NumBatchesBeforeMerge = NumBlocks;
158
159 // Make a Cache with many small batches.
160 CacheT Cache;
161 Cache.init();
162 for (scudo::uptr I = 0; I < NumBlocks; ++I) {
163 CacheT From;
164 From.init();
165 From.enqueue(Cb, FakePtr, BlockSize);
166 Cache.transfer(&From);
167 }
168
169 EXPECT_EQ(BlockSize * NumBlocks +
170 sizeof(scudo::QuarantineBatch) * NumBatchesBeforeMerge,
171 Cache.getSize());
172
173 CacheT ToDeallocate;
174 ToDeallocate.init();
175 Cache.mergeBatches(&ToDeallocate);
176
177 // All blocks should fit Into 3 batches.
178 EXPECT_EQ(BlockSize * NumBlocks +
179 sizeof(scudo::QuarantineBatch) * NumBatchesAfterMerge,
180 Cache.getSize());
181
182 EXPECT_EQ(ToDeallocate.getSize(),
183 sizeof(scudo::QuarantineBatch) *
184 (NumBatchesBeforeMerge - NumBatchesAfterMerge));
185
186 deallocateCache(&Cache);
187 deallocateCache(&ToDeallocate);
188 }
189
190 static const scudo::uptr MaxQuarantineSize = 1024UL << 10; // 1MB
191 static const scudo::uptr MaxCacheSize = 256UL << 10; // 256KB
192
TEST(ScudoQuarantineTest,GlobalQuarantine)193 TEST(ScudoQuarantineTest, GlobalQuarantine) {
194 QuarantineT Quarantine;
195 CacheT Cache;
196 Cache.init();
197 Quarantine.init(MaxQuarantineSize, MaxCacheSize);
198 EXPECT_EQ(Quarantine.getMaxSize(), MaxQuarantineSize);
199 EXPECT_EQ(Quarantine.getCacheSize(), MaxCacheSize);
200
201 bool DrainOccurred = false;
202 scudo::uptr CacheSize = Cache.getSize();
203 EXPECT_EQ(Cache.getSize(), 0UL);
204 // We quarantine enough blocks that a drain has to occur. Verify this by
205 // looking for a decrease of the size of the cache.
206 for (scudo::uptr I = 0; I < 128UL; I++) {
207 Quarantine.put(&Cache, Cb, FakePtr, LargeBlockSize);
208 if (!DrainOccurred && Cache.getSize() < CacheSize)
209 DrainOccurred = true;
210 CacheSize = Cache.getSize();
211 }
212 EXPECT_TRUE(DrainOccurred);
213
214 Quarantine.drainAndRecycle(&Cache, Cb);
215 EXPECT_EQ(Cache.getSize(), 0UL);
216
217 scudo::ScopedString Str;
218 Quarantine.getStats(&Str);
219 Str.output();
220 }
221
222 struct PopulateQuarantineThread {
223 pthread_t Thread;
224 QuarantineT *Quarantine;
225 CacheT Cache;
226 };
227
populateQuarantine(void * Param)228 void *populateQuarantine(void *Param) {
229 PopulateQuarantineThread *P = static_cast<PopulateQuarantineThread *>(Param);
230 P->Cache.init();
231 for (scudo::uptr I = 0; I < 128UL; I++)
232 P->Quarantine->put(&P->Cache, Cb, FakePtr, LargeBlockSize);
233 return 0;
234 }
235
TEST(ScudoQuarantineTest,ThreadedGlobalQuarantine)236 TEST(ScudoQuarantineTest, ThreadedGlobalQuarantine) {
237 QuarantineT Quarantine;
238 Quarantine.init(MaxQuarantineSize, MaxCacheSize);
239
240 const scudo::uptr NumberOfThreads = 32U;
241 PopulateQuarantineThread T[NumberOfThreads];
242 for (scudo::uptr I = 0; I < NumberOfThreads; I++) {
243 T[I].Quarantine = &Quarantine;
244 pthread_create(&T[I].Thread, 0, populateQuarantine, &T[I]);
245 }
246 for (scudo::uptr I = 0; I < NumberOfThreads; I++)
247 pthread_join(T[I].Thread, 0);
248
249 scudo::ScopedString Str;
250 Quarantine.getStats(&Str);
251 Str.output();
252
253 for (scudo::uptr I = 0; I < NumberOfThreads; I++)
254 Quarantine.drainAndRecycle(&T[I].Cache, Cb);
255 }
256