Skip to content

clang-tidy-diff's changed-lines scope hides 101 findings in include/morph/, each a trap for the next edit to that line #580

Description

@Yaraslaut

Summary

The clang-tidy-diff gate analyses changed lines only. That is a reasonable
policy for a repository adopting a large check set, but it has a consequence
nobody has measured: the public headers under include/morph/ carry a body of
findings the gate structurally cannot see, and each one is a trap for whoever
next edits the line it sits on — the gate goes red on a change that did not
introduce the finding.

101 distinct findings, across 17 headers, from six test translation units.
That is a lower bound, not a total: six TUs out of ~180, and the check set is
the repository's own .clang-tidy with WarningsAsErrors: "*" and
HeaderFilterRegex: "include/morph/.*".

Verification status

Reproduced, on cab05413 plus PR #579's branch (which touches only
completion.hpp, none of the files below), clang-tidy 22.1.8, against
build/clang-debug's compile_commands.json:

for f in tests/test_completion.cpp tests/test_bridge_local.cpp \
         tests/test_remote_extra.cpp tests/test_wire_hardening.cpp \
         tests/test_offline_queue.cpp tests/test_forms_layout.cpp; do
  clang-tidy -p build/clang-debug --quiet "$f"
done

Deduplicated by normalised path + line + column (the same header reaches
clang-tidy through several ../ include chains, so a raw count double-counts):

distinct header findings: 101
files: 17

  21  include/morph/util/quantity.hpp
  18  include/morph/forms/forms.hpp
  17  include/morph/detail/quantity_equation.hpp
   7  include/morph/core/backend.hpp
   7  include/morph/core/wire.hpp
   6  include/morph/core/registry.hpp
   5  include/morph/core/bridge.hpp
   4  include/morph/journal/action_log.hpp
   3  include/morph/offline/offline_queue.hpp
   3  include/morph/session/session_auth.hpp
   2  include/morph/util/rational.hpp
   2  include/morph/core/remote.hpp
   2  include/morph/core/timeout_scheduler.hpp
   1  include/morph/attributes.hpp
   1  include/morph/core/model.hpp

By check:

  17  modernize-use-designated-initializers
  14  readability-redundant-member-init
  13  performance-unnecessary-value-param
  11  cppcoreguidelines-avoid-do-while
   7  cppcoreguidelines-macro-usage
   6  bugprone-easily-swappable-parameters
   4  misc-const-correctness
   4  misc-no-recursion

Real output, three representative lines:

include/morph/core/wire.hpp:414:37: error: the parameter 'message' of type
  'std::string' is copied for each invocation but only used as a const
  reference; consider making it a const reference
  [performance-unnecessary-value-param,-warnings-as-errors]

include/morph/core/backend.hpp:182:108: error: the parameter 'factory' of type
  'std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()>' is
  copied for each invocation but only used as a const reference
  [performance-unnecessary-value-param,-warnings-as-errors]

include/morph/core/detail/../wire.hpp:313:20: error: do not use array subscript
  when the index is not an integer constant expression
  [cppcoreguidelines-pro-bounds-constant-array-index,-warnings-as-errors]

The full deduplicated list is reproducible with the loop above; it is not pasted
here because it is 101 lines and will drift.

Not verified: whether any of the 101 is a defect rather than a style
finding. Spot-checking two:

  • wire.hpp:414 — makeErr(std::string message) is called with a string
    literal at ~30 sites in remote.hpp and then passes message to
    detail::sanitizeControlChars(std::string_view), which returns a fresh
    string. The by-value std::string parameter is a wasted allocation on every
    error reply. Small, real.
  • executor.hpp:211 — runTask(std::function<void()> task) is called only as
    runTask(std::move(task)) from two sites, so the by-value parameter is a
    move, not a copy. The finding is technically correct and practically inert.

That mix is the point: nobody has looked, and the gate is not built to make
anyone look.

Why this matters more than the count

The cost is not the 101 findings. It is that the gate's failure is displaced
from its cause
. A contributor who reformats one line of backend.hpp, or
whose change makes clang-tidy re-analyse a region, gets a red clang-tidy-diff
naming a finding they did not introduce — and the cheapest way out is a
NOLINT, which is how a check set decays.

This is the same shape AGENTS.md's "Verify rather than assert" names: the gate
reports success while measuring only the sliver of the codebase that moved.
It measures exactly what it claims to and no more; the gap is that nobody
has stated what it does not cover.

What would change the verdict

Close this when either:

  • the 101 (or whatever the full-tree number turns out to be) are fixed or
    explicitly suppressed with a reason, so a full-tree clang-tidy run over
    include/morph/ is clean and could be gated; or
  • a deliberate decision is recorded that changed-lines-only is the policy and
    latent header findings are accepted, in which case the trap should at least
    be documented where a contributor hitting it will find it
    (.clang-tidy's own header comment, or AGENTS.md's CI notes).

Re-open if the count grows without either.

Notes

Found while running the repository's own clang-tidy-diff as a pre-flight for
PR #579 (morph#553 / morph#565). Outside that PR's remit and deliberately not
folded into it. Nothing here is introduced by #579 — its own changed lines are
clean, and none of the 17 files above are in its diff except
include/morph/core/completion.hpp, which reports none.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ciSubsystem: cienhancementNew feature or requesttriage: validWell-framed; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions