Skip to content

Commit 49ffa46

Browse files
committed
list-test: non-standard-type example
1 parent 73997e0 commit 49ffa46

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

tests/list-test.cc

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,152 @@ int test_list()
284284
debug_log("Done testing the list.");
285285
return 0;
286286
}
287+
288+
namespace
289+
{
290+
/**
291+
* \defgroup TypedLinkedObject Non-Standard-Layout Intrusive Lists
292+
*
293+
* We can also use C++'s inheritance system for non-standard-layout classes.
294+
* This requires a bit more work up front, but doesn't rely on `offsetof`.
295+
*
296+
* See `test_non_standard_layout_lists()`
297+
*
298+
* @{
299+
*/
300+
301+
/**
302+
* Linkage cell 1 for TypedLinkedObject
303+
*/
304+
struct BaseCell1 : public ds::linked_list::cell::PointerF<BaseCell1>
305+
{
306+
};
307+
static_assert(ds::linked_list::cell::HasCellOperationsReset<BaseCell1>);
308+
309+
/**
310+
* Linkage cell 2 for TypedLinkedObject
311+
*/
312+
struct BaseCell2 : public ds::linked_list::cell::PointerF<BaseCell2>
313+
{
314+
};
315+
static_assert(ds::linked_list::cell::HasCellOperationsReset<BaseCell2>);
316+
317+
struct DerivedObject : public BaseCell1, public BaseCell2
318+
{
319+
int data;
320+
321+
__always_inline struct BaseCell1 *toCell1()
322+
{
323+
return static_cast<BaseCell1 *>(this);
324+
}
325+
326+
__always_inline static struct DerivedObject *fromCell1(BaseCell1 *c)
327+
{
328+
return static_cast<DerivedObject *>(c);
329+
}
330+
331+
/**
332+
* We can even define TypedSentinel "containers" within the derived
333+
* object itself, which is handy if we want this type to maintain things
334+
* like "collections of all instances" or "all instances such that ...".
335+
*/
336+
static inline ds::linked_list::TypedSentinel<DerivedObject,
337+
BaseCell1,
338+
&DerivedObject::toCell1,
339+
DerivedObject::fromCell1>
340+
sentinel1 = {};
341+
342+
__always_inline struct BaseCell2 *toCell2()
343+
{
344+
return static_cast<BaseCell2 *>(this);
345+
}
346+
347+
__always_inline static struct DerivedObject *fromCell2(BaseCell2 *c)
348+
{
349+
return static_cast<DerivedObject *>(c);
350+
}
351+
352+
DerivedObject()
353+
{
354+
sentinel1.append(this);
355+
}
356+
357+
~DerivedObject()
358+
{
359+
ds::linked_list::unsafe_remove(toCell1());
360+
}
361+
};
362+
363+
/*
364+
* This is emphatically not a standard layout class, since we have two base
365+
* types with members as well as a member in the derived class itself.
366+
*/
367+
static_assert(!std::is_standard_layout_v<DerivedObject>);
368+
369+
/**
370+
* @}
371+
*/
372+
373+
} // namespace
374+
375+
ds::linked_list::TypedSentinel<DerivedObject,
376+
BaseCell2,
377+
&DerivedObject::toCell2,
378+
DerivedObject::fromCell2>
379+
typedSentinel2 = {};
380+
381+
int test_non_standard_layout_list()
382+
{
383+
debug_log("Testing the non-standard layout list implementation.");
384+
385+
// Number of elements we will add to the list in the test.
386+
static constexpr int NumberOfListElements = 10;
387+
388+
auto heapAtStart = heap_quota_remaining(MALLOC_CAPABILITY);
389+
390+
for (int i = 0; i < 10; i++)
391+
{
392+
auto *d = new DerivedObject();
393+
d->data = i;
394+
395+
// Thread half the objects onto one list; the constructor threads
396+
// everything onto the other
397+
if (i & 1)
398+
{
399+
// new runs constructors, so linkages are initialized and we need
400+
// not use the emplace variant.
401+
typedSentinel2.prepend(d);
402+
}
403+
}
404+
405+
// Free all the objects on the typedSentinel2 ring.
406+
typedSentinel2.search_safe([](DerivedObject *d) {
407+
ds::linked_list::unsafe_remove(d->toCell2());
408+
409+
// The destructor will manage unlinking us from DerivedObject::sentinel1
410+
delete d;
411+
412+
return false;
413+
});
414+
415+
DerivedObject::sentinel1.search_safe([](DerivedObject *d) {
416+
// Nodes still on the list were never part of the typedSentinel2 ring,
417+
// so they should have singleton BaseCell2 linkages.
418+
TEST(ds::linked_list::is_singleton(d->toCell2()),
419+
"Object has bad Cell2 linkages");
420+
421+
delete d;
422+
return false;
423+
});
424+
425+
// Check that we didn't leak anything in the process
426+
auto heapAtEnd = heap_quota_remaining(MALLOC_CAPABILITY);
427+
TEST(heapAtStart == heapAtEnd,
428+
"The list leaked {} bytes ({} vs. {})",
429+
heapAtEnd - heapAtStart,
430+
heapAtStart,
431+
heapAtEnd);
432+
433+
debug_log("Done testing the non-standard layout list.");
434+
return 0;
435+
}

tests/test-runner.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ int __cheri_compartment("test_runner") run_tests()
198198
run_timed("Futex", test_futex);
199199
run_timed("Locks", test_locks);
200200
run_timed("List", test_list);
201+
run_timed("List (non-standard-layout)", test_non_standard_layout_list);
201202
run_timed("Event groups", test_eventgroup);
202203
run_timed("Multiwaiter", test_multiwaiter);
203204
run_timed("Allocator", test_allocator);

tests/tests.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ __cheri_compartment("futex_test") int test_futex();
1313
__cheri_compartment("queue_test") int test_queue();
1414
__cheri_compartment("locks_test") int test_locks();
1515
__cheri_compartment("list_test") int test_list();
16+
__cheri_compartment("list_test") int test_non_standard_layout_list();
1617
__cheri_compartment("crash_recovery_test") int test_crash_recovery();
1718
__cheri_compartment("multiwaiter_test") int test_multiwaiter();
1819
__cheri_compartment("stack_test") int test_stack();

0 commit comments

Comments
 (0)