You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
multislab: iteration and free-space tracking share one list #4
next/prev carry both iteration order and the active/full split. The iterator walks active_, then jumps to the full_ head captured at construction (second_list_, multislab.cppm:272). Any relink breaks live iterators:
Erase from a full slab: move_to_active prepends it. An iterator inside follows next into active_, revisits nodes and never reaches the rest of full_.
Insert that fills a slab: move_to_full. An iterator inside follows next into the old full_, then walks second_list_ again.
Slab held as second_list_ emptied and freed: use-after-free.
Separately, allocate_raw (:403) moves a full head to full_ and grows unconditionally. deallocate prepends to active_, so slabs with free slots can sit below a full head: over-growth without a cap, spurious failure with one. cfiber fixed this in 1fd1edf; not ported.
Repro (confirmed, ASan)
libmem::pool<int, 4> p; // insert 0..11: active [C], full [B, A]for (auto it = p.begin(); it != p.end();)
it = (*it % 2 == 0) ? p.erase(it) : std::next(it);
// visits 10 of 12, two evens remain
Insert while iterating (11 elements, insert on visiting 8 and 9): 8 duplicate visits.
libmem::pool<int, 4> p{libmem::threshold_policy{.max_empty_reserve = 0}};
// insert 0..11, it = p.begin() (in C), erase 4..7 (B freed)for (; it != p.end(); ++it) {} // heap-use-after-free
libmem::pool<int, 4> p{libmem::slab_limit{2}};
// insert 0..4, erase 0, insert 100, insert 101 -> end(); B has 3 free// uncapped: 3 slabs where 2 suffice
Fix
Permanent iteration links per node (iter_next/iter_prev), changed only on grow and free. The iterator holds node + slab position; drop second_list_ and on_second_list_.
next/prev track free space only.
Move a node to full_ eagerly, when used == BlocksPerSlab after an allocation. Invariant: every node on active_ has a free slot, so allocate_raw needs no retry. (Alternative: loop over active_ before growing, as cfiber does.)
on_full becomes used == BlocksPerSlab: keep or derive.
Drop the empty_count_ > 0 guard (:429), the count is exact.
Tests
The four repros. Across erase during iteration, every element live for the whole traversal is visited exactly once. Across insert, pre-existing elements exactly once, new ones at most once.
Activity