summaryrefslogtreecommitdiff
path: root/absl/base/internal/exception_safety_testing.h
diff options
context:
space:
mode:
authorGravatar Abseil Team <absl-team@google.com>2018-06-08 08:14:48 -0700
committerGravatar Gennadiy Civil <misterg@google.com>2018-06-08 11:24:58 -0400
commit92020a042c0cd46979db9f6f0cb32783dc07765e (patch)
tree5f4ebb5a8b0a8c4f0e5e2a061e63665b2071d92f /absl/base/internal/exception_safety_testing.h
parent7aacab8ae05d7115049923da9cfbf584dc1f8338 (diff)
- abacaab4b11a69dd4db627bd183571d7cabb8def Refinement to previous time.h edit (in this same github p... by Greg Falcon <gfalcon@google.com>
- 64db19b773134c6c8004e3b23c9ca892efbf8bae Move SpinLock's adaptive spin count computation from a st... by Derek Mauro <dmauro@google.com> - 6f9533fb44a52485a7c2bbb9b4efc7bf8d6c359a Import of CCTZ from GitHub. by Abseil Team <absl-team@google.com> - a211d7255c986e8dd4ceada362c0d054a6a1969a Cleanup exception flags by Abseil Team <absl-team@google.com> - babdb29c590126fe9bba5229fe91034b5b5c358a Release time benchmarks. by Alex Strelnikov <strel@google.com> - 5803b32a3ff123d1fb57a0c471d199c818357c9f Release memutil microbenchmarks. by Alex Strelnikov <strel@google.com> - 5357d4890d30e80c53beb05af32500fb20e9402b Add parens around expansion of ABSL_PREDICT_{FALSE,TRUE} ... by Abseil Team <absl-team@google.com> - 32023f61a239a5f6b1c59e577bfe81b179bbcd2d Reformat build rule tag. by Alex Strelnikov <strel@google.com> - 833758ecf2b0cf7a42bbd50b5b127e416425c168 Release uint128 microbenchmarks. by Alex Strelnikov <strel@google.com> - c115a9bca1f944b90fdc78a56b2de176466b124f Disambiguate bitwise-not of size_type by Abseil Team <absl-team@google.com> - f6905f5b5f6e425792de646edafde440548d9346 Updates ConstructorTracker and TrackedObjects with 1) a m... by Abseil Team <absl-team@google.com> - 147c553bdd5d2db20a38f75c4d1ef973d6c709c5 Changes the absl::Duration factory functions to disallow ... by Greg Miller <jgm@google.com> - dba2b96d11b5264546b283ba452f2de1303b0f07 White space fix by Alex Strelnikov <strel@google.com> GitOrigin-RevId: abacaab4b11a69dd4db627bd183571d7cabb8def Change-Id: I6fa34f20d0b2f898e7b5475a603111413bb80a67
Diffstat (limited to 'absl/base/internal/exception_safety_testing.h')
-rw-r--r--absl/base/internal/exception_safety_testing.h182
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 c3ff34c5..8c2f5093 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.