xref: /llvm-project/compiler-rt/lib/scudo/standalone/tests/quarantine_test.cpp (revision 868317b3fd765830c07ecf16cbfcf6c7708adc3c)
13fa38318SNico Weber //===-- quarantine_test.cpp -------------------------------------*- C++ -*-===//
23fa38318SNico Weber //
33fa38318SNico Weber // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43fa38318SNico Weber // See https://llvm.org/LICENSE.txt for license information.
53fa38318SNico Weber // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63fa38318SNico Weber //
73fa38318SNico Weber //===----------------------------------------------------------------------===//
83fa38318SNico Weber 
90d3d4d3bSKostya Kortchinsky #include "tests/scudo_unit_test.h"
100d3d4d3bSKostya Kortchinsky 
113fa38318SNico Weber #include "quarantine.h"
123fa38318SNico Weber 
130d3d4d3bSKostya Kortchinsky #include <pthread.h>
143fa38318SNico Weber #include <stdlib.h>
153fa38318SNico Weber 
163fa38318SNico Weber static void *FakePtr = reinterpret_cast<void *>(0xFA83FA83);
173fa38318SNico Weber static const scudo::uptr BlockSize = 8UL;
183fa38318SNico Weber static const scudo::uptr LargeBlockSize = 16384UL;
193fa38318SNico Weber 
203fa38318SNico Weber struct QuarantineCallback {
recycleQuarantineCallback213fa38318SNico Weber   void recycle(void *P) { EXPECT_EQ(P, FakePtr); }
allocateQuarantineCallback223fa38318SNico Weber   void *allocate(scudo::uptr Size) { return malloc(Size); }
deallocateQuarantineCallback233fa38318SNico Weber   void deallocate(void *P) { free(P); }
243fa38318SNico Weber };
253fa38318SNico Weber 
263fa38318SNico Weber typedef scudo::GlobalQuarantine<QuarantineCallback, void> QuarantineT;
273fa38318SNico Weber typedef typename QuarantineT::CacheT CacheT;
283fa38318SNico Weber 
293fa38318SNico Weber static QuarantineCallback Cb;
303fa38318SNico Weber 
deallocateCache(CacheT * Cache)313fa38318SNico Weber static void deallocateCache(CacheT *Cache) {
323fa38318SNico Weber   while (scudo::QuarantineBatch *Batch = Cache->dequeueBatch())
333fa38318SNico Weber     Cb.deallocate(Batch);
343fa38318SNico Weber }
353fa38318SNico Weber 
TEST(ScudoQuarantineTest,QuarantineBatchMerge)363fa38318SNico Weber TEST(ScudoQuarantineTest, QuarantineBatchMerge) {
373fa38318SNico Weber   // Verify the trivial case.
383fa38318SNico Weber   scudo::QuarantineBatch Into;
393fa38318SNico Weber   Into.init(FakePtr, 4UL);
403fa38318SNico Weber   scudo::QuarantineBatch From;
413fa38318SNico Weber   From.init(FakePtr, 8UL);
423fa38318SNico Weber 
433fa38318SNico Weber   Into.merge(&From);
443fa38318SNico Weber 
453fa38318SNico Weber   EXPECT_EQ(Into.Count, 2UL);
463fa38318SNico Weber   EXPECT_EQ(Into.Batch[0], FakePtr);
473fa38318SNico Weber   EXPECT_EQ(Into.Batch[1], FakePtr);
483fa38318SNico Weber   EXPECT_EQ(Into.Size, 12UL + sizeof(scudo::QuarantineBatch));
493fa38318SNico Weber   EXPECT_EQ(Into.getQuarantinedSize(), 12UL);
503fa38318SNico Weber 
513fa38318SNico Weber   EXPECT_EQ(From.Count, 0UL);
523fa38318SNico Weber   EXPECT_EQ(From.Size, sizeof(scudo::QuarantineBatch));
533fa38318SNico Weber   EXPECT_EQ(From.getQuarantinedSize(), 0UL);
543fa38318SNico Weber 
553fa38318SNico Weber   // Merge the batch to the limit.
563fa38318SNico Weber   for (scudo::uptr I = 2; I < scudo::QuarantineBatch::MaxCount; ++I)
573fa38318SNico Weber     From.push_back(FakePtr, 8UL);
583fa38318SNico Weber   EXPECT_TRUE(Into.Count + From.Count == scudo::QuarantineBatch::MaxCount);
593fa38318SNico Weber   EXPECT_TRUE(Into.canMerge(&From));
603fa38318SNico Weber 
613fa38318SNico Weber   Into.merge(&From);
623fa38318SNico Weber   EXPECT_TRUE(Into.Count == scudo::QuarantineBatch::MaxCount);
633fa38318SNico Weber 
643fa38318SNico Weber   // No more space, not even for one element.
653fa38318SNico Weber   From.init(FakePtr, 8UL);
663fa38318SNico Weber 
673fa38318SNico Weber   EXPECT_FALSE(Into.canMerge(&From));
683fa38318SNico Weber }
693fa38318SNico Weber 
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesEmpty)703fa38318SNico Weber TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesEmpty) {
713fa38318SNico Weber   CacheT Cache;
723fa38318SNico Weber   CacheT ToDeallocate;
733fa38318SNico Weber   Cache.init();
743fa38318SNico Weber   ToDeallocate.init();
753fa38318SNico Weber   Cache.mergeBatches(&ToDeallocate);
763fa38318SNico Weber 
773fa38318SNico Weber   EXPECT_EQ(ToDeallocate.getSize(), 0UL);
783fa38318SNico Weber   EXPECT_EQ(ToDeallocate.dequeueBatch(), nullptr);
793fa38318SNico Weber }
803fa38318SNico Weber 
TEST(SanitizerCommon,QuarantineCacheMergeBatchesOneBatch)813fa38318SNico Weber TEST(SanitizerCommon, QuarantineCacheMergeBatchesOneBatch) {
823fa38318SNico Weber   CacheT Cache;
833fa38318SNico Weber   Cache.init();
843fa38318SNico Weber   Cache.enqueue(Cb, FakePtr, BlockSize);
853fa38318SNico Weber   EXPECT_EQ(BlockSize + sizeof(scudo::QuarantineBatch), Cache.getSize());
863fa38318SNico Weber 
873fa38318SNico Weber   CacheT ToDeallocate;
883fa38318SNico Weber   ToDeallocate.init();
893fa38318SNico Weber   Cache.mergeBatches(&ToDeallocate);
903fa38318SNico Weber 
913fa38318SNico Weber   // Nothing to merge, nothing to deallocate.
923fa38318SNico Weber   EXPECT_EQ(BlockSize + sizeof(scudo::QuarantineBatch), Cache.getSize());
933fa38318SNico Weber 
943fa38318SNico Weber   EXPECT_EQ(ToDeallocate.getSize(), 0UL);
953fa38318SNico Weber   EXPECT_EQ(ToDeallocate.dequeueBatch(), nullptr);
963fa38318SNico Weber 
973fa38318SNico Weber   deallocateCache(&Cache);
983fa38318SNico Weber }
993fa38318SNico Weber 
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesSmallBatches)1003fa38318SNico Weber TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesSmallBatches) {
1013fa38318SNico Weber   // Make a Cache with two batches small enough to merge.
1023fa38318SNico Weber   CacheT From;
1033fa38318SNico Weber   From.init();
1043fa38318SNico Weber   From.enqueue(Cb, FakePtr, BlockSize);
1053fa38318SNico Weber   CacheT Cache;
1063fa38318SNico Weber   Cache.init();
1073fa38318SNico Weber   Cache.enqueue(Cb, FakePtr, BlockSize);
1083fa38318SNico Weber 
1093fa38318SNico Weber   Cache.transfer(&From);
1103fa38318SNico Weber   EXPECT_EQ(BlockSize * 2 + sizeof(scudo::QuarantineBatch) * 2,
1113fa38318SNico Weber             Cache.getSize());
1123fa38318SNico Weber 
1133fa38318SNico Weber   CacheT ToDeallocate;
1143fa38318SNico Weber   ToDeallocate.init();
1153fa38318SNico Weber   Cache.mergeBatches(&ToDeallocate);
1163fa38318SNico Weber 
1173fa38318SNico Weber   // Batches merged, one batch to deallocate.
1183fa38318SNico Weber   EXPECT_EQ(BlockSize * 2 + sizeof(scudo::QuarantineBatch), Cache.getSize());
1193fa38318SNico Weber   EXPECT_EQ(ToDeallocate.getSize(), sizeof(scudo::QuarantineBatch));
1203fa38318SNico Weber 
1213fa38318SNico Weber   deallocateCache(&Cache);
1223fa38318SNico Weber   deallocateCache(&ToDeallocate);
1233fa38318SNico Weber }
1243fa38318SNico Weber 
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesTooBigToMerge)1253fa38318SNico Weber TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesTooBigToMerge) {
1263fa38318SNico Weber   const scudo::uptr NumBlocks = scudo::QuarantineBatch::MaxCount - 1;
1273fa38318SNico Weber 
1283fa38318SNico Weber   // Make a Cache with two batches small enough to merge.
1293fa38318SNico Weber   CacheT From;
1303fa38318SNico Weber   CacheT Cache;
1313fa38318SNico Weber   From.init();
1323fa38318SNico Weber   Cache.init();
1333fa38318SNico Weber   for (scudo::uptr I = 0; I < NumBlocks; ++I) {
1343fa38318SNico Weber     From.enqueue(Cb, FakePtr, BlockSize);
1353fa38318SNico Weber     Cache.enqueue(Cb, FakePtr, BlockSize);
1363fa38318SNico Weber   }
1373fa38318SNico Weber   Cache.transfer(&From);
1383fa38318SNico Weber   EXPECT_EQ(BlockSize * NumBlocks * 2 + sizeof(scudo::QuarantineBatch) * 2,
1393fa38318SNico Weber             Cache.getSize());
1403fa38318SNico Weber 
1413fa38318SNico Weber   CacheT ToDeallocate;
1423fa38318SNico Weber   ToDeallocate.init();
1433fa38318SNico Weber   Cache.mergeBatches(&ToDeallocate);
1443fa38318SNico Weber 
1453fa38318SNico Weber   // Batches cannot be merged.
1463fa38318SNico Weber   EXPECT_EQ(BlockSize * NumBlocks * 2 + sizeof(scudo::QuarantineBatch) * 2,
1473fa38318SNico Weber             Cache.getSize());
1483fa38318SNico Weber   EXPECT_EQ(ToDeallocate.getSize(), 0UL);
1493fa38318SNico Weber 
1503fa38318SNico Weber   deallocateCache(&Cache);
1513fa38318SNico Weber }
1523fa38318SNico Weber 
TEST(ScudoQuarantineTest,QuarantineCacheMergeBatchesALotOfBatches)1533fa38318SNico Weber TEST(ScudoQuarantineTest, QuarantineCacheMergeBatchesALotOfBatches) {
1543fa38318SNico Weber   const scudo::uptr NumBatchesAfterMerge = 3;
1553fa38318SNico Weber   const scudo::uptr NumBlocks =
1563fa38318SNico Weber       scudo::QuarantineBatch::MaxCount * NumBatchesAfterMerge;
1573fa38318SNico Weber   const scudo::uptr NumBatchesBeforeMerge = NumBlocks;
1583fa38318SNico Weber 
1593fa38318SNico Weber   // Make a Cache with many small batches.
1603fa38318SNico Weber   CacheT Cache;
1613fa38318SNico Weber   Cache.init();
1623fa38318SNico Weber   for (scudo::uptr I = 0; I < NumBlocks; ++I) {
1633fa38318SNico Weber     CacheT From;
1643fa38318SNico Weber     From.init();
1653fa38318SNico Weber     From.enqueue(Cb, FakePtr, BlockSize);
1663fa38318SNico Weber     Cache.transfer(&From);
1673fa38318SNico Weber   }
1683fa38318SNico Weber 
1693fa38318SNico Weber   EXPECT_EQ(BlockSize * NumBlocks +
1703fa38318SNico Weber                 sizeof(scudo::QuarantineBatch) * NumBatchesBeforeMerge,
1713fa38318SNico Weber             Cache.getSize());
1723fa38318SNico Weber 
1733fa38318SNico Weber   CacheT ToDeallocate;
1743fa38318SNico Weber   ToDeallocate.init();
1753fa38318SNico Weber   Cache.mergeBatches(&ToDeallocate);
1763fa38318SNico Weber 
1773fa38318SNico Weber   // All blocks should fit Into 3 batches.
1783fa38318SNico Weber   EXPECT_EQ(BlockSize * NumBlocks +
1793fa38318SNico Weber                 sizeof(scudo::QuarantineBatch) * NumBatchesAfterMerge,
1803fa38318SNico Weber             Cache.getSize());
1813fa38318SNico Weber 
1823fa38318SNico Weber   EXPECT_EQ(ToDeallocate.getSize(),
1833fa38318SNico Weber             sizeof(scudo::QuarantineBatch) *
1843fa38318SNico Weber                 (NumBatchesBeforeMerge - NumBatchesAfterMerge));
1853fa38318SNico Weber 
1863fa38318SNico Weber   deallocateCache(&Cache);
1873fa38318SNico Weber   deallocateCache(&ToDeallocate);
1883fa38318SNico Weber }
1893fa38318SNico Weber 
1903fa38318SNico Weber static const scudo::uptr MaxQuarantineSize = 1024UL << 10; // 1MB
1913fa38318SNico Weber static const scudo::uptr MaxCacheSize = 256UL << 10;       // 256KB
1923fa38318SNico Weber 
TEST(ScudoQuarantineTest,GlobalQuarantine)1933fa38318SNico Weber TEST(ScudoQuarantineTest, GlobalQuarantine) {
1943fa38318SNico Weber   QuarantineT Quarantine;
1953fa38318SNico Weber   CacheT Cache;
1963fa38318SNico Weber   Cache.init();
1973fa38318SNico Weber   Quarantine.init(MaxQuarantineSize, MaxCacheSize);
1983fa38318SNico Weber   EXPECT_EQ(Quarantine.getMaxSize(), MaxQuarantineSize);
1993fa38318SNico Weber   EXPECT_EQ(Quarantine.getCacheSize(), MaxCacheSize);
2003fa38318SNico Weber 
2013fa38318SNico Weber   bool DrainOccurred = false;
2023fa38318SNico Weber   scudo::uptr CacheSize = Cache.getSize();
2033fa38318SNico Weber   EXPECT_EQ(Cache.getSize(), 0UL);
2043fa38318SNico Weber   // We quarantine enough blocks that a drain has to occur. Verify this by
2053fa38318SNico Weber   // looking for a decrease of the size of the cache.
2063fa38318SNico Weber   for (scudo::uptr I = 0; I < 128UL; I++) {
2073fa38318SNico Weber     Quarantine.put(&Cache, Cb, FakePtr, LargeBlockSize);
2083fa38318SNico Weber     if (!DrainOccurred && Cache.getSize() < CacheSize)
2093fa38318SNico Weber       DrainOccurred = true;
2103fa38318SNico Weber     CacheSize = Cache.getSize();
2113fa38318SNico Weber   }
2123fa38318SNico Weber   EXPECT_TRUE(DrainOccurred);
2133fa38318SNico Weber 
2143fa38318SNico Weber   Quarantine.drainAndRecycle(&Cache, Cb);
2153fa38318SNico Weber   EXPECT_EQ(Cache.getSize(), 0UL);
2163fa38318SNico Weber 
217*868317b3SKostya Kortchinsky   scudo::ScopedString Str;
218f7b1489fSKostya Kortchinsky   Quarantine.getStats(&Str);
219f7b1489fSKostya Kortchinsky   Str.output();
2203fa38318SNico Weber }
2213fa38318SNico Weber 
2226ef07111SRoland McGrath struct PopulateQuarantineThread {
2236ef07111SRoland McGrath   pthread_t Thread;
2246ef07111SRoland McGrath   QuarantineT *Quarantine;
2253fa38318SNico Weber   CacheT Cache;
2266ef07111SRoland McGrath };
2276ef07111SRoland McGrath 
populateQuarantine(void * Param)2286ef07111SRoland McGrath void *populateQuarantine(void *Param) {
2296ef07111SRoland McGrath   PopulateQuarantineThread *P = static_cast<PopulateQuarantineThread *>(Param);
2306ef07111SRoland McGrath   P->Cache.init();
2313fa38318SNico Weber   for (scudo::uptr I = 0; I < 128UL; I++)
2326ef07111SRoland McGrath     P->Quarantine->put(&P->Cache, Cb, FakePtr, LargeBlockSize);
2333fa38318SNico Weber   return 0;
2343fa38318SNico Weber }
2353fa38318SNico Weber 
TEST(ScudoQuarantineTest,ThreadedGlobalQuarantine)2363fa38318SNico Weber TEST(ScudoQuarantineTest, ThreadedGlobalQuarantine) {
2373fa38318SNico Weber   QuarantineT Quarantine;
2383fa38318SNico Weber   Quarantine.init(MaxQuarantineSize, MaxCacheSize);
2393fa38318SNico Weber 
2403fa38318SNico Weber   const scudo::uptr NumberOfThreads = 32U;
2416ef07111SRoland McGrath   PopulateQuarantineThread T[NumberOfThreads];
2426ef07111SRoland McGrath   for (scudo::uptr I = 0; I < NumberOfThreads; I++) {
2436ef07111SRoland McGrath     T[I].Quarantine = &Quarantine;
2446ef07111SRoland McGrath     pthread_create(&T[I].Thread, 0, populateQuarantine, &T[I]);
2456ef07111SRoland McGrath   }
2463fa38318SNico Weber   for (scudo::uptr I = 0; I < NumberOfThreads; I++)
2476ef07111SRoland McGrath     pthread_join(T[I].Thread, 0);
2483fa38318SNico Weber 
249*868317b3SKostya Kortchinsky   scudo::ScopedString Str;
250f7b1489fSKostya Kortchinsky   Quarantine.getStats(&Str);
251f7b1489fSKostya Kortchinsky   Str.output();
2526ef07111SRoland McGrath 
2536ef07111SRoland McGrath   for (scudo::uptr I = 0; I < NumberOfThreads; I++)
2546ef07111SRoland McGrath     Quarantine.drainAndRecycle(&T[I].Cache, Cb);
2553fa38318SNico Weber }
256