From 92020a042c0cd46979db9f6f0cb32783dc07765e Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 8 Jun 2018 08:14:48 -0700 Subject: - abacaab4b11a69dd4db627bd183571d7cabb8def Refinement to previous time.h edit (in this same github p... by Greg Falcon - 64db19b773134c6c8004e3b23c9ca892efbf8bae Move SpinLock's adaptive spin count computation from a st... by Derek Mauro - 6f9533fb44a52485a7c2bbb9b4efc7bf8d6c359a Import of CCTZ from GitHub. by Abseil Team - a211d7255c986e8dd4ceada362c0d054a6a1969a Cleanup exception flags by Abseil Team - babdb29c590126fe9bba5229fe91034b5b5c358a Release time benchmarks. by Alex Strelnikov - 5803b32a3ff123d1fb57a0c471d199c818357c9f Release memutil microbenchmarks. by Alex Strelnikov - 5357d4890d30e80c53beb05af32500fb20e9402b Add parens around expansion of ABSL_PREDICT_{FALSE,TRUE} ... by Abseil Team - 32023f61a239a5f6b1c59e577bfe81b179bbcd2d Reformat build rule tag. by Alex Strelnikov - 833758ecf2b0cf7a42bbd50b5b127e416425c168 Release uint128 microbenchmarks. by Alex Strelnikov - c115a9bca1f944b90fdc78a56b2de176466b124f Disambiguate bitwise-not of size_type by Abseil Team - f6905f5b5f6e425792de646edafde440548d9346 Updates ConstructorTracker and TrackedObjects with 1) a m... by Abseil Team - 147c553bdd5d2db20a38f75c4d1ef973d6c709c5 Changes the absl::Duration factory functions to disallow ... by Greg Miller - dba2b96d11b5264546b283ba452f2de1303b0f07 White space fix by Alex Strelnikov GitOrigin-RevId: abacaab4b11a69dd4db627bd183571d7cabb8def Change-Id: I6fa34f20d0b2f898e7b5475a603111413bb80a67 --- absl/base/internal/exception_safety_testing.h | 182 ++++++++++++++++++-------- 1 file changed, 125 insertions(+), 57 deletions(-) (limited to 'absl/base/internal/exception_safety_testing.h') diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h index c3ff34c50abc..8c2f5093fc4d 100644 --- a/absl/base/internal/exception_safety_testing.h +++ b/absl/base/internal/exception_safety_testing.h @@ -62,6 +62,9 @@ constexpr AllocSpec operator&(AllocSpec a, AllocSpec b) { namespace exceptions_internal { +std::string GetSpecString(TypeSpec); +std::string GetSpecString(AllocSpec); + struct NoThrowTag {}; struct StrongGuaranteeTagType {}; @@ -101,70 +104,96 @@ void MaybeThrow(absl::string_view msg, bool throw_bad_alloc = false); testing::AssertionResult FailureMessage(const TestException& e, int countdown) noexcept; -class ConstructorTracker; +struct TrackedAddress { + bool is_alive; + std::string description; +}; -class TrackedObject { +// 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: - TrackedObject(const TrackedObject&) = delete; - TrackedObject(TrackedObject&&) = delete; + explicit ConstructorTracker(int count) : countdown_(count) { + assert(current_tracker_instance_ == nullptr); + current_tracker_instance_ = this; + } - protected: - explicit TrackedObject(const char* child_ctor) { - if (!GetInstanceMap().emplace(this, child_ctor).second) { - ADD_FAILURE() << "Object at address " << static_cast(this) - << " re-constructed in ctor " << child_ctor; + ~ConstructorTracker() { + assert(current_tracker_instance_ == this); + current_tracker_instance_ = nullptr; + + for (auto& it : address_map_) { + void* address = it.first; + TrackedAddress& tracked_address = it.second; + if (tracked_address.is_alive) { + ADD_FAILURE() << "Object at address " << address + << " with countdown of " << countdown_ + << " was not destroyed [" << tracked_address.description + << "]"; + } } } - ~TrackedObject() noexcept { - if (GetInstanceMap().erase(this) == 0) { - ADD_FAILURE() << "Object at address " << static_cast(this) - << " destroyed improperly"; + static void ObjectConstructed(void* address, std::string description) { + if (!CurrentlyTracking()) return; + + TrackedAddress& tracked_address = + current_tracker_instance_->address_map_[address]; + if (tracked_address.is_alive) { + ADD_FAILURE() << "Object at address " << address << " with countdown of " + << current_tracker_instance_->countdown_ + << " was re-constructed. Previously: [" + << tracked_address.description << "] Now: [" << description + << "]"; } + tracked_address = {true, std::move(description)}; + } + + static void ObjectDestructed(void* address) { + if (!CurrentlyTracking()) return; + + auto it = current_tracker_instance_->address_map_.find(address); + // Not tracked. Ignore. + if (it == current_tracker_instance_->address_map_.end()) return; + + TrackedAddress& tracked_address = it->second; + if (!tracked_address.is_alive) { + ADD_FAILURE() << "Object at address " << address << " with countdown of " + << current_tracker_instance_->countdown_ + << " was re-destroyed or created prior to construction " + << "tracking [" << tracked_address.description << "]"; + } + tracked_address.is_alive = false; } private: - using InstanceMap = std::unordered_map; - static InstanceMap& GetInstanceMap() { - static auto* instance_map = new InstanceMap(); - return *instance_map; + static bool CurrentlyTracking() { + return current_tracker_instance_ != nullptr; } - friend class ConstructorTracker; + std::unordered_map address_map_; + int countdown_; + + static ConstructorTracker* current_tracker_instance_; }; -// 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 { +class TrackedObject { 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; - } - } + TrackedObject(const TrackedObject&) = delete; + TrackedObject(TrackedObject&&) = delete; + + protected: + explicit TrackedObject(std::string description) { + ConstructorTracker::ObjectConstructed(this, std::move(description)); } - private: - int init_count_; - TrackedObject::InstanceMap init_instances_; + ~TrackedObject() noexcept { ConstructorTracker::ObjectDestructed(this); } }; template absl::optional TestSingleInvariantAtCountdownImpl( - const Factory& factory, Operation operation, int count, + const Factory& factory, const Operation& operation, int count, const Invariant& invariant) { auto t_ptr = factory(); absl::optional current_res; @@ -229,7 +258,6 @@ inline absl::optional TestAllInvariantsAtCountdown( extern exceptions_internal::NoThrowTag nothrow_ctor; -bool nothrow_guarantee(const void*); extern exceptions_internal::StrongGuaranteeTagType strong_guarantee; // A test class which is convertible to bool. The conversion can be @@ -283,17 +311,18 @@ class ThrowingValue : private exceptions_internal::TrackedObject { return static_cast(Spec & spec); } + static constexpr int kDefaultValue = 0; static constexpr int kBadValue = 938550620; public: - ThrowingValue() : TrackedObject(ABSL_PRETTY_FUNCTION) { + ThrowingValue() : TrackedObject(GetInstanceString(kDefaultValue)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - dummy_ = 0; + dummy_ = kDefaultValue; } ThrowingValue(const ThrowingValue& other) noexcept( IsSpecified(TypeSpec::kNoThrowCopy)) - : TrackedObject(ABSL_PRETTY_FUNCTION) { + : TrackedObject(GetInstanceString(other.dummy_)) { if (!IsSpecified(TypeSpec::kNoThrowCopy)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); } @@ -302,20 +331,20 @@ class ThrowingValue : private exceptions_internal::TrackedObject { ThrowingValue(ThrowingValue&& other) noexcept( IsSpecified(TypeSpec::kNoThrowMove)) - : TrackedObject(ABSL_PRETTY_FUNCTION) { + : TrackedObject(GetInstanceString(other.dummy_)) { if (!IsSpecified(TypeSpec::kNoThrowMove)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); } dummy_ = other.dummy_; } - explicit ThrowingValue(int i) : TrackedObject(ABSL_PRETTY_FUNCTION) { + explicit ThrowingValue(int i) : TrackedObject(GetInstanceString(i)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); dummy_ = i; } ThrowingValue(int i, exceptions_internal::NoThrowTag) noexcept - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(i) {} + : TrackedObject(GetInstanceString(i)), dummy_(i) {} // absl expects nothrow destructors ~ThrowingValue() noexcept = default; @@ -548,9 +577,9 @@ class ThrowingValue : private exceptions_internal::TrackedObject { void operator&() const = delete; // NOLINT(runtime/operator) // Stream operators - friend std::ostream& operator<<(std::ostream& os, const ThrowingValue&) { + friend std::ostream& operator<<(std::ostream& os, const ThrowingValue& tv) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return os; + return os << GetInstanceString(tv.dummy_); } friend std::istream& operator>>(std::istream& is, const ThrowingValue&) { @@ -606,6 +635,12 @@ class ThrowingValue : private exceptions_internal::TrackedObject { const int& Get() const noexcept { return dummy_; } private: + static std::string GetInstanceString(int dummy) { + return absl::StrCat("ThrowingValue<", + exceptions_internal::GetSpecString(Spec), ">(", dummy, + ")"); + } + int dummy_; }; // While not having to do with exceptions, explicitly delete comma operator, to @@ -658,26 +693,30 @@ class ThrowingAllocator : private exceptions_internal::TrackedObject { using propagate_on_container_swap = std::true_type; using is_always_equal = std::false_type; - ThrowingAllocator() : TrackedObject(ABSL_PRETTY_FUNCTION) { + ThrowingAllocator() : TrackedObject(GetInstanceString(next_id_)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); dummy_ = std::make_shared(next_id_++); } template ThrowingAllocator(const ThrowingAllocator& other) noexcept // NOLINT - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(other.State()) {} // According to C++11 standard [17.6.3.5], Table 28, the move/copy ctors of // allocator shall not exit via an exception, thus they are marked noexcept. ThrowingAllocator(const ThrowingAllocator& other) noexcept - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(other.State()) {} template ThrowingAllocator(ThrowingAllocator&& other) noexcept // NOLINT - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(std::move(other.State())) {} ThrowingAllocator(ThrowingAllocator&& other) noexcept - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(std::move(other.State())) {} ~ThrowingAllocator() noexcept = default; @@ -758,6 +797,12 @@ class ThrowingAllocator : private exceptions_internal::TrackedObject { friend class ThrowingAllocator; private: + static std::string GetInstanceString(int dummy) { + return absl::StrCat("ThrowingAllocator<", + exceptions_internal::GetSpecString(Spec), ">(", dummy, + ")"); + } + const std::shared_ptr& State() const { return dummy_; } std::shared_ptr& State() { return dummy_; } @@ -801,6 +846,29 @@ void TestThrowingCtor(Args&&... args) { } } +// Tests the nothrow guarantee of the provided nullary operation. If the an +// exception is thrown, the result will be AssertionFailure(). Otherwise, it +// will be AssertionSuccess(). +template +testing::AssertionResult TestNothrowOp(const Operation& operation) { + struct Cleanup { + Cleanup() { exceptions_internal::SetCountdown(); } + ~Cleanup() { exceptions_internal::UnsetCountdown(); } + } c; + try { + operation(); + return testing::AssertionSuccess(); + } catch (exceptions_internal::TestException) { + return testing::AssertionFailure() + << "TestException thrown during call to operation() when nothrow " + "guarantee was expected."; + } catch (...) { + return testing::AssertionFailure() + << "Unknown exception thrown during call to operation() when " + "nothrow guarantee was expected."; + } +} + namespace exceptions_internal { // Dummy struct for ExceptionSafetyTester<> partial state. -- cgit 1.4.1