Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright 2013 Google Inc. |
michael@0 | 3 | * |
michael@0 | 4 | * Use of this source code is governed by a BSD-style license that can be |
michael@0 | 5 | * found in the LICENSE file. |
michael@0 | 6 | */ |
michael@0 | 7 | |
michael@0 | 8 | #ifndef SkTDynamicHash_DEFINED |
michael@0 | 9 | #define SkTDynamicHash_DEFINED |
michael@0 | 10 | |
michael@0 | 11 | #include "SkMath.h" |
michael@0 | 12 | #include "SkTemplates.h" |
michael@0 | 13 | #include "SkTypes.h" |
michael@0 | 14 | |
michael@0 | 15 | template <typename T, |
michael@0 | 16 | typename Key, |
michael@0 | 17 | const Key& (GetKey)(const T&), |
michael@0 | 18 | uint32_t (Hash)(const Key&), |
michael@0 | 19 | bool (Equal)(const T&, const Key&), |
michael@0 | 20 | int kGrowPercent = 75> // Larger -> more memory efficient, but slower. |
michael@0 | 21 | class SkTDynamicHash { |
michael@0 | 22 | public: |
michael@0 | 23 | SkTDynamicHash() : fCount(0), fDeleted(0), fCapacity(0), fArray(NULL) { |
michael@0 | 24 | SkASSERT(this->validate()); |
michael@0 | 25 | } |
michael@0 | 26 | |
michael@0 | 27 | ~SkTDynamicHash() { |
michael@0 | 28 | sk_free(fArray); |
michael@0 | 29 | } |
michael@0 | 30 | |
michael@0 | 31 | class Iter { |
michael@0 | 32 | public: |
michael@0 | 33 | explicit Iter(SkTDynamicHash* hash) : fHash(hash), fCurrentIndex(-1) { |
michael@0 | 34 | SkASSERT(hash); |
michael@0 | 35 | ++(*this); |
michael@0 | 36 | } |
michael@0 | 37 | bool done() const { |
michael@0 | 38 | SkASSERT(fCurrentIndex <= fHash->fCapacity); |
michael@0 | 39 | return fCurrentIndex == fHash->fCapacity; |
michael@0 | 40 | } |
michael@0 | 41 | T& operator*() const { |
michael@0 | 42 | SkASSERT(!this->done()); |
michael@0 | 43 | return *this->current(); |
michael@0 | 44 | } |
michael@0 | 45 | void operator++() { |
michael@0 | 46 | do { |
michael@0 | 47 | fCurrentIndex++; |
michael@0 | 48 | } while (!this->done() && (this->current() == Empty() || this->current() == Deleted())); |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | private: |
michael@0 | 52 | T* current() const { return fHash->fArray[fCurrentIndex]; } |
michael@0 | 53 | |
michael@0 | 54 | SkTDynamicHash* fHash; |
michael@0 | 55 | int fCurrentIndex; |
michael@0 | 56 | }; |
michael@0 | 57 | |
michael@0 | 58 | int count() const { return fCount; } |
michael@0 | 59 | |
michael@0 | 60 | // Return the entry with this key if we have it, otherwise NULL. |
michael@0 | 61 | T* find(const Key& key) const { |
michael@0 | 62 | int index = this->firstIndex(key); |
michael@0 | 63 | for (int round = 0; round < fCapacity; round++) { |
michael@0 | 64 | T* candidate = fArray[index]; |
michael@0 | 65 | if (Empty() == candidate) { |
michael@0 | 66 | return NULL; |
michael@0 | 67 | } |
michael@0 | 68 | if (Deleted() != candidate && Equal(*candidate, key)) { |
michael@0 | 69 | return candidate; |
michael@0 | 70 | } |
michael@0 | 71 | index = this->nextIndex(index, round); |
michael@0 | 72 | } |
michael@0 | 73 | SkASSERT(fCapacity == 0); |
michael@0 | 74 | return NULL; |
michael@0 | 75 | } |
michael@0 | 76 | |
michael@0 | 77 | // Add an entry with this key. We require that no entry with newEntry's key is already present. |
michael@0 | 78 | void add(T* newEntry) { |
michael@0 | 79 | SkASSERT(NULL == this->find(GetKey(*newEntry))); |
michael@0 | 80 | this->maybeGrow(); |
michael@0 | 81 | this->innerAdd(newEntry); |
michael@0 | 82 | SkASSERT(this->validate()); |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | // Remove the entry with this key. We reqire that an entry with this key is present. |
michael@0 | 86 | void remove(const Key& key) { |
michael@0 | 87 | SkASSERT(NULL != this->find(key)); |
michael@0 | 88 | this->innerRemove(key); |
michael@0 | 89 | SkASSERT(this->validate()); |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | protected: |
michael@0 | 93 | // These methods are used by tests only. |
michael@0 | 94 | |
michael@0 | 95 | int capacity() const { return fCapacity; } |
michael@0 | 96 | |
michael@0 | 97 | // How many collisions do we go through before finding where this entry should be inserted? |
michael@0 | 98 | int countCollisions(const Key& key) const { |
michael@0 | 99 | int index = this->firstIndex(key); |
michael@0 | 100 | for (int round = 0; round < fCapacity; round++) { |
michael@0 | 101 | const T* candidate = fArray[index]; |
michael@0 | 102 | if (Empty() == candidate || Deleted() == candidate || Equal(*candidate, key)) { |
michael@0 | 103 | return round; |
michael@0 | 104 | } |
michael@0 | 105 | index = this->nextIndex(index, round); |
michael@0 | 106 | } |
michael@0 | 107 | SkASSERT(fCapacity == 0); |
michael@0 | 108 | return 0; |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | private: |
michael@0 | 112 | // We have two special values to indicate an empty or deleted entry. |
michael@0 | 113 | static T* Empty() { return reinterpret_cast<T*>(0); } // i.e. NULL |
michael@0 | 114 | static T* Deleted() { return reinterpret_cast<T*>(1); } // Also an invalid pointer. |
michael@0 | 115 | |
michael@0 | 116 | bool validate() const { |
michael@0 | 117 | #define SKTDYNAMICHASH_CHECK(x) SkASSERT((x)); if (!(x)) return false |
michael@0 | 118 | static const int kLarge = 50; // Arbitrary, tweak to suit your patience. |
michael@0 | 119 | |
michael@0 | 120 | // O(1) checks, always done. |
michael@0 | 121 | // Is capacity sane? |
michael@0 | 122 | SKTDYNAMICHASH_CHECK(SkIsPow2(fCapacity)); |
michael@0 | 123 | |
michael@0 | 124 | // O(N) checks, skipped when very large. |
michael@0 | 125 | if (fCount < kLarge * kLarge) { |
michael@0 | 126 | // Are fCount and fDeleted correct, and are all elements findable? |
michael@0 | 127 | int count = 0, deleted = 0; |
michael@0 | 128 | for (int i = 0; i < fCapacity; i++) { |
michael@0 | 129 | if (Deleted() == fArray[i]) { |
michael@0 | 130 | deleted++; |
michael@0 | 131 | } else if (Empty() != fArray[i]) { |
michael@0 | 132 | count++; |
michael@0 | 133 | SKTDYNAMICHASH_CHECK(NULL != this->find(GetKey(*fArray[i]))); |
michael@0 | 134 | } |
michael@0 | 135 | } |
michael@0 | 136 | SKTDYNAMICHASH_CHECK(count == fCount); |
michael@0 | 137 | SKTDYNAMICHASH_CHECK(deleted == fDeleted); |
michael@0 | 138 | } |
michael@0 | 139 | |
michael@0 | 140 | // O(N^2) checks, skipped when large. |
michael@0 | 141 | if (fCount < kLarge) { |
michael@0 | 142 | // Are all entries unique? |
michael@0 | 143 | for (int i = 0; i < fCapacity; i++) { |
michael@0 | 144 | if (Empty() == fArray[i] || Deleted() == fArray[i]) { |
michael@0 | 145 | continue; |
michael@0 | 146 | } |
michael@0 | 147 | for (int j = i+1; j < fCapacity; j++) { |
michael@0 | 148 | if (Empty() == fArray[j] || Deleted() == fArray[j]) { |
michael@0 | 149 | continue; |
michael@0 | 150 | } |
michael@0 | 151 | SKTDYNAMICHASH_CHECK(fArray[i] != fArray[j]); |
michael@0 | 152 | SKTDYNAMICHASH_CHECK(!Equal(*fArray[i], GetKey(*fArray[j]))); |
michael@0 | 153 | SKTDYNAMICHASH_CHECK(!Equal(*fArray[j], GetKey(*fArray[i]))); |
michael@0 | 154 | } |
michael@0 | 155 | } |
michael@0 | 156 | } |
michael@0 | 157 | #undef SKTDYNAMICHASH_CHECK |
michael@0 | 158 | return true; |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | void innerAdd(T* newEntry) { |
michael@0 | 162 | const Key& key = GetKey(*newEntry); |
michael@0 | 163 | int index = this->firstIndex(key); |
michael@0 | 164 | for (int round = 0; round < fCapacity; round++) { |
michael@0 | 165 | const T* candidate = fArray[index]; |
michael@0 | 166 | if (Empty() == candidate || Deleted() == candidate) { |
michael@0 | 167 | if (Deleted() == candidate) { |
michael@0 | 168 | fDeleted--; |
michael@0 | 169 | } |
michael@0 | 170 | fCount++; |
michael@0 | 171 | fArray[index] = newEntry; |
michael@0 | 172 | return; |
michael@0 | 173 | } |
michael@0 | 174 | index = this->nextIndex(index, round); |
michael@0 | 175 | } |
michael@0 | 176 | SkASSERT(fCapacity == 0); |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | void innerRemove(const Key& key) { |
michael@0 | 180 | const int firstIndex = this->firstIndex(key); |
michael@0 | 181 | int index = firstIndex; |
michael@0 | 182 | for (int round = 0; round < fCapacity; round++) { |
michael@0 | 183 | const T* candidate = fArray[index]; |
michael@0 | 184 | if (Deleted() != candidate && Equal(*candidate, key)) { |
michael@0 | 185 | fDeleted++; |
michael@0 | 186 | fCount--; |
michael@0 | 187 | fArray[index] = Deleted(); |
michael@0 | 188 | return; |
michael@0 | 189 | } |
michael@0 | 190 | index = this->nextIndex(index, round); |
michael@0 | 191 | } |
michael@0 | 192 | SkASSERT(fCapacity == 0); |
michael@0 | 193 | } |
michael@0 | 194 | |
michael@0 | 195 | void maybeGrow() { |
michael@0 | 196 | if (100 * (fCount + fDeleted + 1) > fCapacity * kGrowPercent) { |
michael@0 | 197 | this->resize(fCapacity > 0 ? fCapacity * 2 : 4); |
michael@0 | 198 | } |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | void resize(int newCapacity) { |
michael@0 | 202 | SkDEBUGCODE(int oldCount = fCount;) |
michael@0 | 203 | int oldCapacity = fCapacity; |
michael@0 | 204 | SkAutoTMalloc<T*> oldArray(fArray); |
michael@0 | 205 | |
michael@0 | 206 | fCount = fDeleted = 0; |
michael@0 | 207 | fCapacity = newCapacity; |
michael@0 | 208 | fArray = (T**)sk_calloc_throw(sizeof(T*) * fCapacity); |
michael@0 | 209 | |
michael@0 | 210 | for (int i = 0; i < oldCapacity; i++) { |
michael@0 | 211 | T* entry = oldArray[i]; |
michael@0 | 212 | if (Empty() != entry && Deleted() != entry) { |
michael@0 | 213 | this->innerAdd(entry); |
michael@0 | 214 | } |
michael@0 | 215 | } |
michael@0 | 216 | SkASSERT(oldCount == fCount); |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | // fCapacity is always a power of 2, so this masks the correct low bits to index into our hash. |
michael@0 | 220 | uint32_t hashMask() const { return fCapacity - 1; } |
michael@0 | 221 | |
michael@0 | 222 | int firstIndex(const Key& key) const { |
michael@0 | 223 | return Hash(key) & this->hashMask(); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | // Given index at round N, what is the index to check at N+1? round should start at 0. |
michael@0 | 227 | int nextIndex(int index, int round) const { |
michael@0 | 228 | // This will search a power-of-two array fully without repeating an index. |
michael@0 | 229 | return (index + round + 1) & this->hashMask(); |
michael@0 | 230 | } |
michael@0 | 231 | |
michael@0 | 232 | int fCount; // Number of non Empty(), non Deleted() entries in fArray. |
michael@0 | 233 | int fDeleted; // Number of Deleted() entries in fArray. |
michael@0 | 234 | int fCapacity; // Number of entries in fArray. Always a power of 2. |
michael@0 | 235 | T** fArray; |
michael@0 | 236 | }; |
michael@0 | 237 | |
michael@0 | 238 | #endif |