diff --git a/xpcom/glue/pldhash.cpp b/xpcom/glue/pldhash.cpp index b434f43da513..162c9ea16abc 100644 --- a/xpcom/glue/pldhash.cpp +++ b/xpcom/glue/pldhash.cpp @@ -697,6 +697,8 @@ PLDHashTable::Enumerate(PLDHashEnumerator aEtor, void* aArg) { INCREMENT_RECURSION_LEVEL(this); + // Please keep this method in sync with the PLDHashTable::Iterator constructor + // and ::NextEntry methods below in this file. char* entryAddr = mEntryStore; uint32_t capacity = Capacity(); uint32_t tableSize = capacity * mEntrySize; @@ -835,6 +837,97 @@ PL_DHashTableSizeOfIncludingThis( aMallocSizeOf, aArg); } +PLDHashTable::Iterator::Iterator(const PLDHashTable* aTable) +: mTable(aTable), + mEntryAddr(mTable->mEntryStore), + mEntryOffset(0) +{ + // Make sure that modifications can't simultaneously happen while the iterator + // is active. + INCREMENT_RECURSION_LEVEL(mTable); + + // The following code is taken from, and should be kept in sync with, the + // PLDHashTable::Enumerate method above. The variables i and entryAddr (which + // vary over the course of the for loop) are converted into mEntryOffset and + // mEntryAddr, respectively. + uint32_t capacity = mTable->Capacity(); + uint32_t tableSize = capacity * mTable->EntrySize(); + char* entryLimit = mEntryAddr + tableSize; + + if (ChaosMode::isActive()) { + // Start iterating at a random point in the hashtable. It would be + // even more chaotic to iterate in fully random order, but that's a lot + // more work. + mEntryAddr += ChaosMode::randomUint32LessThan(capacity) * mTable->mEntrySize; + if (mEntryAddr >= entryLimit) { + mEntryAddr -= tableSize; + } + } +} + +PLDHashTable::Iterator::Iterator(const Iterator& aIterator) +: mTable(aIterator.mTable), + mEntryAddr(aIterator.mEntryAddr), + mEntryOffset(aIterator.mEntryOffset) +{ + // We need the copy constructor only so that we can keep the recursion level + // consistent. + INCREMENT_RECURSION_LEVEL(mTable); +} + +PLDHashTable::Iterator::~Iterator() +{ + DECREMENT_RECURSION_LEVEL(mTable); +} + +bool PLDHashTable::Iterator::HasMoreEntries() const +{ + // Check the number of live entries seen, not the total number of entries + // seen. To see why, consider what happens if the last entry is not live: we + // would have to iterate after returning an entry to see if more live entries + // exist. + return mEntryOffset < mTable->EntryCount(); +} + +PLDHashEntryHdr* PLDHashTable::Iterator::NextEntry() +{ + MOZ_ASSERT(HasMoreEntries()); + + // The following code is taken from, and should be kept in sync with, the + // PLDHashTable::Enumerate method above. The variables i and entryAddr (which + // vary over the course of the for loop) are converted into mEntryOffset and + // mEntryAddr, respectively. + uint32_t capacity = mTable->Capacity(); + uint32_t tableSize = capacity * mTable->mEntrySize; + char* entryLimit = mEntryAddr + tableSize; + + // Strictly speaking, we don't need to iterate over the full capacity each + // time. However, it is simpler to do so rather than unnecessarily track the + // current number of entries checked as opposed to only live entries. If debug + // checks pass, then this method will only iterate through the full capacity + // once. If they fail, then this loop may end up returning the early entries + // more than once. + for (uint32_t e = 0; e < capacity; ++e) { + PLDHashEntryHdr* entry = (PLDHashEntryHdr*)mEntryAddr; + + // Increment the count before returning so we don't keep returning the same + // address. This may wrap around if ChaosMode is enabled. + mEntryAddr += mTable->mEntrySize; + if (mEntryAddr >= entryLimit) { + mEntryAddr -= tableSize; + } + if (ENTRY_IS_LIVE(entry)) { + ++mEntryOffset; + return entry; + } + } + + // If the debug checks pass, then the above loop should always find a live + // entry. If those checks are disabled, then it may be possible to reach this + // if the table is empty and this method is called. + MOZ_CRASH("Flagrant misuse of hashtable iterators not caught by checks."); +} + #ifdef DEBUG MOZ_ALWAYS_INLINE void PLDHashTable::MarkImmutable() diff --git a/xpcom/glue/pldhash.h b/xpcom/glue/pldhash.h index 9c5f4867af83..47d0feedda69 100644 --- a/xpcom/glue/pldhash.h +++ b/xpcom/glue/pldhash.h @@ -194,7 +194,7 @@ private: * non-DEBUG components. (Actually, even if it were removed, * sizeof(PLDHashTable) wouldn't change, due to struct padding.) */ - uint16_t mRecursionLevel;/* used to detect unsafe re-entry */ + mutable uint16_t mRecursionLevel;/* used to detect unsafe re-entry */ uint32_t mEntrySize; /* number of bytes in an entry */ uint32_t mEntryCount; /* number of entries in table */ uint32_t mRemovedCount; /* removed entry sentinels in table */ @@ -272,6 +272,27 @@ public: void DumpMeter(PLDHashEnumerator aDump, FILE* aFp); #endif + /** + * This is an iterator that works over the elements of PLDHashtable. It is not + * safe to modify the hashtable while it is being iterated over; on debug + * builds, attempting to do so will result in an assertion failure. + */ + class Iterator { + public: + Iterator(const PLDHashTable* aTable); + Iterator(const Iterator& aIterator); + ~Iterator(); + bool HasMoreEntries() const; + PLDHashEntryHdr* NextEntry(); + + private: + const PLDHashTable* mTable; /* Main table pointer */ + char* mEntryAddr; /* Pointer to the next entry to check */ + uint32_t mEntryOffset; /* The number of the elements returned */ + }; + + Iterator Iterate() const { return Iterator(this); } + private: PLDHashEntryHdr* PL_DHASH_FASTCALL SearchTable(const void* aKey, PLDHashNumber aKeyHash, PLDHashOperator aOp);