Skip to content

Commit 7a522a5

Browse files
ezbrcopybara-github
authored andcommitted
Refactor swisstable iterator debug messages code. The motivations are (a) distinguish between the "likely erased" and "could have rehashed" cases when generations are enabled, (b) suggest running under ASan when generations aren't enabled and doing so would narrow down the possible error cases, and (c) make ABSL_INTERNAL_ASSERT_IS_FULL not be a macro.
PiperOrigin-RevId: 511255275 Change-Id: I5a44a813cd310837d0bd0209d2187b100be201e7
1 parent d9ae096 commit 7a522a5

File tree

2 files changed

+101
-56
lines changed

2 files changed

+101
-56
lines changed

absl/container/internal/raw_hash_set.h

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
#include <iterator>
180180
#include <limits>
181181
#include <memory>
182+
#include <string>
182183
#include <tuple>
183184
#include <type_traits>
184185
#include <utility>
@@ -1038,35 +1039,75 @@ size_t SelectBucketCountForIterRange(InputIter first, InputIter last,
10381039
return 0;
10391040
}
10401041

1041-
#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, generation, generation_ptr, \
1042-
operation) \
1043-
do { \
1044-
ABSL_HARDENING_ASSERT((ctrl != nullptr) && operation \
1045-
" called on end() iterator."); \
1046-
ABSL_HARDENING_ASSERT((ctrl != EmptyGroup()) && operation \
1047-
" called on default-constructed iterator."); \
1048-
if (SwisstableGenerationsEnabled() && generation != *generation_ptr) \
1049-
ABSL_INTERNAL_LOG(FATAL, operation \
1050-
" called on invalidated iterator. The table could " \
1051-
"have rehashed since this iterator was initialized."); \
1052-
ABSL_HARDENING_ASSERT( \
1053-
(IsFull(*ctrl)) && operation \
1054-
" called on invalid iterator. The element might have been erased or " \
1055-
"the table might have rehashed."); \
1056-
} while (0)
1042+
constexpr bool SwisstableDebugEnabled() {
1043+
#if defined(ABSL_SWISSTABLE_ENABLE_GENERATIONS) || \
1044+
ABSL_OPTION_HARDENED == 1 || !defined(NDEBUG)
1045+
return true;
1046+
#else
1047+
return false;
1048+
#endif
1049+
}
1050+
1051+
inline void AssertIsFull(const ctrl_t* ctrl, GenerationType generation,
1052+
const GenerationType* generation_ptr,
1053+
const char* operation) {
1054+
if (!SwisstableDebugEnabled()) return;
1055+
if (ctrl == nullptr) {
1056+
ABSL_INTERNAL_LOG(FATAL,
1057+
std::string(operation) + " called on end() iterator.");
1058+
}
1059+
if (ctrl == EmptyGroup()) {
1060+
ABSL_INTERNAL_LOG(FATAL, std::string(operation) +
1061+
" called on default-constructed iterator.");
1062+
}
1063+
if (SwisstableGenerationsEnabled()) {
1064+
if (generation != *generation_ptr) {
1065+
ABSL_INTERNAL_LOG(FATAL,
1066+
std::string(operation) +
1067+
" called on invalid iterator. The table could have "
1068+
"rehashed since this iterator was initialized.");
1069+
}
1070+
if (!IsFull(*ctrl)) {
1071+
ABSL_INTERNAL_LOG(
1072+
FATAL,
1073+
std::string(operation) +
1074+
" called on invalid iterator. The element was likely erased.");
1075+
}
1076+
} else {
1077+
if (!IsFull(*ctrl)) {
1078+
ABSL_INTERNAL_LOG(
1079+
FATAL,
1080+
std::string(operation) +
1081+
" called on invalid iterator. The element might have been erased "
1082+
"or the table might have rehashed. Consider running with "
1083+
"--config=asan to diagnose rehashing issues.");
1084+
}
1085+
}
1086+
}
10571087

10581088
// Note that for comparisons, null/end iterators are valid.
10591089
inline void AssertIsValidForComparison(const ctrl_t* ctrl,
10601090
GenerationType generation,
10611091
const GenerationType* generation_ptr) {
1062-
ABSL_HARDENING_ASSERT(
1063-
(ctrl == nullptr || ctrl == EmptyGroup() || IsFull(*ctrl)) &&
1064-
"Invalid iterator comparison. The element might have "
1065-
"been erased or the table might have rehashed.");
1066-
if (SwisstableGenerationsEnabled() && generation != *generation_ptr) {
1067-
ABSL_INTERNAL_LOG(FATAL,
1068-
"Invalid iterator comparison. The table could have "
1069-
"rehashed since this iterator was initialized.");
1092+
if (!SwisstableDebugEnabled()) return;
1093+
const bool ctrl_is_valid_for_comparison =
1094+
ctrl == nullptr || ctrl == EmptyGroup() || IsFull(*ctrl);
1095+
if (SwisstableGenerationsEnabled()) {
1096+
if (generation != *generation_ptr) {
1097+
ABSL_INTERNAL_LOG(FATAL,
1098+
"Invalid iterator comparison. The table could have "
1099+
"rehashed since this iterator was initialized.");
1100+
}
1101+
if (!ctrl_is_valid_for_comparison) {
1102+
ABSL_INTERNAL_LOG(
1103+
FATAL, "Invalid iterator comparison. The element was likely erased.");
1104+
}
1105+
} else {
1106+
ABSL_HARDENING_ASSERT(
1107+
ctrl_is_valid_for_comparison &&
1108+
"Invalid iterator comparison. The element might have been erased or "
1109+
"the table might have rehashed. Consider running with --config=asan to "
1110+
"diagnose rehashing issues.");
10701111
}
10711112
}
10721113

@@ -1097,8 +1138,7 @@ inline void AssertSameContainer(const ctrl_t* ctrl_a, const ctrl_t* ctrl_b,
10971138
const void* const& slot_b,
10981139
const GenerationType* generation_ptr_a,
10991140
const GenerationType* generation_ptr_b) {
1100-
#if defined(ABSL_SWISSTABLE_ENABLE_GENERATIONS) || \
1101-
ABSL_OPTION_HARDENED == 1 || !defined(NDEBUG)
1141+
if (!SwisstableDebugEnabled()) return;
11021142
const bool a_is_default = ctrl_a == EmptyGroup();
11031143
const bool b_is_default = ctrl_b == EmptyGroup();
11041144
if (a_is_default != b_is_default) {
@@ -1108,9 +1148,9 @@ inline void AssertSameContainer(const ctrl_t* ctrl_a, const ctrl_t* ctrl_b,
11081148
"with non-default-constructed iterator.");
11091149
}
11101150
if (a_is_default && b_is_default) return;
1111-
#endif
11121151

1113-
if (SwisstableGenerationsEnabled() && generation_ptr_a != generation_ptr_b) {
1152+
if (SwisstableGenerationsEnabled()) {
1153+
if (generation_ptr_a == generation_ptr_b) return;
11141154
const bool a_is_empty = generation_ptr_a == EmptyGeneration();
11151155
const bool b_is_empty = generation_ptr_b == EmptyGeneration();
11161156
if (a_is_empty != b_is_empty) {
@@ -1129,11 +1169,13 @@ inline void AssertSameContainer(const ctrl_t* ctrl_a, const ctrl_t* ctrl_b,
11291169
ABSL_INTERNAL_LOG(FATAL,
11301170
"Invalid iterator comparison. Comparing non-end() "
11311171
"iterators from different hashtables.");
1172+
} else {
1173+
ABSL_HARDENING_ASSERT(
1174+
AreItersFromSameContainer(ctrl_a, ctrl_b, slot_a, slot_b) &&
1175+
"Invalid iterator comparison. The iterators may be from different "
1176+
"containers or the container might have rehashed. Consider running "
1177+
"with --config=asan to diagnose rehashing issues.");
11321178
}
1133-
ABSL_HARDENING_ASSERT(
1134-
AreItersFromSameContainer(ctrl_a, ctrl_b, slot_a, slot_b) &&
1135-
"Invalid iterator comparison. The iterators may be from different "
1136-
"containers or the container might have rehashed.");
11371179
}
11381180

11391181
struct FindInfo {
@@ -1471,22 +1513,19 @@ class raw_hash_set {
14711513

14721514
// PRECONDITION: not an end() iterator.
14731515
reference operator*() const {
1474-
ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(),
1475-
"operator*()");
1516+
AssertIsFull(ctrl_, generation(), generation_ptr(), "operator*()");
14761517
return PolicyTraits::element(slot_);
14771518
}
14781519

14791520
// PRECONDITION: not an end() iterator.
14801521
pointer operator->() const {
1481-
ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(),
1482-
"operator->");
1522+
AssertIsFull(ctrl_, generation(), generation_ptr(), "operator->");
14831523
return &operator*();
14841524
}
14851525

14861526
// PRECONDITION: not an end() iterator.
14871527
iterator& operator++() {
1488-
ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(),
1489-
"operator++");
1528+
AssertIsFull(ctrl_, generation(), generation_ptr(), "operator++");
14901529
++ctrl_;
14911530
++slot_;
14921531
skip_empty_or_deleted();
@@ -2052,8 +2091,7 @@ class raw_hash_set {
20522091
// This overload is necessary because otherwise erase<K>(const K&) would be
20532092
// a better match if non-const iterator is passed as an argument.
20542093
void erase(iterator it) {
2055-
ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, it.generation(), it.generation_ptr(),
2056-
"erase()");
2094+
AssertIsFull(it.ctrl_, it.generation(), it.generation_ptr(), "erase()");
20572095
PolicyTraits::destroy(&alloc_ref(), it.slot_);
20582096
erase_meta_only(it);
20592097
}
@@ -2087,9 +2125,8 @@ class raw_hash_set {
20872125
}
20882126

20892127
node_type extract(const_iterator position) {
2090-
ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_,
2091-
position.inner_.generation(),
2092-
position.inner_.generation_ptr(), "extract()");
2128+
AssertIsFull(position.inner_.ctrl_, position.inner_.generation(),
2129+
position.inner_.generation_ptr(), "extract()");
20932130
auto node =
20942131
CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_);
20952132
erase_meta_only(position);
@@ -2739,6 +2776,5 @@ ABSL_NAMESPACE_END
27392776
} // namespace absl
27402777

27412778
#undef ABSL_SWISSTABLE_ENABLE_GENERATIONS
2742-
#undef ABSL_INTERNAL_ASSERT_IS_FULL
27432779

27442780
#endif // ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_

absl/container/internal/raw_hash_set_test.cc

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,7 +2056,8 @@ bool IsAssertEnabled() {
20562056
}
20572057

20582058
TEST(TableDeathTest, InvalidIteratorAsserts) {
2059-
if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
2059+
if (!IsAssertEnabled() && !SwisstableGenerationsEnabled())
2060+
GTEST_SKIP() << "Assertions not enabled.";
20602061

20612062
IntTable t;
20622063
// Extra simple "regexp" as regexp support is highly varied across platforms.
@@ -2068,9 +2069,12 @@ TEST(TableDeathTest, InvalidIteratorAsserts) {
20682069
t.insert(0);
20692070
iter = t.begin();
20702071
t.erase(iter);
2071-
EXPECT_DEATH_IF_SUPPORTED(++iter,
2072-
"operator.* called on invalid iterator. The "
2073-
"element might have been erased");
2072+
const char* const kErasedDeathMessage =
2073+
SwisstableGenerationsEnabled()
2074+
? "operator.* called on invalid iterator.*was likely erased"
2075+
: "operator.* called on invalid iterator.*might have been "
2076+
"erased.*config=asan";
2077+
EXPECT_DEATH_IF_SUPPORTED(++iter, kErasedDeathMessage);
20742078
}
20752079

20762080
// Invalid iterator use can trigger heap-use-after-free in asan,
@@ -2087,7 +2091,8 @@ constexpr bool kMsvc = false;
20872091
#endif
20882092

20892093
TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) {
2090-
if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
2094+
if (!IsAssertEnabled() && !SwisstableGenerationsEnabled())
2095+
GTEST_SKIP() << "Assertions not enabled.";
20912096

20922097
IntTable t;
20932098
t.insert(1);
@@ -2100,8 +2105,9 @@ TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) {
21002105
t.erase(iter1);
21012106
// Extra simple "regexp" as regexp support is highly varied across platforms.
21022107
const char* const kErasedDeathMessage =
2103-
"Invalid iterator comparison. The element might have .*been erased or "
2104-
"the table might have rehashed.";
2108+
SwisstableGenerationsEnabled()
2109+
? "Invalid iterator comparison.*was likely erased"
2110+
: "Invalid iterator comparison.*might have been erased.*config=asan";
21052111
EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage);
21062112
EXPECT_DEATH_IF_SUPPORTED(void(iter2 != iter1), kErasedDeathMessage);
21072113
t.erase(iter2);
@@ -2114,17 +2120,20 @@ TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) {
21142120
iter2 = t2.begin();
21152121
const char* const kContainerDiffDeathMessage =
21162122
SwisstableGenerationsEnabled()
2117-
? "Invalid iterator comparison.*non-end"
2118-
: "Invalid iterator comparison. The iterators may be from different "
2119-
".*containers or the container might have rehashed.";
2123+
? "Invalid iterator comparison.*iterators from different hashtables"
2124+
: "Invalid iterator comparison.*may be from different "
2125+
".*containers.*config=asan";
21202126
EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kContainerDiffDeathMessage);
21212127
EXPECT_DEATH_IF_SUPPORTED(void(iter2 == iter1), kContainerDiffDeathMessage);
21222128

21232129
for (int i = 0; i < 10; ++i) t1.insert(i);
21242130
// There should have been a rehash in t1.
21252131
if (kMsvc) return; // MSVC doesn't support | in regex.
2126-
EXPECT_DEATH_IF_SUPPORTED(void(iter1 == t1.begin()),
2127-
kInvalidIteratorDeathMessage);
2132+
const char* const kRehashedDeathMessage =
2133+
SwisstableGenerationsEnabled()
2134+
? kInvalidIteratorDeathMessage
2135+
: "Invalid iterator comparison.*might have rehashed.*config=asan";
2136+
EXPECT_DEATH_IF_SUPPORTED(void(iter1 == t1.begin()), kRehashedDeathMessage);
21282137
}
21292138

21302139
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)

0 commit comments

Comments
 (0)