Raised by independent review of the #51 fix. Filed rather than fixed there, because adding a CI leg is an infrastructure decision beyond that issue's scope.
The gap
#51 was a one-past-the-end reference formation with no load or store. That class is invisible to:
- ordinary builds — the whole suite passed on trunk with the defect present;
agent-check.yml and R-CMD-check.yml — no hardening flags;
- Valgrind (
memcheck.yml) — no access occurs, so nothing to flag;
- AddressSanitizer's own instrumentation — it watches accesses, not address arithmetic, and
co.kids.reserve(n_node) left the address inside the live allocation anyway.
The only thing that caught it was libstdc++'s own precondition check inside operator[], compiled in by -D_GLIBCXX_ASSERTIONS. That is also, incidentally, why gcc-ASAN aborted: the r-hub container enables the hardened library, and the job stopped on the assertion, not on a sanitizer report. Note that means the flag comes from the container image, not from ASan.yml — grepping .github/workflows/ for _GLIBCXX_ASSERTIONS finds nothing and is misleading.
Why this recurs
src/ts_hsj.cpp alone carries the same CSR-indexing idiom three times (:190, :257, :309). Two had the nk == 0 guard; the third did not, and nothing mechanical noticed for as long as it took a human to run a hardened build by hand. #58 is a second unguarded bound in the same subsystem with the same assertion signature. The project's memory notes already record container-index OOB as this codebase's dominant bug class.
Suggested shape
A leg in agent-check.yml that installs with PKG_CPPFLAGS=-D_GLIBCXX_ASSERTIONS and runs testthat. Cheap relative to ASan: no sanitizer runtime, no container, and the build is an ordinary -O2 one. On Windows locally it reproduces this class in seconds (recipe now in .AGENTS/memory/feature-inapplicable.md), so a Linux leg should be undemanding.
Worth deciding: whether it blocks or is advisory, and whether it belongs in agent-check.yml or as its own workflow. gcc-ASAN already gates pull_request on src/**, so this would be about speed of feedback and about not depending on a container whose flags are implicit.
Raised by independent review of the #51 fix. Filed rather than fixed there, because adding a CI leg is an infrastructure decision beyond that issue's scope.
The gap
#51 was a one-past-the-end reference formation with no load or store. That class is invisible to:
agent-check.ymlandR-CMD-check.yml— no hardening flags;memcheck.yml) — no access occurs, so nothing to flag;co.kids.reserve(n_node)left the address inside the live allocation anyway.The only thing that caught it was libstdc++'s own precondition check inside
operator[], compiled in by-D_GLIBCXX_ASSERTIONS. That is also, incidentally, whygcc-ASANaborted: the r-hub container enables the hardened library, and the job stopped on the assertion, not on a sanitizer report. Note that means the flag comes from the container image, not fromASan.yml— grepping.github/workflows/for_GLIBCXX_ASSERTIONSfinds nothing and is misleading.Why this recurs
src/ts_hsj.cppalone carries the same CSR-indexing idiom three times (:190,:257,:309). Two had thenk == 0guard; the third did not, and nothing mechanical noticed for as long as it took a human to run a hardened build by hand. #58 is a second unguarded bound in the same subsystem with the same assertion signature. The project's memory notes already record container-index OOB as this codebase's dominant bug class.Suggested shape
A leg in
agent-check.ymlthat installs withPKG_CPPFLAGS=-D_GLIBCXX_ASSERTIONSand runstestthat. Cheap relative to ASan: no sanitizer runtime, no container, and the build is an ordinary-O2one. On Windows locally it reproduces this class in seconds (recipe now in.AGENTS/memory/feature-inapplicable.md), so a Linux leg should be undemanding.Worth deciding: whether it blocks or is advisory, and whether it belongs in
agent-check.ymlor as its own workflow.gcc-ASANalready gatespull_requestonsrc/**, so this would be about speed of feedback and about not depending on a container whose flags are implicit.