diff options
Diffstat (limited to 'absl/base/internal/exception_safety_testing.h')
-rw-r--r-- | absl/base/internal/exception_safety_testing.h | 182 |
1 files changed, 125 insertions, 57 deletions
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<void*>(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<void*>(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<TrackedObject*, absl::string_view>; - 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<void*, TrackedAddress> 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<void*>(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 <typename Factory, typename Operation, typename Invariant> absl::optional<testing::AssertionResult> 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<testing::AssertionResult> current_res; @@ -229,7 +258,6 @@ inline absl::optional<testing::AssertionResult> 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<bool>(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<const int>(next_id_++); } template <typename U> ThrowingAllocator(const ThrowingAllocator<U, Spec>& 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 <typename U> ThrowingAllocator(ThrowingAllocator<U, Spec>&& 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<const int>& State() const { return dummy_; } std::shared_ptr<const int>& 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 <typename Operation> +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. |