From 8db6cfd1ea64dbd06e69e9c78ec5a2f83846d9ad Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Mon, 30 Oct 2017 15:55:37 -0700 Subject: Changes imported from Abseil "staging" branch: - 8bcd472c6f1a7c8a3a7aac07d7e5e3f90685fe5e Create an exception-safety testing framework for Abseil by Jon Cohen - 17cc4bb19ba86e2bc45e9381bd07450c06134903 Fix typo. by Abseil Team GitOrigin-RevId: 8bcd472c6f1a7c8a3a7aac07d7e5e3f90685fe5e Change-Id: Ia1f4f12d25c375e0af34fea052a4a82dc964eeff --- absl/base/internal/exception_safety_testing.cc | 21 + absl/base/internal/exception_safety_testing.h | 679 +++++++++++++++++++++++++ absl/base/internal/pretty_function.h | 19 + 3 files changed, 719 insertions(+) create mode 100644 absl/base/internal/exception_safety_testing.cc create mode 100644 absl/base/internal/exception_safety_testing.h create mode 100644 absl/base/internal/pretty_function.h (limited to 'absl/base/internal') diff --git a/absl/base/internal/exception_safety_testing.cc b/absl/base/internal/exception_safety_testing.cc new file mode 100644 index 00000000..383c9c59 --- /dev/null +++ b/absl/base/internal/exception_safety_testing.cc @@ -0,0 +1,21 @@ +#include "absl/base/internal/exception_safety_testing.h" + +#include "gtest/gtest.h" +#include "absl/meta/type_traits.h" + +namespace absl { +namespace exceptions_internal { + +int countdown = -1; + +void MaybeThrow(absl::string_view msg) { + if (countdown-- == 0) throw TestException(msg); +} + +testing::AssertionResult FailureMessage(const TestException& e, + int countdown) noexcept { + return testing::AssertionFailure() + << "Exception number " << countdown + 1 << " thrown from " << e.what(); +} +} // namespace exceptions_internal +} // namespace absl diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h new file mode 100644 index 00000000..aca27a57 --- /dev/null +++ b/absl/base/internal/exception_safety_testing.h @@ -0,0 +1,679 @@ +// Utilities for testing exception-safety + +#ifndef ABSL_BASE_INTERNAL_EXCEPTION_TESTING_H_ +#define ABSL_BASE_INTERNAL_EXCEPTION_TESTING_H_ + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/internal/pretty_function.h" +#include "absl/meta/type_traits.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" + +namespace absl { +struct AllocInspector; + +// 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. +enum class NoThrow : uint8_t { + kNone = 0, + kMoveCtor = 1, + kMoveAssign = 1 << 1, + kAllocation = 1 << 2, + kIntCtor = 1 << 3, + kNoThrow = static_cast(-1) +}; + +constexpr NoThrow operator|(NoThrow a, NoThrow b) { + using T = absl::underlying_type_t; + return static_cast(static_cast(a) | static_cast(b)); +} + +constexpr NoThrow operator&(NoThrow a, NoThrow b) { + using T = absl::underlying_type_t; + return static_cast(static_cast(a) & static_cast(b)); +} + +namespace exceptions_internal { +constexpr bool ThrowingAllowed(NoThrow flags, NoThrow flag) { + return !static_cast(flags & flag); +} + +// A simple exception class. We throw this so that test code can catch +// exceptions specifically thrown by ThrowingValue. +class TestException { + public: + explicit TestException(absl::string_view msg) : msg_(msg) {} + absl::string_view what() const { return msg_; } + + private: + std::string msg_; +}; + +extern int countdown; + +void MaybeThrow(absl::string_view msg); + +testing::AssertionResult FailureMessage(const TestException& e, + int countdown) noexcept; + +class TrackedObject { + protected: + explicit TrackedObject(absl::string_view child_ctor) { + if (!GetAllocs().emplace(this, child_ctor).second) { + ADD_FAILURE() << "Object at address " << static_cast(this) + << " re-constructed in ctor " << child_ctor; + } + } + + TrackedObject(const TrackedObject&) = delete; + TrackedObject(TrackedObject&&) = delete; + + static std::unordered_map& GetAllocs() { + static auto* m = + new std::unordered_map(); + return *m; + } + + ~TrackedObject() noexcept { + if (GetAllocs().erase(this) == 0) { + ADD_FAILURE() << "Object at address " << static_cast(this) + << " destroyed improperly"; + } + } + + friend struct ::absl::AllocInspector; +}; +} // namespace exceptions_internal + +// A test class which is contextually convertible to bool. The conversion can +// be instrumented to throw at a controlled time. +class ThrowingBool { + public: + ThrowingBool(bool b) noexcept : b_(b) {} // NOLINT(runtime/explicit) + explicit operator bool() const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return b_; + } + + private: + bool b_; +}; + +// A testing class instrumented to throw an exception at a controlled time. +// +// ThrowingValue implements a slightly relaxed version of the Regular concept -- +// that is it's a value type with the expected semantics. It also implements +// arithmetic operations. It doesn't implement member and pointer operators +// like operator-> or operator[]. +// +// ThrowingValue can be instrumented to have certain operations be noexcept by +// using compile-time bitfield flag template arguments. That is, to make an +// ThrowingValue which has a noexcept move constructor and noexcept move +// assignment, use +// ThrowingValue. +template +class ThrowingValue : private exceptions_internal::TrackedObject { + public: + ThrowingValue() : TrackedObject(ABSL_PRETTY_FUNCTION) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ = 0; + } + + ThrowingValue(const ThrowingValue& other) + : TrackedObject(ABSL_PRETTY_FUNCTION) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ = other.dummy_; + } + + ThrowingValue(ThrowingValue&& other) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveCtor)) + : TrackedObject(ABSL_PRETTY_FUNCTION) { + if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveCtor)) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + } + dummy_ = other.dummy_; + } + + explicit ThrowingValue(int i) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kIntCtor)) + : TrackedObject(ABSL_PRETTY_FUNCTION) { + if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kIntCtor)) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + } + dummy_ = i; + } + + // absl expects nothrow destructors + ~ThrowingValue() noexcept = default; + + ThrowingValue& operator=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ = other.dummy_; + return *this; + } + + ThrowingValue& operator=(ThrowingValue&& other) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveAssign)) { + if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveAssign)) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + } + dummy_ = other.dummy_; + return *this; + } + + // Arithmetic Operators + ThrowingValue operator+(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ + other.dummy_, NoThrowTag{}); + } + + ThrowingValue operator+() const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_, NoThrowTag{}); + } + + ThrowingValue operator-(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ - other.dummy_, NoThrowTag{}); + } + + ThrowingValue operator-() const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(-dummy_, NoThrowTag{}); + } + + ThrowingValue& operator++() { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + ++dummy_; + return *this; + } + + ThrowingValue operator++(int) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + auto out = ThrowingValue(dummy_, NoThrowTag{}); + ++dummy_; + return out; + } + + ThrowingValue& operator--() { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + --dummy_; + return *this; + } + + ThrowingValue operator--(int) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + auto out = ThrowingValue(dummy_, NoThrowTag{}); + --dummy_; + return out; + } + + ThrowingValue operator*(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ * other.dummy_, NoThrowTag{}); + } + + ThrowingValue operator/(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ / other.dummy_, NoThrowTag{}); + } + + ThrowingValue operator%(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ % other.dummy_, NoThrowTag{}); + } + + ThrowingValue operator<<(int shift) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ << shift, NoThrowTag{}); + } + + ThrowingValue operator>>(int shift) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ >> shift, NoThrowTag{}); + } + + // Comparison Operators + friend ThrowingBool operator==(const ThrowingValue& a, + const ThrowingValue& b) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return a.dummy_ == b.dummy_; + } + friend ThrowingBool operator!=(const ThrowingValue& a, + const ThrowingValue& b) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return a.dummy_ != b.dummy_; + } + friend ThrowingBool operator<(const ThrowingValue& a, + const ThrowingValue& b) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return a.dummy_ < b.dummy_; + } + friend ThrowingBool operator<=(const ThrowingValue& a, + const ThrowingValue& b) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return a.dummy_ <= b.dummy_; + } + friend ThrowingBool operator>(const ThrowingValue& a, + const ThrowingValue& b) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return a.dummy_ > b.dummy_; + } + friend ThrowingBool operator>=(const ThrowingValue& a, + const ThrowingValue& b) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return a.dummy_ >= b.dummy_; + } + + // Logical Operators + ThrowingBool operator!() const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return !dummy_; + } + + ThrowingBool operator&&(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return dummy_ && other.dummy_; + } + + ThrowingBool operator||(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return dummy_ || other.dummy_; + } + + // Bitwise Logical Operators + ThrowingValue operator~() const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(~dummy_, NoThrowTag{}); + } + + ThrowingValue operator&(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ & other.dummy_, NoThrowTag{}); + } + + ThrowingValue operator|(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ | other.dummy_, NoThrowTag{}); + } + + ThrowingValue operator^(const ThrowingValue& other) const { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return ThrowingValue(dummy_ ^ other.dummy_, NoThrowTag{}); + } + + // Compound Assignment operators + ThrowingValue& operator+=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ += other.dummy_; + return *this; + } + + ThrowingValue& operator-=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ -= other.dummy_; + return *this; + } + + ThrowingValue& operator*=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ *= other.dummy_; + return *this; + } + + ThrowingValue& operator/=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ /= other.dummy_; + return *this; + } + + ThrowingValue& operator%=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ %= other.dummy_; + return *this; + } + + ThrowingValue& operator&=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ &= other.dummy_; + return *this; + } + + ThrowingValue& operator|=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ |= other.dummy_; + return *this; + } + + ThrowingValue& operator^=(const ThrowingValue& other) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ ^= other.dummy_; + return *this; + } + + ThrowingValue& operator<<=(int shift) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ <<= shift; + return *this; + } + + ThrowingValue& operator>>=(int shift) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ >>= shift; + return *this; + } + + // Pointer operators + void operator&() const = delete; // NOLINT(runtime/operator) + + // Stream operators + friend std::ostream& operator<<(std::ostream& os, const ThrowingValue&) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return os; + } + + friend std::istream& operator>>(std::istream& is, const ThrowingValue&) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + return is; + } + + // Memory management operators + // Args.. allows us to overload regular and placement new in one shot + template + static void* operator new(size_t s, Args&&... args) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) { + if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + } + return ::operator new(s, std::forward(args)...); + } + + template + static void* operator new[](size_t s, Args&&... args) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) { + if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + } + return ::operator new[](s, std::forward(args)...); + } + + // Abseil doesn't support throwing overloaded operator delete. These are + // provided so a throwing operator-new can clean up after itself. + // + // We provide both regular and templated operator delete because if only the + // templated version is provided as we did with operator new, the compiler has + // no way of knowing which overload of operator delete to call. See + // http://en.cppreference.com/w/cpp/memory/new/operator_delete and + // http://en.cppreference.com/w/cpp/language/delete for the gory details. + void operator delete(void* p) noexcept { ::operator delete(p); } + + template + void operator delete(void* p, Args&&... args) noexcept { + ::operator delete(p, std::forward(args)...); + } + + void operator delete[](void* p) noexcept { return ::operator delete[](p); } + + template + void operator delete[](void* p, Args&&... args) noexcept { + return ::operator delete[](p, std::forward(args)...); + } + + // Non-standard access to the actual contained value. No need for this to + // throw. + int& Get() noexcept { return dummy_; } + const int& Get() const noexcept { return dummy_; } + + private: + struct NoThrowTag {}; + ThrowingValue(int i, NoThrowTag) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(i) {} + + int dummy_; +}; +// While not having to do with exceptions, explicitly delete comma operator, to +// make sure we don't use it on user-supplied types. +template +void operator,(const ThrowingValue& ef, T&& t) = delete; +template +void operator,(T&& t, const ThrowingValue& ef) = delete; + +// An allocator type which is instrumented to throw at a controlled time, or not +// to throw, using NoThrow. The supported settings are the default of every +// function which is allowed to throw in a conforming allocator possibly +// throwing, or nothing throws, in line with the ABSL_ALLOCATOR_THROWS +// configuration macro. +template +class ThrowingAllocator : private exceptions_internal::TrackedObject { + static_assert(Flags == NoThrow::kNone || Flags == NoThrow::kNoThrow, + "Invalid flag"); + + public: + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using void_pointer = void*; + using const_void_pointer = const void*; + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + + using is_nothrow = std::integral_constant; + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + using is_always_equal = std::false_type; + + ThrowingAllocator() : TrackedObject(ABSL_PRETTY_FUNCTION) { + exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); + dummy_ = std::make_shared(next_id_++); + } + + template + ThrowingAllocator( // NOLINT + const ThrowingAllocator& other) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + + ThrowingAllocator(const ThrowingAllocator& other) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + + template + ThrowingAllocator( // NOLINT + ThrowingAllocator&& other) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {} + + ThrowingAllocator(ThrowingAllocator&& other) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {} + + ~ThrowingAllocator() noexcept = default; + + template + ThrowingAllocator& operator=( + const ThrowingAllocator& other) noexcept { + dummy_ = other.State(); + return *this; + } + + template + ThrowingAllocator& operator=(ThrowingAllocator&& other) noexcept { + dummy_ = std::move(other.State()); + return *this; + } + + template + struct rebind { + using other = ThrowingAllocator; + }; + + pointer allocate(size_type n) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) { + ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION); + return static_cast(::operator new(n * sizeof(T))); + } + pointer allocate(size_type n, const_void_pointer) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) { + return allocate(n); + } + + void deallocate(pointer ptr, size_type) noexcept { + ReadState(); + ::operator delete(static_cast(ptr)); + } + + template + void construct(U* ptr, Args&&... args) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) { + ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION); + ::new (static_cast(ptr)) U(std::forward(args)...); + } + + template + void destroy(U* p) noexcept { + ReadState(); + p->~U(); + } + + size_type max_size() const + noexcept(!exceptions_internal::ThrowingAllowed(Flags, + NoThrow::kNoThrow)) { + ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION); + return std::numeric_limits::max() / sizeof(value_type); + } + + ThrowingAllocator select_on_container_copy_construction() noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) { + auto& out = *this; + ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION); + return out; + } + + template + bool operator==(const ThrowingAllocator& other) const noexcept { + return dummy_ == other.dummy_; + } + + template + bool operator!=(const ThrowingAllocator& other) const noexcept { + return dummy_ != other.dummy_; + } + + template + friend class ThrowingAllocator; + + private: + const std::shared_ptr& State() const { return dummy_; } + std::shared_ptr& State() { return dummy_; } + + void ReadState() { + // we know that this will never be true, but the compiler doesn't, so this + // should safely force a read of the value. + if (*dummy_ < 0) std::abort(); + } + + void ReadStateAndMaybeThrow(absl::string_view msg) const { + if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) { + exceptions_internal::MaybeThrow( + absl::Substitute("Allocator id $0 threw from $1", *dummy_, msg)); + } + } + + static int next_id_; + std::shared_ptr dummy_; +}; + +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. +struct AllocInspector { + AllocInspector() = default; + ~AllocInspector() { + 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 that performing operation Op on a T follows the basic exception safety +// guarantee. +// +// Parameters: +// * T: the type under test. +// * FunctionFromTPtrToVoid: A functor exercising the function under test. It +// should take a T* and return void. +// +// There must also be a function named `AbslCheckInvariants` in an associated +// namespace of T which takes a const T& and returns true if the T's class +// invariants hold, and false if they don't. +template +testing::AssertionResult TestBasicGuarantee(T* t, FunctionFromTPtrToVoid&& op) { + for (int countdown = 0;; ++countdown) { + exceptions_internal::countdown = countdown; + try { + op(t); + break; + } catch (const exceptions_internal::TestException& e) { + if (!AbslCheckInvariants(*t)) { + return exceptions_internal::FailureMessage(e, countdown) + << " broke invariants."; + } + } + } + exceptions_internal::countdown = -1; + return testing::AssertionSuccess(); +} + +// Tests that performing operation Op on a T follows the strong exception safety +// guarantee. +// +// Parameters: +// * T: the type under test. T must be copy-constructable and +// equality-comparible. +// * FunctionFromTPtrToVoid: A functor exercising the function under test. It +// should take a T* and return void. +// +// There must also be a function named `AbslCheckInvariants` in an associated +// namespace of T which takes a const T& and returns true if the T's class +// invariants hold, and false if they don't. +template +testing::AssertionResult TestStrongGuarantee(T* t, + FunctionFromTPtrToVoid&& op) { + exceptions_internal::countdown = -1; + for (auto countdown = 0;; ++countdown) { + T dup = *t; + exceptions_internal::countdown = countdown; + try { + op(t); + break; + } catch (const exceptions_internal::TestException& e) { + if (!AbslCheckInvariants(*t)) { + return exceptions_internal::FailureMessage(e, countdown) + << " broke invariants."; + } + if (dup != *t) + return exceptions_internal::FailureMessage(e, countdown) + << " changed state."; + } + } + exceptions_internal::countdown = -1; + return testing::AssertionSuccess(); +} + +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_ diff --git a/absl/base/internal/pretty_function.h b/absl/base/internal/pretty_function.h new file mode 100644 index 00000000..6be3936f --- /dev/null +++ b/absl/base/internal/pretty_function.h @@ -0,0 +1,19 @@ +#ifndef ABSL_BASE_INTERNAL_PRETTY_FUNCTION_H_ +#define ABSL_BASE_INTERNAL_PRETTY_FUNCTION_H_ + +// ABSL_PRETTY_FUNCTION +// +// In C++11, __func__ gives the undecorated name of the current function. That +// is, "main", not "int main()". Various compilers give extra macros to get the +// decorated function name, including return type and arguments, to +// differentiate between overload sets. ABSL_PRETTY_FUNCTION is a portable +// version of these macros which forwards to the correct macro on each compiler. +#if defined(_MSC_VER) +#define ABSL_PRETTY_FUNCTION __FUNCSIG__ +#elif defined(__GNUC__) +#define ABSL_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#else +#error "Unsupported compiler" +#endif + +#endif // ABSL_BASE_INTERNAL_PRETTY_FUNCTION_H_ -- cgit v1.2.3