[llvm] [CoverageMapping] Fix LineCoverageStats incorrectly using gap region count (PR #203732)
Maksim Levental via llvm-commits
llvm-commits at lists.llvm.org
Sat Jun 13 16:44:06 PDT 2026
https://github.com/makslevental created https://github.com/llvm/llvm-project/pull/203732
## Summary
Fix a bug in `LineCoverageStats` where lines are incorrectly reported as uncovered (count=0) when:
1. The wrapping segment is a non-gap region with count=0 (e.g., from a never-taken if-branch)
2. The line has no region entry segments (`MinRegionCount == 0`)
3. The line DOES have non-entry, non-gap segments with count > 0 (returning to the parent function's coverage)
This causes `llvm-cov show` and `llvm-cov report` to display count=0 for lines that are genuinely executed, leading to incorrect coverage percentages.
## Example
```cpp
void foo(bool cond) {
if (cond) {
int x = 1; // genuinely uncovered
(void)x; // genuinely uncovered
} // <-- BUG: reported as uncovered, but this line IS executed
int y = 2; // covered
}
```
When `foo(false)` is called, the closing `}` on line 5 is reached (it's part of the parent function's control flow), but `LineCoverageStats` reports it with count=0 because:
- The wrapping segment (from the if-body) has count=0
- No region entries exist on line 5
- The original code returned early with `ExecutionCount = WrappedSegment->Count = 0`
## Fix
Two changes to `LineCoverageStats::LineCoverageStats()`:
1. **Skip gap wrapping segments** when initializing `ExecutionCount` — gap regions should not contribute to a line's execution count.
2. **Check non-entry segments** when `MinRegionCount == 0` — iterate `LineSegments` for any non-gap, `HasCount` segment and use its max count.
## Test plan
- Added `llvm/test/tools/llvm-cov/gap-region-line-coverage.test` (REQUIRES: system-darwin)
- Test inputs generated with Apple clang which produces the specific segment pattern
- Verifies both `show` (line displays count=1) and `report` (line not counted as missed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
>From 0085eabcf96c95bd11222d8a66c770122d7cc5be Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Sat, 13 Jun 2026 16:33:04 -0700
Subject: [PATCH 1/2] [llvm-cov] Add failing test for gap region line coverage
bug
LineCoverageStats incorrectly reports a line as uncovered when the
wrapping segment has count=0 and the line has no region entries, even
if it has non-entry segments with count > 0.
This test uses coverage data from Apple clang which produces the
specific segment pattern that triggers this: a closing brace "}" after
a never-taken if-block, where the non-gap segment returning to the
parent function's count is ignored.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply at anthropic.com>
---
.../gap-region-quirk/gap-quirk.covmapping | Bin 0 -> 213 bytes
.../Inputs/gap-region-quirk/gap-quirk.cpp | 13 +++++++
.../gap-region-quirk/gap-quirk.profdata | Bin 0 -> 712 bytes
.../llvm-cov/gap-region-line-coverage.test | 32 ++++++++++++++++++
4 files changed, 45 insertions(+)
create mode 100644 llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.covmapping
create mode 100644 llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.cpp
create mode 100644 llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.profdata
create mode 100644 llvm/test/tools/llvm-cov/gap-region-line-coverage.test
diff --git a/llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.covmapping b/llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.covmapping
new file mode 100644
index 0000000000000000000000000000000000000000..138bb3f29a5d53fb78e0440eeaaf32e4f47424d1
GIT binary patch
literal 213
zcmd1FDa%dHFUw_QfB?Q1%sdS7QO0Tc`ALkqiJ5sePzfNV4P~+cX(kN at N&S-C0{xuK
zyi{GF38_Vi>8ZLoiMdHBiM;8F1-gZ$nMK)p$pr;qUGBSYzkS1X-C7mMo45pGkcv{_
z>B#d-t}_AwD=Q<TC_57)BRd1P0xJ^(kYZ)vVd!mWXy9OF;ALikXkqwuwfDJM%GBGO
SK)wW23}FrfBO at 0NGZO#{H!g_)
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.cpp b/llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.cpp
new file mode 100644
index 0000000000000..ad98fbb84f1ff
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.cpp
@@ -0,0 +1,13 @@
+void foo(bool cond) {
+ if (cond) {
+ int x = 1;
+ (void)x;
+ }
+ int y = 2;
+ (void)y;
+}
+
+int main() {
+ foo(false);
+ return 0;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.profdata b/llvm/test/tools/llvm-cov/Inputs/gap-region-quirk/gap-quirk.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..586f1295ca0fc237c8abd5823915a4e468da19c4
GIT binary patch
literal 712
zcmeyLQ&5zjmf6V5fE_GgLKWYE#y<e%vq6;!KxvpVW+<N#jfQd=VCn?aVIrvXf;N~=
zRB;Dhn8~Q(4<@sq3P5=cQZN^yN-Hd62MfS#fw|{^Iww}~gmqk4#TiU_u!=9(4GCFv
z^FP$_VO4J+&5u<)!VbIm9Y^fqW=`0}?Qw`RG5osP``j#L>TOu~X at K3qz>u4mnI{2d
z!+Zgwp~4ItP=@>N+i%}+UAKmWDFcH6R6IV)I4wUvY2p$H9~Q{y#-JMq<inJ}v~a)}
IFttz`04JCuo&W#<
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/gap-region-line-coverage.test b/llvm/test/tools/llvm-cov/gap-region-line-coverage.test
new file mode 100644
index 0000000000000..dfbeede7f52be
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/gap-region-line-coverage.test
@@ -0,0 +1,32 @@
+// REQUIRES: system-darwin
+//
+// Tests that LineCoverageStats correctly handles lines where a zero-count
+// non-gap wrapping segment precedes a non-entry segment with count > 0.
+//
+// In this test, line 5 ("}") is the closing brace of an if-block that was
+// never entered. The wrapping segment from the if-body has count=0, but
+// line 5's own segment has count=1 (returning to the parent function's
+// coverage region). LineCoverageStats should report count=1 for line 5.
+//
+// Test inputs generated with Apple clang (-O0) which produces the specific
+// segment pattern needed to trigger this bug.
+
+// RUN: llvm-cov show %S/Inputs/gap-region-quirk/gap-quirk.covmapping \
+// RUN: -instr-profile %S/Inputs/gap-region-quirk/gap-quirk.profdata \
+// RUN: -path-equivalence=/tmp/line-coverage-lambda,%S/Inputs/gap-region-quirk \
+// RUN: 2>&1 | FileCheck -check-prefix=SHOW %s
+
+// Line 5 ("}") should show count 1, not 0.
+// SHOW: 5| 1| }
+
+// RUN: llvm-cov report %S/Inputs/gap-region-quirk/gap-quirk.covmapping \
+// RUN: -instr-profile %S/Inputs/gap-region-quirk/gap-quirk.profdata \
+// RUN: -path-equivalence=/tmp/line-coverage-lambda,%S/Inputs/gap-region-quirk \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=REPORT %s
+
+// 12 mapped lines, 2 genuinely missed (lines 3-4: if-body never entered).
+// Line 5 should NOT be counted as missed.
+// REPORT: TOTAL
+// REPORT-SAME: 12
+// REPORT-SAME: 2
>From 983fd8919f15bc3b065b93a10fc5859bcfb1aee8 Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Sat, 13 Jun 2026 16:34:35 -0700
Subject: [PATCH 2/2] [CoverageMapping] Fix LineCoverageStats incorrectly using
gap region count
When a line's wrapping segment is a gap region with count=0, but the
line has non-gap segments with count > 0 (not region entries),
LineCoverageStats incorrectly reports the line as uncovered.
This happens when a zero-count region (e.g., from a never-taken
if-branch) extends past subsequent covered lines via a gap segment.
The fix:
1. Skip initializing ExecutionCount from gap wrapping segments
2. When MinRegionCount is 0, check non-gap HasCount segments on the
line and use their max count
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply at anthropic.com>
---
llvm/lib/ProfileData/Coverage/CoverageMapping.cpp | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
index 0d9a5a6758f06..c6d432c6be20c 100644
--- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
+++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp
@@ -1602,10 +1602,18 @@ LineCoverageStats::LineCoverageStats(
// Pick the max count from the non-gap, region entry segments and the
// wrapped count.
- if (WrappedSegment)
+ if (WrappedSegment && !WrappedSegment->IsGapRegion)
ExecutionCount = WrappedSegment->Count;
- if (!MinRegionCount)
+ if (!MinRegionCount) {
+ // If no region entries on this line, check for non-gap segments with
+ // HasCount that aren't region entries (e.g., continuation of a covered
+ // region after a gap). Use their max count instead of the wrapping
+ // gap segment's count.
+ for (const auto *LS : LineSegments)
+ if (!LS->IsGapRegion && LS->HasCount)
+ ExecutionCount = std::max(ExecutionCount, LS->Count);
return;
+ }
for (const auto *LS : LineSegments)
if (isStartOfRegion(LS))
ExecutionCount = std::max(ExecutionCount, LS->Count);
More information about the llvm-commits
mailing list