From f250b914cdf4720c76baadec7ef2625f9c1544d8 Mon Sep 17 00:00:00 2001 From: R script <1695515+ms609@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:25:09 +0100 Subject: [PATCH 1/2] ci: add libstdc++ hardened-assertions leg to agent-check.yml Builds with PKG_CPPFLAGS=-D_GLIBCXX_ASSERTIONS and runs the full test suite, catching container-bounds address-formation bugs (#51) that plain builds, R CMD check, Valgrind and ASan's own instrumentation all miss. Landed blocking: a build of current cpp-search under the flag plus the full testthat suite ran clean locally. Fixes #60 --- .github/workflows/agent-check.yml | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/.github/workflows/agent-check.yml b/.github/workflows/agent-check.yml index 1f8319baa..5ab12b6eb 100644 --- a/.github/workflows/agent-check.yml +++ b/.github/workflows/agent-check.yml @@ -92,6 +92,67 @@ jobs: " shell: bash + glibcxx-assertions: + runs-on: ubuntu-24.04-arm + name: libstdc++ hardened assertions + + # libstdc++'s -D_GLIBCXX_ASSERTIONS turns container out-of-bounds *address + # formation* (e.g. `vec[n]` where n == vec.size(), with no load or store) + # into a hard abort. That class is invisible to plain builds, to the R CMD + # check leg above, and to ASan itself (which watches accesses, not address + # arithmetic) -- see agent-issues/TreeSearch#60 and #51. Runs independently + # of `ubuntu` for the fastest possible feedback, and skips vignettes/manual + # since it only needs testthat, not a full R CMD check. + env: + NOT_CRAN: "true" + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + RSPM: "https://packagemanager.posit.co/cran/__linux__/noble/2026-07-30" + + steps: + - name: Checkout git repo + uses: actions/checkout@v6 + + - name: Set up R + uses: r-lib/actions/setup-r@v2 + with: + r-version: "release" + + - name: Set up R dependencies + uses: r-lib/actions/setup-r-dependencies@v2 + with: + needs: check + extra-packages: | + shinytest2=?ignore + url::https://ms609.github.io/packages/bin/linux/aarch64-release/MaxMin_latest.tar.gz + cache-version: 2 + + - name: Build source tarball + run: R CMD build --no-build-vignettes --no-manual --no-resave-data . + + - name: Install with libstdc++ hardened assertions + # MUST be PKG_CPPFLAGS, not PKG_CXXFLAGS: a user Makevars can zero the + # latter (it does on the maintainer's own dev machine), and the flag + # would then silently not reach the compiler. + env: + PKG_CPPFLAGS: -D_GLIBCXX_ASSERTIONS + run: | + R CMD INSTALL TreeSearch_*.tar.gz 2>&1 | tee /tmp/install.log + flag_count=$(grep -c -- '-D_GLIBCXX_ASSERTIONS' /tmp/install.log || true) + echo "Compiler invocations carrying the flag: $flag_count" + if [ "$flag_count" -eq 0 ]; then + echo "::error::-D_GLIBCXX_ASSERTIONS never reached a compiler invocation -- this leg would silently provide no coverage" + exit 1 + fi + + - name: Run test suite under hardened libstdc++ + run: | + Rscript -e " + library(testthat) + library(TreeSearch) + test_dir('tests/testthat', package = 'TreeSearch', load_package = 'installed', + reporter = 'summary', stop_on_failure = TRUE) + " + windows: needs: ubuntu runs-on: windows-latest From 25ce60bf8989e42ba729c48a8ae8a569453ba03c Mon Sep 17 00:00:00 2001 From: R script <1695515+ms609@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:01:54 +0100 Subject: [PATCH 2/2] ci: gate the new leg's install failure and separate its cache review found the tee pipe swallowed R CMD INSTALL's exit code without an explicit bash shell (no pipefail), and the leg shared ubuntu's cache key, so a hardened build could silently overwrite or be overwritten by the unhardened one. Also cover tier-3 tests and fail the flag-count check on an unreadable log rather than erroring past it. --- .github/workflows/agent-check.yml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/agent-check.yml b/.github/workflows/agent-check.yml index 5ab12b6eb..40a927956 100644 --- a/.github/workflows/agent-check.yml +++ b/.github/workflows/agent-check.yml @@ -105,6 +105,10 @@ jobs: # since it only needs testthat, not a full R CMD check. env: NOT_CRAN: "true" + # Cover the same tier-3 paths (long TBR/ratchet/resample searches) as + # `ubuntu`, since that's where a container out-of-bounds is most likely + # to be formed. + TREESEARCH_EXTENDED_TESTS: ${{ inputs.extended }} GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} RSPM: "https://packagemanager.posit.co/cran/__linux__/noble/2026-07-30" @@ -124,27 +128,41 @@ jobs: extra-packages: | shinytest2=?ignore url::https://ms609.github.io/packages/bin/linux/aarch64-release/MaxMin_latest.tar.gz - cache-version: 2 + # A cache-version distinct from `ubuntu`'s: that job's cache is + # saved post-job from the *same* restore key (OS/R-version/needs), + # and would otherwise get overwritten with this leg's hardened + # TreeSearch install -- silently handing `ubuntu` a build it never + # asked for, and next time round handing this leg a stale cached + # library that skips reinstalling under the flag. + cache-version: 3 - name: Build source tarball + shell: bash run: R CMD build --no-build-vignettes --no-manual --no-resave-data . - name: Install with libstdc++ hardened assertions # MUST be PKG_CPPFLAGS, not PKG_CXXFLAGS: a user Makevars can zero the # latter (it does on the maintainer's own dev machine), and the flag # would then silently not reach the compiler. + # + # `shell: bash` (not the stepless default) is load-bearing here: only + # the explicit form runs with `-o pipefail`, so a failing `R CMD + # INSTALL` still fails the step even though its exit code is piped + # through `tee`. env: PKG_CPPFLAGS: -D_GLIBCXX_ASSERTIONS + shell: bash run: | R CMD INSTALL TreeSearch_*.tar.gz 2>&1 | tee /tmp/install.log - flag_count=$(grep -c -- '-D_GLIBCXX_ASSERTIONS' /tmp/install.log || true) + flag_count=$(grep -c -- '-D_GLIBCXX_ASSERTIONS' /tmp/install.log || echo 0) echo "Compiler invocations carrying the flag: $flag_count" - if [ "$flag_count" -eq 0 ]; then + if [ "${flag_count:-0}" -eq 0 ]; then echo "::error::-D_GLIBCXX_ASSERTIONS never reached a compiler invocation -- this leg would silently provide no coverage" exit 1 fi - name: Run test suite under hardened libstdc++ + shell: bash run: | Rscript -e " library(testthat)