blob: 5601c0054f18f762e15f0be485a8d7cfe06dfd23 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef COUNT_NEW_HPP
#define COUNT_NEW_HPP
# include <cstdlib>
# include <cassert>
# include <new>
#include "test_macros.h"
#if defined(TEST_HAS_SANITIZERS)
#define DISABLE_NEW_COUNT
#endif
namespace detail
{
TEST_NORETURN
inline void throw_bad_alloc_helper() {
#ifndef TEST_HAS_NO_EXCEPTIONS
throw std::bad_alloc();
#else
std::abort();
#endif
}
}
class MemCounter
{
public:
// Make MemCounter super hard to accidentally construct or copy.
class MemCounterCtorArg_ {};
explicit MemCounter(MemCounterCtorArg_) { reset(); }
private:
MemCounter(MemCounter const &);
MemCounter & operator=(MemCounter const &);
public:
// All checks return true when disable_checking is enabled.
static const bool disable_checking;
// Disallow any allocations from occurring. Useful for testing that
// code doesn't perform any allocations.
bool disable_allocations;
// number of allocations to throw after. Default (unsigned)-1. If
// throw_after has the default value it will never be decremented.
static const unsigned never_throw_value = static_cast<unsigned>(-1);
unsigned throw_after;
int outstanding_new;
int new_called;
int delete_called;
int aligned_new_called;
int aligned_delete_called;
std::size_t last_new_size;
std::size_t last_new_align;
std::size_t last_delete_align;
int outstanding_array_new;
int new_array_called;
int delete_array_called;
int aligned_new_array_called;
int aligned_delete_array_called;
std::size_t last_new_array_size;
std::size_t last_new_array_align;
std::size_t last_delete_array_align;
public:
void newCalled(std::size_t s)
{
assert(disable_allocations == false);
assert(s);
if (throw_after == 0) {
throw_after = never_throw_value;
detail::throw_bad_alloc_helper();
} else if (throw_after != never_throw_value) {
--throw_after;
}
++new_called;
++outstanding_new;
last_new_size = s;
}
void alignedNewCalled(std::size_t s, std::size_t a) {
newCalled(s);
++aligned_new_called;
last_new_align = a;
}
void deleteCalled(void * p)
{
assert(p);
--outstanding_new;
++delete_called;
}
void alignedDeleteCalled(void *p, std::size_t a) {
deleteCalled(p);
++aligned_delete_called;
last_delete_align = a;
}
void newArrayCalled(std::size_t s)
{
assert(disable_allocations == false);
assert(s);
if (throw_after == 0) {
throw_after = never_throw_value;
detail::throw_bad_alloc_helper();
} else {
// don't decrement throw_after here. newCalled will end up doing that.
}
++outstanding_array_new;
++new_array_called;
last_new_array_size = s;
}
void alignedNewArrayCalled(std::size_t s, std::size_t a) {
newArrayCalled(s);
++aligned_new_array_called;
last_new_array_align = a;
}
void deleteArrayCalled(void * p)
{
assert(p);
--outstanding_array_new;
++delete_array_called;
}
void alignedDeleteArrayCalled(void * p, std::size_t a) {
deleteArrayCalled(p);
++aligned_delete_array_called;
last_delete_array_align = a;
}
void disableAllocations()
{
disable_allocations = true;
}
void enableAllocations()
{
disable_allocations = false;
}
void reset()
{
disable_allocations = false;
throw_after = never_throw_value;
outstanding_new = 0;
new_called = 0;
delete_called = 0;
aligned_new_called = 0;
aligned_delete_called = 0;
last_new_size = 0;
last_new_align = 0;
outstanding_array_new = 0;
new_array_called = 0;
delete_array_called = 0;
aligned_new_array_called = 0;
aligned_delete_array_called = 0;
last_new_array_size = 0;
last_new_array_align = 0;
}
public:
bool checkOutstandingNewEq(int n) const
{
return disable_checking || n == outstanding_new;
}
bool checkOutstandingNewNotEq(int n) const
{
return disable_checking || n != outstanding_new;
}
bool checkNewCalledEq(int n) const
{
return disable_checking || n == new_called;
}
bool checkNewCalledNotEq(int n) const
{
return disable_checking || n != new_called;
}
bool checkNewCalledGreaterThan(int n) const
{
return disable_checking || new_called > n;
}
bool checkDeleteCalledEq(int n) const
{
return disable_checking || n == delete_called;
}
bool checkDeleteCalledNotEq(int n) const
{
return disable_checking || n != delete_called;
}
bool checkAlignedNewCalledEq(int n) const
{
return disable_checking || n == aligned_new_called;
}
bool checkAlignedNewCalledNotEq(int n) const
{
return disable_checking || n != aligned_new_called;
}
bool checkAlignedNewCalledGreaterThan(int n) const
{
return disable_checking || aligned_new_called > n;
}
bool checkAlignedDeleteCalledEq(int n) const
{
return disable_checking || n == aligned_delete_called;
}
bool checkAlignedDeleteCalledNotEq(int n) const
{
return disable_checking || n != aligned_delete_called;
}
bool checkLastNewSizeEq(std::size_t n) const
{
return disable_checking || n == last_new_size;
}
bool checkLastNewSizeNotEq(std::size_t n) const
{
return disable_checking || n != last_new_size;
}
bool checkLastNewAlignEq(std::size_t n) const
{
return disable_checking || n == last_new_align;
}
bool checkLastNewAlignNotEq(std::size_t n) const
{
return disable_checking || n != last_new_align;
}
bool checkLastDeleteAlignEq(std::size_t n) const
{
return disable_checking || n == last_delete_align;
}
bool checkLastDeleteAlignNotEq(std::size_t n) const
{
return disable_checking || n != last_delete_align;
}
bool checkOutstandingArrayNewEq(int n) const
{
return disable_checking || n == outstanding_array_new;
}
bool checkOutstandingArrayNewNotEq(int n) const
{
return disable_checking || n != outstanding_array_new;
}
bool checkNewArrayCalledEq(int n) const
{
return disable_checking || n == new_array_called;
}
bool checkNewArrayCalledNotEq(int n) const
{
return disable_checking || n != new_array_called;
}
bool checkDeleteArrayCalledEq(int n) const
{
return disable_checking || n == delete_array_called;
}
bool checkDeleteArrayCalledNotEq(int n) const
{
return disable_checking || n != delete_array_called;
}
bool checkAlignedNewArrayCalledEq(int n) const
{
return disable_checking || n == aligned_new_array_called;
}
bool checkAlignedNewArrayCalledNotEq(int n) const
{
return disable_checking || n != aligned_new_array_called;
}
bool checkAlignedNewArrayCalledGreaterThan(int n) const
{
return disable_checking || aligned_new_array_called > n;
}
bool checkAlignedDeleteArrayCalledEq(int n) const
{
return disable_checking || n == aligned_delete_array_called;
}
bool checkAlignedDeleteArrayCalledNotEq(int n) const
{
return disable_checking || n != aligned_delete_array_called;
}
bool checkLastNewArraySizeEq(std::size_t n) const
{
return disable_checking || n == last_new_array_size;
}
bool checkLastNewArraySizeNotEq(std::size_t n) const
{
return disable_checking || n != last_new_array_size;
}
bool checkLastNewArrayAlignEq(std::size_t n) const
{
return disable_checking || n == last_new_array_align;
}
bool checkLastNewArrayAlignNotEq(std::size_t n) const
{
return disable_checking || n != last_new_array_align;
}
};
#ifdef DISABLE_NEW_COUNT
const bool MemCounter::disable_checking = true;
#else
const bool MemCounter::disable_checking = false;
#endif
inline MemCounter* getGlobalMemCounter() {
static MemCounter counter((MemCounter::MemCounterCtorArg_()));
return &counter;
}
MemCounter &globalMemCounter = *getGlobalMemCounter();
#ifndef DISABLE_NEW_COUNT
void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc)
{
getGlobalMemCounter()->newCalled(s);
void* ret = std::malloc(s);
if (ret == nullptr)
detail::throw_bad_alloc_helper();
return ret;
}
void operator delete(void* p) TEST_NOEXCEPT
{
getGlobalMemCounter()->deleteCalled(p);
std::free(p);
}
void* operator new[](std::size_t s) TEST_THROW_SPEC(std::bad_alloc)
{
getGlobalMemCounter()->newArrayCalled(s);
return operator new(s);
}
void operator delete[](void* p) TEST_NOEXCEPT
{
getGlobalMemCounter()->deleteArrayCalled(p);
operator delete(p);
}
#ifndef TEST_HAS_NO_ALIGNED_ALLOCATION
#if defined(_LIBCPP_MSVCRT_LIKE) || \
(!defined(_LIBCPP_VERSION) && defined(_WIN32))
#define USE_ALIGNED_ALLOC
#endif
void* operator new(std::size_t s, std::align_val_t av) TEST_THROW_SPEC(std::bad_alloc) {
const std::size_t a = static_cast<std::size_t>(av);
getGlobalMemCounter()->alignedNewCalled(s, a);
void *ret;
#ifdef USE_ALIGNED_ALLOC
ret = _aligned_malloc(s, a);
#else
posix_memalign(&ret, a, s);
#endif
if (ret == nullptr)
detail::throw_bad_alloc_helper();
return ret;
}
void operator delete(void *p, std::align_val_t av) TEST_NOEXCEPT {
const std::size_t a = static_cast<std::size_t>(av);
getGlobalMemCounter()->alignedDeleteCalled(p, a);
if (p) {
#ifdef USE_ALIGNED_ALLOC
::_aligned_free(p);
#else
::free(p);
#endif
}
}
void* operator new[](std::size_t s, std::align_val_t av) TEST_THROW_SPEC(std::bad_alloc) {
const std::size_t a = static_cast<std::size_t>(av);
getGlobalMemCounter()->alignedNewArrayCalled(s, a);
return operator new(s, av);
}
void operator delete[](void *p, std::align_val_t av) TEST_NOEXCEPT {
const std::size_t a = static_cast<std::size_t>(av);
getGlobalMemCounter()->alignedDeleteArrayCalled(p, a);
return operator delete(p, av);
}
#endif // TEST_HAS_NO_ALIGNED_ALLOCATION
#endif // DISABLE_NEW_COUNT
struct DisableAllocationGuard {
explicit DisableAllocationGuard(bool disable = true) : m_disabled(disable)
{
// Don't re-disable if already disabled.
if (globalMemCounter.disable_allocations == true) m_disabled = false;
if (m_disabled) globalMemCounter.disableAllocations();
}
void release() {
if (m_disabled) globalMemCounter.enableAllocations();
m_disabled = false;
}
~DisableAllocationGuard() {
release();
}
private:
bool m_disabled;
DisableAllocationGuard(DisableAllocationGuard const&);
DisableAllocationGuard& operator=(DisableAllocationGuard const&);
};
struct RequireAllocationGuard {
explicit RequireAllocationGuard(std::size_t RequireAtLeast = 1)
: m_req_alloc(RequireAtLeast),
m_new_count_on_init(globalMemCounter.new_called),
m_outstanding_new_on_init(globalMemCounter.outstanding_new),
m_exactly(false)
{
}
void requireAtLeast(std::size_t N) { m_req_alloc = N; m_exactly = false; }
void requireExactly(std::size_t N) { m_req_alloc = N; m_exactly = true; }
~RequireAllocationGuard() {
assert(globalMemCounter.checkOutstandingNewEq(static_cast<int>(m_outstanding_new_on_init)));
std::size_t Expect = m_new_count_on_init + m_req_alloc;
assert(globalMemCounter.checkNewCalledEq(static_cast<int>(Expect)) ||
(!m_exactly && globalMemCounter.checkNewCalledGreaterThan(static_cast<int>(Expect))));
}
private:
std::size_t m_req_alloc;
const std::size_t m_new_count_on_init;
const std::size_t m_outstanding_new_on_init;
bool m_exactly;
RequireAllocationGuard(RequireAllocationGuard const&);
RequireAllocationGuard& operator=(RequireAllocationGuard const&);
};
#endif /* COUNT_NEW_HPP */