feat(engine): UBSan gate blocking + clang-tidy baseline (E2 slice 1)#15
Merged
Conversation
Harden the engine CI gates - the mechanical close-out of Engine track E1's
static-analysis gate.
UBSan -> blocking:
- The advisory baseline's only remaining findings were the iterator operator*
null-binding idiom (timeline.h:293, model.h:8667) - a reference formed to a
null end()-sentinel that is never dereferenced (same UB as *v.end()). Mark
both with FREPPLE_NO_SANITIZE_NULL (new no_sanitize("null") macro in utils.h;
g++/clang, no-op in normal builds), leaving the full golden suite UBSan-clean.
- engine-ubsan.yml flips to halt_on_error=1 + abort - a new UB site now fails
CI, the same contract as engine-asan. Verified locally: g++ accepts the
attribute (no warning) and 0 findings remain across the timeline/problem/
forecast-heavy tests.
clang-tidy baseline (E1 gate item 3):
- .clang-tidy: bug-finder check set (clang-analyzer-* + high-signal bugprone-*),
style/readability off; the two noisy checks (unhandled-self-assignment,
exception-escape) excluded.
- engine-clang-tidy.yml: advisory gate (parse-only, never fails) that reports
the finding count + breakdown and uploads the report. Tightens to "no new
findings on changed files" in E2.
Docs: ubsan-baseline.md updated (Finding 2 resolved, gate now blocking);
clang-tidy-baseline.md added; MODERNIZATION_PLAN E1 gate - sanitizer + tidy
items checked (review report still open).
Self-review of the advisory clang-tidy gate caught two issues: - Parallel clang-tidy processes all redirected to the same clang-tidy.out; concurrent writes can interleave and garble lines. Write each TU's output to its own log under .tidy-logs/, then concatenate. - The reported "1076 warnings" was meaningless: with HeaderFilterRegex a header finding is emitted once per including TU. Dedup by the warning line (path:line:col + check, byte-identical across TUs) so the summary reports DISTINCT findings (54) alongside the raw count (~182), with a deduped per-check breakdown. Update clang-tidy-baseline.md with the real full-src numbers (54 distinct) and the actual breakdown - the ~10 high-signal triage items (NullDereference, CallAndMessage, integer-division, NewDelete, uninitialised) are the E2 work-list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
The mechanical close-out of Engine track E1's static-analysis gate: make the UBSan gate blocking and add a clang-tidy baseline. Both sanitizers are now blocking + clean; static analysis is wired and reporting.
UBSan → blocking
The advisory baseline (#14) left exactly 2 distinct UBSan sites across the entire golden suite (post-merge advisory run: 156 occurrences,
timeline.h:293×78 andmodel.h:8667×78) — both the iteratoroperator*null-binding idiom: a reference formed to a nullend()sentinel that's never dereferenced (same UB the standard library has for*v.end()).operator*are markedFREPPLE_NO_SANITIZE_NULL— a new__attribute__((no_sanitize("null")))macro inutils.h(GCC/Clang, no-op in normal builds; g++ has no runtime-suppressions file so the attribute is the portable route). The full golden suite is then UBSan-clean.engine-ubsan.ymlflips tohalt_on_error=1+ abort — a new UB site now fails CI, same contract asengine-asan.yml.Verified locally (ubuntu g++ container): the attribute compiles with no warning, 0 UBSan findings remain across the timeline/problem-list/forecast-heavy tests + the operationdependency path.
clang-tidy baseline (E1 gate item 3)
.clang-tidy— bug-finders only:clang-analyzer-*+ high-signalbugprone-*; style/readability/modernize off, plus the two noisy checks (unhandled-self-assignment,exception-escape) excluded.engine-clang-tidy.yml— advisory gate (parse-only, never fails), runs on PRs intomodernization+ push, reports findings and uploads the report.src/, 57 TUs): 54 distinct findings (the raw line count over-states it ~3× becauseHeaderFilterRegexre-reports a header finding per including TU). The ~10 high-signal items —NullDereference,CallAndMessage,integer-division,NewDelete, uninitialised reads — are the E2 triage work-list before the gate tightens to "no new findings".Docs
ubsan-baseline.md: Finding 2 resolved, gate posture → blocking.clang-tidy-baseline.md: new — config rationale, the real distinct-finding breakdown, advisory→blocking path.MODERNIZATION_PLAN.md: E1 gate — sanitizer + clang-tidy items checked (the engine review report is the one open E1 item).Self-review fixes (2nd commit)
A bugbot-style pass on my own clang-tidy gate caught two bugs, fixed in
fix(ci): clang-tidy gate - per-file logs + distinct-finding dedup:clang-tidyprocesses shared one redirect → garbled lines. Now each TU writes its own log, then concatenated.Validation
-DFREPPLE_SANITIZER=undefinedDebug build with the annotations: compiles clean (no attribute warning), representative suite 0 UBSan findings.build(ubuntu24 ×2) green on the PR — macro + annotations compile in Release, golden output unchanged. The blockingengine-ubsan/engine-asangates run post-merge onmodernization(the prior advisory run already proved those were the only 2 sites).Note
Per convention, not auto-merging — needs your go-ahead. After this, E1 has just the engine review report left; E2 proper (pegging tests 2→12, structural-invariant assertions, stress baseline, clang-tidy diff-gate) is the larger follow-on.