[libc++][test] Remove non-portable assumption that thread's constructor allocates with ::new

Drive-by:
* Fix potential race between check and update of `throw_one` in `operator new`
* Fix latent bug in `operator delete`, which shouldn't decrement `outstanding_new` when passed a null pointer
* Specifically catch the expected `bad_alloc` in `main` instead of `...`

Differential Revision: https://reviews.llvm.org/D50860

git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@359827 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Casey Carter
2019-05-02 21:19:41 +00:00
parent 07ca5d0973
commit f16a595ec7

View File

@@ -30,9 +30,10 @@ std::atomic<unsigned> outstanding_new(0);
void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc) void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc)
{ {
if (throw_one == 0) unsigned expected = throw_one;
TEST_THROW(std::bad_alloc()); do {
--throw_one; if (expected == 0) TEST_THROW(std::bad_alloc());
} while (!throw_one.compare_exchange_weak(expected, expected - 1));
++outstanding_new; ++outstanding_new;
void* ret = std::malloc(s); void* ret = std::malloc(s);
if (!ret) std::abort(); // placate MSVC's unchecked malloc warning if (!ret) std::abort(); // placate MSVC's unchecked malloc warning
@@ -41,6 +42,7 @@ void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc)
void operator delete(void* p) TEST_NOEXCEPT void operator delete(void* p) TEST_NOEXCEPT
{ {
if (!p) return;
--outstanding_new; --outstanding_new;
std::free(p); std::free(p);
} }
@@ -108,13 +110,17 @@ public:
// B std::thread's constructor should properly handle exceptions and not leak // B std::thread's constructor should properly handle exceptions and not leak
// memory. // memory.
// Plan: // Plan:
// 1 Create a thread and count the number of allocations, 'N', it performs. // 1 Create a thread and count the number of allocations, 'numAllocs', it
// performs.
// 2 For each allocation performed run a test where that allocation throws. // 2 For each allocation performed run a test where that allocation throws.
// 2.1 check that the exception can be caught in the parent thread. // 2.1 check that the exception can be caught in the parent thread.
// 2.2 Check that the functor has not been called. // 2.2 Check that the functor has not been called.
// 2.3 Check that no memory allocated by the creation of the thread is leaked. // 2.3 Check that no memory allocated by the creation of the thread is leaked.
// 3 Finally check that a thread runs successfully if we throw after 'N+1' // 3 Finally check that a thread runs successfully if we throw after
// allocations. // 'numAllocs + 1' allocations.
int numAllocs;
void test_throwing_new_during_thread_creation() { void test_throwing_new_during_thread_creation() {
#ifndef TEST_HAS_NO_EXCEPTIONS #ifndef TEST_HAS_NO_EXCEPTIONS
throw_one = 0xFFF; throw_one = 0xFFF;
@@ -122,7 +128,7 @@ void test_throwing_new_during_thread_creation() {
std::thread t(f); std::thread t(f);
t.join(); t.join();
} }
const int numAllocs = 0xFFF - throw_one; numAllocs = 0xFFF - throw_one;
// i <= numAllocs means the last iteration is expected not to throw. // i <= numAllocs means the last iteration is expected not to throw.
for (int i=0; i <= numAllocs; ++i) { for (int i=0; i <= numAllocs; ++i) {
throw_one = i; throw_one = i;
@@ -166,7 +172,10 @@ int main(int, char**)
} }
G::op_run = false; G::op_run = false;
#ifndef TEST_HAS_NO_EXCEPTIONS #ifndef TEST_HAS_NO_EXCEPTIONS
{ // The test below expects `std::thread` to call `new`, which may not be the
// case for all implementations.
LIBCPP_ASSERT(numAllocs > 0); // libc++ should call new.
if (numAllocs > 0) {
try try
{ {
throw_one = 0; throw_one = 0;
@@ -175,7 +184,7 @@ int main(int, char**)
std::thread t((G())); std::thread t((G()));
assert(false); assert(false);
} }
catch (...) catch (std::bad_alloc const&)
{ {
throw_one = 0xFFFF; throw_one = 0xFFFF;
assert(G::n_alive == 0); assert(G::n_alive == 0);
@@ -201,5 +210,5 @@ int main(int, char**)
} }
#endif #endif
return 0; return 0;
} }