diff options
Diffstat (limited to 'absl/base')
-rw-r--r-- | absl/base/exception_safety_testing_test.cc | 71 | ||||
-rw-r--r-- | absl/base/internal/exception_safety_testing.cc | 31 | ||||
-rw-r--r-- | absl/base/internal/exception_safety_testing.h | 182 | ||||
-rw-r--r-- | absl/base/internal/spinlock.cc | 30 | ||||
-rw-r--r-- | absl/base/optimization.h | 4 |
5 files changed, 224 insertions, 94 deletions
diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc index a8b82b73daf1..97c8d6f83189 100644 --- a/absl/base/exception_safety_testing_test.cc +++ b/absl/base/exception_safety_testing_test.cc @@ -168,8 +168,58 @@ TEST(ThrowingValueTest, ThrowingCompoundAssignmentOps) { TEST(ThrowingValueTest, ThrowingStreamOps) { ThrowingValue<> bomb; - TestOp([&]() { std::cin >> bomb; }); - TestOp([&]() { std::cout << bomb; }); + TestOp([&]() { + std::istringstream stream; + stream >> bomb; + }); + TestOp([&]() { + std::stringstream stream; + stream << bomb; + }); +} + +// Tests the operator<< of ThrowingValue by forcing ConstructorTracker to emit +// a nonfatal failure that contains the std::string representation of the Thrower +TEST(ThrowingValueTest, StreamOpsOutput) { + using ::testing::TypeSpec; + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); + + // Test default spec list (kEverythingThrows) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = ThrowingValue<TypeSpec{}>; + auto thrower = Thrower(123); + thrower.~Thrower(); + }, + "ThrowingValue<>(123)"); + + // Test with one item in spec list (kNoThrowCopy) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = ThrowingValue<TypeSpec::kNoThrowCopy>; + auto thrower = Thrower(234); + thrower.~Thrower(); + }, + "ThrowingValue<kNoThrowCopy>(234)"); + + // Test with multiple items in spec list (kNoThrowMove, kNoThrowNew) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = + ThrowingValue<TypeSpec::kNoThrowMove | TypeSpec::kNoThrowNew>; + auto thrower = Thrower(345); + thrower.~Thrower(); + }, + "ThrowingValue<kNoThrowMove | kNoThrowNew>(345)"); + + // Test with all items in spec list (kNoThrowCopy, kNoThrowMove, kNoThrowNew) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = ThrowingValue<static_cast<TypeSpec>(-1)>; + auto thrower = Thrower(456); + thrower.~Thrower(); + }, + "ThrowingValue<kNoThrowCopy | kNoThrowMove | kNoThrowNew>(456)"); } template <typename F> @@ -653,20 +703,20 @@ struct BasicGuaranteeWithExtraInvariants : public NonNegative { }; constexpr int BasicGuaranteeWithExtraInvariants::kExceptionSentinel; -TEST(ExceptionCheckTest, BasicGuaranteeWithInvariants) { +TEST(ExceptionCheckTest, BasicGuaranteeWithExtraInvariants) { auto tester_with_val = tester.WithInitialValue(BasicGuaranteeWithExtraInvariants{}); EXPECT_TRUE(tester_with_val.Test()); EXPECT_TRUE( tester_with_val - .WithInvariants([](BasicGuaranteeWithExtraInvariants* w) { - if (w->i == BasicGuaranteeWithExtraInvariants::kExceptionSentinel) { + .WithInvariants([](BasicGuaranteeWithExtraInvariants* o) { + if (o->i == BasicGuaranteeWithExtraInvariants::kExceptionSentinel) { return testing::AssertionSuccess(); } return testing::AssertionFailure() << "i should be " << BasicGuaranteeWithExtraInvariants::kExceptionSentinel - << ", but is " << w->i; + << ", but is " << o->i; }) .Test()); } @@ -846,29 +896,28 @@ TEST(ConstructorTrackerTest, NotDestroyedAfter) { new (&storage) Tracked; }, "not destroyed"); - - // Manual destruction of the Tracked instance is not required because - // ~ConstructorTracker() handles that automatically when a leak is found } TEST(ConstructorTrackerTest, DestroyedTwice) { + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); EXPECT_NONFATAL_FAILURE( { Tracked t; t.~Tracked(); }, - "destroyed improperly"); + "re-destroyed"); } TEST(ConstructorTrackerTest, ConstructedTwice) { + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); absl::aligned_storage_t<sizeof(Tracked), alignof(Tracked)> storage; EXPECT_NONFATAL_FAILURE( { new (&storage) Tracked; new (&storage) Tracked; + reinterpret_cast<Tracked*>(&storage)->~Tracked(); }, "re-constructed"); - reinterpret_cast<Tracked*>(&storage)->~Tracked(); } TEST(ThrowingValueTraitsTest, RelationalOperators) { diff --git a/absl/base/internal/exception_safety_testing.cc b/absl/base/internal/exception_safety_testing.cc index d3e94074b822..f1d081f7e50d 100644 --- a/absl/base/internal/exception_safety_testing.cc +++ b/absl/base/internal/exception_safety_testing.cc @@ -21,16 +21,14 @@ namespace testing { exceptions_internal::NoThrowTag nothrow_ctor; -bool nothrow_guarantee(const void*) { - return ::testing::AssertionFailure() - << "Exception thrown violating NoThrow Guarantee"; -} exceptions_internal::StrongGuaranteeTagType strong_guarantee; namespace exceptions_internal { int countdown = -1; +ConstructorTracker* ConstructorTracker::current_tracker_instance_ = nullptr; + void MaybeThrow(absl::string_view msg, bool throw_bad_alloc) { if (countdown-- == 0) { if (throw_bad_alloc) throw TestBadAllocException(msg); @@ -43,6 +41,31 @@ testing::AssertionResult FailureMessage(const TestException& e, return testing::AssertionFailure() << "Exception thrown from " << e.what(); } +std::string GetSpecString(TypeSpec spec) { + std::string out; + absl::string_view sep; + const auto append = [&](absl::string_view s) { + absl::StrAppend(&out, sep, s); + sep = " | "; + }; + if (static_cast<bool>(TypeSpec::kNoThrowCopy & spec)) { + append("kNoThrowCopy"); + } + if (static_cast<bool>(TypeSpec::kNoThrowMove & spec)) { + append("kNoThrowMove"); + } + if (static_cast<bool>(TypeSpec::kNoThrowNew & spec)) { + append("kNoThrowNew"); + } + return out; +} + +std::string GetSpecString(AllocSpec spec) { + return static_cast<bool>(AllocSpec::kNoThrowAllocate & spec) + ? "kNoThrowAllocate" + : ""; +} + } // namespace exceptions_internal } // namespace testing 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. diff --git a/absl/base/internal/spinlock.cc b/absl/base/internal/spinlock.cc index 28a2059f3287..1b97efbccc58 100644 --- a/absl/base/internal/spinlock.cc +++ b/absl/base/internal/spinlock.cc @@ -18,10 +18,12 @@ #include <atomic> #include <limits> +#include "absl/base/attributes.h" #include "absl/base/internal/atomic_hook.h" #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/spinlock_wait.h" #include "absl/base/internal/sysinfo.h" /* For NumCPUs() */ +#include "absl/base/call_once.h" // Description of lock-word: // 31..00: [............................3][2][1][0] @@ -54,30 +56,10 @@ namespace absl { namespace base_internal { -static int adaptive_spin_count = 0; - -namespace { -struct SpinLock_InitHelper { - SpinLock_InitHelper() { - // On multi-cpu machines, spin for longer before yielding - // the processor or sleeping. Reduces idle time significantly. - if (base_internal::NumCPUs() > 1) { - adaptive_spin_count = 1000; - } - } -}; - -// Hook into global constructor execution: -// We do not do adaptive spinning before that, -// but nothing lock-intensive should be going on at that time. -static SpinLock_InitHelper init_helper; - ABSL_CONST_INIT static base_internal::AtomicHook<void (*)(const void *lock, int64_t wait_cycles)> submit_profile_data; -} // namespace - void RegisterSpinLockProfiler(void (*fn)(const void *contendedlock, int64_t wait_cycles)) { submit_profile_data.Store(fn); @@ -120,6 +102,14 @@ void SpinLock::InitLinkerInitializedAndCooperative() { // from the lock is returned from the method. uint32_t SpinLock::SpinLoop(int64_t initial_wait_timestamp, uint32_t *wait_cycles) { + // We are already in the slow path of SpinLock, initialize the + // adaptive_spin_count here. + ABSL_CONST_INIT static absl::once_flag init_adaptive_spin_count; + ABSL_CONST_INIT static int adaptive_spin_count = 0; + base_internal::LowLevelCallOnce(&init_adaptive_spin_count, []() { + adaptive_spin_count = base_internal::NumCPUs() > 1 ? 1000 : 1; + }); + int c = adaptive_spin_count; uint32_t lock_value; do { diff --git a/absl/base/optimization.h b/absl/base/optimization.h index aaaffa495a9a..2fddfc800c1d 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h @@ -158,8 +158,8 @@ #define ABSL_PREDICT_FALSE(x) (__builtin_expect(x, 0)) #define ABSL_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1)) #else -#define ABSL_PREDICT_FALSE(x) x -#define ABSL_PREDICT_TRUE(x) x +#define ABSL_PREDICT_FALSE(x) (x) +#define ABSL_PREDICT_TRUE(x) (x) #endif #endif // ABSL_BASE_OPTIMIZATION_H_ |