xref: /aosp_15_r20/external/cronet/base/containers/id_map.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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