Skip to content

slab/multislab/pool: hardening and limits #6

Closed

Description

@aotodev
# Where Problem Fix
1 multislab::deallocate Release build: a double free still decrements used, so the slab is freed with live blocks (confirmed). Foreign pointer: find_owner returns null, then dereferenced. cfiber 1fd1edf rejects both. Decide: reject or documented UB. Rejecting means returning on null owner, misaligned offset or clear bit, before touching used.
2 multislab::grow (:492) A throwing slab allocation leaks the node header (confirmed). Scope guard, or a single allocation for header + slab.
3 slab::slab (:118) block_count_ <= MaxBlocks assert rejects more memory than required_memory and accepts less. Narrows to uint32_t before checking. min(memory_size / BlockSize, MaxBlocks) in size_t, assert >= 1, fix the doc.
4 pool::erase (:389) erase(end()) dereferences null; only size_ > 0 is asserted. assert(it != end()).
5 slab::owns, ptr_to_index, deallocate constexpr with reinterpret_cast: well-formed (P2448), never constant-evaluable. Drop constexpr. (std::byte* subtraction under if consteval needs the C++26 void* cast, P2738.)
6 geometry BlockSize * MaxBlocks wraps silently; words_for overflows near UINT32_MAX; bitmap_find_free returns an int32_t index. requires on no overflow and MaxBlocks <= INT32_MAX, or use capacity as the not-found value.
7 slab_limit (pool.cppm:87) Implicit: pool<int> p(4) is a cap, p{4} an element. slab_limit{-1} is UINT32_MAX (confirmed). Decide: explicit plus unsigned_integral or a non-negative assert. The comment at :84 is wrong: explicit still accepts slab_limit{4}.

Contract gate

Library checks are plain assert, so a consumer's contract evaluation semantic does not reach them. datagram_engine passes -fcontract-evaluation-semantic=enforce|ignore directory-wide, libmem sources included, and libmem ignores it.

  • Add a header, included in each .cppm global module fragment where <cassert> is today:
    #if defined(__cpp_contracts)
    #define LIBMEM_ASSERT(...) contract_assert(__VA_ARGS__)
    #else
    #define LIBMEM_ASSERT(...) assert(__VA_ARGS__)
    #endif
  • Replace every runtime assert in src/libmem/ with it, not only those in this issue. Checks written for 1, 3 and 4 use it too.
  • pre/post wait for Clang. Adding them later needs no standard bump.
  • GCC 15 has no __cpp_contracts and gets assert, so the floor stays.

Tests

Death tests for 1 (debug) and 4, run on both paths. In a GCC leg, static_assert(__cpp_contracts) when __GNUC__ >= 16, so a toolchain change cannot silently drop libmem back to assert. A throwing resource for 2 under LSan. Oversized and undersized memory for 3. A compile-fail check or static_assert for 7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions