@@ -1379,6 +1379,26 @@ constexpr bool SwisstableDebugEnabled() {
13791379#endif
13801380}
13811381
1382+ // Dereferences `ptr`. The function is named in order to provide a helpful error
1383+ // message when users see crashing stack traces. Note that this function is not
1384+ // guaranteed to crash when `ptr` is invalid if sanitizer mode is not enabled.
1385+ template <typename T>
1386+ T CrashIfIteratorIsInvalid (const T* ptr) {
1387+ // If the following line(s) crash, then it's likely that `ptr` is from a
1388+ // backing array that has been deallocated. If you see a crash here, it likely
1389+ // means that you are comparing an invalid iterator from a table that has
1390+ // rehashed, moved, or been destroyed. In such cases, it is often helpful to
1391+ // reproduce the issue with --config=asan and (assuming there's a crash here)
1392+ // examine the corresponding deallocation stack trace.
1393+ T ret = *ptr;
1394+ // Force a read with inline asm to make sure that a crash happens here, rather
1395+ // than later when the value is used.
1396+ #ifdef __clang__
1397+ asm (" " : " +r" (ret));
1398+ #endif
1399+ return ret;
1400+ }
1401+
13821402inline void AssertIsFull (const ctrl_t * ctrl, GenerationType generation,
13831403 const GenerationType* generation_ptr,
13841404 const char * operation) {
@@ -1396,20 +1416,21 @@ inline void AssertIsFull(const ctrl_t* ctrl, GenerationType generation,
13961416 operation);
13971417 }
13981418 if (SwisstableGenerationsEnabled ()) {
1399- if (ABSL_PREDICT_FALSE (generation != *generation_ptr)) {
1419+ if (ABSL_PREDICT_FALSE (generation !=
1420+ CrashIfIteratorIsInvalid (generation_ptr))) {
14001421 ABSL_RAW_LOG (FATAL,
14011422 " %s called on invalid iterator. The table could have "
14021423 " rehashed or moved since this iterator was initialized." ,
14031424 operation);
14041425 }
1405- if (ABSL_PREDICT_FALSE (!IsFull (* ctrl))) {
1426+ if (ABSL_PREDICT_FALSE (!IsFull (CrashIfIteratorIsInvalid ( ctrl) ))) {
14061427 ABSL_RAW_LOG (
14071428 FATAL,
14081429 " %s called on invalid iterator. The element was likely erased." ,
14091430 operation);
14101431 }
14111432 } else {
1412- if (ABSL_PREDICT_FALSE (!IsFull (* ctrl))) {
1433+ if (ABSL_PREDICT_FALSE (!IsFull (CrashIfIteratorIsInvalid ( ctrl) ))) {
14131434 ABSL_RAW_LOG (
14141435 FATAL,
14151436 " %s called on invalid iterator. The element might have been erased "
@@ -1425,20 +1446,15 @@ inline void AssertIsValidForComparison(const ctrl_t* ctrl,
14251446 GenerationType generation,
14261447 const GenerationType* generation_ptr) {
14271448 if (!SwisstableDebugEnabled ()) return ;
1428- const bool ctrl_is_valid_for_comparison = [ctrl]() {
1429- if (ctrl == nullptr ) return true ;
1430- if (ctrl == DefaultIterControl ()) return true ;
1431- // Note: if the following line crashes, then it's likely that `ctrl` is from
1432- // a backing array that has been deallocated. If you see a crash here, it
1433- // likely means that you are comparing an invalid iterator from a table that
1434- // has rehashed, moved, or been destroyed.
1435- return IsFull (*ctrl);
1436- }();
1449+ const bool ctrl_is_valid_for_comparison =
1450+ ctrl == nullptr || ctrl == DefaultIterControl () ||
1451+ IsFull (CrashIfIteratorIsInvalid (ctrl));
14371452 if (SwisstableGenerationsEnabled ()) {
1438- if (ABSL_PREDICT_FALSE (generation != *generation_ptr)) {
1453+ if (ABSL_PREDICT_FALSE (generation !=
1454+ CrashIfIteratorIsInvalid (generation_ptr))) {
14391455 // Note: in the case of a rehash, we would expect to see a sanitizer crash
1440- // above when `ctrl` is dereferenced so this assertion will only catch
1441- // moved table cases, unless we're using a custom allocator that does not
1456+ // in CrashIfIteratorIsInvalid so this assertion will only catch moved
1457+ // table cases, unless we're using a custom allocator that does not
14421458 // deallocate the old backing array (e.g. an arena allocator).
14431459 ABSL_RAW_LOG (
14441460 FATAL,
0 commit comments