Gate every push on cppcheck: 6 findings, all in src/error.c #33
Notifications
Due Date
No due date set.
Blocks
Depends on
Reference: andrew/libakerror#33
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Add a
cppcheckstatic-analysis gate to this repository: run on every push, fail thepipeline on any finding, and run again in the pre-push hook so it fails on the workstation
first. This is one of four -- akbasic, libakerror, libakstdlib and libakgl all get the same
gate, built to the same plan, and this issue is the libakerror half.
libakerror goes first because it is the smallest tree in the set: one source file, six
findings, no submodules, and a CI job with nothing to check out. Whatever
scripts/cppcheck.shand the
static_analysisjob end up looking like here is what the other three copy.Measured, before any change
cppcheck 2.19.0,
--std=c99 --platform=unix64 --enable=style, oversrc include:All six are in
src/error.c, and none of them is a defect a caller could reach:Three of them are worth reading before reaching for a fix.
refused != 0at 644 is reported always-true becauserefused = 1at 641 sits inside anATTEMPTblock thatFINISH_NORETURNcloses at 643. cppcheck is not following the errormacros -- the same blind spot gcov has, and for the same reason: they expand at the call
site. The condition is not always true; expect this to be an inline suppression with that
reason written down.
oldidat 393 inakerr_release_erroris assigned and never read. That is either deadcode or a check somebody meant to write, and it should be read before it is deleted.
The
constParameterCallbackat 354 isakerr_default_handler_unhandled_error, andcppcheck says so itself: const-qualifying it means casting the function pointer at 247.
Another suppression candidate rather than a change.
The remaining three are mechanical, but two of them touch published declarations --
akerr_valid_error_address'sptrandakerr_name_for_status'sname. Const-qualifyinga pointer parameter is source-compatible for callers and does not move the soname, but the
header and the definition move together, and the C guide requires their parameter names
match.
Scope for this repository
No
examples/,tools/orutil/in this tree.tests/(38 files) is excluded per theplan below.
Work
line above it.
scripts/cppcheck.shwith the scope above.static_analysisjob to.gitea/workflows/ci.yaml..githooks/at all -- that is #32, and this box waits on it.tests/exclusion in TODO.md with its consequence.The plan (identical in all four repositories)
The gate
Four deviations from the sketch, each with a measurement behind it:
An explicit scope, not
..cppcheck .descends intodeps/and into whateverbuild*/happens to be lying around. Measured in libakstdlib:cppcheck . -i testsreports 30 findings, 24 of them inside
deps/libakerror-- the same six defects thisplan already gates in libakerror's own pipeline, reported a second time from a
repository that cannot fix them. akbasic and libakgl vendor SDL, SDL_image, SDL_mixer,
SDL_ttf, jansson, libccd and clay, which is thousands of findings and minutes of
runtime for code nobody here owns. Build directories are the other half: the mutation
harness copies the whole tree into one, and the pre-push hook runs on a workstation
where two or three of them exist.
--error-exitcode=1. cppcheck exits 0 when it has findings. Verified on 2.19.0:exit 0 with six findings reported, exit 1 with the flag, exit 0 on a clean tree. Without
this flag the job is green no matter what it prints.
--inline-suppr, and no baseline file. Every finding is either fixed or carries a// cppcheck-suppress <id>on the line above it with the reason in a comment. Asuppression list in a separate file is a list nobody reads; a suppression at the site is
a documented, tracked defect, which is what the engineering rules ask for. There is no
"current findings" baseline: the tree goes clean before the gate turns on.
-i tests, as sketched -- deferred, not decided against. Tests are where a leak ismost likely to hide, and this excludes them. That is a named deferral for TODO.md, not a
judgement that test code does not matter. (
-i 'tests/*'and-i testsboth work on2.19.0; an explicit scope makes the question moot.)
Where it lives
scripts/cppcheck.sh-- one definition that CI and the pre-push hook both run, so a pushthat would fail CI fails on the workstation first. Same shape as the existing
scripts/coverage.py,scripts/memcheck.shandscripts/mutation_test.py.SCOPEis the repository's own C and nothing else -- named per repository below.CI
A new
static_analysisjob:The check itself is 2 s (libakgl) to 6 s (akbasic) measured on this workstation; the apt
install dominates the job.
Version drift is a real cost and is accepted rather than engineered around: a newer
cppcheck finds new things, and the gate goes red on a push that did not cause it. That is
fix-forward. Printing the version is what makes it diagnosable. Do not pin an old cppcheck
to keep the tree quiet.
pre-push
First step in the hook -- it is the cheapest gate there by an order of magnitude. Skipped
with a warning when cppcheck is not installed, matching the doxygen precedent in
libakstdlib's hook: a hook that fails closed on a missing optional tool only teaches
everyone to pass
--no-verify. CI is the hard gate.Order
job shape; the other three copy it.
.githooks/pre-push.Acceptance, per repository
scripts/cppcheck.shexits 0 on a clean checkout with no suppressions beyond those whosereason is written at the site.
static_analysisjob exists and has been proven red once, by pushing a deliberatefinding on a throwaway branch and watching the pipeline fail.
wiring waits on it.
-i testsdeferral is recorded in TODO.md with its consequence.Measured with cppcheck 2.19.0 on 2026-08-05.
Siblings, same plan:
.githooks/pre-push)Order: libakerror, then libakstdlib, then akbasic and libakgl in parallel.