mfbt/LinkedList.h

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /* A type-safe doubly-linked list class. */
michael@0 8
michael@0 9 /*
michael@0 10 * The classes LinkedList<T> and LinkedListElement<T> together form a
michael@0 11 * convenient, type-safe doubly-linked list implementation.
michael@0 12 *
michael@0 13 * The class T which will be inserted into the linked list must inherit from
michael@0 14 * LinkedListElement<T>. A given object may be in only one linked list at a
michael@0 15 * time.
michael@0 16 *
michael@0 17 * A LinkedListElement automatically removes itself from the list upon
michael@0 18 * destruction, and a LinkedList will fatally assert in debug builds if it's
michael@0 19 * non-empty when it's destructed.
michael@0 20 *
michael@0 21 * For example, you might use LinkedList in a simple observer list class as
michael@0 22 * follows.
michael@0 23 *
michael@0 24 * class Observer : public LinkedListElement<Observer>
michael@0 25 * {
michael@0 26 * public:
michael@0 27 * void observe(char* topic) { ... }
michael@0 28 * };
michael@0 29 *
michael@0 30 * class ObserverContainer
michael@0 31 * {
michael@0 32 * private:
michael@0 33 * LinkedList<Observer> list;
michael@0 34 *
michael@0 35 * public:
michael@0 36 * void addObserver(Observer* observer) {
michael@0 37 * // Will assert if |observer| is part of another list.
michael@0 38 * list.insertBack(observer);
michael@0 39 * }
michael@0 40 *
michael@0 41 * void removeObserver(Observer* observer) {
michael@0 42 * // Will assert if |observer| is not part of some list.
michael@0 43 * observer.remove();
michael@0 44 * // Or, will assert if |observer| is not part of |list| specifically.
michael@0 45 * // observer.removeFrom(list);
michael@0 46 * }
michael@0 47 *
michael@0 48 * void notifyObservers(char* topic) {
michael@0 49 * for (Observer* o = list.getFirst(); o != nullptr; o = o->getNext())
michael@0 50 * o->observe(topic);
michael@0 51 * }
michael@0 52 * };
michael@0 53 *
michael@0 54 */
michael@0 55
michael@0 56 #ifndef mozilla_LinkedList_h
michael@0 57 #define mozilla_LinkedList_h
michael@0 58
michael@0 59 #include "mozilla/Assertions.h"
michael@0 60 #include "mozilla/Attributes.h"
michael@0 61 #include "mozilla/MemoryReporting.h"
michael@0 62 #include "mozilla/Move.h"
michael@0 63 #include "mozilla/NullPtr.h"
michael@0 64
michael@0 65 #ifdef __cplusplus
michael@0 66
michael@0 67 namespace mozilla {
michael@0 68
michael@0 69 template<typename T>
michael@0 70 class LinkedList;
michael@0 71
michael@0 72 template<typename T>
michael@0 73 class LinkedListElement
michael@0 74 {
michael@0 75 /*
michael@0 76 * It's convenient that we return nullptr when getNext() or getPrevious()
michael@0 77 * hits the end of the list, but doing so costs an extra word of storage in
michael@0 78 * each linked list node (to keep track of whether |this| is the sentinel
michael@0 79 * node) and a branch on this value in getNext/getPrevious.
michael@0 80 *
michael@0 81 * We could get rid of the extra word of storage by shoving the "is
michael@0 82 * sentinel" bit into one of the pointers, although this would, of course,
michael@0 83 * have performance implications of its own.
michael@0 84 *
michael@0 85 * But the goal here isn't to win an award for the fastest or slimmest
michael@0 86 * linked list; rather, we want a *convenient* linked list. So we won't
michael@0 87 * waste time guessing which micro-optimization strategy is best.
michael@0 88 *
michael@0 89 *
michael@0 90 * Speaking of unnecessary work, it's worth addressing here why we wrote
michael@0 91 * mozilla::LinkedList in the first place, instead of using stl::list.
michael@0 92 *
michael@0 93 * The key difference between mozilla::LinkedList and stl::list is that
michael@0 94 * mozilla::LinkedList stores the prev/next pointers in the object itself,
michael@0 95 * while stl::list stores the prev/next pointers in a list element which
michael@0 96 * itself points to the object being stored.
michael@0 97 *
michael@0 98 * mozilla::LinkedList's approach makes it harder to store an object in more
michael@0 99 * than one list. But the upside is that you can call next() / prev() /
michael@0 100 * remove() directly on the object. With stl::list, you'd need to store a
michael@0 101 * pointer to its iterator in the object in order to accomplish this. Not
michael@0 102 * only would this waste space, but you'd have to remember to update that
michael@0 103 * pointer every time you added or removed the object from a list.
michael@0 104 *
michael@0 105 * In-place, constant-time removal is a killer feature of doubly-linked
michael@0 106 * lists, and supporting this painlessly was a key design criterion.
michael@0 107 */
michael@0 108
michael@0 109 private:
michael@0 110 LinkedListElement* next;
michael@0 111 LinkedListElement* prev;
michael@0 112 const bool isSentinel;
michael@0 113
michael@0 114 public:
michael@0 115 LinkedListElement()
michael@0 116 : next(MOZ_THIS_IN_INITIALIZER_LIST()),
michael@0 117 prev(MOZ_THIS_IN_INITIALIZER_LIST()),
michael@0 118 isSentinel(false)
michael@0 119 { }
michael@0 120
michael@0 121 LinkedListElement(LinkedListElement<T>&& other)
michael@0 122 : isSentinel(other.isSentinel)
michael@0 123 {
michael@0 124 if (!other.isInList()) {
michael@0 125 next = this;
michael@0 126 prev = this;
michael@0 127 return;
michael@0 128 }
michael@0 129
michael@0 130 MOZ_ASSERT(other.next->prev == &other);
michael@0 131 MOZ_ASSERT(other.prev->next == &other);
michael@0 132
michael@0 133 /*
michael@0 134 * Initialize |this| with |other|'s prev/next pointers, and adjust those
michael@0 135 * element to point to this one.
michael@0 136 */
michael@0 137 next = other.next;
michael@0 138 prev = other.prev;
michael@0 139
michael@0 140 next->prev = this;
michael@0 141 prev->next = this;
michael@0 142
michael@0 143 /*
michael@0 144 * Adjust |other| so it doesn't think it's in a list. This makes it
michael@0 145 * safely destructable.
michael@0 146 */
michael@0 147 other.next = &other;
michael@0 148 other.prev = &other;
michael@0 149 }
michael@0 150
michael@0 151 ~LinkedListElement() {
michael@0 152 if (!isSentinel && isInList())
michael@0 153 remove();
michael@0 154 }
michael@0 155
michael@0 156 /*
michael@0 157 * Get the next element in the list, or nullptr if this is the last element
michael@0 158 * in the list.
michael@0 159 */
michael@0 160 T* getNext() {
michael@0 161 return next->asT();
michael@0 162 }
michael@0 163 const T* getNext() const {
michael@0 164 return next->asT();
michael@0 165 }
michael@0 166
michael@0 167 /*
michael@0 168 * Get the previous element in the list, or nullptr if this is the first
michael@0 169 * element in the list.
michael@0 170 */
michael@0 171 T* getPrevious() {
michael@0 172 return prev->asT();
michael@0 173 }
michael@0 174 const T* getPrevious() const {
michael@0 175 return prev->asT();
michael@0 176 }
michael@0 177
michael@0 178 /*
michael@0 179 * Insert elem after this element in the list. |this| must be part of a
michael@0 180 * linked list when you call setNext(); otherwise, this method will assert.
michael@0 181 */
michael@0 182 void setNext(T* elem) {
michael@0 183 MOZ_ASSERT(isInList());
michael@0 184 setNextUnsafe(elem);
michael@0 185 }
michael@0 186
michael@0 187 /*
michael@0 188 * Insert elem before this element in the list. |this| must be part of a
michael@0 189 * linked list when you call setPrevious(); otherwise, this method will
michael@0 190 * assert.
michael@0 191 */
michael@0 192 void setPrevious(T* elem) {
michael@0 193 MOZ_ASSERT(isInList());
michael@0 194 setPreviousUnsafe(elem);
michael@0 195 }
michael@0 196
michael@0 197 /*
michael@0 198 * Remove this element from the list which contains it. If this element is
michael@0 199 * not currently part of a linked list, this method asserts.
michael@0 200 */
michael@0 201 void remove() {
michael@0 202 MOZ_ASSERT(isInList());
michael@0 203
michael@0 204 prev->next = next;
michael@0 205 next->prev = prev;
michael@0 206 next = this;
michael@0 207 prev = this;
michael@0 208 }
michael@0 209
michael@0 210 /*
michael@0 211 * Identical to remove(), but also asserts in debug builds that this element
michael@0 212 * is in list.
michael@0 213 */
michael@0 214 void removeFrom(const LinkedList<T>& list) {
michael@0 215 list.assertContains(asT());
michael@0 216 remove();
michael@0 217 }
michael@0 218
michael@0 219 /*
michael@0 220 * Return true if |this| part is of a linked list, and false otherwise.
michael@0 221 */
michael@0 222 bool isInList() const {
michael@0 223 MOZ_ASSERT((next == this) == (prev == this));
michael@0 224 return next != this;
michael@0 225 }
michael@0 226
michael@0 227 private:
michael@0 228 friend class LinkedList<T>;
michael@0 229
michael@0 230 enum NodeKind {
michael@0 231 NODE_KIND_NORMAL,
michael@0 232 NODE_KIND_SENTINEL
michael@0 233 };
michael@0 234
michael@0 235 LinkedListElement(NodeKind nodeKind)
michael@0 236 : next(MOZ_THIS_IN_INITIALIZER_LIST()),
michael@0 237 prev(MOZ_THIS_IN_INITIALIZER_LIST()),
michael@0 238 isSentinel(nodeKind == NODE_KIND_SENTINEL)
michael@0 239 { }
michael@0 240
michael@0 241 /*
michael@0 242 * Return |this| cast to T* if we're a normal node, or return nullptr if
michael@0 243 * we're a sentinel node.
michael@0 244 */
michael@0 245 T* asT() {
michael@0 246 if (isSentinel)
michael@0 247 return nullptr;
michael@0 248
michael@0 249 return static_cast<T*>(this);
michael@0 250 }
michael@0 251 const T* asT() const {
michael@0 252 if (isSentinel)
michael@0 253 return nullptr;
michael@0 254
michael@0 255 return static_cast<const T*>(this);
michael@0 256 }
michael@0 257
michael@0 258 /*
michael@0 259 * Insert elem after this element, but don't check that this element is in
michael@0 260 * the list. This is called by LinkedList::insertFront().
michael@0 261 */
michael@0 262 void setNextUnsafe(T* elem) {
michael@0 263 LinkedListElement *listElem = static_cast<LinkedListElement*>(elem);
michael@0 264 MOZ_ASSERT(!listElem->isInList());
michael@0 265
michael@0 266 listElem->next = this->next;
michael@0 267 listElem->prev = this;
michael@0 268 this->next->prev = listElem;
michael@0 269 this->next = listElem;
michael@0 270 }
michael@0 271
michael@0 272 /*
michael@0 273 * Insert elem before this element, but don't check that this element is in
michael@0 274 * the list. This is called by LinkedList::insertBack().
michael@0 275 */
michael@0 276 void setPreviousUnsafe(T* elem) {
michael@0 277 LinkedListElement<T>* listElem = static_cast<LinkedListElement<T>*>(elem);
michael@0 278 MOZ_ASSERT(!listElem->isInList());
michael@0 279
michael@0 280 listElem->next = this;
michael@0 281 listElem->prev = this->prev;
michael@0 282 this->prev->next = listElem;
michael@0 283 this->prev = listElem;
michael@0 284 }
michael@0 285
michael@0 286 private:
michael@0 287 LinkedListElement& operator=(const LinkedListElement<T>& other) MOZ_DELETE;
michael@0 288 LinkedListElement(const LinkedListElement<T>& other) MOZ_DELETE;
michael@0 289 };
michael@0 290
michael@0 291 template<typename T>
michael@0 292 class LinkedList
michael@0 293 {
michael@0 294 private:
michael@0 295 LinkedListElement<T> sentinel;
michael@0 296
michael@0 297 public:
michael@0 298 LinkedList() : sentinel(LinkedListElement<T>::NODE_KIND_SENTINEL) { }
michael@0 299
michael@0 300 LinkedList(LinkedList<T>&& other)
michael@0 301 : sentinel(mozilla::Move(other.sentinel))
michael@0 302 { }
michael@0 303
michael@0 304 ~LinkedList() {
michael@0 305 MOZ_ASSERT(isEmpty());
michael@0 306 }
michael@0 307
michael@0 308 /*
michael@0 309 * Add elem to the front of the list.
michael@0 310 */
michael@0 311 void insertFront(T* elem) {
michael@0 312 /* Bypass setNext()'s this->isInList() assertion. */
michael@0 313 sentinel.setNextUnsafe(elem);
michael@0 314 }
michael@0 315
michael@0 316 /*
michael@0 317 * Add elem to the back of the list.
michael@0 318 */
michael@0 319 void insertBack(T* elem) {
michael@0 320 sentinel.setPreviousUnsafe(elem);
michael@0 321 }
michael@0 322
michael@0 323 /*
michael@0 324 * Get the first element of the list, or nullptr if the list is empty.
michael@0 325 */
michael@0 326 T* getFirst() {
michael@0 327 return sentinel.getNext();
michael@0 328 }
michael@0 329 const T* getFirst() const {
michael@0 330 return sentinel.getNext();
michael@0 331 }
michael@0 332
michael@0 333 /*
michael@0 334 * Get the last element of the list, or nullptr if the list is empty.
michael@0 335 */
michael@0 336 T* getLast() {
michael@0 337 return sentinel.getPrevious();
michael@0 338 }
michael@0 339 const T* getLast() const {
michael@0 340 return sentinel.getPrevious();
michael@0 341 }
michael@0 342
michael@0 343 /*
michael@0 344 * Get and remove the first element of the list. If the list is empty,
michael@0 345 * return nullptr.
michael@0 346 */
michael@0 347 T* popFirst() {
michael@0 348 T* ret = sentinel.getNext();
michael@0 349 if (ret)
michael@0 350 static_cast<LinkedListElement<T>*>(ret)->remove();
michael@0 351 return ret;
michael@0 352 }
michael@0 353
michael@0 354 /*
michael@0 355 * Get and remove the last element of the list. If the list is empty,
michael@0 356 * return nullptr.
michael@0 357 */
michael@0 358 T* popLast() {
michael@0 359 T* ret = sentinel.getPrevious();
michael@0 360 if (ret)
michael@0 361 static_cast<LinkedListElement<T>*>(ret)->remove();
michael@0 362 return ret;
michael@0 363 }
michael@0 364
michael@0 365 /*
michael@0 366 * Return true if the list is empty, or false otherwise.
michael@0 367 */
michael@0 368 bool isEmpty() const {
michael@0 369 return !sentinel.isInList();
michael@0 370 }
michael@0 371
michael@0 372 /*
michael@0 373 * Remove all the elements from the list.
michael@0 374 *
michael@0 375 * This runs in time linear to the list's length, because we have to mark
michael@0 376 * each element as not in the list.
michael@0 377 */
michael@0 378 void clear() {
michael@0 379 while (popFirst())
michael@0 380 continue;
michael@0 381 }
michael@0 382
michael@0 383 /*
michael@0 384 * Measures the memory consumption of the list excluding |this|. Note that
michael@0 385 * it only measures the list elements themselves. If the list elements
michael@0 386 * contain pointers to other memory blocks, those blocks must be measured
michael@0 387 * separately during a subsequent iteration over the list.
michael@0 388 */
michael@0 389 size_t sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
michael@0 390 size_t n = 0;
michael@0 391 for (const T* t = getFirst(); t; t = t->getNext())
michael@0 392 n += mallocSizeOf(t);
michael@0 393 return n;
michael@0 394 }
michael@0 395
michael@0 396 /*
michael@0 397 * Like sizeOfExcludingThis(), but measures |this| as well.
michael@0 398 */
michael@0 399 size_t sizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
michael@0 400 return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
michael@0 401 }
michael@0 402
michael@0 403 /*
michael@0 404 * In a debug build, make sure that the list is sane (no cycles, consistent
michael@0 405 * next/prev pointers, only one sentinel). Has no effect in release builds.
michael@0 406 */
michael@0 407 void debugAssertIsSane() const {
michael@0 408 #ifdef DEBUG
michael@0 409 const LinkedListElement<T>* slow;
michael@0 410 const LinkedListElement<T>* fast1;
michael@0 411 const LinkedListElement<T>* fast2;
michael@0 412
michael@0 413 /*
michael@0 414 * Check for cycles in the forward singly-linked list using the
michael@0 415 * tortoise/hare algorithm.
michael@0 416 */
michael@0 417 for (slow = sentinel.next,
michael@0 418 fast1 = sentinel.next->next,
michael@0 419 fast2 = sentinel.next->next->next;
michael@0 420 slow != &sentinel && fast1 != &sentinel && fast2 != &sentinel;
michael@0 421 slow = slow->next, fast1 = fast2->next, fast2 = fast1->next)
michael@0 422 {
michael@0 423 MOZ_ASSERT(slow != fast1);
michael@0 424 MOZ_ASSERT(slow != fast2);
michael@0 425 }
michael@0 426
michael@0 427 /* Check for cycles in the backward singly-linked list. */
michael@0 428 for (slow = sentinel.prev,
michael@0 429 fast1 = sentinel.prev->prev,
michael@0 430 fast2 = sentinel.prev->prev->prev;
michael@0 431 slow != &sentinel && fast1 != &sentinel && fast2 != &sentinel;
michael@0 432 slow = slow->prev, fast1 = fast2->prev, fast2 = fast1->prev)
michael@0 433 {
michael@0 434 MOZ_ASSERT(slow != fast1);
michael@0 435 MOZ_ASSERT(slow != fast2);
michael@0 436 }
michael@0 437
michael@0 438 /*
michael@0 439 * Check that |sentinel| is the only node in the list with
michael@0 440 * isSentinel == true.
michael@0 441 */
michael@0 442 for (const LinkedListElement<T>* elem = sentinel.next;
michael@0 443 elem != &sentinel;
michael@0 444 elem = elem->next)
michael@0 445 {
michael@0 446 MOZ_ASSERT(!elem->isSentinel);
michael@0 447 }
michael@0 448
michael@0 449 /* Check that the next/prev pointers match up. */
michael@0 450 const LinkedListElement<T>* prev = &sentinel;
michael@0 451 const LinkedListElement<T>* cur = sentinel.next;
michael@0 452 do {
michael@0 453 MOZ_ASSERT(cur->prev == prev);
michael@0 454 MOZ_ASSERT(prev->next == cur);
michael@0 455
michael@0 456 prev = cur;
michael@0 457 cur = cur->next;
michael@0 458 } while (cur != &sentinel);
michael@0 459 #endif /* ifdef DEBUG */
michael@0 460 }
michael@0 461
michael@0 462 private:
michael@0 463 friend class LinkedListElement<T>;
michael@0 464
michael@0 465 void assertContains(const T* t) const {
michael@0 466 #ifdef DEBUG
michael@0 467 for (const T* elem = getFirst();
michael@0 468 elem;
michael@0 469 elem = elem->getNext())
michael@0 470 {
michael@0 471 if (elem == t)
michael@0 472 return;
michael@0 473 }
michael@0 474 MOZ_CRASH("element wasn't found in this list!");
michael@0 475 #endif
michael@0 476 }
michael@0 477
michael@0 478 LinkedList& operator=(const LinkedList<T>& other) MOZ_DELETE;
michael@0 479 LinkedList(const LinkedList<T>& other) MOZ_DELETE;
michael@0 480 };
michael@0 481
michael@0 482 } /* namespace mozilla */
michael@0 483
michael@0 484 #endif /* __cplusplus */
michael@0 485
michael@0 486 #endif /* mozilla_LinkedList_h */

mercurial