Skip to content

chore(release): v1.2.0 (#117) #126

chore(release): v1.2.0 (#117)

chore(release): v1.2.0 (#117) #126

Workflow file for this run

name: ci
# Full enterprise CI gate for pbr-cpp-memory-pool — implements the contract
# fixed by ADR-0005 (toolchain matrix) and the quality bar in AGENTS.md §10.
# Covers ROADMAP §1.8 (build matrix + clang-tidy + sanitizers + CTest),
# §1.10 (ANSI C / C99 verification of the public C header), §1.11
# (zero-external-dependency verification of the library build), §2.8
# (Valgrind verification of spec §6.2 — `ERROR SUMMARY: 0 errors from 0
# contexts`), and §2.9 (microbenchmark smoke run — proves the bench
# binary builds, links, and runs to completion; ADR-0014 §8).
#
# The configure-smoke job in .github/workflows/build-smoke.yml is removed in
# the same PR — its work is now a subset of the per-cell configure step here.
on:
pull_request:
paths:
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'src/**'
- '.clang-format'
- '.clang-tidy'
- '.github/workflows/ci.yml'
push:
branches: [master]
paths:
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'src/**'
- '.clang-format'
- '.clang-tidy'
- '.github/workflows/ci.yml'
# Invoked by .github/workflows/release.yml on `v*` tag push to re-run the
# full PR-gating matrix against the tagged commit (ADR-0004 §4 item 1).
# The `paths:` filter above is intentionally not duplicated here — when
# callers reach this workflow via workflow_call they bypass path gating,
# which is the desired behaviour for a tag-time verification.
workflow_call:
permissions:
contents: read
jobs:
# ---------------------------------------------------------------------------
# Cross-platform build matrix per ADR-0005 §4.
#
# Sanitizer presets (asan/ubsan) are POSIX-only by preset condition; the
# matrix omits them for Windows. TSan is deferred until ROADMAP §4 lands
# threading.
# ---------------------------------------------------------------------------
build:
name: build / ${{ matrix.os }} / ${{ matrix.compiler }} / ${{ matrix.preset }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64
- { os: ubuntu-24.04, compiler: gcc, preset: debug }
- { os: ubuntu-24.04, compiler: gcc, preset: release }
- { os: ubuntu-24.04, compiler: gcc, preset: asan }
- { os: ubuntu-24.04, compiler: gcc, preset: ubsan }
- { os: ubuntu-24.04, compiler: clang, preset: debug }
- { os: ubuntu-24.04, compiler: clang, preset: release }
- { os: ubuntu-24.04, compiler: clang, preset: asan }
- { os: ubuntu-24.04, compiler: clang, preset: ubsan }
# Opt-in debug hardening (ADR-0043) — cross-platform (no sanitizer
# flags); builds the hardened configuration and runs its detection
# tests, the "clean in both configurations" acceptance for #109.
- { os: ubuntu-24.04, compiler: gcc, preset: harden }
- { os: ubuntu-24.04, compiler: clang, preset: harden }
# Windows x86_64 — sanitizer presets are POSIX-only.
- { os: windows-2022, compiler: msvc, preset: debug }
- { os: windows-2022, compiler: msvc, preset: release }
# MSVC has no ASan in this matrix, so hardening is the memory-safety net here.
- { os: windows-2022, compiler: msvc, preset: harden }
# macOS arm64
- { os: macos-14, compiler: apple-clang, preset: debug }
- { os: macos-14, compiler: apple-clang, preset: release }
- { os: macos-14, compiler: apple-clang, preset: asan }
- { os: macos-14, compiler: apple-clang, preset: ubsan }
- { os: macos-14, compiler: apple-clang, preset: harden }
steps:
- name: Check out the source tree
uses: actions/checkout@v6
- name: Install a recent CMake and Ninja
uses: lukka/get-cmake@latest
- name: Set up MSVC environment
if: matrix.compiler == 'msvc'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Pin Linux GCC
if: matrix.compiler == 'gcc'
shell: bash
run: |
# ubuntu-24.04 already ships GCC 13 above the ADR-0005 floor of 11.
gcc --version
g++ --version
echo "CC=gcc" >> "$GITHUB_ENV"
echo "CXX=g++" >> "$GITHUB_ENV"
- name: Pin Linux Clang
if: matrix.compiler == 'clang'
shell: bash
run: |
# ubuntu-24.04 ships Clang 18 above the ADR-0005 floor of 14.
clang --version
clang++ --version
echo "CC=clang" >> "$GITHUB_ENV"
echo "CXX=clang++" >> "$GITHUB_ENV"
- name: Configure
shell: bash
run: cmake --preset ${{ matrix.preset }}
- name: Build
shell: bash
run: cmake --build --preset ${{ matrix.preset }}
- name: Test
shell: bash
run: ctest --preset ${{ matrix.preset }} --output-on-failure
# ---------------------------------------------------------------------------
# clang-format — entire repo, dry-run with -Werror.
# ---------------------------------------------------------------------------
format:
name: format / clang-format check
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6
- name: Ensure clang-format is available
shell: bash
run: clang-format --version
- name: Verify all C and C++ sources are clean
shell: bash
run: |
set -euo pipefail
mapfile -t files < <(git ls-files '*.cpp' '*.hpp' '*.h' '*.c')
if [[ ${#files[@]} -eq 0 ]]; then
echo "No C/C++ sources tracked."
exit 0
fi
printf '%s\n' "${files[@]}"
clang-format --dry-run --Werror "${files[@]}"
# ---------------------------------------------------------------------------
# clang-tidy — diff-based, gated with --warnings-as-errors='*'.
# ---------------------------------------------------------------------------
tidy:
name: tidy / clang-tidy diff gate
runs-on: ubuntu-24.04
steps:
- name: Check out (full history needed for diff)
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Ensure clang-tidy is available
shell: bash
run: clang-tidy --version
- name: Configure debug to produce compile_commands.json
shell: bash
run: |
export CC=clang CXX=clang++
cmake --preset debug
- name: Compute changed C/C++ sources against base
id: changed
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
base_sha="${{ github.event.pull_request.base.sha }}"
else
# Push to master — diff against the previous commit.
base_sha="$(git rev-parse HEAD~1)"
fi
# Limit clang-tidy to C++ sources. .c files belong to the
# ANSI-C / C99 verification job above; running clang-tidy on
# them with the C++ project's compile_commands.json forces it
# into C++ mode and surfaces inapplicable diagnostics
# (modernize-redundant-void-arg, init-variables-with-nullptr,
# etc.).
git diff --name-only --diff-filter=AM "$base_sha"...HEAD \
-- '*.cpp' '*.hpp' '*.h' > changed.txt || true
echo "Changed sources:"
cat changed.txt || true
# Surface to next step
echo "file=changed.txt" >> "$GITHUB_OUTPUT"
- name: Run clang-tidy on the diff
shell: bash
run: |
set -euo pipefail
if [[ ! -s "${{ steps.changed.outputs.file }}" ]]; then
echo "No C/C++ source changes — nothing for clang-tidy to do."
exit 0
fi
mapfile -t files < "${{ steps.changed.outputs.file }}"
clang-tidy -p build/debug --warnings-as-errors='*' "${files[@]}"
# ---------------------------------------------------------------------------
# ROADMAP §1.10 — ANSI C (C89) and C99 compatibility of the public header.
# Uses GCC directly (no CMake) so the standard flag is unambiguous.
# ---------------------------------------------------------------------------
ansi-c-compat:
name: compat / ${{ matrix.std }} pedantic
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
std: [c89, c99]
steps:
- name: Check out
uses: actions/checkout@v6
- name: Compile the minimal C consumer under -std=${{ matrix.std }} -pedantic -Werror
shell: bash
run: |
set -euo pipefail
gcc --version
gcc -std=${{ matrix.std }} -pedantic -Werror \
-Isrc/main/cpp \
-c src/test/c/it/d4np/memorypool/c_consumer_min.c \
-o /tmp/c_consumer_min.o
echo "${{ matrix.std }} verification: OK"
# ---------------------------------------------------------------------------
# ROADMAP §1.11 — zero external dependencies in the library build.
# When tests and benchmarks are OFF, the library target must depend only on
# the C/C++ standard library.
# ---------------------------------------------------------------------------
zero-external-deps:
name: deps / zero external dependencies (library only)
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Configure with tests and benchmarks disabled
shell: bash
run: |
cmake -S . -B build/zero-deps -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DPBR_MEMORY_POOL_BUILD_TESTS=OFF \
-DPBR_MEMORY_POOL_BUILD_BENCHMARKS=OFF
- name: Audit CMakeLists for stray find_package outside test/bench scopes
shell: bash
run: |
set -euo pipefail
# Allow find_package() only under src/test/** and src/bench/**.
stray=$(grep -RIn --include='CMakeLists.txt' '^[[:space:]]*find_package' \
CMakeLists.txt src/main/ 2>/dev/null || true)
if [[ -n "$stray" ]]; then
echo "ERROR: find_package() found in the library scope:"
echo "$stray"
exit 1
fi
echo "OK — no find_package() calls in the library scope."
- name: Audit FetchContent declarations outside test/bench scopes
shell: bash
run: |
set -euo pipefail
stray=$(grep -RIn --include='CMakeLists.txt' 'FetchContent_Declare' \
CMakeLists.txt src/main/ 2>/dev/null || true)
# FetchContent_Declare(doctest ...) lives inside the
# if(PBR_MEMORY_POOL_BUILD_TESTS) block — when tests are off it is
# never reached, so it is allowed at the top-level CMakeLists. Flag
# only declarations that are NOT inside the tests/bench guard.
if [[ -n "$stray" ]]; then
echo "FetchContent declarations found (informational):"
echo "$stray"
echo "Ensure each is gated by PBR_MEMORY_POOL_BUILD_TESTS or"
echo "PBR_MEMORY_POOL_BUILD_BENCHMARKS — verify by inspection."
fi
- name: Build the library and inspect linkage
shell: bash
run: |
set -euo pipefail
cmake --build build/zero-deps
# The static archive should contain only objects from our own
# sources; no transitive third-party object files.
archive="build/zero-deps/libpbr_memory_pool.a"
if [[ ! -f "$archive" ]]; then
echo "ERROR: expected static archive at $archive"
ls -la build/zero-deps
exit 1
fi
ar t "$archive"
# Heuristic: every object in the archive must come from
# src/main/cpp/it/d4np/memorypool/.
stray=$(ar t "$archive" | grep -v '^memory_pool\..*\.o$' || true)
if [[ -n "$stray" ]]; then
echo "ERROR: unexpected objects in the static archive:"
echo "$stray"
exit 1
fi
echo "OK — library archive contains only project-local objects."
# ---------------------------------------------------------------------------
# ROADMAP §2.8 — Valgrind verification of spec §6.2.
#
# The literal command from spec §6.2 is:
#
# gcc -g -O0 test_pool.c memory_pool.c -o test_pool
# valgrind --leak-check=full --show-leak-kinds=all ./test_pool
#
# Success criterion (verbatim): `ERROR SUMMARY: 0 errors from 0 contexts`.
#
# The implementation is C++17 not C (ADR-0009 §1), so `gcc` is replaced
# by `g++` on the implementation TU and `memory_pool.c` is replaced by
# `memory_pool.cpp` on the source name. The C test program itself
# (src/test/cpp/it/d4np/memorypool/spec_6_2_valgrind/test_pool.c) is
# unchanged ANSI C89. The side-by-side mapping is in the README at
# src/test/cpp/it/d4np/memorypool/spec_6_2_valgrind/README.md.
#
# The `--errors-for-leak-kinds=definite,indirect` flag is the only
# non-spec addition — without it, leaks are reported but do not change
# the exit code, so a regression that leaked the backing buffer would
# pass `--error-exitcode=1` while still violating spec §3.1.
# `still reachable` and `possible` stay informational so global
# libstdc++ state does not trip the gate.
# ---------------------------------------------------------------------------
valgrind:
name: valgrind / spec §6.2 leak-check=full
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install Valgrind
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y valgrind
valgrind --version
- name: Toolchain versions
shell: bash
run: |
gcc --version
g++ --version
- name: Compile, link, run under Valgrind (literal spec §6.2 pattern)
shell: bash
run: |
set -euo pipefail
mkdir -p build/valgrind
# 1. Compile the C test program under -std=c89 -pedantic. The
# test file is at the spec-named path
# src/test/cpp/.../spec_6_2_valgrind/test_pool.c so that any
# reader of the spec can grep the repo for the source by name.
gcc -std=c89 -pedantic -g -O0 -Isrc/main/cpp \
-c src/test/cpp/it/d4np/memorypool/spec_6_2_valgrind/test_pool.c \
-o build/valgrind/test_pool.o
# 2. Compile the C++17 implementation TU. -std=c++17 is the
# project's ADR-0005 §3 standard; -g -O0 matches the spec's
# `gcc -g -O0` for human-readable Valgrind stack traces.
g++ -std=c++17 -g -O0 -Isrc/main/cpp \
-c src/main/cpp/it/d4np/memorypool/memory_pool.cpp \
-o build/valgrind/memory_pool.o
# 3. Link with g++ so libstdc++ is brought in for the C++17
# ::operator new(size, std::align_val_t) overloads.
g++ -g -O0 \
build/valgrind/test_pool.o build/valgrind/memory_pool.o \
-o build/valgrind/test_pool
# 4. Run Valgrind with the spec flags plus the leak-kind
# promotion. tee the output so we can grep for the literal
# spec success criterion after the exit code is captured.
set +e
valgrind --leak-check=full --show-leak-kinds=all \
--errors-for-leak-kinds=definite,indirect \
--error-exitcode=1 \
build/valgrind/test_pool \
2>&1 | tee build/valgrind/log.txt
rc=${PIPESTATUS[0]}
set -e
if [[ "$rc" -ne 0 ]]; then
echo "FAIL: Valgrind exited with code $rc"
exit "$rc"
fi
# 5. The literal spec success criterion is the canonical sentence;
# grep is a belt-and-braces check against a future Valgrind
# version that changes the exit-code semantics.
if ! grep -E 'ERROR SUMMARY: 0 errors from 0 contexts' \
build/valgrind/log.txt > /dev/null; then
echo "FAIL: spec §6.2 success criterion not present in output"
exit 1
fi
echo "OK — spec §6.2 success criterion met."
# ---------------------------------------------------------------------------
# ROADMAP §2.9 — microbenchmark smoke run. Builds the bench binary with the
# `bench` preset (Release + benchmarks ON + tests OFF) and runs it briefly.
# Asserts exit code 0 only — numeric thresholds are not gated because
# shared GHA runners exhibit ±30% variance on memory-bound microbenchmarks
# and would produce flaky red. Committed numbers in docs/bench/v<X.Y.Z>-
# <host>.md come from a controlled host disclosed in the file header.
# Full methodology rationale: ADR-0014 §8.
# ---------------------------------------------------------------------------
bench-smoke:
name: bench / pool vs malloc smoke run
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Configure (bench preset — Release + benchmarks ON)
shell: bash
run: |
export CC=gcc CXX=g++
cmake --preset bench
- name: Build the bench binary
shell: bash
run: cmake --build --preset bench
- name: Smoke-run pool_vs_malloc_bench
shell: bash
run: |
set -euo pipefail
bin="build/bench/src/bench/cpp/it/d4np/memorypool/pool_vs_malloc_bench"
if [[ ! -x "$bin" ]]; then
echo "FAIL: expected bench binary at $bin"
ls -la build/bench/src/bench/cpp/it/d4np/memorypool/
exit 1
fi
# Reduced iterations / repeats — proves the binary runs end-to-end
# without measuring the noisy runner. The CI gate is exit-code 0,
# not the numbers.
"$bin" --iterations 10000 --repeats 3
echo "OK — bench binary ran to completion."
# ---------------------------------------------------------------------------
# ROADMAP §9.4 — external-allocator baselines + tail-latency percentiles
# (ADR-0045). External baselines are measured the safe way: re-run the SAME
# bench under LD_PRELOAD, which swaps the whole process allocator (jemalloc /
# tcmalloc take over global malloc on load, so they cannot be linked or dlopen'd
# beside the system allocator). Exercises the opt-in --percentiles table too.
# Linux only; never touches the MSVC leg. Asserts exit code 0, not numbers.
# ---------------------------------------------------------------------------
bench-baselines:
name: bench / external baselines + percentiles
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Install jemalloc and tcmalloc runtime libraries
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libjemalloc2 libtcmalloc-minimal4t64
- name: Configure and build (bench preset)
shell: bash
run: |
export CC=gcc CXX=g++
cmake --preset bench
cmake --build --preset bench
- name: Baseline via LD_PRELOAD + the percentile table
shell: bash
run: |
set -euo pipefail
bin="build/bench/src/bench/cpp/it/d4np/memorypool/pool_vs_malloc_bench"
# LD_PRELOAD resolves a soname through the loader cache (ldconfig ran on
# install), so no filesystem search is needed.
run() { # label, LD_PRELOAD value ("" = none), expected "# allocator:" text
echo "=== allocator: $1 ==="
out="$(LD_PRELOAD="$2" "$bin" --scenario all --iterations 20000 --repeats 3 --percentiles)"
echo "$out"
echo "$out" | grep -q "# allocator: $3" || { echo "FAIL: allocator disclosure ($1)"; exit 1; }
echo "$out" | grep -q "p99_ns/op" || { echo "FAIL: percentile table missing ($1)"; exit 1; }
}
run "system malloc" "" "system malloc"
run "jemalloc" "libjemalloc.so.2" "libjemalloc.so.2"
run "tcmalloc" "libtcmalloc_minimal.so.4" "libtcmalloc_minimal.so.4"
echo "OK — pool benchmarked against system malloc, jemalloc, and tcmalloc; percentile table present."
# ---------------------------------------------------------------------------
# thread-safety — build and run the existing (single-threaded) test suite
# under each non-default PBR_MEMORY_POOL_THREAD_SAFETY policy (ADR-0020 /
# M4.3), proving the compile-time switch compiles and stays single-thread
# correct on both Linux compilers. The concurrent stress tests + TSan land
# in M4.4; this job is the build-correctness gate for the switch.
# ---------------------------------------------------------------------------
thread-safety:
name: thread-safety / ${{ matrix.compiler }} / ${{ matrix.mode }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
mode: [MUTEX, LOCKFREE]
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Select compiler
shell: bash
run: |
if [[ "${{ matrix.compiler }}" == "gcc" ]]; then
echo "CC=gcc" >> "$GITHUB_ENV"
echo "CXX=g++" >> "$GITHUB_ENV"
else
echo "CC=clang" >> "$GITHUB_ENV"
echo "CXX=clang++" >> "$GITHUB_ENV"
fi
- name: Configure with the thread-safety policy
shell: bash
run: |
cmake -S . -B build/ts -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DPBR_MEMORY_POOL_BUILD_TESTS=ON \
-DPBR_MEMORY_POOL_THREAD_SAFETY=${{ matrix.mode }}
- name: Build
shell: bash
run: cmake --build build/ts
- name: Test (full suite, incl. concurrent stress, under the selected policy)
shell: bash
run: ctest --test-dir build/ts --output-on-failure
# ---------------------------------------------------------------------------
# tsan — ThreadSanitizer over the concurrent stress suite under the MUTEX
# policy (M4.4). MutexPolicy is fully TSan-analyzable, so this verifies the
# thread-safe path is data-race free. LOCKFREE is deliberately NOT run under
# TSan: a Treiber-stack pop reads a node's next-link that another thread may
# have recycled — a benign race (the value is discarded when the tagged CAS
# fails, and the pool backing is never unmapped) that cannot be expressed as
# a well-defined atomic without C++20 atomic_ref / hazard pointers. LOCKFREE
# concurrent correctness is covered by the logical invariants in the
# thread-safety job above + ADR-0020 §3. See concurrency_stress_test.cpp.
# ---------------------------------------------------------------------------
tsan:
name: tsan / clang / MUTEX
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Configure (Clang + ThreadSanitizer + MutexPolicy)
shell: bash
run: |
export CC=clang CXX=clang++
cmake -S . -B build/tsan -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DPBR_MEMORY_POOL_BUILD_TESTS=ON \
-DPBR_MEMORY_POOL_THREAD_SAFETY=MUTEX \
-DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread"
- name: Build
shell: bash
run: cmake --build build/tsan
- name: Test under ThreadSanitizer
shell: bash
env:
TSAN_OPTIONS: halt_on_error=1
run: ctest --test-dir build/tsan --output-on-failure
# ---------------------------------------------------------------------------
# bench-policy-smoke — build the benchmark under each thread-safe policy and
# run every scenario briefly (exit-code gate only; no numeric thresholds —
# shared GHA runners are too noisy, ADR-0014 §8). Proves the concurrent
# (M4.5) and growth (M5.4) scenarios compile, link (incl. libatomic for
# LOCKFREE), and run to completion under MUTEX and LOCKFREE — including the
# growth scenario's clean skip under the lock-free build. Committed numbers
# come from a controlled host (docs/bench/).
# ---------------------------------------------------------------------------
bench-policy-smoke:
name: bench / policy smoke / ${{ matrix.mode }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
mode: [MUTEX, LOCKFREE]
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Configure (bench preset + thread-safety policy)
shell: bash
run: |
export CC=gcc CXX=g++
cmake --preset bench -B build/bench-${{ matrix.mode }} \
-DPBR_MEMORY_POOL_THREAD_SAFETY=${{ matrix.mode }}
- name: Build the bench binary
shell: bash
run: cmake --build build/bench-${{ matrix.mode }}
- name: Smoke-run every scenario (incl. concurrent + growth)
shell: bash
run: |
set -euo pipefail
bin="build/bench-${{ matrix.mode }}/src/bench/cpp/it/d4np/memorypool/pool_vs_malloc_bench"
if [[ ! -x "$bin" ]]; then
echo "FAIL: expected bench binary at $bin"
exit 1
fi
# --scenario all covers bulk / interleaved / concurrent / growth. The
# growth scenario grows under MUTEX and cleanly skips under LOCKFREE.
"$bin" --scenario all --threads 4 --iterations 20000 --repeats 3
echo "OK — all bench scenarios ran to completion under ${{ matrix.mode }}."
# ---------------------------------------------------------------------------
# ROADMAP §9.3 — coverage-guided fuzzing (ADR-0044). Builds the libFuzzer
# target under ASan/UBSan (Clang / POSIX only — libFuzzer is a Clang runtime),
# replays the seed corpus as a regression gate, then fuzzes for a bounded time
# on every PR. A crash fails the job and the offending input is uploaded as a
# reproducer for the bug ledger (ADR-0039).
# ---------------------------------------------------------------------------
fuzz:
name: fuzz / libFuzzer (asan+ubsan)
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Configure (fuzz preset, Clang)
shell: bash
run: |
export CC=clang CXX=clang++
cmake --preset fuzz
- name: Build the fuzz target
shell: bash
run: cmake --build build/fuzz --target pool_fuzz
- name: Replay the seed corpus (regression gate)
shell: bash
run: |
set -euo pipefail
bin="build/fuzz/src/test/cpp/it/d4np/memorypool/pool_fuzz"
corpus="src/test/cpp/it/d4np/memorypool/pool_fuzz_corpus"
# Replaying named files runs each once and exits; a crash is a finding.
"$bin" "$corpus"/seed_*
echo "OK — seed corpus replayed clean."
- name: Fuzz for a bounded time
shell: bash
run: |
set -euo pipefail
bin="build/fuzz/src/test/cpp/it/d4np/memorypool/pool_fuzz"
corpus="src/test/cpp/it/d4np/memorypool/pool_fuzz_corpus"
"$bin" -max_total_time=60 -timeout=25 -rss_limit_mb=4096 "$corpus"
echo "OK — libFuzzer ran to the time budget with no crash."
- name: Upload any crash reproducers
if: failure()
uses: actions/upload-artifact@v7
with:
name: fuzz-crashes
path: |
crash-*
timeout-*
oom-*
if-no-files-found: ignore