about summary refs log tree commit diff
path: root/absl/base
diff options
context:
space:
mode:
Diffstat (limited to 'absl/base')
-rw-r--r--absl/base/exception_safety_testing_test.cc71
-rw-r--r--absl/base/internal/exception_safety_testing.cc31
-rw-r--r--absl/base/internal/exception_safety_testing.h182
-rw-r--r--absl/base/internal/spinlock.cc30
-rw-r--r--absl/base/optimization.h4
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_