diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 3db50cae4..c44097c80 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -412,6 +412,7 @@ openthread_core_files = [ "common/notifier.hpp", "common/numeric_limits.hpp", "common/owned_ptr.hpp", + "common/owning_list.hpp", "common/pool.hpp", "common/random.hpp", "common/random_manager.cpp", diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 8cdd27465..31e0b01fe 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -446,6 +446,7 @@ HEADERS_COMMON = \ common/notifier.hpp \ common/numeric_limits.hpp \ common/owned_ptr.hpp \ + common/owning_list.hpp \ common/pool.hpp \ common/random.hpp \ common/random_manager.hpp \ diff --git a/src/core/common/owning_list.hpp b/src/core/common/owning_list.hpp new file mode 100644 index 000000000..969147f11 --- /dev/null +++ b/src/core/common/owning_list.hpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for a singly linked list which owns its entries and frees them upon destruction of + * the list. + */ + +#ifndef OWNING_LIST_HPP_ +#define OWNING_LIST_HPP_ + +#include "openthread-core-config.h" + +#include "common/linked_list.hpp" +#include "common/owned_ptr.hpp" + +namespace ot { + +/** + * This template class represents a singly linked list which owns its entries and frees them upon destruction of the + * list. + * + */ +template class OwningList : public LinkedList +{ + class Iterator; + class ConstIterator; + +public: + /** + * This is the default constructor for `OwningList` + * + */ + OwningList(void) = default; + + /** + * This is the destructor for `OwningList`. + * + * On destruction, all existing entries in the list are freed. + * + */ + ~OwningList(void) { Free(); } + + /** + * This method clears the list and frees all existing entries in it. + * + */ + void Free(void) + { + while (!Pop().IsNull()) + { + } + } + + /** + * This method clears the list and frees all existing entries in it. + * + */ + void Clear(void) { Free(); } + + /** + * This method pops an entry from head of the linked list and return an `OwnedPtr` to it. + * + * @note This method does not change the popped entry itself, i.e., the popped entry next pointer stays as before. + * + * @returns An `OwnerPtr` to the entry that was popped (set to null if list of empty). + * + */ + OwnedPtr Pop(void) { return OwnedPtr(LinkedList::Pop()); } + + /** + * This method pops an entry after a given previous entry. + * + * @note This method does not change the popped entry itself, i.e., the popped entry next pointer stays as before. + * + * @param[in] aPrevEntry A pointer to a previous entry. If it is not nullptr the entry after this will be popped, + * otherwise (if it is nullptr) the entry at the head of the list is popped. + * + * @returns An `OwnerPtr` to the entry that was popped (set to null if there is no entry to pop). + * + */ + OwnedPtr PopAfter(Type *aPrevEntry) { return OwnedPtr(LinkedList::PopAfter(aPrevEntry)); } + + /** + * This template method removes an entry matching a given entry indicator from the linked list. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against entries + * in the list. To check that an entry matches the given indicator, the `Matches()` method is invoked on each + * `Type` entry in the list. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * @note This method does not change the removed entry itself (which is returned in case of success), i.e., the + * entry next pointer stays as before. + * + * @param[in] aIndicator An entry indicator to match against entries in the list. + * + * @returns An `OwnerPtr` to the entry that was removed (set to null if there is no matching entry to remove). + * + */ + template OwnedPtr RemoveMatching(const Indicator &aIndicator) + { + return OwnedPtr(LinkedList::RemoveMatching(aIndicator)); + } +}; + +} // namespace ot + +#endif // OWNING_LIST_HPP_ diff --git a/tests/unit/test_linked_list.cpp b/tests/unit/test_linked_list.cpp index b0b297009..77e5475f2 100644 --- a/tests/unit/test_linked_list.cpp +++ b/tests/unit/test_linked_list.cpp @@ -36,20 +36,24 @@ #include "common/debug.hpp" #include "common/instance.hpp" #include "common/linked_list.hpp" +#include "common/owning_list.hpp" #include "test_util.h" +namespace ot { + struct EntryBase { EntryBase *mNext; }; -struct Entry : public EntryBase, ot::LinkedListEntry +struct Entry : public EntryBase, LinkedListEntry { public: Entry(const char *aName, uint16_t aId) : mName(aName) , mId(aId) + , mWasFreed(false) { } @@ -57,14 +61,19 @@ public: uint16_t GetId(void) const { return mId; } bool Matches(const char *aName) const { return strcmp(mName, aName) == 0; } bool Matches(uint16_t aId) const { return mId == aId; } + void Free(void) { mWasFreed = true; } + + void ResetTestFlags(void) { mWasFreed = false; } + bool WasFreed(void) const { return mWasFreed; } private: const char *mName; uint16_t mId; + bool mWasFreed; }; // This function verifies the content of the linked list matches a given list of entries. -void VerifyLinkedListContent(const ot::LinkedList *aList, ...) +void VerifyLinkedListContent(const LinkedList *aList, ...) { va_list args; Entry * argEntry; @@ -92,6 +101,8 @@ void VerifyLinkedListContent(const ot::LinkedList *aList, ...) VerifyOrQuit(aList->FindMatching(argEntry->GetId(), prev) == argEntry); VerifyOrQuit(prev == argPrev, "List::FindMatching() returned prev entry is incorrect"); + VerifyOrQuit(!argEntry->WasFreed()); + argPrev = argEntry; } @@ -109,25 +120,27 @@ void VerifyLinkedListContent(const ot::LinkedList *aList, ...) void TestLinkedList(void) { - Entry a("a", 1), b("b", 2), c("c", 3), d("d", 4), e("e", 5); - Entry * prev; - ot::LinkedList list; + Entry a("a", 1), b("b", 2), c("c", 3), d("d", 4), e("e", 5); + Entry * prev; + LinkedList list; + + printf("TestLinkedList\n"); VerifyOrQuit(list.IsEmpty(), "failed after init"); VerifyOrQuit(list.GetHead() == nullptr, "failed after init"); VerifyOrQuit(list.Pop() == nullptr, "failed when empty"); - VerifyOrQuit(list.Find(a, prev) == ot::kErrorNotFound, "succeeded when empty"); + VerifyOrQuit(list.Find(a, prev) == kErrorNotFound, "succeeded when empty"); VerifyLinkedListContent(&list, nullptr); list.Push(a); VerifyOrQuit(!list.IsEmpty()); VerifyLinkedListContent(&list, &a, nullptr); - VerifyOrQuit(list.Find(b, prev) == ot::kErrorNotFound, "succeeded for a missing entry"); + VerifyOrQuit(list.Find(b, prev) == kErrorNotFound, "succeeded for a missing entry"); SuccessOrQuit(list.Add(b)); VerifyLinkedListContent(&list, &b, &a, nullptr); - VerifyOrQuit(list.Find(c, prev) == ot::kErrorNotFound, "succeeded for a missing entry"); + VerifyOrQuit(list.Find(c, prev) == kErrorNotFound, "succeeded for a missing entry"); list.Push(c); VerifyLinkedListContent(&list, &c, &b, &a, nullptr); @@ -138,14 +151,14 @@ void TestLinkedList(void) SuccessOrQuit(list.Add(e)); VerifyLinkedListContent(&list, &e, &d, &c, &b, &a, nullptr); - VerifyOrQuit(list.Add(a) == ot::kErrorAlready, "did not detect duplicate"); - VerifyOrQuit(list.Add(b) == ot::kErrorAlready, "did not detect duplicate"); - VerifyOrQuit(list.Add(d) == ot::kErrorAlready, "did not detect duplicate"); - VerifyOrQuit(list.Add(e) == ot::kErrorAlready, "did not detect duplicate"); + VerifyOrQuit(list.Add(a) == kErrorAlready, "did not detect duplicate"); + VerifyOrQuit(list.Add(b) == kErrorAlready, "did not detect duplicate"); + VerifyOrQuit(list.Add(d) == kErrorAlready, "did not detect duplicate"); + VerifyOrQuit(list.Add(e) == kErrorAlready, "did not detect duplicate"); VerifyOrQuit(list.Pop() == &e); VerifyLinkedListContent(&list, &d, &c, &b, &a, nullptr); - VerifyOrQuit(list.Find(e, prev) == ot::kErrorNotFound, "succeeded for a missing entry"); + VerifyOrQuit(list.Find(e, prev) == kErrorNotFound, "succeeded for a missing entry"); VerifyOrQuit(list.FindMatching(d.GetName(), prev) == &d); VerifyOrQuit(prev == nullptr); @@ -164,17 +177,17 @@ void TestLinkedList(void) SuccessOrQuit(list.Remove(c)); VerifyLinkedListContent(&list, &e, &d, &b, &a, nullptr); - VerifyOrQuit(list.Remove(c) == ot::kErrorNotFound); + VerifyOrQuit(list.Remove(c) == kErrorNotFound); VerifyLinkedListContent(&list, &e, &d, &b, &a, nullptr); - VerifyOrQuit(list.Find(c, prev) == ot::kErrorNotFound, "succeeded for a missing entry"); + VerifyOrQuit(list.Find(c, prev) == kErrorNotFound, "succeeded for a missing entry"); SuccessOrQuit(list.Remove(e)); VerifyLinkedListContent(&list, &d, &b, &a, nullptr); - VerifyOrQuit(list.Find(e, prev) == ot::kErrorNotFound, "succeeded for a missing entry"); + VerifyOrQuit(list.Find(e, prev) == kErrorNotFound, "succeeded for a missing entry"); SuccessOrQuit(list.Remove(a)); VerifyLinkedListContent(&list, &d, &b, nullptr); - VerifyOrQuit(list.Find(a, prev) == ot::kErrorNotFound, "succeeded for a missing entry"); + VerifyOrQuit(list.Find(a, prev) == kErrorNotFound, "succeeded for a missing entry"); list.Push(a); list.Push(c); @@ -222,16 +235,114 @@ void TestLinkedList(void) VerifyOrQuit(list.IsEmpty(), "failed after Clear()"); VerifyOrQuit(list.PopAfter(nullptr) == nullptr); VerifyLinkedListContent(&list, nullptr); - VerifyOrQuit(list.Find(a, prev) == ot::kErrorNotFound, "succeeded for a missing entry"); + VerifyOrQuit(list.Find(a, prev) == kErrorNotFound, "succeeded for a missing entry"); VerifyOrQuit(list.FindMatching(b.GetName(), prev) == nullptr, "succeeded when empty"); VerifyOrQuit(list.FindMatching(c.GetId(), prev) == nullptr, "succeeded when empty"); VerifyOrQuit(list.RemoveMatching(a.GetName()) == nullptr, "succeeded when empty"); - VerifyOrQuit(list.Remove(a) == ot::kErrorNotFound, "succeeded when empty"); + VerifyOrQuit(list.Remove(a) == kErrorNotFound, "succeeded when empty"); } +void TestOwningList(void) +{ + Entry a("a", 1), b("b", 2), c("c", 3), d("d", 4), e("e", 5); + OwningList list; + OwnedPtr ptr; + + printf("TestOwningList\n"); + + VerifyOrQuit(list.IsEmpty()); + VerifyOrQuit(list.GetHead() == nullptr); + VerifyOrQuit(list.Pop().IsNull()); + + list.Free(); + VerifyOrQuit(list.IsEmpty()); + VerifyOrQuit(list.GetHead() == nullptr); + VerifyOrQuit(list.Pop().IsNull()); + + // Clear() + + list.Push(a); + VerifyLinkedListContent(&list, &a, nullptr); + list.Free(); + VerifyOrQuit(list.IsEmpty()); + VerifyOrQuit(a.WasFreed()); + + // Test removing entry without taking back the ownership + + a.ResetTestFlags(); + list.Push(a); + list.Push(b); + list.Push(c); + list.Push(d); + list.Push(e); + VerifyLinkedListContent(&list, &e, &d, &c, &b, &a, nullptr); + + list.Pop(); + VerifyLinkedListContent(&list, &d, &c, &b, &a, nullptr); + VerifyOrQuit(e.WasFreed()); + + list.PopAfter(&c); + VerifyLinkedListContent(&list, &d, &c, &a, nullptr); + VerifyOrQuit(b.WasFreed()); + + list.RemoveMatching("c"); + VerifyLinkedListContent(&list, &d, &a, nullptr); + VerifyOrQuit(c.WasFreed()); + + list.Free(); + VerifyLinkedListContent(&list, nullptr); + VerifyOrQuit(d.WasFreed()); + VerifyOrQuit(a.WasFreed()); + + // Test removing entry and taking ownership + + a.ResetTestFlags(); + b.ResetTestFlags(); + c.ResetTestFlags(); + d.ResetTestFlags(); + e.ResetTestFlags(); + list.Push(a); + list.Push(b); + list.Push(c); + list.Push(d); + list.Push(e); + VerifyLinkedListContent(&list, &e, &d, &c, &b, &a, nullptr); + + ptr = list.PopAfter(&a); + VerifyLinkedListContent(&list, &e, &d, &c, &b, &a, nullptr); + VerifyOrQuit(ptr.IsNull()); + + ptr = list.PopAfter(&e); + VerifyLinkedListContent(&list, &e, &c, &b, &a, nullptr); + VerifyOrQuit(ptr.Get() == &d); + VerifyOrQuit(!d.WasFreed()); + + ptr = list.Pop(); + VerifyLinkedListContent(&list, &c, &b, &a, nullptr); + VerifyOrQuit(ptr.Get() == &e); + VerifyOrQuit(!e.WasFreed()); + VerifyOrQuit(d.WasFreed()); + + ptr = list.RemoveMatching(1); + VerifyLinkedListContent(&list, &c, &b, nullptr); + VerifyOrQuit(ptr.Get() == &a); + VerifyOrQuit(!a.WasFreed()); + VerifyOrQuit(e.WasFreed()); + + list.Clear(); + VerifyOrQuit(c.WasFreed()); + VerifyOrQuit(b.WasFreed()); + VerifyOrQuit(!a.WasFreed()); + a.Free(); + VerifyOrQuit(a.WasFreed()); +} + +} // namespace ot + int main(void) { - TestLinkedList(); + ot::TestLinkedList(); + ot::TestOwningList(); printf("All tests passed\n"); return 0; }