xo-gc stack: coverage improvement + related tidying

This commit is contained in:
Roland Conybeare 2026-05-11 09:27:24 -04:00
commit 42e09dd21e
8 changed files with 130 additions and 47 deletions

View file

@ -35,6 +35,8 @@ namespace xo {
// builtin methods // builtin methods
typeseq _typeseq() const noexcept override { return s_typeseq; } typeseq _typeseq() const noexcept override { return s_typeseq; }
// LCOV_EXCL_START
void _drop(Opaque) const noexcept override { _fatal(); } void _drop(Opaque) const noexcept override { _fatal(); }
// const methods // const methods
@ -58,9 +60,6 @@ namespace xo {
[[noreturn]] value_type alloc(Opaque, typeseq, std::size_t) const override { _fatal(); } [[noreturn]] value_type alloc(Opaque, typeseq, std::size_t) const override { _fatal(); }
[[noreturn]] value_type super_alloc(Opaque, typeseq, std::size_t) const override { _fatal(); } [[noreturn]] value_type super_alloc(Opaque, typeseq, std::size_t) const override { _fatal(); }
[[noreturn]] value_type sub_alloc(Opaque, std::size_t, bool) const override { _fatal(); } [[noreturn]] value_type sub_alloc(Opaque, std::size_t, bool) const override { _fatal(); }
#ifdef OBSOLETE
[[noreturn]] value_type alloc_copy(Opaque, value_type) const override { _fatal(); }
#endif
[[noreturn]] void clear(Opaque) const override { _fatal(); } [[noreturn]] void clear(Opaque) const override { _fatal(); }
[[noreturn]] void barrier_assign_aux(Opaque, [[noreturn]] void barrier_assign_aux(Opaque,
void *, void *,
@ -69,6 +68,7 @@ namespace xo {
private: private:
[[noreturn]] static void _fatal(); [[noreturn]] static void _fatal();
// LCOV_EXCL_STOP
public: public:
static typeseq s_typeseq; static typeseq s_typeseq;

View file

@ -78,7 +78,7 @@ namespace xo {
void * parent, void * parent,
AGCObject * lhs_iface, void ** lhs_data, AGCObject * lhs_iface, void ** lhs_data,
AGCObject * rhs_iface, void * rhs_data); AGCObject * rhs_iface, void * rhs_data);
static void destruct_data(DArena &); //static void destruct_data(DArena &);
}; };
// template <> // template <>

View file

@ -52,7 +52,7 @@ public:
void * alloc_copy_for(const T * src) noexcept { void * alloc_copy_for(const T * src) noexcept {
return O::iface()->alloc_copy(O::data(), (std::byte *)const_cast<T *>(src)); return O::iface()->alloc_copy(O::data(), (std::byte *)const_cast<T *>(src));
} }
/** convenience template for move-constructible T (this is common) **/ /** convenience template for move-constructible T (this is common) **/
template <typename T> template <typename T>
T * std_move_for(T * src) noexcept { T * std_move_for(T * src) noexcept {
@ -62,28 +62,28 @@ public:
} }
return nullptr; return nullptr;
} }
/** forward faceted object pointer in place. Defined in GCObject.hpp to avoid #include cycle **/ /** forward faceted object pointer in place. Defined in GCObject.hpp to avoid #include cycle **/
template <typename DRepr> template <typename DRepr>
void forward_inplace(obj<AGCObject,DRepr> * p_obj); void forward_inplace(obj<AGCObject,DRepr> * p_obj);
/** another convenience template for forwarding. /** another convenience template for forwarding.
* Defined in RGCObject.hpp to avoid #include cycle. * Defined in RGCObject.hpp to avoid #include cycle.
**/ **/
template <typename DRepr> template <typename DRepr>
void forward_inplace(DRepr ** pp_repr); void forward_inplace(DRepr ** pp_repr);
/** convenience template where pointer requires pivot **/ /** convenience template where pointer requires pivot **/
template <typename AFacet, typename DRepr> template <typename AFacet, typename DRepr>
requires (!std::is_same_v<AFacet, AGCObject>) requires (!std::is_same_v<AFacet, AGCObject>)
void forward_pivot_inplace(obj<AFacet,DRepr> * p_obj); void forward_pivot_inplace(obj<AFacet,DRepr> * p_obj);
/** add root @p p_root **/ /** add root @p p_root **/
template<typename DRepr> template<typename DRepr>
void add_gc_root(obj<AGCObject, DRepr> * p_root) { void add_gc_root(obj<AGCObject, DRepr> * p_root) {
O::iface()->add_gc_root_poly(O::data(), (obj<AGCObject> *)p_root); O::iface()->add_gc_root_poly(O::data(), (obj<AGCObject> *)p_root);
} }
/** remove root @p p_root **/ /** remove root @p p_root **/
template <typename DRepr> template <typename DRepr>
void remove_gc_root(obj<AGCObject, DRepr> * p_root) { void remove_gc_root(obj<AGCObject, DRepr> * p_root) {
@ -91,6 +91,7 @@ public:
} }
// builtin methods // builtin methods
bool _has_null_vptr() const noexcept { return O::iface()->_has_null_vptr(); }
typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); } typeseq _typeseq() const noexcept { return O::iface()->_typeseq(); }
void _drop() const noexcept { O::iface()->_drop(O::data()); } void _drop() const noexcept { O::iface()->_drop(O::data()); }

View file

@ -14,6 +14,7 @@ namespace xo {
namespace mm { namespace mm {
// LCOV_EXCL_START
void void
IAllocator_Any::_fatal() IAllocator_Any::_fatal()
{ {
@ -29,6 +30,7 @@ namespace xo {
std::terminate(); std::terminate();
} }
// LCOV_EXCL_STOP
typeseq typeseq
IAllocator_Any::s_typeseq = typeseq::id<DVariantPlaceholder>(); IAllocator_Any::s_typeseq = typeseq::id<DVariantPlaceholder>();

View file

@ -173,11 +173,13 @@ namespace xo {
*lhs_data = rhs_data; *lhs_data = rhs_data;
} }
#ifdef OBSOLETE
void void
IAllocator_DArena::destruct_data(DArena & s) IAllocator_DArena::destruct_data(DArena & s)
{ {
s.~DArena(); s.~DArena();
} }
#endif
} /*namespace mm*/ } /*namespace mm*/
} /*namespace xo*/ } /*namespace xo*/

View file

@ -6,6 +6,7 @@ set(UTEST_SRCS
alloc2_utest_main.cpp alloc2_utest_main.cpp
objectmodel.test.cpp objectmodel.test.cpp
arena.test.cpp arena.test.cpp
IAllocator_Any.test.cpp
DArenaIterator.test.cpp DArenaIterator.test.cpp
# Collector.test.cpp # Collector.test.cpp
# DX1CollectorIterator.test.cpp # DX1CollectorIterator.test.cpp

View file

@ -0,0 +1,29 @@
/** @file IAllocator_Any.test.cpp
*
* @author Roland Conybeare, May 2026
**/
#include <xo/alloc2/Allocator.hpp>
#include <catch2/catch.hpp>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
namespace xo {
using xo::mm::AAllocator;
namespace ut {
TEST_CASE("IAllocator_Any", "[alloc2][death]")
{
// null allocator
obj<AAllocator> alloc_any;
// NOTE: tried using a fork() strategy to verify termination,
// but child process doesn't get measured by gcov
}
} /*namespace ut*/
} /*namespace xo*/
/* end IAllocator_Any.test.cpp */

View file

@ -97,6 +97,8 @@ namespace xo {
//obj<AAllocator, DArena> a1o{&arena}; //obj<AAllocator, DArena> a1o{&arena};
auto a1o = with_facet<AAllocator>::mkobj(&arena); auto a1o = with_facet<AAllocator>::mkobj(&arena);
REQUIRE(!a1o._has_null_vptr());
REQUIRE(a1o); REQUIRE(a1o);
REQUIRE(a1o.iface() != nullptr); REQUIRE(a1o.iface() != nullptr);
REQUIRE(a1o.data() != nullptr); REQUIRE(a1o.data() != nullptr);
@ -110,6 +112,12 @@ namespace xo {
REQUIRE(a1o.size() == 0); REQUIRE(a1o.size() == 0);
REQUIRE(a1o.committed() == 0); REQUIRE(a1o.committed() == 0);
REQUIRE(a1o.allocated() == 0); REQUIRE(a1o.allocated() == 0);
a1o._drop();
{
REQUIRE(a1o.allocated() == 0);
REQUIRE(a1o.committed() == 0);
}
} }
TEST_CASE("allocator-expand-1", "[alloc2][AAllocator]") TEST_CASE("allocator-expand-1", "[alloc2][AAllocator]")
@ -122,6 +130,8 @@ namespace xo {
//obj<AAllocator, DArena> a1o{&arena}; //obj<AAllocator, DArena> a1o{&arena};
auto a1o = with_facet<AAllocator>::mkobj(&arena); auto a1o = with_facet<AAllocator>::mkobj(&arena);
REQUIRE(!a1o._has_null_vptr());
REQUIRE(a1o.available() == 0); REQUIRE(a1o.available() == 0);
REQUIRE(a1o.allocated() == 0); REQUIRE(a1o.allocated() == 0);
@ -141,6 +151,11 @@ namespace xo {
REQUIRE(a1o.available() == a1o.committed()); REQUIRE(a1o.available() == a1o.committed());
REQUIRE(a1o.allocated() == 0); REQUIRE(a1o.allocated() == 0);
a1o._drop();
{
REQUIRE(a1o.allocated() == 0);
REQUIRE(a1o.committed() == 0);
}
} }
TEST_CASE("allocator-alloc-1", "[alloc2][AAllocator]") TEST_CASE("allocator-alloc-1", "[alloc2][AAllocator]")
@ -152,6 +167,8 @@ namespace xo {
DArena arena = DArena::map(cfg); DArena arena = DArena::map(cfg);
auto a1o = with_facet<AAllocator>::mkobj(&arena); auto a1o = with_facet<AAllocator>::mkobj(&arena);
REQUIRE(!a1o._has_null_vptr());
REQUIRE(a1o.reserved() >= cfg.size_); REQUIRE(a1o.reserved() >= cfg.size_);
REQUIRE(a1o.committed() == 0); REQUIRE(a1o.committed() == 0);
REQUIRE(a1o.available() == 0); REQUIRE(a1o.available() == 0);
@ -180,6 +197,12 @@ namespace xo {
REQUIRE(a1o.allocated() <= a1o.committed()); REQUIRE(a1o.allocated() <= a1o.committed());
REQUIRE(a1o.allocated() + a1o.available() == a1o.committed()); REQUIRE(a1o.allocated() + a1o.available() == a1o.committed());
REQUIRE(a1o.committed() <= a1o.reserved()); REQUIRE(a1o.committed() <= a1o.reserved());
a1o._drop();
{
REQUIRE(a1o.allocated() == 0);
REQUIRE(a1o.committed() == 0);
}
} }
TEST_CASE("allocator-alloc-2", "[alloc2][Allocator]") TEST_CASE("allocator-alloc-2", "[alloc2][Allocator]")
@ -202,6 +225,8 @@ namespace xo {
//obj<AAllocator, DArena> a1o{&arena}; //obj<AAllocator, DArena> a1o{&arena};
auto a1o = with_facet<AAllocator>::mkobj(&arena); auto a1o = with_facet<AAllocator>::mkobj(&arena);
REQUIRE(!a1o._has_null_vptr());
REQUIRE(a1o.reserved() >= cfg.size_); REQUIRE(a1o.reserved() >= cfg.size_);
REQUIRE(a1o.committed() == 0); REQUIRE(a1o.committed() == 0);
REQUIRE(a1o.available() == 0); REQUIRE(a1o.available() == 0);
@ -244,19 +269,26 @@ namespace xo {
} }
a1o.clear(); a1o.clear();
{
// allocated size got reset
REQUIRE(a1o.allocated() == 0);
// committed size unchanged
REQUIRE(a1o.committed() == committed0_z);
REQUIRE(a1o.last_error().error_ == error::ok);
REQUIRE(a1o.last_error().error_seq_ == 0);
// allocated size got reset // allocator no longer contains m0 (now points to unallocated but committed memory
REQUIRE(a1o.allocated() == 0); // (not exposed via AAllocator!
// committed size unchanged // REQUIRE(a1o.contains_allocated(m0) == false);
REQUIRE(a1o.committed() == committed0_z);
REQUIRE(a1o.last_error().error_ == error::ok);
REQUIRE(a1o.last_error().error_seq_ == 0);
// allocator no longer contains m0 (now points to unallocated but committed memory REQUIRE(a1o.contains(m0));
// (not exposed via AAllocator! }
// REQUIRE(a1o.contains_allocated(m0) == false);
REQUIRE(a1o.contains(m0)); a1o._drop();
{
REQUIRE(a1o.allocated() == 0);
REQUIRE(a1o.committed() == 0);
}
} }
TEST_CASE("allocator-alloc-3", "[alloc2][Allocator]") TEST_CASE("allocator-alloc-3", "[alloc2][Allocator]")
@ -279,6 +311,8 @@ namespace xo {
//obj<AAllocator, DArena> a1o{&arena}; //obj<AAllocator, DArena> a1o{&arena};
auto a1o = with_facet<AAllocator>::mkobj(&arena); auto a1o = with_facet<AAllocator>::mkobj(&arena);
REQUIRE(!a1o._has_null_vptr());
REQUIRE(a1o.reserved() >= cfg.size_); REQUIRE(a1o.reserved() >= cfg.size_);
REQUIRE(a1o.committed() == 0); REQUIRE(a1o.committed() == 0);
REQUIRE(a1o.available() == 0); REQUIRE(a1o.available() == 0);
@ -301,23 +335,30 @@ namespace xo {
header_type* header = (header_type*)(m0 - sizeof(header_type)); header_type* header = (header_type*)(m0 - sizeof(header_type));
size_t pad = padding::with_padding(z0) - z0; size_t pad = padding::with_padding(z0) - z0;
byte * guard1 = m0 + z0 + pad; byte * guard1 = m0 + z0 + pad;
{
REQUIRE(a1o.contains(guard0));
REQUIRE(a1o.contains(header));
REQUIRE(cfg.header_.size(*header) == padding::with_padding(z0));
//REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0));
REQUIRE(a1o.contains(guard0)); REQUIRE(a1o.last_error().error_ == error::ok);
REQUIRE(a1o.contains(header)); REQUIRE(a1o.last_error().error_seq_ == 0);
REQUIRE(cfg.header_.size(*header) == padding::with_padding(z0));
//REQUIRE(((*header) & cfg.header_size_mask_) == padding::with_padding(z0));
REQUIRE(a1o.last_error().error_ == error::ok); REQUIRE(a1o.allocated() == (cfg.header_.guard_z_
REQUIRE(a1o.last_error().error_seq_ == 0); + sizeof(header_type)
+ z0
+ pad
+ cfg.header_.guard_z_));
REQUIRE(a1o.allocated() <= a1o.committed());
REQUIRE(a1o.allocated() + a1o.available() == a1o.committed());
REQUIRE(a1o.committed() <= a1o.reserved());
}
REQUIRE(a1o.allocated() == (cfg.header_.guard_z_ a1o._drop();
+ sizeof(header_type) {
+ z0 REQUIRE(a1o.allocated() == 0);
+ pad REQUIRE(a1o.committed() == 0);
+ cfg.header_.guard_z_)); }
REQUIRE(a1o.allocated() <= a1o.committed());
REQUIRE(a1o.allocated() + a1o.available() == a1o.committed());
REQUIRE(a1o.committed() <= a1o.reserved());
} }
TEST_CASE("allocator-fail-1", "[alloc2][AAllocator]") TEST_CASE("allocator-fail-1", "[alloc2][AAllocator]")
@ -332,6 +373,8 @@ namespace xo {
REQUIRE(cfg.size_ <= cfg.hugepage_z_); REQUIRE(cfg.size_ <= cfg.hugepage_z_);
REQUIRE(!a1o._has_null_vptr());
REQUIRE(a1o.reserved() >= cfg.size_); REQUIRE(a1o.reserved() >= cfg.size_);
REQUIRE(a1o.committed() == 0); REQUIRE(a1o.committed() == 0);
REQUIRE(a1o.available() == 0); REQUIRE(a1o.available() == 0);
@ -339,17 +382,22 @@ namespace xo {
size_t z0 = cfg.hugepage_z_ + 1; size_t z0 = cfg.hugepage_z_ + 1;
byte * m0 = a1o.alloc(typeseq::sentinel(), z0); byte * m0 = a1o.alloc(typeseq::sentinel(), z0);
REQUIRE(!m0);
AllocError err = a1o.last_error(); AllocError err = a1o.last_error();
{
REQUIRE(!m0);
REQUIRE(err.error_ == error::reserve_exhausted);
REQUIRE(err.error_seq_ == 1);
REQUIRE(err.request_z_ >= z0);
REQUIRE(err.request_z_ < z0 + padding::c_alloc_alignment);
REQUIRE(err.committed_z_ == 0);
REQUIRE(err.reserved_z_ == arena.reserved());
}
REQUIRE(err.error_ == error::reserve_exhausted); a1o._drop();
REQUIRE(err.error_seq_ == 1); {
REQUIRE(err.request_z_ >= z0); REQUIRE(a1o.allocated() == 0);
REQUIRE(err.request_z_ < z0 + padding::c_alloc_alignment); REQUIRE(a1o.committed() == 0);
REQUIRE(err.committed_z_ == 0); }
REQUIRE(err.reserved_z_ == arena.reserved());
} }
} /*namespace ut*/ } /*namespace ut*/
} /*namespace xo*/ } /*namespace xo*/