From 28f5b890702139effabf3576f20e1a4db4a90a80 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Thu, 26 Apr 2018 06:47:58 -0700 Subject: - 81cdce434ff1bd8fa54c832a11dda59af46e79cc Adds a failure signal handler to Abseil. by Derek Mauro - 40a973dd1b159e7455dd5fc06ac2d3f494d72c3e Remove test fixture requirement for ExceptionSafetyTester... by Abseil Team GitOrigin-RevId: 81cdce434ff1bd8fa54c832a11dda59af46e79cc Change-Id: Ia9fca98e38f229b68f7ec45600dee1bbd5dcff33 --- absl/base/exception_safety_testing_test.cc | 120 ++++++++++++++------------ absl/base/internal/exception_safety_testing.h | 82 +++++++++++------- 2 files changed, 116 insertions(+), 86 deletions(-) (limited to 'absl/base') diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc index 94a7e4f7..ab029e1f 100644 --- a/absl/base/exception_safety_testing_test.cc +++ b/absl/base/exception_safety_testing_test.cc @@ -41,15 +41,7 @@ void ExpectNoThrow(const F& f) { } } -class ThrowingValueTest : public ::testing::Test { - protected: - void SetUp() override { UnsetCountdown(); } - - private: - ConstructorTracker clouseau_; -}; - -TEST_F(ThrowingValueTest, Throws) { +TEST(ThrowingValueTest, Throws) { SetCountdown(); EXPECT_THROW(ThrowingValue<> bomb, TestException); @@ -60,6 +52,8 @@ TEST_F(ThrowingValueTest, Throws) { ExpectNoThrow([]() { ThrowingValue<> bomb; }); ExpectNoThrow([]() { ThrowingValue<> bomb; }); EXPECT_THROW(ThrowingValue<> bomb, TestException); + + UnsetCountdown(); } // Tests that an operation throws when the countdown is at 0, doesn't throw when @@ -67,7 +61,6 @@ TEST_F(ThrowingValueTest, Throws) { // ThrowingValue if it throws template void TestOp(const F& f) { - UnsetCountdown(); ExpectNoThrow(f); SetCountdown(); @@ -75,7 +68,7 @@ void TestOp(const F& f) { UnsetCountdown(); } -TEST_F(ThrowingValueTest, ThrowingCtors) { +TEST(ThrowingValueTest, ThrowingCtors) { ThrowingValue<> bomb; TestOp([]() { ThrowingValue<> bomb(1); }); @@ -83,14 +76,14 @@ TEST_F(ThrowingValueTest, ThrowingCtors) { TestOp([&]() { ThrowingValue<> bomb1 = std::move(bomb); }); } -TEST_F(ThrowingValueTest, ThrowingAssignment) { +TEST(ThrowingValueTest, ThrowingAssignment) { ThrowingValue<> bomb, bomb1; TestOp([&]() { bomb = bomb1; }); TestOp([&]() { bomb = std::move(bomb1); }); } -TEST_F(ThrowingValueTest, ThrowingComparisons) { +TEST(ThrowingValueTest, ThrowingComparisons) { ThrowingValue<> bomb1, bomb2; TestOp([&]() { return bomb1 == bomb2; }); TestOp([&]() { return bomb1 != bomb2; }); @@ -100,7 +93,7 @@ TEST_F(ThrowingValueTest, ThrowingComparisons) { TestOp([&]() { return bomb1 >= bomb2; }); } -TEST_F(ThrowingValueTest, ThrowingArithmeticOps) { +TEST(ThrowingValueTest, ThrowingArithmeticOps) { ThrowingValue<> bomb1(1), bomb2(2); TestOp([&bomb1]() { +bomb1; }); @@ -118,7 +111,7 @@ TEST_F(ThrowingValueTest, ThrowingArithmeticOps) { TestOp([&]() { bomb1 >> 1; }); } -TEST_F(ThrowingValueTest, ThrowingLogicalOps) { +TEST(ThrowingValueTest, ThrowingLogicalOps) { ThrowingValue<> bomb1, bomb2; TestOp([&bomb1]() { !bomb1; }); @@ -126,7 +119,7 @@ TEST_F(ThrowingValueTest, ThrowingLogicalOps) { TestOp([&]() { bomb1 || bomb2; }); } -TEST_F(ThrowingValueTest, ThrowingBitwiseOps) { +TEST(ThrowingValueTest, ThrowingBitwiseOps) { ThrowingValue<> bomb1, bomb2; TestOp([&bomb1]() { ~bomb1; }); @@ -135,7 +128,7 @@ TEST_F(ThrowingValueTest, ThrowingBitwiseOps) { TestOp([&]() { bomb1 ^ bomb2; }); } -TEST_F(ThrowingValueTest, ThrowingCompoundAssignmentOps) { +TEST(ThrowingValueTest, ThrowingCompoundAssignmentOps) { ThrowingValue<> bomb1(1), bomb2(2); TestOp([&]() { bomb1 += bomb2; }); @@ -149,7 +142,7 @@ TEST_F(ThrowingValueTest, ThrowingCompoundAssignmentOps) { TestOp([&]() { bomb1 *= bomb2; }); } -TEST_F(ThrowingValueTest, ThrowingStreamOps) { +TEST(ThrowingValueTest, ThrowingStreamOps) { ThrowingValue<> bomb; TestOp([&]() { std::cin >> bomb; }); @@ -158,7 +151,6 @@ TEST_F(ThrowingValueTest, ThrowingStreamOps) { template void TestAllocatingOp(const F& f) { - UnsetCountdown(); ExpectNoThrow(f); SetCountdown(); @@ -166,32 +158,34 @@ void TestAllocatingOp(const F& f) { UnsetCountdown(); } -TEST_F(ThrowingValueTest, ThrowingAllocatingOps) { +TEST(ThrowingValueTest, ThrowingAllocatingOps) { // make_unique calls unqualified operator new, so these exercise the // ThrowingValue overloads. TestAllocatingOp([]() { return absl::make_unique>(1); }); TestAllocatingOp([]() { return absl::make_unique[]>(2); }); } -TEST_F(ThrowingValueTest, NonThrowingMoveCtor) { +TEST(ThrowingValueTest, NonThrowingMoveCtor) { ThrowingValue nothrow_ctor; SetCountdown(); ExpectNoThrow([¬hrow_ctor]() { ThrowingValue nothrow1 = std::move(nothrow_ctor); }); + UnsetCountdown(); } -TEST_F(ThrowingValueTest, NonThrowingMoveAssign) { +TEST(ThrowingValueTest, NonThrowingMoveAssign) { ThrowingValue nothrow_assign1, nothrow_assign2; SetCountdown(); ExpectNoThrow([¬hrow_assign1, ¬hrow_assign2]() { nothrow_assign1 = std::move(nothrow_assign2); }); + UnsetCountdown(); } -TEST_F(ThrowingValueTest, ThrowingSwap) { +TEST(ThrowingValueTest, ThrowingSwap) { ThrowingValue<> bomb1, bomb2; TestOp([&]() { std::swap(bomb1, bomb2); }); @@ -202,12 +196,12 @@ TEST_F(ThrowingValueTest, ThrowingSwap) { TestOp([&]() { std::swap(bomb5, bomb6); }); } -TEST_F(ThrowingValueTest, NonThrowingSwap) { +TEST(ThrowingValueTest, NonThrowingSwap) { ThrowingValue bomb1, bomb2; ExpectNoThrow([&]() { std::swap(bomb1, bomb2); }); } -TEST_F(ThrowingValueTest, NonThrowingAllocation) { +TEST(ThrowingValueTest, NonThrowingAllocation) { ThrowingValue* allocated; ThrowingValue* array; @@ -221,7 +215,7 @@ TEST_F(ThrowingValueTest, NonThrowingAllocation) { }); } -TEST_F(ThrowingValueTest, NonThrowingDelete) { +TEST(ThrowingValueTest, NonThrowingDelete) { auto* allocated = new ThrowingValue<>(1); auto* array = new ThrowingValue<>[2]; @@ -229,12 +223,14 @@ TEST_F(ThrowingValueTest, NonThrowingDelete) { ExpectNoThrow([allocated]() { delete allocated; }); SetCountdown(); ExpectNoThrow([array]() { delete[] array; }); + + UnsetCountdown(); } using Storage = absl::aligned_storage_t), alignof(ThrowingValue<>)>; -TEST_F(ThrowingValueTest, NonThrowingPlacementDelete) { +TEST(ThrowingValueTest, NonThrowingPlacementDelete) { constexpr int kArrayLen = 2; // We intentionally create extra space to store the tag allocated by placement // new[]. @@ -256,16 +252,19 @@ TEST_F(ThrowingValueTest, NonThrowingPlacementDelete) { for (int i = 0; i < kArrayLen; ++i) placed_array[i].~ThrowingValue<>(); ThrowingValue<>::operator delete[](placed_array, &array_buf); }); + + UnsetCountdown(); } -TEST_F(ThrowingValueTest, NonThrowingDestructor) { +TEST(ThrowingValueTest, NonThrowingDestructor) { auto* allocated = new ThrowingValue<>(); + SetCountdown(); ExpectNoThrow([allocated]() { delete allocated; }); + UnsetCountdown(); } TEST(ThrowingBoolTest, ThrowingBool) { - UnsetCountdown(); ThrowingBool t = true; // Test that it's contextually convertible to bool @@ -276,15 +275,7 @@ TEST(ThrowingBoolTest, ThrowingBool) { TestOp([&]() { (void)!t; }); } -class ThrowingAllocatorTest : public ::testing::Test { - protected: - void SetUp() override { UnsetCountdown(); } - - private: - ConstructorTracker borlu_; -}; - -TEST_F(ThrowingAllocatorTest, MemoryManagement) { +TEST(ThrowingAllocatorTest, MemoryManagement) { // Just exercise the memory management capabilities under LSan to make sure we // don't leak. ThrowingAllocator int_alloc; @@ -300,7 +291,7 @@ TEST_F(ThrowingAllocatorTest, MemoryManagement) { ef_alloc.deallocate(ef_array, 2); } -TEST_F(ThrowingAllocatorTest, CallsGlobalNew) { +TEST(ThrowingAllocatorTest, CallsGlobalNew) { ThrowingAllocator, NoThrow::kNoThrow> nothrow_alloc; ThrowingValue<>* ptr; @@ -308,9 +299,11 @@ TEST_F(ThrowingAllocatorTest, CallsGlobalNew) { // This will only throw if ThrowingValue::new is called. ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); }); nothrow_alloc.deallocate(ptr, 1); + + UnsetCountdown(); } -TEST_F(ThrowingAllocatorTest, ThrowingConstructors) { +TEST(ThrowingAllocatorTest, ThrowingConstructors) { ThrowingAllocator int_alloc; int* ip = nullptr; @@ -323,22 +316,27 @@ TEST_F(ThrowingAllocatorTest, ThrowingConstructors) { EXPECT_THROW(int_alloc.construct(ip, 2), TestException); EXPECT_EQ(*ip, 1); int_alloc.deallocate(ip, 1); + + UnsetCountdown(); } -TEST_F(ThrowingAllocatorTest, NonThrowingConstruction) { +TEST(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(); } - UnsetCountdown(); { ThrowingAllocator int_alloc; int* ip = nullptr; @@ -348,37 +346,44 @@ TEST_F(ThrowingAllocatorTest, NonThrowingConstruction) { 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(); } - UnsetCountdown(); { ThrowingAllocator a; + SetCountdown(); ExpectNoThrow([&]() { ThrowingAllocator a1 = a; }); + SetCountdown(); ExpectNoThrow([&]() { ThrowingAllocator a1 = std::move(a); }); + + UnsetCountdown(); } } -TEST_F(ThrowingAllocatorTest, ThrowingAllocatorConstruction) { +TEST(ThrowingAllocatorTest, ThrowingAllocatorConstruction) { ThrowingAllocator a; TestOp([]() { ThrowingAllocator a; }); TestOp([&]() { a.select_on_container_copy_construction(); }); } -TEST_F(ThrowingAllocatorTest, State) { +TEST(ThrowingAllocatorTest, State) { ThrowingAllocator a1, a2; EXPECT_NE(a1, a2); @@ -390,13 +395,13 @@ TEST_F(ThrowingAllocatorTest, State) { EXPECT_EQ(a3, a1); } -TEST_F(ThrowingAllocatorTest, InVector) { +TEST(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) { +TEST(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(); @@ -772,19 +777,28 @@ struct Tracked : private exceptions_internal::TrackedObject { Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {} }; -TEST(ConstructorTrackerTest, Pass) { - ConstructorTracker javert; - Tracked t; +TEST(ConstructorTrackerTest, CreatedBefore) { + Tracked a, b, c; + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); +} + +TEST(ConstructorTrackerTest, CreatedAfter) { + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); + Tracked a, b, c; } -TEST(ConstructorTrackerTest, NotDestroyed) { +TEST(ConstructorTrackerTest, NotDestroyedAfter) { absl::aligned_storage_t storage; EXPECT_NONFATAL_FAILURE( { - ConstructorTracker gadget; + exceptions_internal::ConstructorTracker ct( + exceptions_internal::countdown); new (&storage) Tracked; }, "not destroyed"); + + // Manual destruction of the Tracked instance is not required because + // ~ConstructorTracker() handles that automatically when a leak is found } TEST(ConstructorTrackerTest, DestroyedTwice) { diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h index 48a292b3..c014fb30 100644 --- a/absl/base/internal/exception_safety_testing.h +++ b/absl/base/internal/exception_safety_testing.h @@ -37,8 +37,6 @@ namespace absl { -struct ConstructorTracker; - // A configuration enum for Throwing*. Operations whose flags are set will // throw, everything else won't. This isn't meant to be exhaustive, more flags // can always be made in the future. @@ -105,6 +103,8 @@ void MaybeThrow(absl::string_view msg, bool throw_bad_alloc = false); testing::AssertionResult FailureMessage(const TestException& e, int countdown) noexcept; +class ConstructorTracker; + class TrackedObject { public: TrackedObject(const TrackedObject&) = delete; @@ -112,26 +112,56 @@ class TrackedObject { protected: explicit TrackedObject(const char* child_ctor) { - if (!GetAllocs().emplace(this, child_ctor).second) { + if (!GetInstanceMap().emplace(this, child_ctor).second) { ADD_FAILURE() << "Object at address " << static_cast(this) << " re-constructed in ctor " << child_ctor; } } - static std::unordered_map& GetAllocs() { - static auto* m = - new std::unordered_map(); - return *m; - } - ~TrackedObject() noexcept { - if (GetAllocs().erase(this) == 0) { + if (GetInstanceMap().erase(this) == 0) { ADD_FAILURE() << "Object at address " << static_cast(this) << " destroyed improperly"; } } - friend struct ::absl::ConstructorTracker; + private: + using InstanceMap = std::unordered_map; + static InstanceMap& GetInstanceMap() { + static auto* instance_map = new InstanceMap(); + return *instance_map; + } + + friend class ConstructorTracker; +}; + +// Inspects the constructions and destructions of anything inheriting from +// TrackedObject. This allows us to safely "leak" TrackedObjects, as +// ConstructorTracker will destroy everything left over in its destructor. +class ConstructorTracker { + public: + explicit ConstructorTracker(int c) + : init_count_(c), init_instances_(TrackedObject::GetInstanceMap()) {} + ~ConstructorTracker() { + auto& cur_instances = TrackedObject::GetInstanceMap(); + for (auto it = cur_instances.begin(); it != cur_instances.end();) { + if (init_instances_.count(it->first) == 0) { + ADD_FAILURE() << "Object at address " << static_cast(it->first) + << " constructed from " << it->second + << " where the exception countdown was set to " + << init_count_ << " was not destroyed"; + // Erasing an item inside an unordered_map invalidates the existing + // iterator. A new one is returned for iteration to continue. + it = cur_instances.erase(it); + } else { + ++it; + } + } + } + + private: + int init_count_; + TrackedObject::InstanceMap init_instances_; }; template @@ -707,37 +737,21 @@ class ThrowingAllocator : private exceptions_internal::TrackedObject { template int ThrowingAllocator::next_id_ = 0; -// Inspects the constructions and destructions of anything inheriting from -// TrackedObject. Place this as a member variable in a test fixture to ensure -// that every ThrowingValue was constructed and destroyed correctly. This also -// allows us to safely "leak" TrackedObjects, as ConstructorTracker will destroy -// everything left over in its destructor. -struct ConstructorTracker { - ConstructorTracker() = default; - ~ConstructorTracker() { - auto& allocs = exceptions_internal::TrackedObject::GetAllocs(); - for (const auto& kv : allocs) { - ADD_FAILURE() << "Object at address " << static_cast(kv.first) - << " constructed from " << kv.second << " not destroyed"; - } - allocs.clear(); - } -}; - // Tests for resource leaks by attempting to construct a T using args repeatedly // until successful, using the countdown method. Side effects can then be -// tested for resource leaks. If a ConstructorTracker is present in the test -// fixture, then this will also test that memory resources are not leaked as -// long as T allocates TrackedObjects. +// tested for resource leaks. template -T TestThrowingCtor(Args&&... args) { +void TestThrowingCtor(Args&&... args) { struct Cleanup { ~Cleanup() { exceptions_internal::UnsetCountdown(); } } c; for (int count = 0;; ++count) { + exceptions_internal::ConstructorTracker ct(count); exceptions_internal::SetCountdown(count); try { - return T(std::forward(args)...); + T temp(std::forward(args)...); + static_cast(temp); + break; } catch (const exceptions_internal::TestException&) { } } @@ -934,6 +948,8 @@ class ExceptionSafetyTester { // Starting from 0 and counting upwards until one of the exit conditions is // hit... for (int count = 0;; ++count) { + exceptions_internal::ConstructorTracker ct(count); + // Run the full exception safety test algorithm for the current countdown auto reduced_res = TestAllInvariantsAtCountdown(factory_, selected_operation, count, -- cgit v1.2.3