1 // Copyright 2011 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_CONTAINERS_ID_MAP_H_ 6 #define BASE_CONTAINERS_ID_MAP_H_ 7 8 #include <stddef.h> 9 #include <stdint.h> 10 11 #include <iterator> 12 #include <limits> 13 #include <memory> 14 #include <ostream> 15 #include <type_traits> 16 #include <unordered_map> 17 #include <utility> 18 19 #include "base/check.h" 20 #include "base/check_op.h" 21 #include "base/containers/flat_set.h" 22 #include "base/memory/raw_ptr.h" 23 #include "base/sequence_checker.h" 24 25 namespace base { 26 27 // This object maintains a list of IDs that can be quickly converted to 28 // pointers to objects. It is implemented as a hash table, optimized for 29 // relatively small data sets (in the common case, there will be exactly one 30 // item in the list). 31 // 32 // Items can be inserted into the container with arbitrary ID, but the caller 33 // must ensure they are unique. Inserting IDs and relying on automatically 34 // generated ones is not allowed because they can collide. 35 // 36 // The map's value type (the V param) can be any dereferenceable type, such as a 37 // raw pointer or smart pointer, and must be comparable with nullptr. 38 template <typename V, typename K = int32_t> 39 class IDMap final { 40 public: 41 using KeyType = K; 42 43 private: 44 // The value type `V` must be pointer-like and support operator*. 45 using T = typename std::remove_reference<decltype(*V())>::type; 46 47 using HashTable = std::unordered_map<KeyType, V>; 48 49 public: IDMap()50 IDMap() : iteration_depth_(0), next_id_(1), check_on_null_data_(false) { 51 // A number of consumers of IDMap create it on one thread but always 52 // access it from a different, but consistent, thread (or sequence) 53 // post-construction. The first call to CalledOnValidSequence() will re-bind 54 // it. 55 DETACH_FROM_SEQUENCE(sequence_checker_); 56 } 57 58 IDMap(const IDMap&) = delete; 59 IDMap& operator=(const IDMap&) = delete; 60 ~IDMap()61 ~IDMap() { 62 // Many IDMap's are static, and hence will be destroyed on the main 63 // thread. However, all the accesses may take place on another thread (or 64 // sequence), such as the IO thread. Detaching again to clean this up. 65 DETACH_FROM_SEQUENCE(sequence_checker_); 66 } 67 68 // Sets whether Add and Replace should DCHECK if passed in NULL data. 69 // Default is false. set_check_on_null_data(bool value)70 void set_check_on_null_data(bool value) { check_on_null_data_ = value; } 71 72 // Adds a view with an automatically generated unique ID. See AddWithID. 73 // 74 // The generated key comes from the template type `K`, with each key being 75 // generated by incrementing `K`. The key type should not generate duplicate 76 // keys or this function can CHECK-fail. Add(V data)77 KeyType Add(V data) { return AddInternal(std::move(data)); } 78 79 // Adds a new data member with the specified ID. The ID must not be in 80 // the list. The caller either must generate all unique IDs itself and use 81 // this function, or allow this object to generate IDs and call Add. These 82 // two methods may not be mixed, or duplicate IDs may be generated. AddWithID(V data,KeyType id)83 void AddWithID(V data, KeyType id) { AddWithIDInternal(std::move(data), id); } 84 85 // Removes the `id` from the map. 86 // 87 // Does nothing if the `id` is not in the map. Remove(KeyType id)88 void Remove(KeyType id) { 89 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 90 typename HashTable::iterator i = data_.find(id); 91 if (i == data_.end() || IsRemoved(id)) { 92 return; 93 } 94 95 if (iteration_depth_ == 0) { 96 data_.erase(i); 97 } else { 98 removed_ids_.insert(id); 99 } 100 } 101 102 // Replaces the value for `id` with `new_data` and returns the existing value. 103 // 104 // May only be called with an id that is in the map, and will CHECK() 105 // otherwise. It is up to the caller to keep track whether the `id` is in the 106 // map, as Lookup() can return null for ids that are in the map but have an 107 // empty value. Replace(KeyType id,V new_data)108 V Replace(KeyType id, V new_data) { 109 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 110 DCHECK(!check_on_null_data_ || new_data); 111 typename HashTable::iterator i = data_.find(id); 112 CHECK(i != data_.end()); 113 CHECK(!IsRemoved(id)); 114 115 using std::swap; 116 swap(i->second, new_data); 117 return new_data; 118 } 119 Clear()120 void Clear() { 121 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 122 if (iteration_depth_ == 0) { 123 data_.clear(); 124 } else { 125 removed_ids_.reserve(data_.size()); 126 removed_ids_.insert(KeyIterator(data_.begin()), KeyIterator(data_.end())); 127 } 128 } 129 IsEmpty()130 bool IsEmpty() const { 131 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 132 return size() == 0u; 133 } 134 135 // Returns a pointer to raw value associated with `id` if the `id` is in the 136 // map and is not empty. 137 // 138 // The raw value is obtained by dereferencing the stored value type `V`. 139 // 140 // If the `id` is not in the map, or the value type compares as equal to 141 // nullptr, this function will return null. Lookup(KeyType id)142 T* Lookup(KeyType id) const { 143 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 144 typename HashTable::const_iterator i = data_.find(id); 145 if (i == data_.end() || IsRemoved(id)) { 146 return nullptr; 147 } 148 // The IDMap contains a pointer or pointer-like object. We don't want to 149 // dereference null, so this acts as an extension point for 150 // IDMap, where if the value object compares as equal to nullptr, it its 151 // dereference type will not be returned from the map. 152 if (i->second == nullptr) { 153 return nullptr; 154 } 155 return std::addressof(*i->second); 156 } 157 size()158 size_t size() const { 159 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 160 return data_.size() - removed_ids_.size(); 161 } 162 163 #if defined(UNIT_TEST) iteration_depth()164 int iteration_depth() const { return iteration_depth_; } 165 #endif // defined(UNIT_TEST) 166 167 // It is safe to remove elements from the map during iteration. All iterators 168 // will remain valid. 169 template <class ReturnType> 170 class Iterator { 171 public: Iterator(IDMap<V,K> * map)172 Iterator(IDMap<V, K>* map) : map_(map), iter_(map_->data_.begin()) { 173 Init(); 174 } 175 Iterator(const Iterator & iter)176 Iterator(const Iterator& iter) : map_(iter.map_), iter_(iter.iter_) { 177 Init(); 178 } 179 180 const Iterator& operator=(const Iterator& iter) { 181 map_ = iter.map; 182 iter_ = iter.iter; 183 Init(); 184 return *this; 185 } 186 ~Iterator()187 ~Iterator() { 188 DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_); 189 190 if (--map_->iteration_depth_ == 0) { 191 map_->Compact(); 192 } else { 193 // The iteration depth should not become negative, it would mean there 194 // was an untracked iterator which is now being destroyed, and the 195 // Compact() call would have happened while an iterator was live. 196 CHECK_GT(map_->iteration_depth_, 0); 197 } 198 } 199 IsAtEnd()200 bool IsAtEnd() const { 201 DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_); 202 return iter_ == map_->data_.end(); 203 } 204 GetCurrentKey()205 KeyType GetCurrentKey() const { 206 DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_); 207 return iter_->first; 208 } 209 GetCurrentValue()210 ReturnType* GetCurrentValue() const { 211 DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_); 212 if (!iter_->second || map_->IsRemoved(iter_->first)) { 213 return nullptr; 214 } 215 return &*iter_->second; 216 } 217 Advance()218 void Advance() { 219 DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_); 220 ++iter_; 221 SkipRemovedEntries(); 222 } 223 224 private: Init()225 void Init() { 226 DCHECK_CALLED_ON_VALID_SEQUENCE(map_->sequence_checker_); 227 // Guard signed integer overflow. 228 CHECK(map_->iteration_depth_ < std::numeric_limits<int>::max()); 229 ++map_->iteration_depth_; 230 SkipRemovedEntries(); 231 } 232 SkipRemovedEntries()233 void SkipRemovedEntries() { 234 while (iter_ != map_->data_.end() && map_->IsRemoved(iter_->first)) { 235 ++iter_; 236 } 237 } 238 239 raw_ptr<IDMap<V, K>> map_; 240 typename HashTable::const_iterator iter_; 241 }; 242 243 typedef Iterator<T> iterator; 244 typedef Iterator<const T> const_iterator; 245 246 private: 247 // Transforms a map iterator to an iterator on the keys of the map. 248 // Used by Clear() to populate |removed_ids_| in bulk. 249 struct KeyIterator { 250 using iterator_category = std::forward_iterator_tag; 251 using value_type = KeyType; 252 using difference_type = std::ptrdiff_t; 253 using pointer = KeyType*; 254 using reference = KeyType&; 255 256 using inner_iterator = typename HashTable::iterator; 257 inner_iterator iter_; 258 KeyIteratorKeyIterator259 KeyIterator(inner_iterator iter) : iter_(iter) {} 260 KeyType operator*() const { return iter_->first; } 261 KeyIterator& operator++() { 262 ++iter_; 263 return *this; 264 } 265 KeyIterator operator++(int) { return KeyIterator(iter_++); } 266 267 friend bool operator==(const KeyIterator&, const KeyIterator&) = default; 268 }; 269 AddInternal(V data)270 KeyType AddInternal(V data) { 271 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 272 DCHECK(!check_on_null_data_ || data); 273 KeyType this_id = next_id_; 274 275 AddWithIDInternal(std::move(data), this_id); 276 277 if constexpr (std::is_integral_v<K>) { 278 // Guard signed integer overflow, and duplicate unsigned keys. 279 CHECK(next_id_ < std::numeric_limits<K>::max()); 280 } 281 next_id_++; 282 283 return this_id; 284 } 285 AddWithIDInternal(V data,KeyType id)286 void AddWithIDInternal(V data, KeyType id) { 287 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); 288 DCHECK(!check_on_null_data_ || data); 289 if (IsRemoved(id)) { 290 removed_ids_.erase(id); 291 data_[id] = std::move(data); 292 } else { 293 auto [_, inserted] = data_.emplace(id, std::move(data)); 294 CHECK(inserted) << "Inserting duplicate item"; 295 } 296 } 297 IsRemoved(KeyType key)298 bool IsRemoved(KeyType key) const { 299 return removed_ids_.find(key) != removed_ids_.end(); 300 } 301 Compact()302 void Compact() { 303 DCHECK_EQ(0, iteration_depth_); 304 for (const auto& i : removed_ids_) { 305 data_.erase(i); 306 } 307 removed_ids_.clear(); 308 } 309 310 // Keep track of how many iterators are currently iterating on us to safely 311 // handle removing items during iteration. 312 int iteration_depth_; 313 314 // Keep set of IDs that should be removed after the outermost iteration has 315 // finished. This way we manage to not invalidate the iterator when an element 316 // is removed. 317 base::flat_set<KeyType> removed_ids_; 318 319 // The next ID that we will return from Add() 320 KeyType next_id_; 321 322 HashTable data_; 323 324 // See description above setter. 325 bool check_on_null_data_; 326 327 SEQUENCE_CHECKER(sequence_checker_); 328 }; 329 330 } // namespace base 331 332 #endif // BASE_CONTAINERS_ID_MAP_H_ 333