Commit 0064d9db by Evan Brown Committed by Copybara-Service

Improve error messages when dereferencing invalid swisstable iterators.

- Separate the failure cases into different assertions: end/default constructed vs rehashed or erased.
- Update the assertion error for AssertIsValid to not mention the end iterator case because end iterators are considered valid by AssertIsValid.
- Also fix an out-of-date comment for skip_empty_or_deleted.

PiperOrigin-RevId: 485402559
Change-Id: I593056abdc6c3565d0396fb885923fef643bf4e4
parent 2b403ec7
...@@ -797,15 +797,22 @@ size_t SelectBucketCountForIterRange(InputIter first, InputIter last, ...@@ -797,15 +797,22 @@ size_t SelectBucketCountForIterRange(InputIter first, InputIter last,
return 0; return 0;
} }
#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, msg) \ #define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, operation) \
ABSL_HARDENING_ASSERT((ctrl != nullptr && IsFull(*ctrl)) && msg) do { \
ABSL_HARDENING_ASSERT( \
(ctrl != nullptr) && operation \
" called on invalid iterator. The iterator might be an end() " \
"iterator or may have been default constructed."); \
ABSL_HARDENING_ASSERT( \
(IsFull(*ctrl)) && operation \
" called on invalid iterator. The element might have been erased or " \
"the table might have rehashed."); \
} while (0)
inline void AssertIsValid(ctrl_t* ctrl) { inline void AssertIsValid(ctrl_t* ctrl) {
ABSL_HARDENING_ASSERT( ABSL_HARDENING_ASSERT((ctrl == nullptr || IsFull(*ctrl)) &&
(ctrl == nullptr || IsFull(*ctrl)) && "Invalid operation on iterator. The element might have "
"Invalid operation on iterator. The element might have " "been erased or the table might have rehashed.");
"been erased, the table might have rehashed, or this may "
"be an end() iterator.");
} }
struct FindInfo { struct FindInfo {
...@@ -1034,22 +1041,19 @@ class raw_hash_set { ...@@ -1034,22 +1041,19 @@ class raw_hash_set {
// PRECONDITION: not an end() iterator. // PRECONDITION: not an end() iterator.
reference operator*() const { reference operator*() const {
ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator*()");
"operator*() called on invalid iterator.");
return PolicyTraits::element(slot_); return PolicyTraits::element(slot_);
} }
// PRECONDITION: not an end() iterator. // PRECONDITION: not an end() iterator.
pointer operator->() const { pointer operator->() const {
ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator->");
"operator-> called on invalid iterator.");
return &operator*(); return &operator*();
} }
// PRECONDITION: not an end() iterator. // PRECONDITION: not an end() iterator.
iterator& operator++() { iterator& operator++() {
ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, "operator++");
"operator++ called on invalid iterator.");
++ctrl_; ++ctrl_;
++slot_; ++slot_;
skip_empty_or_deleted(); skip_empty_or_deleted();
...@@ -1081,7 +1085,7 @@ class raw_hash_set { ...@@ -1081,7 +1085,7 @@ class raw_hash_set {
// Fixes up `ctrl_` to point to a full by advancing it and `slot_` until // Fixes up `ctrl_` to point to a full by advancing it and `slot_` until
// they reach one. // they reach one.
// //
// If a sentinel is reached, we null both of them out instead. // If a sentinel is reached, we null `ctrl_` out instead.
void skip_empty_or_deleted() { void skip_empty_or_deleted() {
while (IsEmptyOrDeleted(*ctrl_)) { while (IsEmptyOrDeleted(*ctrl_)) {
uint32_t shift = Group{ctrl_}.CountLeadingEmptyOrDeleted(); uint32_t shift = Group{ctrl_}.CountLeadingEmptyOrDeleted();
...@@ -1601,8 +1605,7 @@ class raw_hash_set { ...@@ -1601,8 +1605,7 @@ class raw_hash_set {
// This overload is necessary because otherwise erase<K>(const K&) would be // This overload is necessary because otherwise erase<K>(const K&) would be
// a better match if non-const iterator is passed as an argument. // a better match if non-const iterator is passed as an argument.
void erase(iterator it) { void erase(iterator it) {
ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, "erase()");
"erase() called on invalid iterator.");
PolicyTraits::destroy(&alloc_ref(), it.slot_); PolicyTraits::destroy(&alloc_ref(), it.slot_);
erase_meta_only(it); erase_meta_only(it);
} }
...@@ -1636,8 +1639,7 @@ class raw_hash_set { ...@@ -1636,8 +1639,7 @@ class raw_hash_set {
} }
node_type extract(const_iterator position) { node_type extract(const_iterator position) {
ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, "extract()");
"extract() called on invalid iterator.");
auto node = auto node =
CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_); CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_);
erase_meta_only(position); erase_meta_only(position);
......
...@@ -2036,20 +2036,59 @@ TEST(Table, UnstablePointers) { ...@@ -2036,20 +2036,59 @@ TEST(Table, UnstablePointers) {
EXPECT_NE(old_ptr, addr(0)); EXPECT_NE(old_ptr, addr(0));
} }
// Confirm that we assert if we try to erase() end(). bool IsAssertEnabled() {
TEST(TableDeathTest, EraseOfEndAsserts) {
// Use an assert with side-effects to figure out if they are actually enabled. // Use an assert with side-effects to figure out if they are actually enabled.
bool assert_enabled = false; bool assert_enabled = false;
assert([&]() { // NOLINT assert([&]() { // NOLINT
assert_enabled = true; assert_enabled = true;
return true; return true;
}()); }());
if (!assert_enabled) return; return assert_enabled;
}
TEST(TableDeathTest, InvalidIteratorAsserts) {
if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
IntTable t;
// Extra simple "regexp" as regexp support is highly varied across platforms.
EXPECT_DEATH_IF_SUPPORTED(
t.erase(t.end()),
"erase.* called on invalid iterator. The iterator might be an "
"end.*iterator or may have been default constructed.");
typename IntTable::iterator iter;
EXPECT_DEATH_IF_SUPPORTED(
++iter,
"operator.* called on invalid iterator. The iterator might be an "
"end.*iterator or may have been default constructed.");
t.insert(0);
iter = t.begin();
t.erase(iter);
EXPECT_DEATH_IF_SUPPORTED(
++iter,
"operator.* called on invalid iterator. The element might have been "
"erased or .*the table might have rehashed.");
}
TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) {
if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
IntTable t; IntTable t;
t.insert(1);
t.insert(2);
t.insert(3);
auto iter1 = t.begin();
auto iter2 = std::next(iter1);
ASSERT_NE(iter1, t.end());
ASSERT_NE(iter2, t.end());
t.erase(iter1);
// Extra simple "regexp" as regexp support is highly varied across platforms. // Extra simple "regexp" as regexp support is highly varied across platforms.
constexpr char kDeathMsg[] = "erase.. called on invalid iterator"; const char* const kDeathMessage =
EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg); "Invalid operation on iterator. The element might have .*been erased or "
"the table might have rehashed.";
EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kDeathMessage);
EXPECT_DEATH_IF_SUPPORTED(void(iter2 != iter1), kDeathMessage);
t.erase(iter2);
EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kDeathMessage);
} }
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment