Gate every push on cppcheck: 102 findings, including a missing return in src/sink_akgl.c that no CI build compiles #48
Notifications
Due Date
No due date set.
Depends on
Reference: andrew/akbasic#48
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 akbasic 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. akbasic is 102 findings and is 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: 2 warning, 12 style.102 findings in total. The style bulk is mechanical --
constParameterPointer26,unreadVariable19,constVariablePointer15,variableScope13 -- and spread thinly:src/host.c12,src/value.c8,src/runtime.c8, then a long tail of 3--7 per file.The nine that are not style are where the value is:
Three of these deserve reading rather than patching:
src/sink_akgl.c:66is a control path out of a non-void function with no return. Thatfile is only compiled with
AKBASIC_WITH_AKGL=ON, so the default CI build has nevercompiled it -- and cppcheck reads it regardless of the CMake option, which is one of the
concrete things this gate buys.
invalidPointerCastinsrc/host.care the embedding boundary punning a bytebuffer as
float */double *. That may well be deliberate and documented, in whichcase it is an inline suppression with the alignment argument written next to it -- but
"deliberate" needs to be established, not assumed, because it is genuine UB when the
buffer is not suitably aligned.
nullPointerArithmeticRedundantCheckatsrc/host.c:64and:121say thehostbase != NULLguard and the pointer arithmetic disagree about whether NULL ispossible. One of the two is wrong.
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, libccd, clay, libakerror and libakstdlib. A bare
cppcheck .would check all of them -- thousands of findings in code this repositorycannot fix, and minutes of runtime.
tests/(46 files) is excluded per the plan below.Work
defects get their own issue and a link back here, rather than being fixed quietly
inside this one.
line above it.
scripts/cppcheck.shwith the scope above.static_analysisjob to.gitea/workflows/ci.yaml-- inci.yamlratherthan
release.yaml: the check is 6 seconds measured, which is a per-commit gate, nota release gate.
.githooks/at all -- that is #47, 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.