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