Gate every push on cppcheck: 103 findings, and two files cppcheck cannot parse at all #83
Notifications
Due Date
No due date set.
Depends on
Reference: andrew/libakgl#83
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 libakgl half.
Do libakerror and libakstdlib first. They are 6 and 15 findings and they are where the
shape of
scripts/cppcheck.shand thestatic_analysisjob gets settled; this repositorycopies it. libakgl is 103 findings and, with akbasic, the larger cleanup of the four.
Measured, before any change
cppcheck 2.19.0,
--std=c99 --platform=unix64 --enable=style.Over
src include:Over
examples tools util: 1 error, 11 style.103 findings in total. The style bulk is two ids and nothing else worth naming:
unreadVariable40 andvariableScope35, thenconstParameterPointer8 andconstVariablePointer3. Concentrated rather than spread:src/collision.c16,src/tilemap.c14,src/draw.c14,src/controller.c11,src/collision_grid.c11.Both "errors" are cppcheck failing to parse, not defects:
These are the two that decide whether this gate is viable here, so take them first. An
internalAstErrormeans cppcheck gave up on part of a translation unit -- so whatever elseis in that file went unchecked, silently. Both sites are UI code, and Clay's declarative
macros are the obvious suspect: they build layout blocks out of
for-loop andcompound-literal tricks that a C parser which is not a compiler can legitimately choke on.
Two of the three ways out are acceptable and one is not. Acceptable: find the construct and
establish whether cppcheck can be helped over it (
-D/-Uon the offending macro, or--max-configs), or accept the limit and exclude the specific file with a comment sayingwhat is not being checked and why. Not acceptable: leave the id suppressed globally, which
turns every future parse failure anywhere in the tree into silence.
The 35
variableScopeand 40unreadVariableare mechanical, but 40 assigned-and-never-readvariables in a graphics library is worth a skim before a mass edit -- some fraction of those
are a result computed and then not checked.
Scope for this repository
deps/is excluded by the scope and that is load-bearing here: this tree vendors SDL,SDL_image, SDL_mixer, SDL_ttf, jansson, semver, libccd, clay and tg. A bare
cppcheck .would check all of them -- thousands of findings in code this repository cannot fix, and
minutes of runtime.
tests/(29 files) is excluded per the plan below.One local wrinkle:
include/akgl/SDL_GameControllerDB.his generated, andscripts/hooks/pre-commitalready excludes it from the reindent scope for that reason. Ifit produces findings, it is excluded here on the same grounds -- fix generators, never
generated output.
Work
internalAstErrorsites first; they decide how much of the tree thisgate can actually see. No global suppression of that id.
unreadVariablefor results that were computed and never checked. Anythat turn out to be real defects get their own issue and a link back here.
the line above it.
scripts/cppcheck.shwith the scope above.static_analysisjob to.gitea/workflows/ci.yaml. It needs no submodulesand none of the X11/freetype dependency list the other jobs carry: cppcheck does not
link, and the scope is this repository's own C.
scripts/hooks/with apre-commit hook but no pre-push hook -- that is #82, 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.