#include "absl/base/internal/exception_safety_testing.h" #include #include #include #include #include #include "gtest/gtest-spi.h" #include "gtest/gtest.h" #include "absl/memory/memory.h" namespace absl { namespace { using ::absl::exceptions_internal::TestException; void SetCountdown() { exceptions_internal::countdown = 0; } void UnsetCountdown() { exceptions_internal::countdown = -1; } // EXPECT_NO_THROW can't inspect the thrown inspection in general. template void ExpectNoThrow(const F& f) { try { f(); } catch (TestException e) { ADD_FAILURE() << "Unexpected exception thrown from " << e.what(); } } class ThrowingValueTest : public ::testing::Test { protected: void SetUp() override { UnsetCountdown(); } private: AllocInspector clouseau_; }; TEST_F(ThrowingValueTest, Throws) { SetCountdown(); EXPECT_THROW(ThrowingValue<> bomb, TestException); // It's not guaranteed that every operator only throws *once*. The default // ctor only throws once, though, so use it to make sure we only throw when // the countdown hits 0 exceptions_internal::countdown = 2; ExpectNoThrow([]() { ThrowingValue<> bomb; }); ExpectNoThrow([]() { ThrowingValue<> bomb; }); EXPECT_THROW(ThrowingValue<> bomb, TestException); } // Tests that an operation throws when the countdown is at 0, doesn't throw when // the countdown doesn't hit 0, and doesn't modify the state of the // ThrowingValue if it throws template void TestOp(F&& f) { UnsetCountdown(); ExpectNoThrow(f); SetCountdown(); EXPECT_THROW(f(), TestException); UnsetCountdown(); } TEST_F(ThrowingValueTest, ThrowingCtors) { ThrowingValue<> bomb; TestOp([]() { ThrowingValue<> bomb(1); }); TestOp([&]() { ThrowingValue<> bomb1 = bomb; }); TestOp([&]() { ThrowingValue<> bomb1 = std::move(bomb); }); } TEST_F(ThrowingValueTest, ThrowingAssignment) { ThrowingValue<> bomb, bomb1; TestOp([&]() { bomb = bomb1; }); TestOp([&]() { bomb = std::move(bomb1); }); } TEST_F(ThrowingValueTest, ThrowingComparisons) { ThrowingValue<> bomb1, bomb2; TestOp([&]() { return bomb1 == bomb2; }); TestOp([&]() { return bomb1 != bomb2; }); TestOp([&]() { return bomb1 < bomb2; }); TestOp([&]() { return bomb1 <= bomb2; }); TestOp([&]() { return bomb1 > bomb2; }); TestOp([&]() { return bomb1 >= bomb2; }); } TEST_F(ThrowingValueTest, ThrowingArithmeticOps) { ThrowingValue<> bomb1(1), bomb2(2); TestOp([&bomb1]() { +bomb1; }); TestOp([&bomb1]() { -bomb1; }); TestOp([&bomb1]() { ++bomb1; }); TestOp([&bomb1]() { bomb1++; }); TestOp([&bomb1]() { --bomb1; }); TestOp([&bomb1]() { bomb1--; }); TestOp([&]() { bomb1 + bomb2; }); TestOp([&]() { bomb1 - bomb2; }); TestOp([&]() { bomb1* bomb2; }); TestOp([&]() { bomb1 / bomb2; }); TestOp([&]() { bomb1 << 1; }); TestOp([&]() { bomb1 >> 1; }); } TEST_F(ThrowingValueTest, ThrowingLogicalOps) { ThrowingValue<> bomb1, bomb2; TestOp([&bomb1]() { !bomb1; }); TestOp([&]() { bomb1&& bomb2; }); TestOp([&]() { bomb1 || bomb2; }); } TEST_F(ThrowingValueTest, ThrowingBitwiseOps) { ThrowingValue<> bomb1, bomb2; TestOp([&bomb1]() { ~bomb1; }); TestOp([&]() { bomb1& bomb2; }); TestOp([&]() { bomb1 | bomb2; }); TestOp([&]() { bomb1 ^ bomb2; }); } TEST_F(ThrowingValueTest, ThrowingCompoundAssignmentOps) { ThrowingValue<> bomb1(1), bomb2(2); TestOp([&]() { bomb1 += bomb2; }); TestOp([&]() { bomb1 -= bomb2; }); TestOp([&]() { bomb1 *= bomb2; }); TestOp([&]() { bomb1 /= bomb2; }); TestOp([&]() { bomb1 %= bomb2; }); TestOp([&]() { bomb1 &= bomb2; }); TestOp([&]() { bomb1 |= bomb2; }); TestOp([&]() { bomb1 ^= bomb2; }); TestOp([&]() { bomb1 *= bomb2; }); } TEST_F(ThrowingValueTest, ThrowingStreamOps) { ThrowingValue<> bomb; TestOp([&]() { std::cin >> bomb; }); TestOp([&]() { std::cout << bomb; }); } TEST_F(ThrowingValueTest, ThrowingAllocatingOps) { // make_unique calls unqualified operator new, so these exercise the // ThrowingValue overloads. TestOp([]() { return absl::make_unique>(1); }); TestOp([]() { return absl::make_unique[]>(2); }); } TEST_F(ThrowingValueTest, NonThrowingMoveCtor) { ThrowingValue nothrow_ctor; SetCountdown(); ExpectNoThrow([¬hrow_ctor]() { ThrowingValue nothrow1 = std::move(nothrow_ctor); }); } TEST_F(ThrowingValueTest, NonThrowingMoveAssign) { ThrowingValue nothrow_assign1, nothrow_assign2; SetCountdown(); ExpectNoThrow([¬hrow_assign1, ¬hrow_assign2]() { nothrow_assign1 = std::move(nothrow_assign2); }); } TEST_F(ThrowingValueTest, ThrowingSwap) { ThrowingValue<> bomb1, bomb2; TestOp([&]() { std::swap(bomb1, bomb2); }); ThrowingValue bomb3, bomb4; TestOp([&]() { std::swap(bomb3, bomb4); }); ThrowingValue bomb5, bomb6; TestOp([&]() { std::swap(bomb5, bomb6); }); } TEST_F(ThrowingValueTest, NonThrowingSwap) { ThrowingValue bomb1, bomb2; ExpectNoThrow([&]() { std::swap(bomb1, bomb2); }); } TEST_F(ThrowingValueTest, NonThrowingAllocation) { ThrowingValue* allocated; ThrowingValue* array; ExpectNoThrow([&allocated]() { allocated = new ThrowingValue(1); delete allocated; }); ExpectNoThrow([&array]() { array = new ThrowingValue[2]; delete[] array; }); } TEST_F(ThrowingValueTest, NonThrowingDelete) { auto* allocated = new ThrowingValue<>(1); auto* array = new ThrowingValue<>[2]; SetCountdown(); ExpectNoThrow([allocated]() { delete allocated; }); SetCountdown(); ExpectNoThrow([array]() { delete[] array; }); } using Storage = absl::aligned_storage_t), alignof(ThrowingValue<>)>; TEST_F(ThrowingValueTest, NonThrowingPlacementDelete) { constexpr int kArrayLen = 2; // We intentionally create extra space to store the tag allocated by placement // new[]. constexpr int kStorageLen = 4; Storage buf; Storage array_buf[kStorageLen]; auto* placed = new (&buf) ThrowingValue<>(1); auto placed_array = new (&array_buf) ThrowingValue<>[kArrayLen]; SetCountdown(); ExpectNoThrow([placed, &buf]() { placed->~ThrowingValue<>(); ThrowingValue<>::operator delete(placed, &buf); }); SetCountdown(); ExpectNoThrow([&, placed_array]() { for (int i = 0; i < kArrayLen; ++i) placed_array[i].~ThrowingValue<>(); ThrowingValue<>::operator delete[](placed_array, &array_buf); }); } TEST_F(ThrowingValueTest, NonThrowingDestructor) { auto* allocated = new ThrowingValue<>(); SetCountdown(); ExpectNoThrow([allocated]() { delete allocated; }); } TEST(ThrowingBoolTest, ThrowingBool) { UnsetCountdown(); ThrowingBool t = true; // Test that it's contextually convertible to bool if (t) { // NOLINT(whitespace/empty_if_body) } EXPECT_TRUE(t); TestOp([&]() { (void)!t; }); } class ThrowingAllocatorTest : public ::testing::Test { protected: void SetUp() override { UnsetCountdown(); } private: AllocInspector borlu_; }; TEST_F(ThrowingAllocatorTest, MemoryManagement) { // Just exercise the memory management capabilities under LSan to make sure we // don't leak. ThrowingAllocator int_alloc; int* ip = int_alloc.allocate(1); int_alloc.deallocate(ip, 1); int* i_array = int_alloc.allocate(2); int_alloc.deallocate(i_array, 2); ThrowingAllocator> ef_alloc; ThrowingValue<>* efp = ef_alloc.allocate(1); ef_alloc.deallocate(efp, 1); ThrowingValue<>* ef_array = ef_alloc.allocate(2); ef_alloc.deallocate(ef_array, 2); } TEST_F(ThrowingAllocatorTest, CallsGlobalNew) { ThrowingAllocator, NoThrow::kNoThrow> nothrow_alloc; ThrowingValue<>* ptr; SetCountdown(); // This will only throw if ThrowingValue::new is called. ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); }); nothrow_alloc.deallocate(ptr, 1); } TEST_F(ThrowingAllocatorTest, ThrowingConstructors) { ThrowingAllocator int_alloc; int* ip = nullptr; SetCountdown(); EXPECT_THROW(ip = int_alloc.allocate(1), TestException); ExpectNoThrow([&]() { ip = int_alloc.allocate(1); }); *ip = 1; SetCountdown(); EXPECT_THROW(int_alloc.construct(ip, 2), TestException); EXPECT_EQ(*ip, 1); int_alloc.deallocate(ip, 1); } TEST_F(ThrowingAllocatorTest, NonThrowingConstruction) { { ThrowingAllocator int_alloc; int* ip = nullptr; SetCountdown(); ExpectNoThrow([&]() { ip = int_alloc.allocate(1); }); SetCountdown(); ExpectNoThrow([&]() { int_alloc.construct(ip, 2); }); EXPECT_EQ(*ip, 2); int_alloc.deallocate(ip, 1); } UnsetCountdown(); { ThrowingAllocator int_alloc; int* ip = nullptr; ExpectNoThrow([&]() { ip = int_alloc.allocate(1); }); ExpectNoThrow([&]() { int_alloc.construct(ip, 2); }); EXPECT_EQ(*ip, 2); int_alloc.deallocate(ip, 1); } UnsetCountdown(); { ThrowingAllocator, NoThrow::kNoThrow> ef_alloc; ThrowingValue* efp; SetCountdown(); ExpectNoThrow([&]() { efp = ef_alloc.allocate(1); }); SetCountdown(); ExpectNoThrow([&]() { ef_alloc.construct(efp, 2); }); EXPECT_EQ(efp->Get(), 2); ef_alloc.destroy(efp); ef_alloc.deallocate(efp, 1); } UnsetCountdown(); { ThrowingAllocator a; SetCountdown(); ExpectNoThrow([&]() { ThrowingAllocator a1 = a; }); SetCountdown(); ExpectNoThrow([&]() { ThrowingAllocator a1 = std::move(a); }); } } TEST_F(ThrowingAllocatorTest, ThrowingAllocatorConstruction) { ThrowingAllocator a; TestOp([]() { ThrowingAllocator a; }); TestOp([&]() { a.select_on_container_copy_construction(); }); } TEST_F(ThrowingAllocatorTest, State) { ThrowingAllocator a1, a2; EXPECT_NE(a1, a2); auto a3 = a1; EXPECT_EQ(a3, a1); int* ip = a1.allocate(1); EXPECT_EQ(a3, a1); a3.deallocate(ip, 1); EXPECT_EQ(a3, a1); } TEST_F(ThrowingAllocatorTest, InVector) { std::vector, ThrowingAllocator>> v; for (int i = 0; i < 20; ++i) v.push_back({}); for (int i = 0; i < 20; ++i) v.pop_back(); } TEST_F(ThrowingAllocatorTest, InList) { std::list, ThrowingAllocator>> l; for (int i = 0; i < 20; ++i) l.push_back({}); for (int i = 0; i < 20; ++i) l.pop_back(); for (int i = 0; i < 20; ++i) l.push_front({}); for (int i = 0; i < 20; ++i) l.pop_front(); } struct CallOperator { template void operator()(T* t) const { (*t)(); } }; struct FailsBasicGuarantee { void operator()() { --i; ThrowingValue<> bomb; ++i; } bool operator!=(const FailsBasicGuarantee& other) const { return i != other.i; } friend bool AbslCheckInvariants(const FailsBasicGuarantee& g) { return g.i >= 0; } int i = 0; }; TEST(ExceptionCheckTest, BasicGuaranteeFailure) { FailsBasicGuarantee g; EXPECT_FALSE(TestBasicGuarantee(&g, CallOperator{})); } struct FollowsBasicGuarantee { void operator()() { ++i; ThrowingValue<> bomb; } bool operator!=(const FollowsBasicGuarantee& other) const { return i != other.i; } friend bool AbslCheckInvariants(const FollowsBasicGuarantee& g) { return g.i >= 0; } int i = 0; }; TEST(ExceptionCheckTest, BasicGuarantee) { FollowsBasicGuarantee g; EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{})); } TEST(ExceptionCheckTest, StrongGuaranteeFailure) { { FailsBasicGuarantee g; EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{})); } { FollowsBasicGuarantee g; EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{})); } } struct FollowsStrongGuarantee { void operator()() { ThrowingValue<> bomb; } bool operator!=(const FollowsStrongGuarantee& other) const { return i != other.i; } friend bool AbslCheckInvariants(const FollowsStrongGuarantee& g) { return g.i >= 0; } int i = 0; }; TEST(ExceptionCheckTest, StrongGuarantee) { FollowsStrongGuarantee g; EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{})); EXPECT_TRUE(TestStrongGuarantee(&g, CallOperator{})); } template struct InstructionCounter { void operator()() { ++counter; T b1; static_cast(b1); ++counter; T b2; static_cast(b2); ++counter; T b3; static_cast(b3); ++counter; } bool operator!=(const InstructionCounter>& other) const { return false; } friend bool AbslCheckInvariants(const InstructionCounter&) { return true; } static int counter; }; template int InstructionCounter::counter = 0; TEST(ExceptionCheckTest, Exhaustiveness) { InstructionCounter int_factory; EXPECT_TRUE(TestBasicGuarantee(&int_factory, CallOperator{})); EXPECT_EQ(InstructionCounter::counter, 4); InstructionCounter> bomb_factory; EXPECT_TRUE(TestBasicGuarantee(&bomb_factory, CallOperator{})); EXPECT_EQ(InstructionCounter>::counter, 10); InstructionCounter>::counter = 0; EXPECT_TRUE(TestStrongGuarantee(&bomb_factory, CallOperator{})); EXPECT_EQ(InstructionCounter>::counter, 10); } struct Tracked : private exceptions_internal::TrackedObject { Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {} }; TEST(AllocInspectorTest, Pass) { AllocInspector javert; Tracked t; } TEST(AllocInspectorTest, NotDestroyed) { absl::aligned_storage_t storage; EXPECT_NONFATAL_FAILURE( { AllocInspector gadget; new (&storage) Tracked; }, "not destroyed"); } TEST(AllocInspectorTest, DestroyedTwice) { EXPECT_NONFATAL_FAILURE( { Tracked t; t.~Tracked(); }, "destroyed improperly"); } TEST(AllocInspectorTest, ConstructedTwice) { absl::aligned_storage_t storage; EXPECT_NONFATAL_FAILURE( { new (&storage) Tracked; new (&storage) Tracked; }, "re-constructed"); } } // namespace } // namespace absl