Found by the N ladder lane while trying to measure morph#411 and morph#412,
and routed through the manager because the compiler cache is shared by every
lane. Structurally confirmed against master be478df1 before filing.
What is wrong
A clang-coverage build uses the shared compiler cache, the cache can serve an
object compiled in a different worktree, and nothing normalises the source
paths that llvm-cov records. The result is a coverage report whose rows carry
two different absolute prefixes for the same logical file.
Measured by N ladder, from a build configured entirely inside
/home/yaraslau/repo/morph-wt/368:
368/examples/crm/include/crm/models/account_model.hpp
372/examples/crm/src/models/account_model.cpp <- from ANOTHER worktree
The .cpp objects came out of lane 372's worktree through
fastcache-cc (FASTCACHE_ADDR=127.0.0.1:6674).
The three structural facts that make it possible
All read on master be478df1:
- The coverage preset does not disable the cache.
CMakePresets.json's
clang-coverage sets CMAKE_BUILD_TYPE=Debug and AF_COVERAGE=ON and
nothing else, while cmake/CompileCache.cmake:48 declares
USE_COMPILER_CACHE with default ON.
- Nothing normalises paths.
grep -rn "coverage-prefix-map\|file-prefix-map\|fprofile-abs-path"
over cmake/ and CMakeLists.txt returns nothing, so clang embeds absolute
source paths verbatim into the instrumented objects.
scripts/coverage.sh filters by relative path — SOURCES=(include/morph),
examples/<rung>/<sub>. A row recorded as .../372/examples/crm/src/...
cannot match examples/crm/src/models, so it is not merely mis-attributed,
it vanishes from the report entirely.
What it costs, measured
Two independent consequences, both observed:
-
Whole file sets disappear. In that run, crm's and lims's entire
src/models/*.cpp set is absent from the combined report, while ledger's and
kanban's are present.
-
llvm-cov drops functions it cannot reconcile, reporting
1071 functions have mismatched data. The same file measured two ways:
| file |
via coverage.sh's 8-object invocation |
against ladder_common_tests alone |
examples/common/gui/event_poller.hpp |
92 lines / 31 missed — 66.30% |
106 lines / 1 missed — 99.06% |
presenter.hpp |
86.54% |
100.00% |
A 33-point swing on the same file, from the cache alone.
Why this matters beyond one lane
Every coverage ticket in the current sprint measures locally before proposing a
target: morph#403, morph#402, morph#404, morph#406, morph#409, morph#410,
morph#411, morph#412. No local coverage number is trustworthy while this
holds, which means a target derived from one is a target derived from noise.
morph#403 in particular is about making the coverage denominator honest; it
cannot succeed while the numerator is contaminated.
What this does NOT claim
CI is probably unaffected, and the Codecov figures may still be real. CI
uses sccache in a single checkout, so there is no second worktree for a path to
come from. The 90.39% (examples/common) and 87.67% (crm) figures that
morph#411 and morph#412 quote are from the Codecov API and are not challenged
here. This issue is about local measurement, and about a hazard CI would
inherit the moment it built from more than one checkout.
Verification status
Reproduced (by N ladder): the two-prefix report, the missing rung sources,
the 1071 functions have mismatched data line, and the two per-file deltas
above. Structurally confirmed (by the manager, independently): the preset's
cache variables, USE_COMPILER_CACHE's ON default, the absence of any
prefix-map flag, and coverage.sh's relative SOURCES filters.
Not verified: that -DUSE_COMPILER_CACHE=OFF alone produces a report
matching CI's numbers. A cache-free rebuild was in progress when this was filed
and its result should be recorded here.
Not verified: whether the same cross-worktree reuse corrupts anything other
than coverage. A non-coverage object carries paths only in debug info, so the
blast radius is probably limited to coverage and to debugger source resolution —
but that is reasoning, not measurement.
What would close this
- A
clang-coverage configure cannot silently consume a foreign worktree's
objects. Either AF_COVERAGE=ON forces USE_COMPILER_CACHE=OFF, or the
build adds -ffile-prefix-map/-fcoverage-prefix-map so recorded paths are
worktree-independent and cache hits become correct rather than merely fast.
State which was chosen and why.
- A coverage report produced from any worktree contains no path outside the
configured source directory — checkable mechanically, and worth a check in
scripts/coverage.sh itself, since a silently shrinking report is precisely
the failure mode that file's own comments warn about twice (morph#141,
morph#179).
llvm-cov's functions have mismatched data count is zero, or the
remaining count is explained. A nonzero count means data is being discarded.
- A local cache-free run and CI's Codecov figures are compared and the
difference stated, so future local measurements have a known relationship to
the number the project gates on.
Blocks the measurement clauses of morph#403, morph#402, morph#404,
morph#406, morph#409, morph#410, morph#411 and morph#412.
Found by the
N ladderlane while trying to measure morph#411 and morph#412,and routed through the manager because the compiler cache is shared by every
lane. Structurally confirmed against
masterbe478df1before filing.What is wrong
A
clang-coveragebuild uses the shared compiler cache, the cache can serve anobject compiled in a different worktree, and nothing normalises the source
paths that llvm-cov records. The result is a coverage report whose rows carry
two different absolute prefixes for the same logical file.
Measured by
N ladder, from a build configured entirely inside/home/yaraslau/repo/morph-wt/368:The
.cppobjects came out of lane 372's worktree throughfastcache-cc(FASTCACHE_ADDR=127.0.0.1:6674).The three structural facts that make it possible
All read on
masterbe478df1:CMakePresets.json'sclang-coveragesetsCMAKE_BUILD_TYPE=DebugandAF_COVERAGE=ONandnothing else, while
cmake/CompileCache.cmake:48declaresUSE_COMPILER_CACHEwith default ON.grep -rn "coverage-prefix-map\|file-prefix-map\|fprofile-abs-path"over
cmake/andCMakeLists.txtreturns nothing, so clang embeds absolutesource paths verbatim into the instrumented objects.
scripts/coverage.shfilters by relative path —SOURCES=(include/morph),examples/<rung>/<sub>. A row recorded as.../372/examples/crm/src/...cannot match
examples/crm/src/models, so it is not merely mis-attributed,it vanishes from the report entirely.
What it costs, measured
Two independent consequences, both observed:
Whole file sets disappear. In that run, crm's and lims's entire
src/models/*.cppset is absent from the combined report, while ledger's andkanban's are present.
llvm-cov drops functions it cannot reconcile, reporting
1071 functions have mismatched data. The same file measured two ways:coverage.sh's 8-object invocationladder_common_testsaloneexamples/common/gui/event_poller.hpppresenter.hppA 33-point swing on the same file, from the cache alone.
Why this matters beyond one lane
Every coverage ticket in the current sprint measures locally before proposing a
target: morph#403, morph#402, morph#404, morph#406, morph#409, morph#410,
morph#411, morph#412. No local coverage number is trustworthy while this
holds, which means a target derived from one is a target derived from noise.
morph#403 in particular is about making the coverage denominator honest; it
cannot succeed while the numerator is contaminated.
What this does NOT claim
CI is probably unaffected, and the Codecov figures may still be real. CI
uses sccache in a single checkout, so there is no second worktree for a path to
come from. The 90.39% (
examples/common) and 87.67% (crm) figures thatmorph#411 and morph#412 quote are from the Codecov API and are not challenged
here. This issue is about local measurement, and about a hazard CI would
inherit the moment it built from more than one checkout.
Verification status
Reproduced (by
N ladder): the two-prefix report, the missing rung sources,the
1071 functions have mismatched dataline, and the two per-file deltasabove. Structurally confirmed (by the manager, independently): the preset's
cache variables,
USE_COMPILER_CACHE's ON default, the absence of anyprefix-map flag, and
coverage.sh's relativeSOURCESfilters.Not verified: that
-DUSE_COMPILER_CACHE=OFFalone produces a reportmatching CI's numbers. A cache-free rebuild was in progress when this was filed
and its result should be recorded here.
Not verified: whether the same cross-worktree reuse corrupts anything other
than coverage. A non-coverage object carries paths only in debug info, so the
blast radius is probably limited to coverage and to debugger source resolution —
but that is reasoning, not measurement.
What would close this
clang-coverageconfigure cannot silently consume a foreign worktree'sobjects. Either
AF_COVERAGE=ONforcesUSE_COMPILER_CACHE=OFF, or thebuild adds
-ffile-prefix-map/-fcoverage-prefix-mapso recorded paths areworktree-independent and cache hits become correct rather than merely fast.
State which was chosen and why.
configured source directory — checkable mechanically, and worth a check in
scripts/coverage.shitself, since a silently shrinking report is preciselythe failure mode that file's own comments warn about twice (morph#141,
morph#179).
llvm-cov'sfunctions have mismatched datacount is zero, or theremaining count is explained. A nonzero count means data is being discarded.
difference stated, so future local measurements have a known relationship to
the number the project gates on.
Blocks the measurement clauses of morph#403, morph#402, morph#404,
morph#406, morph#409, morph#410, morph#411 and morph#412.