//===----------------------------------------------------------------------===// // // 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_H #define COUNT_NEW_H #include #include #include #include #include #include #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(-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); 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); 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 checkOutstandingNewLessThanOrEqual(int n) const { return disable_checking || outstanding_new <= n; } 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 checkDeleteCalledGreaterThan(int n) const { return disable_checking || delete_called > n; } 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 checkLastNewSizeGe(std::size_t n) const { return disable_checking || last_new_size >= n; } 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 checkLastNewAlignGe(std::size_t n) const { return disable_checking || last_new_align >= n; } 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 TEST_DIAGNOSTIC_PUSH TEST_MSVC_DIAGNOSTIC_IGNORED(4640) // '%s' construction of local static object is not thread safe (/Zc:threadSafeInit-) inline MemCounter* getGlobalMemCounter() { static MemCounter counter((MemCounter::MemCounterCtorArg_())); return &counter; } TEST_DIAGNOSTIC_POP MemCounter &globalMemCounter = *getGlobalMemCounter(); #ifndef DISABLE_NEW_COUNT // operator new(size_t[, nothrow_t]) and operator delete(size_t[, nothrow_t]) void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc) { getGlobalMemCounter()->newCalled(s); void* p = std::malloc(s); if (p == nullptr) detail::throw_bad_alloc_helper(); return p; } void* operator new(std::size_t s, std::nothrow_t const&) TEST_NOEXCEPT { # ifdef TEST_HAS_NO_EXCEPTIONS getGlobalMemCounter()->newCalled(s); # else try { getGlobalMemCounter()->newCalled(s); } catch (std::bad_alloc const&) { return nullptr; } # endif return std::malloc(s); } void operator delete(void* p) TEST_NOEXCEPT { getGlobalMemCounter()->deleteCalled(p); std::free(p); } void operator delete(void* p, std::nothrow_t const&) TEST_NOEXCEPT { getGlobalMemCounter()->deleteCalled(p); std::free(p); } // operator new[](size_t[, nothrow_t]) and operator delete[](size_t[, nothrow_t]) void* operator new[](std::size_t s) TEST_THROW_SPEC(std::bad_alloc) { getGlobalMemCounter()->newArrayCalled(s); void* p = std::malloc(s); if (p == nullptr) detail::throw_bad_alloc_helper(); return p; } void* operator new[](std::size_t s, std::nothrow_t const&) TEST_NOEXCEPT { # ifdef TEST_HAS_NO_EXCEPTIONS getGlobalMemCounter()->newArrayCalled(s); # else try { getGlobalMemCounter()->newArrayCalled(s); } catch (std::bad_alloc const&) { return nullptr; } # endif return std::malloc(s); } void operator delete[](void* p) TEST_NOEXCEPT { getGlobalMemCounter()->deleteArrayCalled(p); std::free(p); } void operator delete[](void* p, std::nothrow_t const&) TEST_NOEXCEPT { getGlobalMemCounter()->deleteArrayCalled(p); std::free(p); } # ifndef TEST_HAS_NO_ALIGNED_ALLOCATION # if defined(_LIBCPP_MSVCRT_LIKE) || (!defined(_LIBCPP_VERSION) && defined(_WIN32)) # define USE_ALIGNED_ALLOC # endif inline void* alocate_aligned_impl(std::size_t size, std::align_val_t align) { const std::size_t alignment = static_cast(align); void* ret = nullptr; # ifdef USE_ALIGNED_ALLOC ret = _aligned_malloc(size, alignment); # else assert(posix_memalign(&ret, std::max(alignment, sizeof(void*)), size) != EINVAL); # endif return ret; } inline void free_aligned_impl(void* ptr, std::align_val_t) { if (ptr) { # ifdef USE_ALIGNED_ALLOC ::_aligned_free(ptr); # else ::free(ptr); # endif } } // operator new(size_t, align_val_t[, nothrow_t]) and operator delete(size_t, align_val_t[, nothrow_t]) void* operator new(std::size_t s, std::align_val_t av) TEST_THROW_SPEC(std::bad_alloc) { getGlobalMemCounter()->alignedNewCalled(s, static_cast(av)); void* p = alocate_aligned_impl(s, av); if (p == nullptr) detail::throw_bad_alloc_helper(); return p; } void* operator new(std::size_t s, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { # ifdef TEST_HAS_NO_EXCEPTIONS getGlobalMemCounter()->alignedNewCalled(s, static_cast(av)); # else try { getGlobalMemCounter()->alignedNewCalled(s, static_cast(av)); } catch (std::bad_alloc const&) { return nullptr; } # endif return alocate_aligned_impl(s, av); } void operator delete(void* p, std::align_val_t av) TEST_NOEXCEPT { getGlobalMemCounter()->alignedDeleteCalled(p, static_cast(av)); free_aligned_impl(p, av); } void operator delete(void* p, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { getGlobalMemCounter()->alignedDeleteCalled(p, static_cast(av)); free_aligned_impl(p, av); } // operator new[](size_t, align_val_t[, nothrow_t]) and operator delete[](size_t, align_val_t[, nothrow_t]) void* operator new[](std::size_t s, std::align_val_t av) TEST_THROW_SPEC(std::bad_alloc) { getGlobalMemCounter()->alignedNewArrayCalled(s, static_cast(av)); void* p = alocate_aligned_impl(s, av); if (p == nullptr) detail::throw_bad_alloc_helper(); return p; } void* operator new[](std::size_t s, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { # ifdef TEST_HAS_NO_EXCEPTIONS getGlobalMemCounter()->alignedNewArrayCalled(s, static_cast(av)); # else try { getGlobalMemCounter()->alignedNewArrayCalled(s, static_cast(av)); } catch (std::bad_alloc const&) { return nullptr; } # endif return alocate_aligned_impl(s, av); } void operator delete[](void* p, std::align_val_t av) TEST_NOEXCEPT { getGlobalMemCounter()->alignedDeleteArrayCalled(p, static_cast(av)); free_aligned_impl(p, av); } void operator delete[](void* p, std::align_val_t av, std::nothrow_t const&) TEST_NOEXCEPT { getGlobalMemCounter()->alignedDeleteArrayCalled(p, static_cast(av)); free_aligned_impl(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&); }; #if TEST_STD_VER >= 20 struct ConstexprDisableAllocationGuard { TEST_CONSTEXPR_CXX14 explicit ConstexprDisableAllocationGuard(bool disable = true) : m_disabled(disable) { if (!TEST_IS_CONSTANT_EVALUATED) { // Don't re-disable if already disabled. if (globalMemCounter.disable_allocations == true) m_disabled = false; if (m_disabled) globalMemCounter.disableAllocations(); } else { m_disabled = false; } } TEST_CONSTEXPR_CXX14 void release() { if (!TEST_IS_CONSTANT_EVALUATED) { if (m_disabled) globalMemCounter.enableAllocations(); m_disabled = false; } } TEST_CONSTEXPR_CXX20 ~ConstexprDisableAllocationGuard() { release(); } private: bool m_disabled; ConstexprDisableAllocationGuard(ConstexprDisableAllocationGuard const&); ConstexprDisableAllocationGuard& operator=(ConstexprDisableAllocationGuard const&); }; #endif 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(m_outstanding_new_on_init))); std::size_t Expect = m_new_count_on_init + m_req_alloc; assert(globalMemCounter.checkNewCalledEq(static_cast(Expect)) || (!m_exactly && globalMemCounter.checkNewCalledGreaterThan(static_cast(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_H */