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
16static void *FakePtr = reinterpret_cast<void *>(0xFA83FA83);
17static const scudo::uptr BlockSize = 8UL;
18static const scudo::uptr LargeBlockSize = 16384UL;
19
20struct QuarantineCallback {
21 void recycle(void *P) { EXPECT_EQ(P, FakePtr); }
22 void *allocate(scudo::uptr Size) { return malloc(size: Size); }
23 void deallocate(void *P) { free(ptr: P); }
24};
25
26typedef scudo::GlobalQuarantine<QuarantineCallback, void> QuarantineT;
27typedef typename QuarantineT::CacheT CacheT;
28
29static QuarantineCallback Cb;
30
31static void deallocateCache(CacheT *Cache) {
32 while (scudo::QuarantineBatch *Batch = Cache->dequeueBatch())
33 Cb.deallocate(P: Batch);
34}
35
36TEST(ScudoQuarantineTest, QuarantineBatchMerge) {
37 // Verify the trivial case.
38 scudo::QuarantineBatch Into;
39 Into.init(Ptr: FakePtr, Size: 4UL);
40 scudo::QuarantineBatch From;
41 From.init(Ptr: FakePtr, Size: 8UL);
42
43 Into.merge(From: &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(Ptr: FakePtr, Size: 8UL);
58 EXPECT_TRUE(Into.Count + From.Count == scudo::QuarantineBatch::MaxCount);
59 EXPECT_TRUE(Into.canMerge(From: &From));
60
61 Into.merge(From: &From);
62 EXPECT_TRUE(Into.Count == scudo::QuarantineBatch::MaxCount);
63
64 // No more space, not even for one element.
65 From.init(Ptr: FakePtr, Size: 8UL);
66
67 EXPECT_FALSE(Into.canMerge(From: &From));
68}
69
70TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesEmpty) {
71 CacheT Cache;
72 CacheT ToDeallocate;
73 Cache.init();
74 ToDeallocate.init();
75 Cache.mergeBatches(ToDeallocate: &ToDeallocate);
76
77 EXPECT_EQ(ToDeallocate.getSize(), 0UL);
78 EXPECT_EQ(ToDeallocate.dequeueBatch(), nullptr);
79}
80
81TEST(SanitizerCommon, QuarantineCacheMergeBatchesOneBatch) {
82 CacheT Cache;
83 Cache.init();
84 Cache.enqueue(Cb, Ptr: FakePtr, Size: BlockSize);
85 EXPECT_EQ(BlockSize + sizeof(scudo::QuarantineBatch), Cache.getSize());
86
87 CacheT ToDeallocate;
88 ToDeallocate.init();
89 Cache.mergeBatches(ToDeallocate: &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: &Cache);
98}
99
100TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesSmallBatches) {
101 // Make a Cache with two batches small enough to merge.
102 CacheT From;
103 From.init();
104 From.enqueue(Cb, Ptr: FakePtr, Size: BlockSize);
105 CacheT Cache;
106 Cache.init();
107 Cache.enqueue(Cb, Ptr: FakePtr, Size: BlockSize);
108
109 Cache.transfer(From: &From);
110 EXPECT_EQ(BlockSize * 2 + sizeof(scudo::QuarantineBatch) * 2,
111 Cache.getSize());
112
113 CacheT ToDeallocate;
114 ToDeallocate.init();
115 Cache.mergeBatches(ToDeallocate: &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: &Cache);
122 deallocateCache(Cache: &ToDeallocate);
123}
124
125TEST(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, Ptr: FakePtr, Size: BlockSize);
135 Cache.enqueue(Cb, Ptr: FakePtr, Size: BlockSize);
136 }
137 Cache.transfer(From: &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: &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: &Cache);
151}
152
153TEST(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, Ptr: FakePtr, Size: BlockSize);
166 Cache.transfer(From: &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: &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: &Cache);
187 deallocateCache(Cache: &ToDeallocate);
188}
189
190static const scudo::uptr MaxQuarantineSize = 1024UL << 10; // 1MB
191static const scudo::uptr MaxCacheSize = 256UL << 10; // 256KB
192
193TEST(ScudoQuarantineTest, GlobalQuarantine) {
194 QuarantineT Quarantine;
195 CacheT Cache;
196 Cache.init();
197 Quarantine.init(Size: MaxQuarantineSize, CacheSize: 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(C: &Cache, Cb, Ptr: FakePtr, Size: LargeBlockSize);
208 if (!DrainOccurred && Cache.getSize() < CacheSize)
209 DrainOccurred = true;
210 CacheSize = Cache.getSize();
211 }
212 EXPECT_TRUE(DrainOccurred);
213
214 Quarantine.drainAndRecycle(C: &Cache, Cb);
215 EXPECT_EQ(Cache.getSize(), 0UL);
216
217 scudo::ScopedString Str;
218 Quarantine.getStats(Str: &Str);
219 Str.output();
220}
221
222struct PopulateQuarantineThread {
223 pthread_t Thread;
224 QuarantineT *Quarantine;
225 CacheT Cache;
226};
227
228void *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(C: &P->Cache, Cb, Ptr: FakePtr, Size: LargeBlockSize);
233 return 0;
234}
235
236TEST(ScudoQuarantineTest, ThreadedGlobalQuarantine) {
237 QuarantineT Quarantine;
238 Quarantine.init(Size: MaxQuarantineSize, CacheSize: 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(newthread: &T[I].Thread, attr: 0, start_routine: populateQuarantine, arg: &T[I]);
245 }
246 for (scudo::uptr I = 0; I < NumberOfThreads; I++)
247 pthread_join(th: T[I].Thread, thread_return: 0);
248
249 scudo::ScopedString Str;
250 Quarantine.getStats(Str: &Str);
251 Str.output();
252
253 for (scudo::uptr I = 0; I < NumberOfThreads; I++)
254 Quarantine.drainAndRecycle(C: &T[I].Cache, Cb);
255}
256

source code of compiler-rt/lib/scudo/standalone/tests/quarantine_test.cpp