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