[tint] Add assertions for iterator invalidation

For HashmapBase iterators, check that the container being iterated
over has not been modified since the iterator was created.

Change-Id: I13dd6ac590b6146f5f87a10e4d462574ec84eff6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/135765
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/utils/hashmap_base.h b/src/tint/utils/hashmap_base.h
index f31dcf2..73cb0d7 100644
--- a/src/tint/utils/hashmap_base.h
+++ b/src/tint/utils/hashmap_base.h
@@ -25,6 +25,10 @@
 #include "src/tint/utils/hash.h"
 #include "src/tint/utils/vector.h"
 
+#ifndef NDEBUG
+#define TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+#endif
+
 namespace tint::utils {
 
 /// Action taken by a map mutation
@@ -190,10 +194,20 @@
     class IteratorT {
       public:
         /// @returns the value pointed to by this iterator
-        EntryRef<IS_CONST> operator->() const { return *this; }
+        EntryRef<IS_CONST> operator->() const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+            TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+                                   "iterator invalidated by container modification");
+#endif
+            return *this;
+        }
 
         /// @returns a reference to the value at the iterator
         EntryRef<IS_CONST> operator*() const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+            TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+                                   "iterator invalidated by container modification");
+#endif
             auto& ref = current->entry.value();
             if constexpr (ValueIsVoid) {
                 return ref;
@@ -205,6 +219,10 @@
         /// Increments the iterator
         /// @returns this iterator
         IteratorT& operator++() {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+            TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+                                   "iterator invalidated by container modification");
+#endif
             if (current == end) {
                 return *this;
             }
@@ -216,12 +234,24 @@
         /// Equality operator
         /// @param other the other iterator to compare this iterator to
         /// @returns true if this iterator is equal to other
-        bool operator==(const IteratorT& other) const { return current == other.current; }
+        bool operator==(const IteratorT& other) const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+            TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+                                   "iterator invalidated by container modification");
+#endif
+            return current == other.current;
+        }
 
         /// Inequality operator
         /// @param other the other iterator to compare this iterator to
         /// @returns true if this iterator is not equal to other
-        bool operator!=(const IteratorT& other) const { return current != other.current; }
+        bool operator!=(const IteratorT& other) const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+            TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+                                   "iterator invalidated by container modification");
+#endif
+            return current != other.current;
+        }
 
       private:
         /// Friend class
@@ -229,7 +259,17 @@
 
         using SLOT = std::conditional_t<IS_CONST, const Slot, Slot>;
 
-        IteratorT(SLOT* c, SLOT* e) : current(c), end(e) { SkipToNextValue(); }
+        IteratorT(SLOT* c, SLOT* e, [[maybe_unused]] const HashmapBase& m)
+            : current(c),
+              end(e)
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+              ,
+              map(m),
+              initial_generation(m.Generation())
+#endif
+        {
+            SkipToNextValue();
+        }
 
         /// Moves the iterator forward, stopping at the next slot that is not empty.
         void SkipToNextValue() {
@@ -240,6 +280,11 @@
 
         SLOT* current;  /// The slot the iterator is pointing to
         SLOT* end;      /// One past the last slot in the map
+
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+        const HashmapBase& map;     /// The hashmap that is being iterated over.
+        size_t initial_generation;  /// The generation ID when the iterator was created.
+#endif
     };
 
     /// An immutable key and mutable value iterator
@@ -373,16 +418,16 @@
     size_t Generation() const { return generation_; }
 
     /// @returns an immutable iterator to the start of the map.
-    ConstIterator begin() const { return ConstIterator{slots_.begin(), slots_.end()}; }
+    ConstIterator begin() const { return ConstIterator{slots_.begin(), slots_.end(), *this}; }
 
     /// @returns an immutable iterator to the end of the map.
-    ConstIterator end() const { return ConstIterator{slots_.end(), slots_.end()}; }
+    ConstIterator end() const { return ConstIterator{slots_.end(), slots_.end(), *this}; }
 
     /// @returns an iterator to the start of the map.
-    Iterator begin() { return Iterator{slots_.begin(), slots_.end()}; }
+    Iterator begin() { return Iterator{slots_.begin(), slots_.end(), *this}; }
 
     /// @returns an iterator to the end of the map.
-    Iterator end() { return Iterator{slots_.end(), slots_.end()}; }
+    Iterator end() { return Iterator{slots_.end(), slots_.end(), *this}; }
 
     /// A debug function for checking that the map is in good health.
     /// Asserts if the map is corrupted.