From 6b0674519a3d6eafbe2b95a85f4f8da14ac24f47 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Fri, 21 Aug 2026 16:16:43 -0400 Subject: [PATCH] Implement List::copy List inherited Object's copy, which memcpy's the instance. For a List that duplicates head, tail and count, so both instances address the same nodes: appending to either corrupts the other's tail, and releasing either frees nodes the survivor still walks. Callers wanting a copy therefore had to build one node by node, as Array's callers have never had to. Copy as Array does, and as List's own filteredList and mappedList already do: allocate a new List and append each element in order. Elements are not retained, since append does not retain them either, and the copy does not inherit destroy, so it borrows the elements rather than owning them. That is the only safe choice for a container that does not retain, since two owning lists over the same elements would double free. The test asserts the copy is independent: same elements in the same order, distinct nodes, appending to one leaves the other's count alone, and the original survives the copy's release. It fails on the inherited implementation, on the distinct-nodes assertion. --- Sources/Objectively/List.c | 21 +++++++++++++++++++ Tests/Objectively/List.c | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/Sources/Objectively/List.c b/Sources/Objectively/List.c index fb4d2f3..87743ef 100644 --- a/Sources/Objectively/List.c +++ b/Sources/Objectively/List.c @@ -32,6 +32,26 @@ #pragma mark - Object +/** + * @see Object::copy(const Object *) + * @remarks Elements are not retained, since List does not retain them on + * insertion either. The copy does not inherit `destroy`, and so borrows the + * elements rather than owning them, as `filteredList` and `mappedList` do. + */ +static Object *copy(const Object *self) { + + const List *this = (List *) self; + + List *copy = $(alloc(List), init); + assert(copy); + + for (ListNode *node = this->head; node; node = node->next) { + $(copy, append, node->element); + } + + return (Object *) copy; +} + /** * @see Object::dealloc(Object *) */ @@ -357,6 +377,7 @@ static void _sort(List *self, Comparator comparator) { */ static void initialize(Class *clazz) { + ((ObjectInterface *) clazz->interface)->copy = copy; ((ObjectInterface *) clazz->interface)->dealloc = dealloc; ((ListInterface *) clazz->interface)->append = append; diff --git a/Tests/Objectively/List.c b/Tests/Objectively/List.c index 1d1755a..e121ac2 100644 --- a/Tests/Objectively/List.c +++ b/Tests/Objectively/List.c @@ -25,6 +25,46 @@ #include "Objectively.h" +START_TEST(copyList) { + + int one = 1, two = 2, three = 3, four = 4; + + List *list = $(alloc(List), init); + + $(list, append, &one); + $(list, append, &two); + $(list, append, &three); + + List *copy = (List *) $((Object *) list, copy); + + ck_assert_ptr_ne(list, copy); + ck_assert_int_eq(3, copy->count); + + ListNode *a = list->head, *b = copy->head; + while (a && b) { + ck_assert_ptr_eq(a->element, b->element); + ck_assert_ptr_ne(a, b); + a = a->next; + b = b->next; + } + ck_assert_ptr_eq(NULL, a); + ck_assert_ptr_eq(NULL, b); + + $(copy, append, &four); + + ck_assert_int_eq(4, copy->count); + ck_assert_int_eq(3, list->count); + + release(copy); + + ck_assert_int_eq(3, list->count); + ck_assert_ptr_eq(&one, list->head->element); + ck_assert_ptr_eq(&three, list->tail->element); + + release(list); + +} END_TEST + START_TEST(appendElement) { int one = 1, two = 2, three = 3; @@ -384,6 +424,7 @@ int main(int argc, char **argv) { TCase *tcase = tcase_create("List"); tcase_add_test(tcase, appendElement); tcase_add_test(tcase, containsElement); + tcase_add_test(tcase, copyList); tcase_add_test(tcase, enumerate); tcase_add_test(tcase, filter); tcase_add_test(tcase, filteredList);