xo-arena: + DArena::unmap() + defeat DSElim
All checks were successful
CI / smoke-test (push) Successful in 1s

This commit is contained in:
Roland Conybeare 2026-05-26 02:51:53 +00:00
commit 17dbb78a20
2 changed files with 40 additions and 10 deletions

View file

@ -241,6 +241,22 @@ namespace xo {
**/ **/
void clear() noexcept; void clear() noexcept;
/** release backing memory and reset bookkeeping to the empty state.
*
* Unmaps [@ref lo_, @ref hi_) (if mapped) and zeroes the bookkeeping
* fields {lo_, committed_z_, last_header_, free_, limit_, hi_,
* error_count_, last_error_}. @ref config_ (and page_z_/arena_align_z_)
* are left intact.
*
* Idempotent: a second call is a no-op (lo_ has been cleared).
* Invoked by ~DArena(); also safe to call on a live arena.
*
* Note: application code must not rely on observing the zeroed state
* after destruction (that would be UB) -- the zeroing is defensive,
* to avoid leaving dangling pointers behind.
**/
void unmap() noexcept;
/** swap contents (including configuration) with another arena **/ /** swap contents (including configuration) with another arena **/
void swap(DArena & other) noexcept; void swap(DArena & other) noexcept;

View file

@ -13,6 +13,7 @@
#include <xo/indentlog/print/tag.hpp> #include <xo/indentlog/print/tag.hpp>
#include <cassert> #include <cassert>
#include <exception> #include <exception>
#include <new> // for std::launder()
#include <sys/mman.h> // for ::munmap() #include <sys/mman.h> // for ::munmap()
#include <unistd.h> // for ::getpagesize() #include <unistd.h> // for ::getpagesize()
#include <string.h> // for ::memset() #include <string.h> // for ::memset()
@ -142,7 +143,8 @@ namespace xo {
return *this; return *this;
} }
DArena::~DArena() void
DArena::unmap() noexcept
{ {
if (lo_) { if (lo_) {
//log && log("unmap [lo,hi)", //log && log("unmap [lo,hi)",
@ -153,15 +155,27 @@ namespace xo {
::munmap(lo_, hi_ - lo_); ::munmap(lo_, hi_ - lo_);
} }
// hygiene /* Mandatory hygiene: zero the bookkeeping tail so no dangling
lo_ = nullptr; * pointers survive (e.g. after ~DArena() runs the dtor of a
committed_z_ = 0; * stack-owned arena). config_, page_z_, arena_align_z_ are
// checkpoint_ = nullptr; * preserved -- only {lo_ .. last_error_} get cleared.
free_ = nullptr; *
limit_ = nullptr; * The memset goes through a std::launder'd self pointer so the
hi_ = nullptr; * compiler cannot prove it writes to the dying object and elide
error_count_ = 0; * the stores as dead (gcc>=15 does that with a plain member
last_error_ = AllocError(); * assignment / un-laundered memset at -O1). All cleared fields
* are trivially-copyable, so byte-zeroing them is well-defined.
*/
DArena * self = std::launder(this);
byte * tail = reinterpret_cast<byte *>(&self->lo_);
byte * end = reinterpret_cast<byte *>(self) + sizeof(DArena);
::memset(tail, 0, end - tail);
}
DArena::~DArena()
{
this->unmap();
} }
auto auto