Index: lib/scudo/standalone/list.h =================================================================== --- lib/scudo/standalone/list.h +++ lib/scudo/standalone/list.h @@ -13,43 +13,93 @@ namespace scudo { -// Intrusive POD singly-linked list. +// Intrusive POD singly and doubly linked list. // An object with all zero fields should represent a valid empty list. clear() // should be called on all non-zero-initialized objects before using. -template struct IntrusiveList { - friend class Iterator; + +template class IteratorBase { +public: + explicit IteratorBase(T *CurrentT) : Current(CurrentT) {} + IteratorBase &operator++() { + Current = Current->Next; + return *this; + } + bool operator!=(IteratorBase Other) const { return Current != Other.Current; } + T &operator*() { return *Current; } + +private: + T *Current; +}; + +template struct IntrusiveList { + bool empty() const { return Size == 0; } + uptr size() const { return Size; } + + T *front() { return First; } + const T *front() const { return First; } + T *back() { return Last; } + const T *back() const { return Last; } void clear() { First = Last = nullptr; Size = 0; } - bool empty() const { return Size == 0; } - uptr size() const { return Size; } + typedef IteratorBase Iterator; + typedef IteratorBase ConstIterator; - void push_back(Item *X) { - if (empty()) { - X->Next = nullptr; - First = Last = X; - Size = 1; - } else { - X->Next = nullptr; + Iterator begin() { return Iterator(First); } + Iterator end() { return Iterator(nullptr); } + + ConstIterator begin() const { return ConstIterator(First); } + ConstIterator end() const { return ConstIterator(nullptr); } + + void checkConsistency() const; + +protected: + uptr Size; + T *First; + T *Last; +}; + +template void IntrusiveList::checkConsistency() const { + if (Size == 0) { + CHECK_EQ(First, nullptr); + CHECK_EQ(Last, nullptr); + } else { + uptr Count = 0; + for (T *I = First;; I = I->Next) { + Count++; + if (I == Last) + break; + } + CHECK_EQ(this->size(), Count); + CHECK_EQ(Last->Next, nullptr); + } +} + +template struct SinglyLinkedList : public IntrusiveList { + using IntrusiveList::First; + using IntrusiveList::Last; + using IntrusiveList::Size; + using IntrusiveList::empty; + + void push_back(T *X) { + X->Next = nullptr; + if (empty()) + First = X; + else Last->Next = X; - Last = X; - Size++; - } + Last = X; + Size++; } - void push_front(Item *X) { - if (empty()) { - X->Next = nullptr; - First = Last = X; - Size = 1; - } else { - X->Next = First; - First = X; - Size++; - } + void push_front(T *X) { + if (empty()) + Last = X; + X->Next = First; + First = X; + Size++; } void pop_front() { @@ -60,7 +110,7 @@ Size--; } - void extract(Item *Prev, Item *X) { + void extract(T *Prev, T *X) { DCHECK(!empty()); DCHECK_NE(Prev, nullptr); DCHECK_NE(X, nullptr); @@ -71,84 +121,105 @@ Size--; } - Item *front() { return First; } - const Item *front() const { return First; } - Item *back() { return Last; } - const Item *back() const { return Last; } - - void append_front(IntrusiveList *L) { + void append_back(SinglyLinkedList *L) { DCHECK_NE(this, L); if (L->empty()) return; if (empty()) { *this = *L; - } else if (!L->empty()) { - L->Last->Next = First; - First = L->First; + } else { + Last->Next = L->First; + Last = L->Last; Size += L->size(); } L->clear(); } +}; - void append_back(IntrusiveList *L) { - DCHECK_NE(this, L); - if (L->empty()) - return; +template struct DoublyLinkedList : IntrusiveList { + using IntrusiveList::First; + using IntrusiveList::Last; + using IntrusiveList::Size; + using IntrusiveList::empty; + + void push_front(T *X) { + X->Prev = nullptr; if (empty()) { - *this = *L; + Last = X; } else { - Last->Next = L->First; - Last = L->Last; - Size += L->size(); + DCHECK_EQ(First->Prev, nullptr); } - L->clear(); + X->Next = First; + First = X; + Size++; + } + + // Inserts X before Y. + void insert(T *X, T *Y) { + if (Y == First) + return push_front(X); + T *Prev = Y->Prev; + // This is a hard CHECK to ensure consistency in the event of an intentional + // corruption of Y->Prev, to prevent a potential write-{4,8}. + CHECK_EQ(Prev->Next, Y); + Prev->Next = X; + X->Prev = Prev; + X->Next = Y; + Y->Prev = X; + Size++; } - void checkConsistency() { - if (Size == 0) { - CHECK_EQ(First, nullptr); - CHECK_EQ(Last, nullptr); + void push_back(T *X) { + X->Next = nullptr; + if (empty()) { + First = X; } else { - uptr Count = 0; - for (Item *I = First;; I = I->Next) { - Count++; - if (I == Last) - break; - } - CHECK_EQ(size(), Count); - CHECK_EQ(Last->Next, nullptr); + DCHECK_EQ(Last->Next, nullptr); + Last->Next = X; } + X->Prev = Last; + Last = X; + Size++; } - template class IteratorBase { - public: - explicit IteratorBase(ItemT *CurrentItem) : Current(CurrentItem) {} - IteratorBase &operator++() { - Current = Current->Next; - return *this; - } - bool operator!=(IteratorBase Other) const { - return Current != Other.Current; - } - ItemT &operator*() { return *Current; } - - private: - ItemT *Current; - }; - - typedef IteratorBase Iterator; - typedef IteratorBase ConstIterator; - - Iterator begin() { return Iterator(First); } - Iterator end() { return Iterator(nullptr); } - - ConstIterator begin() const { return ConstIterator(First); } - ConstIterator end() const { return ConstIterator(nullptr); } + void pop_front() { + DCHECK(!empty()); + First = First->Next; + if (!First) + Last = nullptr; + else + First->Prev = nullptr; + Size--; + } -private: - uptr Size; - Item *First; - Item *Last; + // The consistency of the adjacent links is aggressively checked in order to + // catch potential corruption attempts, that could yield a mirrored + // write-{4,8} primitive. nullptr checks are deemed less vital. + void remove(T *X) { + T *Prev = X->Prev; + T *Next = X->Next; + if (Prev) { + CHECK_EQ(Prev->Next, X); + Prev->Next = Next; + } + if (Next) { + CHECK_EQ(Next->Prev, X); + Next->Prev = Prev; + } + if (First == X) { + DCHECK_EQ(Prev, nullptr); + First = Next; + } else { + DCHECK_NE(Prev, nullptr); + } + if (Last == X) { + DCHECK_EQ(Next, nullptr); + Last = Prev; + } else { + DCHECK_NE(Next, nullptr); + } + Size--; + } }; } // namespace scudo Index: lib/scudo/standalone/primary32.h =================================================================== --- lib/scudo/standalone/primary32.h +++ lib/scudo/standalone/primary32.h @@ -197,7 +197,7 @@ struct ALIGNED(SCUDO_CACHE_LINE_SIZE) SizeClassInfo { HybridMutex Mutex; - IntrusiveList FreeList; + SinglyLinkedList FreeList; SizeClassStats Stats; bool CanRelease; u32 RandState; @@ -376,7 +376,7 @@ for (uptr I = MinRegionIndex; I <= MaxRegionIndex; I++) { if (PossibleRegions[I] == ClassId) { ReleaseRecorder Recorder(I * RegionSize); - releaseFreeMemoryToOS(&Sci->FreeList, I * RegionSize, + releaseFreeMemoryToOS(Sci->FreeList, I * RegionSize, RegionSize / PageSize, BlockSize, &Recorder); if (Recorder.getReleasedRangesCount() > 0) { Sci->ReleaseInfo.PushedBlocksAtLastRelease = Sci->Stats.PushedBlocks; Index: lib/scudo/standalone/primary64.h =================================================================== --- lib/scudo/standalone/primary64.h +++ lib/scudo/standalone/primary64.h @@ -202,7 +202,7 @@ struct ALIGNED(SCUDO_CACHE_LINE_SIZE) RegionInfo { HybridMutex Mutex; - IntrusiveList FreeList; + SinglyLinkedList FreeList; RegionStats Stats; bool CanRelease; bool Exhausted; @@ -372,7 +372,7 @@ } ReleaseRecorder Recorder(Region->RegionBeg, &Region->Data); - releaseFreeMemoryToOS(&Region->FreeList, Region->RegionBeg, + releaseFreeMemoryToOS(Region->FreeList, Region->RegionBeg, roundUpTo(Region->AllocatedUser, PageSize) / PageSize, BlockSize, &Recorder); Index: lib/scudo/standalone/quarantine.h =================================================================== --- lib/scudo/standalone/quarantine.h +++ lib/scudo/standalone/quarantine.h @@ -160,7 +160,7 @@ } private: - IntrusiveList List; + SinglyLinkedList List; atomic_uptr Size; void addToSize(uptr add) { atomic_store_relaxed(&Size, getSize() + add); } Index: lib/scudo/standalone/release.h =================================================================== --- lib/scudo/standalone/release.h +++ lib/scudo/standalone/release.h @@ -149,7 +149,7 @@ template NOINLINE void -releaseFreeMemoryToOS(const IntrusiveList *FreeList, uptr Base, +releaseFreeMemoryToOS(const IntrusiveList &FreeList, uptr Base, uptr AllocatedPagesCount, uptr BlockSize, ReleaseRecorderT *Recorder) { const uptr PageSize = getPageSizeCached(); @@ -199,18 +199,18 @@ // allocated page. if (BlockSize <= PageSize && PageSize % BlockSize == 0) { // Each chunk affects one page only. - for (auto It = FreeList->begin(); It != FreeList->end(); ++It) { - for (u32 I = 0; I < (*It).getCount(); I++) { - const uptr P = reinterpret_cast((*It).get(I)); + for (const auto &It : FreeList) { + for (u32 I = 0; I < It.getCount(); I++) { + const uptr P = reinterpret_cast(It.get(I)); if (P >= Base && P < End) Counters.inc((P - Base) >> PageSizeLog); } } } else { // In all other cases chunks might affect more than one page. - for (auto It = FreeList->begin(); It != FreeList->end(); ++It) { - for (u32 I = 0; I < (*It).getCount(); I++) { - const uptr P = reinterpret_cast((*It).get(I)); + for (const auto &It : FreeList) { + for (u32 I = 0; I < It.getCount(); I++) { + const uptr P = reinterpret_cast(It.get(I)); if (P >= Base && P < End) Counters.incRange((P - Base) >> PageSizeLog, (P - Base + BlockSize - 1) >> PageSizeLog); Index: lib/scudo/standalone/secondary.h =================================================================== --- lib/scudo/standalone/secondary.h +++ lib/scudo/standalone/secondary.h @@ -10,6 +10,7 @@ #define SCUDO_SECONDARY_H_ #include "common.h" +#include "list.h" #include "mutex.h" #include "stats.h" #include "string_utils.h" @@ -78,13 +79,13 @@ void enable() { Mutex.unlock(); } template void iterateOverBlocks(F Callback) const { - for (LargeBlock::Header *H = Tail; H != nullptr; H = H->Prev) - Callback(reinterpret_cast(H) + LargeBlock::getHeaderSize()); + for (const auto &H : InUseBlocks) + Callback(reinterpret_cast(&H) + LargeBlock::getHeaderSize()); } private: HybridMutex Mutex; - LargeBlock::Header *Tail; + DoublyLinkedList InUseBlocks; uptr AllocatedBytes; uptr FreedBytes; uptr LargestSize; Index: lib/scudo/standalone/secondary.cpp =================================================================== --- lib/scudo/standalone/secondary.cpp +++ lib/scudo/standalone/secondary.cpp @@ -73,11 +73,7 @@ H->Data = Data; { ScopedLock L(Mutex); - if (LIKELY(Tail)) { - Tail->Next = H; - H->Prev = Tail; - } - Tail = H; + InUseBlocks.push_back(H); AllocatedBytes += CommitSize; if (LargestSize < CommitSize) LargestSize = CommitSize; @@ -94,22 +90,7 @@ LargeBlock::Header *H = LargeBlock::getHeader(Ptr); { ScopedLock L(Mutex); - LargeBlock::Header *Prev = H->Prev; - LargeBlock::Header *Next = H->Next; - if (Prev) { - CHECK_EQ(Prev->Next, H); - Prev->Next = Next; - } - if (Next) { - CHECK_EQ(Next->Prev, H); - Next->Prev = Prev; - } - if (UNLIKELY(Tail == H)) { - CHECK(!Next); - Tail = Prev; - } else { - CHECK(Next); - } + InUseBlocks.remove(H); const uptr CommitSize = H->BlockEnd - reinterpret_cast(H); FreedBytes += CommitSize; NumberOfFrees++; Index: lib/scudo/standalone/stats.h =================================================================== --- lib/scudo/standalone/stats.h +++ lib/scudo/standalone/stats.h @@ -10,6 +10,7 @@ #define SCUDO_STATS_H_ #include "atomic_helpers.h" +#include "list.h" #include "mutex.h" #include @@ -45,20 +46,17 @@ uptr get(StatType I) const { return atomic_load_relaxed(&StatsArray[I]); } -private: - friend class GlobalStats; - atomic_uptr StatsArray[StatCount]; LocalStats *Next; LocalStats *Prev; + +private: + atomic_uptr StatsArray[StatCount]; }; // Global stats, used for aggregation and querying. class GlobalStats : public LocalStats { public: - void initLinkerInitialized() { - Next = this; - Prev = this; - } + void initLinkerInitialized() {} void init() { memset(this, 0, sizeof(*this)); initLinkerInitialized(); @@ -66,30 +64,23 @@ void link(LocalStats *S) { ScopedLock L(Mutex); - S->Next = Next; - S->Prev = this; - Next->Prev = S; - Next = S; + StatsList.push_back(S); } void unlink(LocalStats *S) { ScopedLock L(Mutex); - S->Prev->Next = S->Next; - S->Next->Prev = S->Prev; + StatsList.remove(S); for (uptr I = 0; I < StatCount; I++) add(static_cast(I), S->get(static_cast(I))); } void get(uptr *S) const { - memset(S, 0, StatCount * sizeof(uptr)); ScopedLock L(Mutex); - const LocalStats *Stats = this; - for (;;) { + for (uptr I = 0; I < StatCount; I++) + S[I] = LocalStats::get(static_cast(I)); + for (const auto &Stats : StatsList) { for (uptr I = 0; I < StatCount; I++) - S[I] += Stats->get(static_cast(I)); - Stats = Stats->Next; - if (Stats == this) - break; + S[I] += Stats.get(static_cast(I)); } // All stats must be non-negative. for (uptr I = 0; I < StatCount; I++) @@ -98,6 +89,7 @@ private: mutable HybridMutex Mutex; + DoublyLinkedList StatsList; }; } // namespace scudo Index: lib/scudo/standalone/tests/list_test.cpp =================================================================== --- lib/scudo/standalone/tests/list_test.cpp +++ lib/scudo/standalone/tests/list_test.cpp @@ -11,24 +11,34 @@ struct ListItem { ListItem *Next; + ListItem *Prev; }; -typedef scudo::IntrusiveList List; - -static List StaticList; - -static void setList(List *L, ListItem *X = nullptr, ListItem *Y = nullptr, - ListItem *Z = nullptr) { +static ListItem Items[6]; +static ListItem *X = &Items[0]; +static ListItem *Y = &Items[1]; +static ListItem *Z = &Items[2]; +static ListItem *A = &Items[3]; +static ListItem *B = &Items[4]; +static ListItem *C = &Items[5]; + +typedef scudo::SinglyLinkedList SLList; +typedef scudo::DoublyLinkedList DLList; + +template +static void setList(ListT *L, ListItem *I1 = nullptr, ListItem *I2 = nullptr, + ListItem *I3 = nullptr) { L->clear(); - if (X) - L->push_back(X); - if (Y) - L->push_back(Y); - if (Z) - L->push_back(Z); + if (I1) + L->push_back(I1); + if (I2) + L->push_back(I2); + if (I3) + L->push_back(I3); } -static void checkList(List *L, ListItem *I1, ListItem *I2 = nullptr, +template +static void checkList(ListT *L, ListItem *I1, ListItem *I2 = nullptr, ListItem *I3 = nullptr, ListItem *I4 = nullptr, ListItem *I5 = nullptr, ListItem *I6 = nullptr) { if (I1) { @@ -58,20 +68,10 @@ EXPECT_TRUE(L->empty()); } -TEST(ScudoListTest, IntrusiveList) { - ListItem Items[6]; - EXPECT_EQ(StaticList.size(), 0U); - - List L; +template static void testListCommon(void) { + ListT L; L.clear(); - ListItem *X = &Items[0]; - ListItem *Y = &Items[1]; - ListItem *Z = &Items[2]; - ListItem *A = &Items[3]; - ListItem *B = &Items[4]; - ListItem *C = &Items[5]; - EXPECT_EQ(L.size(), 0U); L.push_back(X); EXPECT_EQ(L.size(), 1U); @@ -122,6 +122,16 @@ L.pop_front(); EXPECT_TRUE(L.empty()); L.checkConsistency(); +} + +TEST(ScudoListTest, LinkedListCommon) { + testListCommon(); + testListCommon(); +} + +TEST(ScudoListTest, SinglyLinkedList) { + SLList L; + L.clear(); L.push_back(X); L.push_back(Y); @@ -139,14 +149,10 @@ L.pop_front(); EXPECT_TRUE(L.empty()); - List L1, L2; + SLList L1, L2; L1.clear(); L2.clear(); - L1.append_front(&L2); - EXPECT_TRUE(L1.empty()); - EXPECT_TRUE(L2.empty()); - L1.append_back(&L2); EXPECT_TRUE(L1.empty()); EXPECT_TRUE(L2.empty()); @@ -160,26 +166,32 @@ checkList(&L1, X, Y, Z, A, B, C); EXPECT_TRUE(L2.empty()); - setList(&L1, X, Y); - setList(&L2); - L1.append_front(&L2); - checkList(&L1, X, Y); - EXPECT_TRUE(L2.empty()); + L1.clear(); + L2.clear(); + L1.push_back(X); + L1.append_back(&L2); + EXPECT_EQ(L1.back(), X); + EXPECT_EQ(L1.front(), X); + EXPECT_EQ(L1.size(), 1U); } -TEST(ScudoListTest, IntrusiveListAppendEmpty) { - ListItem I; - List L; +TEST(ScudoListTest, DoublyLinkedList) { + DLList L; L.clear(); - L.push_back(&I); - List L2; - L2.clear(); - L.append_back(&L2); - EXPECT_EQ(L.back(), &I); - EXPECT_EQ(L.front(), &I); - EXPECT_EQ(L.size(), 1U); - L.append_front(&L2); - EXPECT_EQ(L.back(), &I); - EXPECT_EQ(L.front(), &I); + + L.push_back(X); + L.push_back(Y); + L.push_back(Z); + L.remove(Y); + EXPECT_EQ(L.size(), 2U); + EXPECT_EQ(L.front(), X); + EXPECT_EQ(L.back(), Z); + L.checkConsistency(); + L.remove(Z); EXPECT_EQ(L.size(), 1U); + EXPECT_EQ(L.front(), X); + EXPECT_EQ(L.back(), X); + L.checkConsistency(); + L.pop_front(); + EXPECT_TRUE(L.empty()); } Index: lib/scudo/standalone/tests/release_test.cpp =================================================================== --- lib/scudo/standalone/tests/release_test.cpp +++ lib/scudo/standalone/tests/release_test.cpp @@ -173,7 +173,7 @@ std::shuffle(FreeArray.begin(), FreeArray.end(), R); // Build the FreeList from the FreeArray. - scudo::IntrusiveList FreeList; + scudo::SinglyLinkedList FreeList; FreeList.clear(); Batch *CurrentBatch = nullptr; for (auto const &Block : FreeArray) { @@ -189,7 +189,7 @@ // Release the memory. ReleasedPagesRecorder Recorder; - releaseFreeMemoryToOS(&FreeList, 0, AllocatedPagesCount, BlockSize, + releaseFreeMemoryToOS(FreeList, 0, AllocatedPagesCount, BlockSize, &Recorder); // Verify that there are no released pages touched by used chunks and all