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); }
23  void deallocate(void *P) { free(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(Batch);
34}
35
36TEST(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
70TEST(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
81TEST(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
100TEST(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
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, 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
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, 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
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(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
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(&P->Cache, Cb, FakePtr, LargeBlockSize);
233  return 0;
234}
235
236TEST(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