//===-- sanitizer_quarantine_test.cpp -------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of ThreadSanitizer/AddressSanitizer runtime.
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_quarantine.h"
#include "gtest/gtest.h"
#include <stdlib.h>
namespace __sanitizer {
struct QuarantineCallback {
void Recycle(void *m) {}
void *Allocate(uptr size) {
return malloc(size);
}
void Deallocate(void *p) {
free(p);
}
};
typedef QuarantineCache<QuarantineCallback> Cache;
static void* kFakePtr = reinterpret_cast<void*>(0xFA83FA83);
static const size_t kBlockSize = 8;
static QuarantineCallback cb;
static void DeallocateCache(Cache *cache) {
while (QuarantineBatch *batch = cache->DequeueBatch())
cb.Deallocate(batch);
}
TEST(SanitizerCommon, QuarantineBatchMerge) {
// Verify the trivial case.
QuarantineBatch into;
into.init(kFakePtr, 4UL);
QuarantineBatch from;
from.init(kFakePtr, 8UL);
into.merge(&from);
ASSERT_EQ(into.count, 2UL);
ASSERT_EQ(into.batch[0], kFakePtr);
ASSERT_EQ(into.batch[1], kFakePtr);
ASSERT_EQ(into.size, 12UL + sizeof(QuarantineBatch));
ASSERT_EQ(into.quarantined_size(), 12UL);
ASSERT_EQ(from.count, 0UL);
ASSERT_EQ(from.size, sizeof(QuarantineBatch));
ASSERT_EQ(from.quarantined_size(), 0UL);
// Merge the batch to the limit.
for (uptr i = 2; i < QuarantineBatch::kSize; ++i)
from.push_back(kFakePtr, 8UL);
ASSERT_TRUE(into.count + from.count == QuarantineBatch::kSize);
ASSERT_TRUE(into.can_merge(&from));
into.merge(&from);
ASSERT_TRUE(into.count == QuarantineBatch::kSize);
// No more space, not even for one element.
from.init(kFakePtr, 8UL);
ASSERT_FALSE(into.can_merge(&from));
}
TEST(SanitizerCommon, QuarantineCacheMergeBatchesEmpty) {
Cache cache;
Cache to_deallocate;
cache.MergeBatches(&to_deallocate);
ASSERT_EQ(to_deallocate.Size(), 0UL);
ASSERT_EQ(to_deallocate.DequeueBatch(), nullptr);
}
TEST(SanitizerCommon, QuarantineCacheMergeBatchesOneBatch) {
Cache cache;
cache.Enqueue(cb, kFakePtr, kBlockSize);
ASSERT_EQ(kBlockSize + sizeof(QuarantineBatch), cache.Size());
Cache to_deallocate;
cache.MergeBatches(&to_deallocate);
// Nothing to merge, nothing to deallocate.
ASSERT_EQ(kBlockSize + sizeof(QuarantineBatch), cache.Size());
ASSERT_EQ(to_deallocate.Size(), 0UL);
ASSERT_EQ(to_deallocate.DequeueBatch(), nullptr);
DeallocateCache(&cache);
}
TEST(SanitizerCommon, QuarantineCacheMergeBatchesSmallBatches) {
// Make a cache with two batches small enough to merge.
Cache from;
from.Enqueue(cb, kFakePtr, kBlockSize);
Cache cache;
cache.Enqueue(cb, kFakePtr, kBlockSize);
cache.Transfer(&from);
ASSERT_EQ(kBlockSize * 2 + sizeof(QuarantineBatch) * 2, cache.Size());
Cache to_deallocate;
cache.MergeBatches(&to_deallocate);
// Batches merged, one batch to deallocate.
ASSERT_EQ(kBlockSize * 2 + sizeof(QuarantineBatch), cache.Size());
ASSERT_EQ(to_deallocate.Size(), sizeof(QuarantineBatch));
DeallocateCache(&cache);
DeallocateCache(&to_deallocate);
}
TEST(SanitizerCommon, QuarantineCacheMergeBatchesTooBigToMerge) {
const uptr kNumBlocks = QuarantineBatch::kSize - 1;
// Make a cache with two batches small enough to merge.
Cache from;
Cache cache;
for (uptr i = 0; i < kNumBlocks; ++i) {
from.Enqueue(cb, kFakePtr, kBlockSize);
cache.Enqueue(cb, kFakePtr, kBlockSize);
}
cache.Transfer(&from);
ASSERT_EQ(kBlockSize * kNumBlocks * 2 +
sizeof(QuarantineBatch) * 2, cache.Size());
Cache to_deallocate;
cache.MergeBatches(&to_deallocate);
// Batches cannot be merged.
ASSERT_EQ(kBlockSize * kNumBlocks * 2 +
sizeof(QuarantineBatch) * 2, cache.Size());
ASSERT_EQ(to_deallocate.Size(), 0UL);
DeallocateCache(&cache);
}
TEST(SanitizerCommon, QuarantineCacheMergeBatchesALotOfBatches) {
const uptr kNumBatchesAfterMerge = 3;
const uptr kNumBlocks = QuarantineBatch::kSize * kNumBatchesAfterMerge;
const uptr kNumBatchesBeforeMerge = kNumBlocks;
// Make a cache with many small batches.
Cache cache;
for (uptr i = 0; i < kNumBlocks; ++i) {
Cache from;
from.Enqueue(cb, kFakePtr, kBlockSize);
cache.Transfer(&from);
}
ASSERT_EQ(kBlockSize * kNumBlocks +
sizeof(QuarantineBatch) * kNumBatchesBeforeMerge, cache.Size());
Cache to_deallocate;
cache.MergeBatches(&to_deallocate);
// All blocks should fit into 3 batches.
ASSERT_EQ(kBlockSize * kNumBlocks +
sizeof(QuarantineBatch) * kNumBatchesAfterMerge, cache.Size());
ASSERT_EQ(to_deallocate.Size(),
sizeof(QuarantineBatch) *
(kNumBatchesBeforeMerge - kNumBatchesAfterMerge));
DeallocateCache(&cache);
DeallocateCache(&to_deallocate);
}
} // namespace __sanitizer