diff options
Diffstat (limited to 'absl/base')
-rw-r--r-- | absl/base/BUILD.bazel | 31 | ||||
-rw-r--r-- | absl/base/attributes.h | 2 | ||||
-rw-r--r-- | absl/base/exception_safety_testing_test.cc | 546 | ||||
-rw-r--r-- | absl/base/internal/exception_safety_testing.cc | 21 | ||||
-rw-r--r-- | absl/base/internal/exception_safety_testing.h | 679 | ||||
-rw-r--r-- | absl/base/internal/pretty_function.h | 19 |
6 files changed, 1297 insertions, 1 deletions
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index e68c4500e561..207051254d21 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -226,6 +226,37 @@ cc_library( ], ) +cc_library( + name = "pretty_function", + hdrs = ["internal/pretty_function.h"], +) + +cc_library( + name = "exception_safety_testing", + testonly = 1, + srcs = ["internal/exception_safety_testing.cc"], + hdrs = ["internal/exception_safety_testing.h"], + copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG, + deps = [ + ":config", + ":pretty_function", + "//absl/meta:type_traits", + "//absl/strings", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "exception_safety_testing_test", + srcs = ["exception_safety_testing_test.cc"], + copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG, + deps = [ + ":exception_safety_testing", + "//absl/memory", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "invoke_test", size = "small", diff --git a/absl/base/attributes.h b/absl/base/attributes.h index ddf44584130d..6f3cfe4cbdae 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h @@ -260,7 +260,7 @@ // ABSL_ATTRIBUTE_NO_SANITIZE_UNDEFINED // // Tells the UndefinedSanitizer to ignore a given function. Useful for cases -// where certain behavior (eg. devision by zero) is being used intentionally. +// where certain behavior (eg. division by zero) is being used intentionally. // NOTE: GCC supports UndefinedBehaviorSanitizer(ubsan) since 4.9. // https://gcc.gnu.org/gcc-4.9/changes.html #if defined(__GNUC__) && \ diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc new file mode 100644 index 000000000000..365732c6721a --- /dev/null +++ b/absl/base/exception_safety_testing_test.cc @@ -0,0 +1,546 @@ +#include "absl/base/internal/exception_safety_testing.h" + +#include <cstddef> +#include <exception> +#include <iostream> +#include <list> +#include <vector> + +#include "gtest/gtest-spi.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" + +namespace absl { +namespace { +using ::absl::exceptions_internal::TestException; + +void SetCountdown() { exceptions_internal::countdown = 0; } + +void UnsetCountdown() { exceptions_internal::countdown = -1; } + +// EXPECT_NO_THROW can't inspect the thrown inspection in general. +template <typename F> +void ExpectNoThrow(const F& f) { + try { + f(); + } catch (TestException e) { + ADD_FAILURE() << "Unexpected exception thrown from " << e.what(); + } +} + +class ThrowingValueTest : public ::testing::Test { + protected: + void SetUp() override { UnsetCountdown(); } + + private: + AllocInspector clouseau_; +}; + +TEST_F(ThrowingValueTest, Throws) { + SetCountdown(); + EXPECT_THROW(ThrowingValue<> bomb, TestException); + + // It's not guaranteed that every operator only throws *once*. The default + // ctor only throws once, though, so use it to make sure we only throw when + // the countdown hits 0 + exceptions_internal::countdown = 2; + ExpectNoThrow([]() { ThrowingValue<> bomb; }); + ExpectNoThrow([]() { ThrowingValue<> bomb; }); + EXPECT_THROW(ThrowingValue<> bomb, TestException); +} + +// Tests that an operation throws when the countdown is at 0, doesn't throw when +// the countdown doesn't hit 0, and doesn't modify the state of the +// ThrowingValue if it throws +template <typename F> +void TestOp(F&& f) { + UnsetCountdown(); + ExpectNoThrow(f); + + SetCountdown(); + EXPECT_THROW(f(), TestException); + UnsetCountdown(); +} + +TEST_F(ThrowingValueTest, ThrowingCtors) { + ThrowingValue<> bomb; + + TestOp([]() { ThrowingValue<> bomb(1); }); + TestOp([&]() { ThrowingValue<> bomb1 = bomb; }); + TestOp([&]() { ThrowingValue<> bomb1 = std::move(bomb); }); +} + +TEST_F(ThrowingValueTest, ThrowingAssignment) { + ThrowingValue<> bomb, bomb1; + + TestOp([&]() { bomb = bomb1; }); + TestOp([&]() { bomb = std::move(bomb1); }); +} + +TEST_F(ThrowingValueTest, ThrowingComparisons) { + ThrowingValue<> bomb1, bomb2; + TestOp([&]() { return bomb1 == bomb2; }); + TestOp([&]() { return bomb1 != bomb2; }); + TestOp([&]() { return bomb1 < bomb2; }); + TestOp([&]() { return bomb1 <= bomb2; }); + TestOp([&]() { return bomb1 > bomb2; }); + TestOp([&]() { return bomb1 >= bomb2; }); +} + +TEST_F(ThrowingValueTest, ThrowingArithmeticOps) { + ThrowingValue<> bomb1(1), bomb2(2); + + TestOp([&bomb1]() { +bomb1; }); + TestOp([&bomb1]() { -bomb1; }); + TestOp([&bomb1]() { ++bomb1; }); + TestOp([&bomb1]() { bomb1++; }); + TestOp([&bomb1]() { --bomb1; }); + TestOp([&bomb1]() { bomb1--; }); + + TestOp([&]() { bomb1 + bomb2; }); + TestOp([&]() { bomb1 - bomb2; }); + TestOp([&]() { bomb1* bomb2; }); + TestOp([&]() { bomb1 / bomb2; }); + TestOp([&]() { bomb1 << 1; }); + TestOp([&]() { bomb1 >> 1; }); +} + +TEST_F(ThrowingValueTest, ThrowingLogicalOps) { + ThrowingValue<> bomb1, bomb2; + + TestOp([&bomb1]() { !bomb1; }); + TestOp([&]() { bomb1&& bomb2; }); + TestOp([&]() { bomb1 || bomb2; }); +} + +TEST_F(ThrowingValueTest, ThrowingBitwiseOps) { + ThrowingValue<> bomb1, bomb2; + + TestOp([&bomb1]() { ~bomb1; }); + TestOp([&]() { bomb1& bomb2; }); + TestOp([&]() { bomb1 | bomb2; }); + TestOp([&]() { bomb1 ^ bomb2; }); +} + +TEST_F(ThrowingValueTest, ThrowingCompoundAssignmentOps) { + ThrowingValue<> bomb1(1), bomb2(2); + + TestOp([&]() { bomb1 += bomb2; }); + TestOp([&]() { bomb1 -= bomb2; }); + TestOp([&]() { bomb1 *= bomb2; }); + TestOp([&]() { bomb1 /= bomb2; }); + TestOp([&]() { bomb1 %= bomb2; }); + TestOp([&]() { bomb1 &= bomb2; }); + TestOp([&]() { bomb1 |= bomb2; }); + TestOp([&]() { bomb1 ^= bomb2; }); + TestOp([&]() { bomb1 *= bomb2; }); +} + +TEST_F(ThrowingValueTest, ThrowingStreamOps) { + ThrowingValue<> bomb; + + TestOp([&]() { std::cin >> bomb; }); + TestOp([&]() { std::cout << bomb; }); +} + +TEST_F(ThrowingValueTest, ThrowingAllocatingOps) { + // make_unique calls unqualified operator new, so these exercise the + // ThrowingValue overloads. + TestOp([]() { return absl::make_unique<ThrowingValue<>>(1); }); + TestOp([]() { return absl::make_unique<ThrowingValue<>[]>(2); }); +} + +TEST_F(ThrowingValueTest, NonThrowingMoveCtor) { + ThrowingValue<NoThrow::kMoveCtor> nothrow_ctor; + + SetCountdown(); + ExpectNoThrow([¬hrow_ctor]() { + ThrowingValue<NoThrow::kMoveCtor> nothrow1 = std::move(nothrow_ctor); + }); +} + +TEST_F(ThrowingValueTest, NonThrowingMoveAssign) { + ThrowingValue<NoThrow::kMoveAssign> nothrow_assign1, nothrow_assign2; + + SetCountdown(); + ExpectNoThrow([¬hrow_assign1, ¬hrow_assign2]() { + nothrow_assign1 = std::move(nothrow_assign2); + }); +} + +TEST_F(ThrowingValueTest, ThrowingSwap) { + ThrowingValue<> bomb1, bomb2; + TestOp([&]() { std::swap(bomb1, bomb2); }); + + ThrowingValue<NoThrow::kMoveCtor> bomb3, bomb4; + TestOp([&]() { std::swap(bomb3, bomb4); }); + + ThrowingValue<NoThrow::kMoveAssign> bomb5, bomb6; + TestOp([&]() { std::swap(bomb5, bomb6); }); +} + +TEST_F(ThrowingValueTest, NonThrowingSwap) { + ThrowingValue<NoThrow::kMoveAssign | NoThrow::kMoveCtor> bomb1, bomb2; + ExpectNoThrow([&]() { std::swap(bomb1, bomb2); }); +} + +TEST_F(ThrowingValueTest, NonThrowingAllocation) { + ThrowingValue<NoThrow::kAllocation>* allocated; + ThrowingValue<NoThrow::kAllocation>* array; + + ExpectNoThrow([&allocated]() { + allocated = new ThrowingValue<NoThrow::kAllocation>(1); + delete allocated; + }); + ExpectNoThrow([&array]() { + array = new ThrowingValue<NoThrow::kAllocation>[2]; + delete[] array; + }); +} + +TEST_F(ThrowingValueTest, NonThrowingDelete) { + auto* allocated = new ThrowingValue<>(1); + auto* array = new ThrowingValue<>[2]; + + SetCountdown(); + ExpectNoThrow([allocated]() { delete allocated; }); + SetCountdown(); + ExpectNoThrow([array]() { delete[] array; }); +} + +using Storage = + absl::aligned_storage_t<sizeof(ThrowingValue<>), alignof(ThrowingValue<>)>; + +TEST_F(ThrowingValueTest, NonThrowingPlacementDelete) { + constexpr int kArrayLen = 2; + // We intentionally create extra space to store the tag allocated by placement + // new[]. + constexpr int kStorageLen = 4; + + Storage buf; + Storage array_buf[kStorageLen]; + auto* placed = new (&buf) ThrowingValue<>(1); + auto placed_array = new (&array_buf) ThrowingValue<>[kArrayLen]; + + SetCountdown(); + ExpectNoThrow([placed, &buf]() { + placed->~ThrowingValue<>(); + ThrowingValue<>::operator delete(placed, &buf); + }); + + SetCountdown(); + ExpectNoThrow([&, placed_array]() { + for (int i = 0; i < kArrayLen; ++i) placed_array[i].~ThrowingValue<>(); + ThrowingValue<>::operator delete[](placed_array, &array_buf); + }); +} + +TEST_F(ThrowingValueTest, NonThrowingDestructor) { + auto* allocated = new ThrowingValue<>(); + SetCountdown(); + ExpectNoThrow([allocated]() { delete allocated; }); +} + +TEST(ThrowingBoolTest, ThrowingBool) { + UnsetCountdown(); + ThrowingBool t = true; + + // Test that it's contextually convertible to bool + if (t) { // NOLINT(whitespace/empty_if_body) + } + EXPECT_TRUE(t); + + TestOp([&]() { (void)!t; }); +} + +class ThrowingAllocatorTest : public ::testing::Test { + protected: + void SetUp() override { UnsetCountdown(); } + + private: + AllocInspector borlu_; +}; + +TEST_F(ThrowingAllocatorTest, MemoryManagement) { + // Just exercise the memory management capabilities under LSan to make sure we + // don't leak. + ThrowingAllocator<int> int_alloc; + int* ip = int_alloc.allocate(1); + int_alloc.deallocate(ip, 1); + int* i_array = int_alloc.allocate(2); + int_alloc.deallocate(i_array, 2); + + ThrowingAllocator<ThrowingValue<>> ef_alloc; + ThrowingValue<>* efp = ef_alloc.allocate(1); + ef_alloc.deallocate(efp, 1); + ThrowingValue<>* ef_array = ef_alloc.allocate(2); + ef_alloc.deallocate(ef_array, 2); +} + +TEST_F(ThrowingAllocatorTest, CallsGlobalNew) { + ThrowingAllocator<ThrowingValue<>, NoThrow::kNoThrow> nothrow_alloc; + ThrowingValue<>* ptr; + + SetCountdown(); + // This will only throw if ThrowingValue::new is called. + ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); }); + nothrow_alloc.deallocate(ptr, 1); +} + +TEST_F(ThrowingAllocatorTest, ThrowingConstructors) { + ThrowingAllocator<int> int_alloc; + int* ip = nullptr; + + SetCountdown(); + EXPECT_THROW(ip = int_alloc.allocate(1), TestException); + ExpectNoThrow([&]() { ip = int_alloc.allocate(1); }); + + *ip = 1; + SetCountdown(); + EXPECT_THROW(int_alloc.construct(ip, 2), TestException); + EXPECT_EQ(*ip, 1); + int_alloc.deallocate(ip, 1); +} + +TEST_F(ThrowingAllocatorTest, NonThrowingConstruction) { + { + ThrowingAllocator<int, NoThrow::kNoThrow> 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(); + { + ThrowingAllocator<int> int_alloc; + int* ip = nullptr; + ExpectNoThrow([&]() { ip = int_alloc.allocate(1); }); + ExpectNoThrow([&]() { int_alloc.construct(ip, 2); }); + EXPECT_EQ(*ip, 2); + int_alloc.deallocate(ip, 1); + } + + UnsetCountdown(); + { + ThrowingAllocator<ThrowingValue<NoThrow::kIntCtor>, NoThrow::kNoThrow> + ef_alloc; + ThrowingValue<NoThrow::kIntCtor>* 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(); + { + ThrowingAllocator<int> a; + SetCountdown(); + ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = a; }); + SetCountdown(); + ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = std::move(a); }); + } +} + +TEST_F(ThrowingAllocatorTest, ThrowingAllocatorConstruction) { + ThrowingAllocator<int> a; + TestOp([]() { ThrowingAllocator<int> a; }); + TestOp([&]() { a.select_on_container_copy_construction(); }); +} + +TEST_F(ThrowingAllocatorTest, State) { + ThrowingAllocator<int> a1, a2; + EXPECT_NE(a1, a2); + + auto a3 = a1; + EXPECT_EQ(a3, a1); + int* ip = a1.allocate(1); + EXPECT_EQ(a3, a1); + a3.deallocate(ip, 1); + EXPECT_EQ(a3, a1); +} + +TEST_F(ThrowingAllocatorTest, InVector) { + std::vector<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> 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) { + std::list<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> l; + for (int i = 0; i < 20; ++i) l.push_back({}); + for (int i = 0; i < 20; ++i) l.pop_back(); + for (int i = 0; i < 20; ++i) l.push_front({}); + for (int i = 0; i < 20; ++i) l.pop_front(); +} + +struct CallOperator { + template <typename T> + void operator()(T* t) const { + (*t)(); + } +}; + +struct FailsBasicGuarantee { + void operator()() { + --i; + ThrowingValue<> bomb; + ++i; + } + + bool operator!=(const FailsBasicGuarantee& other) const { + return i != other.i; + } + + friend bool AbslCheckInvariants(const FailsBasicGuarantee& g) { + return g.i >= 0; + } + + int i = 0; +}; + +TEST(ExceptionCheckTest, BasicGuaranteeFailure) { + FailsBasicGuarantee g; + EXPECT_FALSE(TestBasicGuarantee(&g, CallOperator{})); +} + +struct FollowsBasicGuarantee { + void operator()() { + ++i; + ThrowingValue<> bomb; + } + + bool operator!=(const FollowsBasicGuarantee& other) const { + return i != other.i; + } + + friend bool AbslCheckInvariants(const FollowsBasicGuarantee& g) { + return g.i >= 0; + } + + int i = 0; +}; + +TEST(ExceptionCheckTest, BasicGuarantee) { + FollowsBasicGuarantee g; + EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{})); +} + +TEST(ExceptionCheckTest, StrongGuaranteeFailure) { + { + FailsBasicGuarantee g; + EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{})); + } + + { + FollowsBasicGuarantee g; + EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{})); + } +} + +struct FollowsStrongGuarantee { + void operator()() { ThrowingValue<> bomb; } + + bool operator!=(const FollowsStrongGuarantee& other) const { + return i != other.i; + } + + friend bool AbslCheckInvariants(const FollowsStrongGuarantee& g) { + return g.i >= 0; + } + + int i = 0; +}; + +TEST(ExceptionCheckTest, StrongGuarantee) { + FollowsStrongGuarantee g; + EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{})); + EXPECT_TRUE(TestStrongGuarantee(&g, CallOperator{})); +} + +template <typename T> +struct InstructionCounter { + void operator()() { + ++counter; + T b1; + static_cast<void>(b1); + ++counter; + T b2; + static_cast<void>(b2); + ++counter; + T b3; + static_cast<void>(b3); + ++counter; + } + + bool operator!=(const InstructionCounter<ThrowingValue<>>& other) const { + return false; + } + + friend bool AbslCheckInvariants(const InstructionCounter&) { return true; } + + static int counter; +}; +template <typename T> +int InstructionCounter<T>::counter = 0; + +TEST(ExceptionCheckTest, Exhaustiveness) { + InstructionCounter<int> int_factory; + EXPECT_TRUE(TestBasicGuarantee(&int_factory, CallOperator{})); + EXPECT_EQ(InstructionCounter<int>::counter, 4); + + InstructionCounter<ThrowingValue<>> bomb_factory; + EXPECT_TRUE(TestBasicGuarantee(&bomb_factory, CallOperator{})); + EXPECT_EQ(InstructionCounter<ThrowingValue<>>::counter, 10); + + InstructionCounter<ThrowingValue<>>::counter = 0; + EXPECT_TRUE(TestStrongGuarantee(&bomb_factory, CallOperator{})); + EXPECT_EQ(InstructionCounter<ThrowingValue<>>::counter, 10); +} + +struct Tracked : private exceptions_internal::TrackedObject { + Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {} +}; + +TEST(AllocInspectorTest, Pass) { + AllocInspector javert; + Tracked t; +} + +TEST(AllocInspectorTest, NotDestroyed) { + absl::aligned_storage_t<sizeof(Tracked), alignof(Tracked)> storage; + EXPECT_NONFATAL_FAILURE( + { + AllocInspector gadget; + new (&storage) Tracked; + }, + "not destroyed"); +} + +TEST(AllocInspectorTest, DestroyedTwice) { + EXPECT_NONFATAL_FAILURE( + { + Tracked t; + t.~Tracked(); + }, + "destroyed improperly"); +} + +TEST(AllocInspectorTest, ConstructedTwice) { + absl::aligned_storage_t<sizeof(Tracked), alignof(Tracked)> storage; + EXPECT_NONFATAL_FAILURE( + { + new (&storage) Tracked; + new (&storage) Tracked; + }, + "re-constructed"); +} +} // namespace +} // namespace absl diff --git a/absl/base/internal/exception_safety_testing.cc b/absl/base/internal/exception_safety_testing.cc new file mode 100644 index 000000000000..383c9c595251 --- /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 000000000000..aca27a575c34 --- /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 <cstddef> +#include <cstdint> +#include <functional> +#include <iosfwd> +#include <string> +#include <unordered_map> + +#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<uint8_t>(-1) +}; + +constexpr NoThrow operator|(NoThrow a, NoThrow b) { + using T = absl::underlying_type_t<NoThrow>; + return static_cast<NoThrow>(static_cast<T>(a) | static_cast<T>(b)); +} + +constexpr NoThrow operator&(NoThrow a, NoThrow b) { + using T = absl::underlying_type_t<NoThrow>; + return static_cast<NoThrow>(static_cast<T>(a) & static_cast<T>(b)); +} + +namespace exceptions_internal { +constexpr bool ThrowingAllowed(NoThrow flags, NoThrow flag) { + return !static_cast<bool>(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<void*>(this) + << " re-constructed in ctor " << child_ctor; + } + } + + TrackedObject(const TrackedObject&) = delete; + TrackedObject(TrackedObject&&) = delete; + + static std::unordered_map<TrackedObject*, absl::string_view>& GetAllocs() { + static auto* m = + new std::unordered_map<TrackedObject*, absl::string_view>(); + return *m; + } + + ~TrackedObject() noexcept { + if (GetAllocs().erase(this) == 0) { + ADD_FAILURE() << "Object at address " << static_cast<void*>(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<absl::NoThrow::kMoveCtor | absl::NoThrow::kMoveAssign>. +template <NoThrow Flags = NoThrow::kNone> +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 <typename... Args> + 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>(args)...); + } + + template <typename... Args> + 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>(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 <typename... Args> + void operator delete(void* p, Args&&... args) noexcept { + ::operator delete(p, std::forward<Args>(args)...); + } + + void operator delete[](void* p) noexcept { return ::operator delete[](p); } + + template <typename... Args> + void operator delete[](void* p, Args&&... args) noexcept { + return ::operator delete[](p, std::forward<Args>(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 <NoThrow N, typename T> +void operator,(const ThrowingValue<N>& ef, T&& t) = delete; +template <NoThrow N, typename T> +void operator,(T&& t, const ThrowingValue<N>& 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 <typename T, NoThrow Flags = NoThrow::kNone> +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<bool, Flags == NoThrow::kNoThrow>; + 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<const int>(next_id_++); + } + + template <typename U> + ThrowingAllocator( // NOLINT + const ThrowingAllocator<U, Flags>& other) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + + ThrowingAllocator(const ThrowingAllocator& other) noexcept + : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + + template <typename U> + ThrowingAllocator( // NOLINT + ThrowingAllocator<U, Flags>&& 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 <typename U> + ThrowingAllocator& operator=( + const ThrowingAllocator<U, Flags>& other) noexcept { + dummy_ = other.State(); + return *this; + } + + template <typename U> + ThrowingAllocator& operator=(ThrowingAllocator<U, Flags>&& other) noexcept { + dummy_ = std::move(other.State()); + return *this; + } + + template <typename U> + struct rebind { + using other = ThrowingAllocator<U, Flags>; + }; + + pointer allocate(size_type n) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) { + ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION); + return static_cast<pointer>(::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<void*>(ptr)); + } + + template <typename U, typename... Args> + void construct(U* ptr, Args&&... args) noexcept( + !exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) { + ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION); + ::new (static_cast<void*>(ptr)) U(std::forward<Args>(args)...); + } + + template <typename U> + 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<difference_type>::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 <typename U> + bool operator==(const ThrowingAllocator<U, Flags>& other) const noexcept { + return dummy_ == other.dummy_; + } + + template <typename U> + bool operator!=(const ThrowingAllocator<U, Flags>& other) const noexcept { + return dummy_ != other.dummy_; + } + + template <typename U, NoThrow B> + friend class ThrowingAllocator; + + private: + const std::shared_ptr<const int>& State() const { return dummy_; } + std::shared_ptr<const int>& 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<const int> dummy_; +}; + +template <typename T, NoThrow Throws> +int ThrowingAllocator<T, Throws>::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<void*>(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 <typename T, typename FunctionFromTPtrToVoid> +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 <typename T, typename FunctionFromTPtrToVoid> +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 000000000000..6be3936f7c24 --- /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_ |