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