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