Found while validating feature/xform-statespace; pre-existing on cpp-search, not introduced by that branch.
Reproduce
Build cpp-search (verified at 99b79d8f) with libstdc++ assertions on — the flag has to go in PKG_CPPFLAGS, since ~/.R/Makevars.win zeroes PKG_CXXFLAGS:
PKG_CPPFLAGS="-D_GLIBCXX_ASSERTIONS" R CMD INSTALL --library=<lib> --preclean TreeSearch_*.tar.gz
Confirm the flag took (grep -c _GLIBCXX_ASSERTIONS the build log; expect ~34), then:
NOT_CRAN=true Rscript -e "library(TreeSearch, lib.loc=<lib>); testthat::test_file('tests/testthat/test-tree_length.R', reporter='location')"
Result
Aborts on entry to test_that("TreeLength HSJ returns valid score") (tests/testthat/test-tree_length.R:318) — a 4-taxon dataset, CharacterHierarchy("1" = 2:3), BalancedTree, inapplicable = "hsj":
bits/stl_vector.h:1149: std::vector<_Tp, _Alloc>::const_reference
std::vector<_Tp, _Alloc>::operator[](size_type) const [with _Tp = int; ...]:
Assertion '__n < this->size()' failed.
_Tp = int and a const operator[], so this is an out-of-bounds read. The likeliest candidate is the tip_labels[t * n_orig_chars + char_idx] indexing in src/ts_hsj.cpp (e.g. :166, :390, :418) against the flattened vector built in ts_hsj_score (src/ts_rcpp.cpp:3189-3197), but I did not narrow it further.
Attribution
Both cpp-search @ 99b79d8f and the feature branch abort at the identical test under the identical flag, so the defect is on trunk. It is silent in ordinary builds — the whole suite passes without assertions, and ubuntu-arm64 is green on cpp-search — which is why it has gone unnoticed.
Why it matters
An OOB read in a shipped scoring path. It is reading uninitialised or foreign memory, so an HSJ score could in principle depend on heap contents; and it is exactly the class of defect that becomes a hard fault under a different allocator or on a different architecture.
Suggested next step
Re-run the above and bisect within score_hierarchy_block() / fitch_label_char(), checking n_orig_chars against the actual tip_labels_r column count and each block.primary_char / secondary_chars index against it. Note that -D_GLIBCXX_ASSERTIONS is the cheap local reproducer here; plain builds will not show it.
Found while validating
feature/xform-statespace; pre-existing oncpp-search, not introduced by that branch.Reproduce
Build
cpp-search(verified at99b79d8f) with libstdc++ assertions on — the flag has to go inPKG_CPPFLAGS, since~/.R/Makevars.winzeroesPKG_CXXFLAGS:Confirm the flag took (
grep -c _GLIBCXX_ASSERTIONSthe build log; expect ~34), then:Result
Aborts on entry to
test_that("TreeLength HSJ returns valid score")(tests/testthat/test-tree_length.R:318) — a 4-taxon dataset,CharacterHierarchy("1" = 2:3),BalancedTree,inapplicable = "hsj":_Tp = intand a constoperator[], so this is an out-of-bounds read. The likeliest candidate is thetip_labels[t * n_orig_chars + char_idx]indexing insrc/ts_hsj.cpp(e.g.:166,:390,:418) against the flattened vector built ints_hsj_score(src/ts_rcpp.cpp:3189-3197), but I did not narrow it further.Attribution
Both
cpp-search@99b79d8fand the feature branch abort at the identical test under the identical flag, so the defect is on trunk. It is silent in ordinary builds — the whole suite passes without assertions, andubuntu-arm64is green oncpp-search— which is why it has gone unnoticed.Why it matters
An OOB read in a shipped scoring path. It is reading uninitialised or foreign memory, so an HSJ score could in principle depend on heap contents; and it is exactly the class of defect that becomes a hard fault under a different allocator or on a different architecture.
Suggested next step
Re-run the above and bisect within
score_hierarchy_block()/fitch_label_char(), checkingn_orig_charsagainst the actualtip_labels_rcolumn count and eachblock.primary_char/secondary_charsindex against it. Note that-D_GLIBCXX_ASSERTIONSis the cheap local reproducer here; plain builds will not show it.