|  | // Copyright Microsoft and CHERIoT Contributors. | 
|  | // SPDX-License-Identifier: MIT | 
|  |  | 
|  | #define TEST_NAME "List" | 
|  | #include "tests.hh" | 
|  | #include <ds/linked_list.h> | 
|  |  | 
|  | using CHERI::Capability; | 
|  |  | 
|  | namespace | 
|  | { | 
|  | /** | 
|  | * Example class we want to link into a doubly linked list. | 
|  | * | 
|  | * The class contains a single integer for the purposes of the test. | 
|  | * | 
|  | * `ds::linked_list` is an intrusive list: we embed the list node into | 
|  | * the class we want to link.  There are various implementations of the | 
|  | * list nodes. Here we use the most simple one | 
|  | * (`ds::linked_list::cell::Pointer`) which relies on two pointers | 
|  | * `next` and `prev`. | 
|  | */ | 
|  | struct LinkedObject | 
|  | { | 
|  | using ObjectRing = ds::linked_list::cell::Pointer; | 
|  |  | 
|  | int data; | 
|  | /** | 
|  | * List node: links objects into the doubly-linked list. | 
|  | */ | 
|  | ObjectRing ring __attribute__((__cheri_no_subobject_bounds__)) = {}; | 
|  | /** | 
|  | * Container-of for the above field. This is used to retrieve the | 
|  | * corresponding object from a list element. | 
|  | */ | 
|  | __always_inline static struct LinkedObject *from_ring(ObjectRing *c) | 
|  | { | 
|  | return reinterpret_cast<struct LinkedObject *>( | 
|  | reinterpret_cast<uintptr_t>(c) - | 
|  | offsetof(struct LinkedObject, ring)); | 
|  | } | 
|  | }; | 
|  | } // namespace | 
|  |  | 
|  | /** | 
|  | * `ds::linked_list`s are circular, doubly-linked collections. While they can | 
|  | * stand on their own as rings of objects, it is sometimes convenient to create | 
|  | * a designated 'sentinel' node that participates in the collection without | 
|  | * being part of the collection: | 
|  | * | 
|  | * - a sentinel node provides pointers to the effective head and tail of the | 
|  | *   collection (the successor and predecessor of the sentinel, respectively) | 
|  | * | 
|  | * - a sentinel allows not having to special-case 'the collection is empty' in | 
|  | *   as many places as some other representations (that is, collections with | 
|  | *   sentinels need fewer NULL pointer checks) | 
|  | * | 
|  | * - a sentinel provides many handy functions to operate on the list | 
|  | * | 
|  | * Note: do not allocate the sentinel (or any list cell) on the stack, because | 
|  | * it would lead some list nodes to hold a pointer to a stack value, i.e., to | 
|  | * an invalid capability. This would manifest as a crash while using the list. | 
|  | */ | 
|  | ds::linked_list::Sentinel<LinkedObject::ObjectRing> objects = {}; | 
|  |  | 
|  | int test_list() | 
|  | { | 
|  | debug_log("Testing the list implementation."); | 
|  |  | 
|  | // Number of elements we will add to the list in the test. Must be | 
|  | // divisible by two. | 
|  | static constexpr int NumberOfListElements = 30; | 
|  |  | 
|  | auto heapAtStart = heap_quota_remaining(MALLOC_CAPABILITY); | 
|  |  | 
|  | TEST(objects.is_empty(), "Newly created list is not empty"); | 
|  |  | 
|  | // Create heap-allocated objects, and link them into the linked list. | 
|  | for (int i = 0; i < NumberOfListElements; i++) | 
|  | { | 
|  | Timeout       t{UnlimitedTimeout}; | 
|  | LinkedObject *o = static_cast<LinkedObject *>( | 
|  | heap_allocate(&t, MALLOC_CAPABILITY, sizeof(LinkedObject))); | 
|  | TEST(Capability{o}.is_valid(), "Cannot allocate linked object"); | 
|  |  | 
|  | // Use the object integer as an index. | 
|  | o->data = i; | 
|  | // The list node has not yet been initialized. | 
|  | o->ring.cell_reset(); | 
|  |  | 
|  | // Test that we can retrieve the object from the link node and | 
|  | // that this results in a capability which is identical to what | 
|  | // we got from the allocator. | 
|  | TEST(Capability{o} == Capability{LinkedObject::from_ring(&(o->ring))}, | 
|  | "The container of method does not return the right object"); | 
|  | TEST(Capability{LinkedObject::from_ring(&(o->ring))}.is_valid(), | 
|  | "Capability retrieved from `from_ring` is invalid"); | 
|  |  | 
|  | // Add the new object to the list through the sentinel node. | 
|  | objects.append(&(o->ring)); | 
|  | } | 
|  |  | 
|  | TEST(!objects.is_empty(), "The list is empty after adding objects"); | 
|  |  | 
|  | // Test that the sentinel can be used to retrieve the first and last | 
|  | // elements of the list as expected. | 
|  | TEST(LinkedObject::from_ring(objects.last())->data == | 
|  | NumberOfListElements - 1, | 
|  | "Last element of the list is incorrect, expected {}, got {}", | 
|  | NumberOfListElements - 1, | 
|  | LinkedObject::from_ring(objects.last())->data); | 
|  | TEST(objects.last()->cell_next() == &objects.sentinel, | 
|  | "Last element in not followed by the sentinel"); | 
|  | TEST(objects.last() == objects.sentinel.cell_prev(), | 
|  | "Sentinel is not preceeded by the last element"); | 
|  |  | 
|  | TEST(LinkedObject::from_ring(objects.first())->data == 0, | 
|  | "First element of the list is incorrect, expected {}, got {}", | 
|  | 0, | 
|  | LinkedObject::from_ring(objects.last())->data); | 
|  | TEST(objects.first()->cell_prev() == &objects.sentinel, | 
|  | "First element in not preceeded by the sentinel"); | 
|  | TEST(objects.first() == objects.sentinel.cell_next(), | 
|  | "Sentinel is not followed by the first element"); | 
|  |  | 
|  | // Test that we can go through the list by following `cell_next` | 
|  | // pointers as expected. | 
|  | int counter = 0; | 
|  | // While at it, retrieve a pointer to the middle element which we will | 
|  | // use to cleave the list later. | 
|  | LinkedObject::ObjectRing *middle = nullptr; | 
|  | // We reach the sentinel when we have gone through all elements of the | 
|  | // list. | 
|  | for (auto *cell = objects.first(); cell != &objects.sentinel; | 
|  | cell       = cell->cell_next()) | 
|  | { | 
|  | struct LinkedObject *o = LinkedObject::from_ring(cell); | 
|  | TEST( | 
|  | o->data == counter, | 
|  | "Ordering of elements in the list is incorrect, expected {}, got {}", | 
|  | o->data, | 
|  | counter); | 
|  | if (counter == NumberOfListElements / 2) | 
|  | { | 
|  | middle = cell; | 
|  | } | 
|  | counter++; | 
|  | } | 
|  |  | 
|  | TEST(middle != nullptr, "Could not find middle element of the list"); | 
|  |  | 
|  | // Cut the list in the middle. `middle` is now a handle to the (valid) | 
|  | // collection of objects [middle, last] that have become detached from | 
|  | // the sentinel. | 
|  | ds::linked_list::remove(middle, objects.last()); | 
|  |  | 
|  | // This should leave us with a list of size `NumberOfListElements / 2`. | 
|  | counter = 0; | 
|  | for (auto *cell = objects.first(); cell != &objects.sentinel; | 
|  | cell       = cell->cell_next()) | 
|  | { | 
|  | counter++; | 
|  | } | 
|  | TEST(counter == NumberOfListElements / 2, | 
|  | "Cleaving didn't leave a list with the right number of elements"); | 
|  |  | 
|  | // Now remove (and free) a single element from the list. | 
|  | TEST(LinkedObject::from_ring(objects.first())->data == 0, | 
|  | "First element of the list is incorrect, expected {}, got {}", | 
|  | 0, | 
|  | LinkedObject::from_ring(objects.first())->data); | 
|  | // We must keep a reference to the removed object to free it, as | 
|  | // `remove` returns a pointer to the residual list (return value which | 
|  | // we do not use here), not to the removed element. | 
|  | LinkedObject::ObjectRing *removedCell = objects.first(); | 
|  | ds::linked_list::remove(objects.first()); | 
|  | heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(removedCell)); | 
|  | TEST(LinkedObject::from_ring(objects.first())->data == 1, | 
|  | "First element of the list is incorrect after removing the first " | 
|  | "element, expected {}, got {}", | 
|  | 1, | 
|  | LinkedObject::from_ring(objects.first())->data); | 
|  | TEST(objects.first()->cell_prev() == &objects.sentinel, | 
|  | "First element in not preceeded by the sentinel after removing the " | 
|  | "first object"); | 
|  |  | 
|  | // We are done with the list, free it. | 
|  | counter                        = 0; | 
|  | LinkedObject::ObjectRing *cell = objects.first(); | 
|  | while (cell != &objects.sentinel) | 
|  | { | 
|  | struct LinkedObject *o = LinkedObject::from_ring(cell); | 
|  | cell                   = cell->cell_next(); | 
|  | heap_free(MALLOC_CAPABILITY, o); | 
|  | counter++; | 
|  | } | 
|  |  | 
|  | TEST(counter == (NumberOfListElements / 2) - 1, | 
|  | "Incorrect number of elements freed, expected {}, got {}", | 
|  | (NumberOfListElements / 2) - 1, | 
|  | counter); | 
|  |  | 
|  | // Now that the list is freed, reset the sentinel. | 
|  | objects.reset(); | 
|  |  | 
|  | TEST(objects.is_empty(), "Reset-ed list is not empty"); | 
|  |  | 
|  | // We must also free the span of the list which we removed earlier. | 
|  | // This time use the `::search` method to go through the collection. | 
|  | ds::linked_list::search( | 
|  | middle, [&counter](LinkedObject::ObjectRing *&cell) { | 
|  | // `unsafe_remove` does not update the node pointers of the | 
|  | // removed cell. This is great here because we will free the | 
|  | // object anyways. We could also use `remove` here. | 
|  | auto l = ds::linked_list::unsafe_remove(cell); | 
|  | heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(cell)); | 
|  | // `l` is the predecessor of `cell` in the residual ring, so | 
|  | // this does exactly what we want when `::search` iterates. | 
|  | cell = l; | 
|  | counter++; | 
|  | return false; | 
|  | }); | 
|  | // `::search` does not visit the element passed (`middle`) | 
|  | heap_free(MALLOC_CAPABILITY, LinkedObject::from_ring(middle)); | 
|  | counter++; | 
|  |  | 
|  | TEST(counter == NumberOfListElements - 1, | 
|  | "Incorrect number of elements freed, expected {}, got {}", | 
|  | NumberOfListElements - 1, | 
|  | counter); | 
|  |  | 
|  | // Check that we didn't leak anything in the process | 
|  | auto heapAtEnd = heap_quota_remaining(MALLOC_CAPABILITY); | 
|  | TEST(heapAtStart == heapAtEnd, | 
|  | "The list leaked {} bytes ({} vs. {})", | 
|  | heapAtEnd - heapAtStart, | 
|  | heapAtStart, | 
|  | heapAtEnd); | 
|  |  | 
|  | debug_log("Done testing the list."); | 
|  | return 0; | 
|  | } |