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