[libcxx] [test] Generalize defines for skipping allocation checks

This allows waiving the right amount of asserts on Windows and zOS.
This should supersede D107124 and D105910.

Differential Revision: https://reviews.llvm.org/D107755
This commit is contained in:
Martin Storsjö 2021-08-09 13:57:55 +03:00
parent 81f057c253
commit 128b2136ec
12 changed files with 94 additions and 60 deletions

View File

@ -194,22 +194,23 @@ void doAppendSourceAllocTest(AppendOperatorTestcase const& TC)
// For "char" no allocations will be performed because no conversion is
// required.
// On Windows, the append method is more complex and uses intermediate
// path objects, which causes extra allocations.
// In DLL builds on Windows, the overridden operator new won't pick up
// allocations done within the DLL, so the RequireAllocationGuard below
// won't necessarily see allocations in the cases where they're expected.
#ifdef _WIN32
bool DisableAllocations = false;
#else
bool DisableAllocations = std::is_same<CharT, char>::value;
// path objects, which causes extra allocations. This is checked by comparing
// path::value_type with "char" - on Windows, it's wchar_t.
#if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
// Only check allocations if we can pick up allocations done within the
// library implementation.
bool ExpectNoAllocations = std::is_same<CharT, char>::value &&
std::is_same<path::value_type, char>::value;
#endif
{
path LHS(L); PathReserve(LHS, ReserveSize);
InputIter RHS(R);
{
RequireAllocationGuard g; // requires 1 or more allocations occur by default
if (DisableAllocations) g.requireExactly(0);
else TEST_ONLY_WIN32_DLL(g.requireAtLeast(0));
RequireAllocationGuard g(0); // require "at least zero" allocations by default
#if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
if (ExpectNoAllocations)
g.requireExactly(0);
#endif
LHS /= RHS;
}
assert(PathEq(LHS, E));
@ -219,9 +220,11 @@ void doAppendSourceAllocTest(AppendOperatorTestcase const& TC)
InputIter RHS(R);
InputIter REnd(StrEnd(R));
{
RequireAllocationGuard g;
if (DisableAllocations) g.requireExactly(0);
else TEST_ONLY_WIN32_DLL(g.requireAtLeast(0));
RequireAllocationGuard g(0); // require "at least zero" allocations by default
#if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
if (ExpectNoAllocations)
g.requireExactly(0);
#endif
LHS.append(RHS, REnd);
}
assert(PathEq(LHS, E));

View File

@ -28,9 +28,8 @@ int main(int, char**) {
assert(globalMemCounter.checkOutstandingNewEq(0));
const std::string s("we really really really really really really really "
"really really long string so that we allocate");
// On windows, the operator new from count_new.h can't override the default
// operator for calls within the libc++ DLL.
TEST_NOT_WIN32_DLL(assert(globalMemCounter.checkOutstandingNewEq(1)));
ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(
globalMemCounter.checkOutstandingNewEq(1));
const fs::path::string_type ps(s.begin(), s.end());
path p(s);
{

View File

@ -141,17 +141,20 @@ void doConcatSourceAllocTest(ConcatOperatorTestcase const& TC)
// For the path native type, no allocations will be performed because no
// conversion is required.
// In DLL builds on Windows, the overridden operator new won't pick up
// allocations done within the DLL, so the RequireAllocationGuard below
// won't necessarily see allocations in the cases where they're expected.
bool DisableAllocations = std::is_same<CharT, path::value_type>::value;
#if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
// Only check allocations if we can pick up allocations done within the
// library implementation.
bool ExpectNoAllocations = std::is_same<CharT, path::value_type>::value;
#endif
{
path LHS(L); PathReserve(LHS, ReserveSize);
InputIter RHS(R);
{
RequireAllocationGuard g; // requires 1 or more allocations occur by default
if (DisableAllocations) g.requireExactly(0);
else TEST_ONLY_WIN32_DLL(g.requireAtLeast(0));
RequireAllocationGuard g(0); // require "at least zero" allocations by default
#if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
if (ExpectNoAllocations)
g.requireExactly(0);
#endif
LHS += RHS;
}
assert(LHS == E);
@ -161,9 +164,11 @@ void doConcatSourceAllocTest(ConcatOperatorTestcase const& TC)
InputIter RHS(R);
InputIter REnd(StrEnd(R));
{
RequireAllocationGuard g;
if (DisableAllocations) g.requireExactly(0);
else TEST_ONLY_WIN32_DLL(g.requireAtLeast(0));
RequireAllocationGuard g(0); // require "at least zero" allocations by default
#if TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS
if (ExpectNoAllocations)
g.requireExactly(0);
#endif
LHS.concat(RHS, REnd);
}
assert(LHS == E);

View File

@ -28,9 +28,8 @@ int main(int, char**) {
assert(globalMemCounter.checkOutstandingNewEq(0));
const std::string s("we really really really really really really really "
"really really long string so that we allocate");
// On windows, the operator new from count_new.h can't override the default
// operator for calls within the libc++ DLL.
TEST_NOT_WIN32_DLL(assert(globalMemCounter.checkOutstandingNewEq(1)));
ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(
globalMemCounter.checkOutstandingNewEq(1));
const fs::path::string_type ps(s.begin(), s.end());
path p(s);
{

View File

@ -62,9 +62,9 @@ void* operator new[](std::size_t s, std::align_val_t a) TEST_THROW_SPEC(std::bad
void operator delete[](void* p, std::align_val_t a) TEST_NOEXCEPT
{
assert(p == Buff);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(p == Buff);
assert(static_cast<std::size_t>(a) == OverAligned);
assert(new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called);
--new_called;
}
@ -74,18 +74,18 @@ int main(int, char**)
A* ap = new (std::nothrow) A[2];
assert(ap);
assert(A_constructed == 2);
assert(new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called);
delete [] ap;
assert(A_constructed == 0);
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
}
{
B* bp = new (std::nothrow) B[2];
assert(bp);
assert(B_constructed == 2);
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
delete [] bp;
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
assert(!B_constructed);
}

View File

@ -50,11 +50,11 @@ int main(int, char**)
DoNotOptimize(ap);
assert(ap);
assert(A_constructed == 3);
assert(new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called);
delete [] ap;
DoNotOptimize(ap);
assert(A_constructed == 0);
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
return 0;
}

View File

@ -51,11 +51,11 @@ int main(int, char**)
DoNotOptimize(ap);
assert(ap);
assert(A_constructed == 3);
assert(new_called == 1);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called == 1);
delete [] ap;
DoNotOptimize(ap);
assert(A_constructed == 0);
assert(new_called == 0);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called == 0);
return 0;
}

View File

@ -62,9 +62,9 @@ void* operator new(std::size_t s, std::align_val_t a) TEST_THROW_SPEC(std::bad_a
void operator delete(void* p, std::align_val_t a) TEST_NOEXCEPT
{
assert(p == Buff);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(p == Buff);
assert(static_cast<std::size_t>(a) == OverAligned);
assert(new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called);
--new_called;
}
@ -75,18 +75,18 @@ int main(int, char**)
A* ap = new (std::nothrow) A;
assert(ap);
assert(A_constructed);
assert(new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called);
delete ap;
assert(!A_constructed);
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
}
{
B* bp = new (std::nothrow) B;
assert(bp);
assert(B_constructed);
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
delete bp;
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
assert(!B_constructed);
}

View File

@ -50,11 +50,11 @@ int main(int, char**)
DoNotOptimize(ap);
assert(ap);
assert(A_constructed);
assert(new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(new_called);
delete ap;
DoNotOptimize(ap);
assert(!A_constructed);
assert(!new_called);
ASSERT_WITH_OPERATOR_NEW_FALLBACKS(!new_called);
return 0;
}

View File

@ -37,9 +37,8 @@ int main(int, char**)
new std::ctype<char>(new std::ctype<char>::mask[256], true));
assert(globalMemCounter.checkDeleteArrayCalledEq(0));
}
// On windows, the operator new from count_new.h can't override the default
// operator for calls within the libc++ DLL.
TEST_NOT_WIN32_DLL(assert(globalMemCounter.checkDeleteArrayCalledEq(1)));
ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(
globalMemCounter.checkDeleteArrayCalledEq(1));
return 0;
}

View File

@ -136,7 +136,7 @@ void test_throwing_new_during_thread_creation() {
for (int i=0; i <= numAllocs; ++i) {
throw_one = i;
f_run = false;
TEST_NOT_WIN32_DLL(unsigned old_outstanding = outstanding_new);
unsigned old_outstanding = outstanding_new;
try {
std::thread t(f);
assert(i == numAllocs); // Only final iteration will not throw.
@ -146,9 +146,7 @@ void test_throwing_new_during_thread_creation() {
assert(i < numAllocs);
assert(!f_run); // (2.2)
}
// In DLL builds on Windows, the overridden operators new/delete won't
// override calls from within the DLL, so this won't match.
TEST_NOT_WIN32_DLL(assert(old_outstanding == outstanding_new)); // (2.3)
ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(old_outstanding == outstanding_new); // (2.3)
}
f_run = false;
throw_one = 0xFFF;

View File

@ -381,12 +381,43 @@ inline void DoNotOptimize(Tp const& value) {
#define TEST_NOT_WIN32(...) __VA_ARGS__
#endif
#if defined(_WIN32) && !defined(_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS)
#define TEST_NOT_WIN32_DLL(...) ((void)0)
#define TEST_ONLY_WIN32_DLL(...) __VA_ARGS__
#if (defined(_WIN32) && !defined(_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS)) || \
defined(__MVS__)
// Macros for waiving cases when we can't count allocations done within
// the library implementation.
//
// On Windows, when libc++ is built as a DLL, references to operator new/delete
// within the DLL are bound at link time to the operator new/delete within
// the library; replacing them in the user executable doesn't override the
// calls within the library.
//
// The same goes on IBM zOS.
#define ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(...) ((void)(__VA_ARGS__))
#define TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS 0
#else
#define TEST_NOT_WIN32_DLL(...) __VA_ARGS__
#define TEST_ONLY_WIN32_DLL(...) ((void)0)
#define ASSERT_WITH_LIBRARY_INTERNAL_ALLOCATIONS(...) assert(__VA_ARGS__)
#define TEST_SUPPORTS_LIBRARY_INTERNAL_ALLOCATIONS 1
#endif
#if (defined(_WIN32) && !defined(_MSC_VER) && \
!defined(_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS)) || \
defined(__MVS__)
// Normally, a replaced e.g. 'operator new' ends up used if the user code
// does a call to e.g. 'operator new[]'; it's enough to replace the base
// versions and have it override all of them.
//
// When the fallback operators are located within the libc++ library and we
// can't override the calls within it (see above), this fallback mechanism
// doesn't work either.
//
// On Windows, when using the MSVC vcruntime, the operator new/delete fallbacks
// are linked separately from the libc++ library, linked statically into
// the end user executable, and these fallbacks work even in DLL configurations.
// In MinGW configurations when built as a DLL, and on zOS, these fallbacks
// don't work though.
#define ASSERT_WITH_OPERATOR_NEW_FALLBACKS(...) ((void)(__VA_ARGS__))
#else
#define ASSERT_WITH_OPERATOR_NEW_FALLBACKS(...) assert(__VA_ARGS__)
#endif
#ifdef _WIN32