//===----------------------------------------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is dual licensed under the MIT and the University of Illinois Open // Source Licenses. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // UNSUPPORTED: libcpp-has-no-threads // // class thread // template thread(F&& f, Args&&... args); // UNSUPPORTED: sanitizer-new-delete #include #include #include #include #include #include "test_macros.h" std::atomic throw_one(0xFFFF); std::atomic outstanding_new(0); void* operator new(std::size_t s) TEST_THROW_SPEC(std::bad_alloc) { if (throw_one == 0) TEST_THROW(std::bad_alloc()); --throw_one; ++outstanding_new; void* ret = std::malloc(s); if (!ret) std::abort(); // placate MSVC's unchecked malloc warning return ret; } void operator delete(void* p) TEST_NOEXCEPT { --outstanding_new; std::free(p); } bool f_run = false; void f() { f_run = true; } class G { int alive_; public: static int n_alive; static bool op_run; G() : alive_(1) {++n_alive;} G(const G& g) : alive_(g.alive_) {++n_alive;} ~G() {alive_ = 0; --n_alive;} void operator()() { assert(alive_ == 1); assert(n_alive >= 1); op_run = true; } void operator()(int i, double j) { assert(alive_ == 1); assert(n_alive >= 1); assert(i == 5); assert(j == 5.5); op_run = true; } }; int G::n_alive = 0; bool G::op_run = false; #if TEST_STD_VER >= 11 class MoveOnly { MoveOnly(const MoveOnly&); public: MoveOnly() {} MoveOnly(MoveOnly&&) {} void operator()(MoveOnly&&) { } }; #endif // Test throwing std::bad_alloc //----------------------------- // Concerns: // A Each allocation performed during thread construction should be performed // in the parent thread so that std::terminate is not called if // std::bad_alloc is thrown by new. // B std::thread's constructor should properly handle exceptions and not leak // memory. // Plan: // 1 Create a thread and count the number of allocations, 'N', it performs. // 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.2 Check that the functor has not been called. // 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' // allocations. void test_throwing_new_during_thread_creation() { #ifndef TEST_HAS_NO_EXCEPTIONS throw_one = 0xFFF; { std::thread t(f); t.join(); } const int numAllocs = 0xFFF - throw_one; // i <= numAllocs means the last iteration is expected not to throw. for (int i=0; i <= numAllocs; ++i) { throw_one = i; f_run = false; unsigned old_outstanding = outstanding_new; try { std::thread t(f); assert(i == numAllocs); // Only final iteration will not throw. t.join(); assert(f_run); } catch (std::bad_alloc const&) { assert(i < numAllocs); assert(!f_run); // (2.2) } assert(old_outstanding == outstanding_new); // (2.3) } f_run = false; throw_one = 0xFFF; #endif } int main() { test_throwing_new_during_thread_creation(); { std::thread t(f); t.join(); assert(f_run == true); } { assert(G::n_alive == 0); assert(!G::op_run); std::thread t((G())); t.join(); assert(G::n_alive == 0); assert(G::op_run); } G::op_run = false; #ifndef TEST_HAS_NO_EXCEPTIONS { try { throw_one = 0; assert(G::n_alive == 0); assert(!G::op_run); std::thread t((G())); assert(false); } catch (...) { throw_one = 0xFFFF; assert(G::n_alive == 0); assert(!G::op_run); } } #endif #if TEST_STD_VER >= 11 { assert(G::n_alive == 0); assert(!G::op_run); std::thread t(G(), 5, 5.5); t.join(); assert(G::n_alive == 0); assert(G::op_run); } { std::thread t = std::thread(MoveOnly(), MoveOnly()); t.join(); } #endif }