[llvm] [llvm-cov] Add inline exclusion marker support for coverage reporting (PR #203724)
Maksim Levental via llvm-commits
llvm-commits at lists.llvm.org
Sat Jun 13 13:14:18 PDT 2026
https://github.com/makslevental updated https://github.com/llvm/llvm-project/pull/203724
>From ebb56102f1a3c253410a988e6d148467817a41e9 Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Sat, 13 Jun 2026 12:03:15 -0700
Subject: [PATCH] [llvm-cov] Add inline exclusion marker support for coverage
reporting
Add --exclude-line-regex, --exclude-region-start-regex, and
--exclude-region-stop-regex options to llvm-cov. These allow excluding
lines from coverage totals based on inline source comments.
Defaults: LCOV_EXCL_LINE (single line), LCOV_EXCL_START/LCOV_EXCL_STOP
(region). This brings parity with lcov/gcov exclusion markers and
kcov --exclude-line regex support.
The implementation scans source files for markers when loaded, builds a
per-file set of excluded line numbers, and subtracts them from the line
coverage totals in prepareFileReports().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply at anthropic.com>
---
.../report-custom-region.covmapping | Bin 0 -> 333 bytes
.../report-custom-region.cpp | 18 ++
.../report-custom-region.profdata | Bin 0 -> 840 bytes
.../report-custom.covmapping | Bin 0 -> 333 bytes
.../exclude-markers-custom/report-custom.cpp | 16 ++
.../report-custom.profdata | Bin 0 -> 840 bytes
.../report-region.covmapping | Bin 0 -> 333 bytes
.../exclude-markers-region/report-region.cpp | 18 ++
.../report-region.profdata | Bin 0 -> 840 bytes
.../Inputs/exclude-markers/report.covmapping | Bin 0 -> 325 bytes
.../Inputs/exclude-markers/report.cpp | 16 ++
.../Inputs/exclude-markers/report.profdata | Bin 0 -> 840 bytes
llvm/test/tools/llvm-cov/exclude-markers.test | 210 ++++++++++++++++++
llvm/tools/llvm-cov/CodeCoverage.cpp | 63 +++++-
llvm/tools/llvm-cov/CoverageReport.cpp | 16 ++
llvm/tools/llvm-cov/CoverageSummaryInfo.h | 9 +
llvm/tools/llvm-cov/CoverageViewOptions.h | 58 +++++
17 files changed, 423 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.covmapping
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.cpp
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.profdata
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.covmapping
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.cpp
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.profdata
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.covmapping
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.cpp
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.profdata
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.covmapping
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.cpp
create mode 100644 llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.profdata
create mode 100644 llvm/test/tools/llvm-cov/exclude-markers.test
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.covmapping b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.covmapping
new file mode 100644
index 0000000000000000000000000000000000000000..a5d3c31255967d69f2a62bba44768879b47fbdac
GIT binary patch
literal 333
zcmd1FDa%dHFUw_QfB at AWtg^}#x4Qd{{7#?uPwD~EUOHOLm(((sR-R1l+1WX>l3^kn
z=feP~dJts~WwQZkCR3Y=TdW5exf&ceSPtZ~>^=C-H}SM!>Zw&1KIf;&ry72EeOvs9
zpIw4iRq#f^hdLL}6$vNZ`zI^4kwNsWh%tlv?%QwQa9y`n1)4Q+3B+LgYfG2D)0$nu
z2n4LGjEthpOpJ`|4BQH=ObkGZm4Sz$x1phdgO!1okp=3O6sKp58bX>MIiY+Ig)oPK
wk&%sukqIc4;xv1!+_j8&oa(rE!Rmfp?R{>RGW9k{odn1*AV4vXkB5Z`08^e=2mk;8
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.cpp b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.cpp
new file mode 100644
index 0000000000000..ee9b0b21b3ef3
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.cpp
@@ -0,0 +1,18 @@
+void foo(bool cond) {
+ if (cond) {
+ }
+}
+
+void bar() {
+}
+
+// BEGIN_NO_COV
+void func() {
+}
+// END_NO_COV
+
+int main() {
+ foo(false);
+ bar();
+ return 0;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.profdata b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom-region/report-custom-region.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..a50aa35e8b159980755080f57ff66a6dcedaf177
GIT binary patch
literal 840
zcmeyLQ&5zjmf6V5fE~PGLKU}QM&&y|`D{>O0VoYq#scLtqR~(;15BNOI!pwWUeE^9
zi7M{E3)6`z{$Mi9MpW?zDToVUronV8EMx~OL>E7x&WTk#VI3D%aRyT!tl|rHLqZna
z{10_}Sk)Uy^J5i{u){8X#}T`@nG<$#dmQ4748N}SJ~vC5dK(sg8en%YFytm?=1D-=
zFkirEs4xQuTt3C=8KZ`f=0`}VGBChQiH|Z)N-QdaX at D63qhaC*gWPxDe*1>&x;4xo
t1E><9L23E<NfVbq_^{-OZV<YGFoR+74wHw`9592R0)A+Gn7ZjW!~v(gDV+cS
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.covmapping b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.covmapping
new file mode 100644
index 0000000000000000000000000000000000000000..067571d04a1f4c795738ab590ffb95a59adae379
GIT binary patch
literal 333
zcmd1FDa%dHFUw_QfB at AWtg^}#x4Qd{{7#?uPwD~EUOHOLm(((sR-R1l+1WX>l3^kn
z=R+T;dJts^WwQZkCOxx?Ta&NppVYnJzt-cNrmyGKv%Wf~{B*Uh>7LWq_3{(d at zOn^
zuXWK^Pgn2c`K#V~8Vn2*lmx4xX1MRZ{q_ylb!$~1f8r8|;6}z{J|0^N>llH6m6efE
zl$nW<k)45Cft85?NU<{TF!VMwG;pvo at G`PM%}H^3#;75r`H>UK2T=%f7#JDZco><0
pVku6ux5{11h{vgplNYS+*VW$VW+_u|gVafY3<Clb^LThzm;l-!PU-*v
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.cpp b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.cpp
new file mode 100644
index 0000000000000..c5b6688a3978e
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.cpp
@@ -0,0 +1,16 @@
+void foo(bool cond) {
+ if (cond) {
+ }
+}
+
+void bar() {
+}
+
+void func() { // MY_SKIP
+} // MY_SKIP
+
+int main() {
+ foo(false);
+ bar();
+ return 0;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.profdata b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-custom/report-custom.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..a50aa35e8b159980755080f57ff66a6dcedaf177
GIT binary patch
literal 840
zcmeyLQ&5zjmf6V5fE~PGLKU}QM&&y|`D{>O0VoYq#scLtqR~(;15BNOI!pwWUeE^9
zi7M{E3)6`z{$Mi9MpW?zDToVUronV8EMx~OL>E7x&WTk#VI3D%aRyT!tl|rHLqZna
z{10_}Sk)Uy^J5i{u){8X#}T`@nG<$#dmQ4748N}SJ~vC5dK(sg8en%YFytm?=1D-=
zFkirEs4xQuTt3C=8KZ`f=0`}VGBChQiH|Z)N-QdaX at D63qhaC*gWPxDe*1>&x;4xo
t1E><9L23E<NfVbq_^{-OZV<YGFoR+74wHw`9592R0)A+Gn7ZjW!~v(gDV+cS
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.covmapping b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.covmapping
new file mode 100644
index 0000000000000000000000000000000000000000..53f23eb687ebabdbc1b2ed4580d2b2098145cd6a
GIT binary patch
literal 333
zcmd1FDa%dHFUw_QfB at AWtg^}#x4Qd{{7#?uPwD~EUOHOLm(((sR-R1l+1WX>l3^kn
z=R+T;dJts^WwQZkCOxx?Ta&NppVYnJzt-cNrmyGKv%Wf~{B*Uh>7LWq_3{(d at zOn^
zuXRz^%lG`5^OwE#G#D8A8w69JX1MRZ{q_ylb!$~1f8r8|;O#QK!*5Py?`8x7R#rwv
zQD!DaMs@~n1y&{oAjQhS!_eE%(7?gUz{|)2H7CXC8KZ`f=0{E_A4DO{VPIrr<6&e1
qilsQs-YR!3BOa$ZE?%&@Usrpdo25*>4N at loG7Jb%%;V!>VFCbt;Z>~w
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.cpp b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.cpp
new file mode 100644
index 0000000000000..e43570b76c416
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.cpp
@@ -0,0 +1,18 @@
+void foo(bool cond) {
+ if (cond) {
+ }
+}
+
+void bar() {
+}
+
+// LCOV_EXCL_START
+void func() {
+}
+// LCOV_EXCL_STOP
+
+int main() {
+ foo(false);
+ bar();
+ return 0;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.profdata b/llvm/test/tools/llvm-cov/Inputs/exclude-markers-region/report-region.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..a50aa35e8b159980755080f57ff66a6dcedaf177
GIT binary patch
literal 840
zcmeyLQ&5zjmf6V5fE~PGLKU}QM&&y|`D{>O0VoYq#scLtqR~(;15BNOI!pwWUeE^9
zi7M{E3)6`z{$Mi9MpW?zDToVUronV8EMx~OL>E7x&WTk#VI3D%aRyT!tl|rHLqZna
z{10_}Sk)Uy^J5i{u){8X#}T`@nG<$#dmQ4748N}SJ~vC5dK(sg8en%YFytm?=1D-=
zFkirEs4xQuTt3C=8KZ`f=0`}VGBChQiH|Z)N-QdaX at D63qhaC*gWPxDe*1>&x;4xo
t1E><9L23E<NfVbq_^{-OZV<YGFoR+74wHw`9592R0)A+Gn7ZjW!~v(gDV+cS
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.covmapping b/llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.covmapping
new file mode 100644
index 0000000000000000000000000000000000000000..8fda4ada796e230ddbef2e7dd91747eccd3ff04c
GIT binary patch
literal 325
zcmd1FDa%dHFUw_QfB at AGtg^}#x4Qd{{7#?uPwD~EUOHOLm(((sR-R1l+1WX>l3^kn
z=R+5$dJts<WwQZkCUyOaTa&NppVYnJzt-cNrmyGKv%Wf~{B*Uh>7LWq_40eF<)wQ<
zU+bc`o(2Ph>^#0T5Y6tpZ at +!Rb=_JO$ep+ZBKECeM=?uf_-jTWU}a at w6lG>&WMpUH
zR$ygf08*?BJPf at J4GkQu47`ji5G at QTPR|%Mgfu^LLir#HVGaW$BO4DR6HqM0Y4%pR
jYZ>u4)p7EI)&08K``j#L>TQrZ36NnxfMOmG4+|3jnA}fG
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.cpp b/llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.cpp
new file mode 100644
index 0000000000000..3c255cdc932f4
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.cpp
@@ -0,0 +1,16 @@
+void foo(bool cond) {
+ if (cond) {
+ }
+}
+
+void bar() {
+}
+
+void func() { // LCOV_EXCL_LINE
+} // LCOV_EXCL_LINE
+
+int main() {
+ foo(false);
+ bar();
+ return 0;
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.profdata b/llvm/test/tools/llvm-cov/Inputs/exclude-markers/report.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..a50aa35e8b159980755080f57ff66a6dcedaf177
GIT binary patch
literal 840
zcmeyLQ&5zjmf6V5fE~PGLKU}QM&&y|`D{>O0VoYq#scLtqR~(;15BNOI!pwWUeE^9
zi7M{E3)6`z{$Mi9MpW?zDToVUronV8EMx~OL>E7x&WTk#VI3D%aRyT!tl|rHLqZna
z{10_}Sk)Uy^J5i{u){8X#}T`@nG<$#dmQ4748N}SJ~vC5dK(sg8en%YFytm?=1D-=
zFkirEs4xQuTt3C=8KZ`f=0`}VGBChQiH|Z)N-QdaX at D63qhaC*gWPxDe*1>&x;4xo
t1E><9L23E<NfVbq_^{-OZV<YGFoR+74wHw`9592R0)A+Gn7ZjW!~v(gDV+cS
literal 0
HcmV?d00001
diff --git a/llvm/test/tools/llvm-cov/exclude-markers.test b/llvm/test/tools/llvm-cov/exclude-markers.test
new file mode 100644
index 0000000000000..6b389baef7be6
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/exclude-markers.test
@@ -0,0 +1,210 @@
+// Tests for --exclude-line-regex and --exclude-region-start/stop-regex.
+
+// ===== LCOV_EXCL_LINE (per-line exclusion) =====
+
+// Baseline: disable all exclusion markers, verify original totals.
+// RUN: llvm-cov report %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --exclude-line-regex="" --exclude-region-start-regex="" \
+// RUN: --exclude-region-stop-regex="" --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=BASELINE %s
+
+// Original: 13 lines total, 3 missed, 76.92% covered.
+// BASELINE: TOTAL
+// BASELINE-SAME: 13
+// BASELINE-SAME: 3
+// BASELINE-SAME: 76.92%
+
+// With default LCOV_EXCL_LINE markers: func() lines excluded (2 lines removed).
+// RUN: llvm-cov report %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=EXCL %s
+
+// 13 - 2 excluded = 11 lines, 1 missed, 90.91%.
+// EXCL: TOTAL
+// EXCL-SAME: 11
+// EXCL-SAME: 1
+// EXCL-SAME: 90.91%
+
+// Custom regex that doesn't match: no exclusion.
+// RUN: llvm-cov report %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --exclude-line-regex="MY_CUSTOM_EXCL" \
+// RUN: --exclude-region-start-regex="" --exclude-region-stop-regex="" \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=CUSTOM_NOMATCH %s
+
+// CUSTOM_NOMATCH: TOTAL
+// CUSTOM_NOMATCH-SAME: 13
+// CUSTOM_NOMATCH-SAME: 3
+// CUSTOM_NOMATCH-SAME: 76.92%
+
+// Custom regex that matches the markers:
+// RUN: llvm-cov report %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --exclude-line-regex="LCOV_EXCL" \
+// RUN: --exclude-region-start-regex="" --exclude-region-stop-regex="" \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=CUSTOM_MATCH %s
+
+// CUSTOM_MATCH: TOTAL
+// CUSTOM_MATCH-SAME: 11
+// CUSTOM_MATCH-SAME: 1
+// CUSTOM_MATCH-SAME: 90.91%
+
+// JSON export with exclusion:
+// RUN: llvm-cov export --summary-only \
+// RUN: %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: 2>&1 | FileCheck -check-prefix=JSON %s
+
+// JSON: "lines":{"count":11,"covered":10,"percent":90
+
+// ===== LCOV_EXCL_START / LCOV_EXCL_STOP (region exclusion) =====
+
+// Region exclusion with defaults:
+// RUN: llvm-cov report %S/Inputs/exclude-markers-region/report-region.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers-region/report-region.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers-region \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=REGION %s
+
+// func() is inside EXCL_START/STOP region (2 mapped lines excluded).
+// 13 - 2 = 11 lines, 1 missed, 90.91%.
+// REGION: TOTAL
+// REGION-SAME: 11
+// REGION-SAME: 1
+// REGION-SAME: 90.91%
+
+// Region exclusion with explicit custom regex (same patterns):
+// RUN: llvm-cov report %S/Inputs/exclude-markers-region/report-region.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers-region/report-region.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers-region \
+// RUN: --exclude-line-regex="" \
+// RUN: --exclude-region-start-regex="LCOV_EXCL_START" \
+// RUN: --exclude-region-stop-regex="LCOV_EXCL_STOP" \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=REGION_CUSTOM %s
+
+// REGION_CUSTOM: TOTAL
+// REGION_CUSTOM-SAME: 11
+// REGION_CUSTOM-SAME: 1
+// REGION_CUSTOM-SAME: 90.91%
+
+// Disabling all exclusion leaves totals unchanged:
+// RUN: llvm-cov report %S/Inputs/exclude-markers-region/report-region.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers-region/report-region.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers-region \
+// RUN: --exclude-line-regex="" --exclude-region-start-regex="" \
+// RUN: --exclude-region-stop-regex="" \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=REGION_DISABLED %s
+
+// REGION_DISABLED: TOTAL
+// REGION_DISABLED-SAME: 13
+// REGION_DISABLED-SAME: 3
+// REGION_DISABLED-SAME: 76.92%
+
+// ===== Overriding default markers with custom regex =====
+
+// Custom per-line marker "MY_SKIP" (default LCOV_EXCL_LINE won't match):
+// RUN: llvm-cov report %S/Inputs/exclude-markers-custom/report-custom.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers-custom/report-custom.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers-custom \
+// RUN: --exclude-line-regex="MY_SKIP" \
+// RUN: --exclude-region-start-regex="" --exclude-region-stop-regex="" \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=OVERRIDE_LINE %s
+
+// OVERRIDE_LINE: TOTAL
+// OVERRIDE_LINE-SAME: 11
+// OVERRIDE_LINE-SAME: 1
+// OVERRIDE_LINE-SAME: 90.91%
+
+// Default markers don't match "MY_SKIP":
+// RUN: llvm-cov report %S/Inputs/exclude-markers-custom/report-custom.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers-custom/report-custom.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers-custom \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=OVERRIDE_LINE_DEFAULT %s
+
+// OVERRIDE_LINE_DEFAULT: TOTAL
+// OVERRIDE_LINE_DEFAULT-SAME: 13
+// OVERRIDE_LINE_DEFAULT-SAME: 3
+// OVERRIDE_LINE_DEFAULT-SAME: 76.92%
+
+// Custom region markers "BEGIN_NO_COV" / "END_NO_COV":
+// RUN: llvm-cov report %S/Inputs/exclude-markers-custom-region/report-custom-region.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers-custom-region/report-custom-region.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers-custom-region \
+// RUN: --exclude-line-regex="" \
+// RUN: --exclude-region-start-regex="BEGIN_NO_COV" \
+// RUN: --exclude-region-stop-regex="END_NO_COV" \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=OVERRIDE_REGION %s
+
+// OVERRIDE_REGION: TOTAL
+// OVERRIDE_REGION-SAME: 11
+// OVERRIDE_REGION-SAME: 1
+// OVERRIDE_REGION-SAME: 90.91%
+
+// Default region markers don't match "BEGIN_NO_COV" / "END_NO_COV":
+// RUN: llvm-cov report %S/Inputs/exclude-markers-custom-region/report-custom-region.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers-custom-region/report-custom-region.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers-custom-region \
+// RUN: --show-branch-summary=false \
+// RUN: 2>&1 | FileCheck -check-prefix=OVERRIDE_REGION_DEFAULT %s
+
+// OVERRIDE_REGION_DEFAULT: TOTAL
+// OVERRIDE_REGION_DEFAULT-SAME: 13
+// OVERRIDE_REGION_DEFAULT-SAME: 3
+// OVERRIDE_REGION_DEFAULT-SAME: 76.92%
+
+// ===== "show" command with exclusions disabled =====
+// Exercises the scanForExclusionMarkers early-return path when both regexes
+// are empty (show always loads source files regardless of exclusion settings).
+// RUN: llvm-cov show %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --exclude-line-regex="" --exclude-region-start-regex="" \
+// RUN: --exclude-region-stop-regex="" \
+// RUN: 2>&1 | FileCheck -check-prefix=SHOW_DISABLED %s
+
+// Source is displayed normally; LCOV_EXCL_LINE markers have no effect.
+// SHOW_DISABLED: func()
+// SHOW_DISABLED: LCOV_EXCL_LINE
+
+// ===== Invalid regex error handling =====
+
+// Invalid --exclude-line-regex:
+// RUN: not llvm-cov report %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --exclude-line-regex="[unclosed" \
+// RUN: 2>&1 | FileCheck -check-prefix=BAD_LINE_RE %s
+
+// BAD_LINE_RE: error: invalid --exclude-line-regex:
+
+// Invalid --exclude-region-start-regex:
+// RUN: not llvm-cov report %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --exclude-region-start-regex="(unmatched" \
+// RUN: 2>&1 | FileCheck -check-prefix=BAD_START_RE %s
+
+// BAD_START_RE: error: invalid --exclude-region-start-regex:
+
+// Invalid --exclude-region-stop-regex:
+// RUN: not llvm-cov report %S/Inputs/exclude-markers/report.covmapping \
+// RUN: -instr-profile %S/Inputs/exclude-markers/report.profdata \
+// RUN: -path-equivalence=/tmp/exclude-markers-source,%S/Inputs/exclude-markers \
+// RUN: --exclude-region-stop-regex="*invalid" \
+// RUN: 2>&1 | FileCheck -check-prefix=BAD_STOP_RE %s
+
+// BAD_STOP_RE: error: invalid --exclude-region-stop-regex:
diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp
index e19101ac76045..14977dc41c8bd 100644
--- a/llvm/tools/llvm-cov/CodeCoverage.cpp
+++ b/llvm/tools/llvm-cov/CodeCoverage.cpp
@@ -119,6 +119,9 @@ class CodeCoverageTool {
/// Load the coverage mapping data. Return nullptr if an error occurred.
std::unique_ptr<CoverageMapping> load();
+ /// Scan all source files for inline exclusion markers.
+ void scanSourcesForExclusionMarkers(const CoverageMapping &Coverage);
+
/// Create a mapping from files in the Coverage data to local copies
/// (path-equivalence).
void remapPathNames(const CoverageMapping &Coverage);
@@ -284,6 +287,7 @@ ErrorOr<const MemoryBuffer &>
CodeCoverageTool::getSourceFile(StringRef SourceFile) {
// If we've remapped filenames, look up the real location for this file.
std::unique_lock<std::mutex> Guard{LoadedSourceFilesLock};
+ StringRef OriginalFile = SourceFile;
if (!RemappedFilenames.empty()) {
auto Loc = RemappedFilenames.find(SourceFile);
if (Loc != RemappedFilenames.end())
@@ -299,7 +303,17 @@ CodeCoverageTool::getSourceFile(StringRef SourceFile) {
}
LoadedSourceFiles.emplace_back(std::string(SourceFile),
std::move(Buffer.get()));
- return *LoadedSourceFiles.back().second;
+ auto &LoadedBuf = *LoadedSourceFiles.back().second;
+ ViewOpts.scanForExclusionMarkers(OriginalFile, LoadedBuf);
+ return LoadedBuf;
+}
+
+void CodeCoverageTool::scanSourcesForExclusionMarkers(
+ const CoverageMapping &Coverage) {
+ if (!ViewOpts.ExcludeLineRE && !ViewOpts.ExcludeRegionStartRE)
+ return;
+ for (StringRef SF : Coverage.getUniqueSourceFiles())
+ getSourceFile(SF);
}
void CodeCoverageTool::attachExpansionSubViews(
@@ -740,6 +754,22 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
"given regular expression"),
cl::cat(FilteringCategory));
+ cl::opt<std::string> ExcludeLineRegex(
+ "exclude-line-regex", cl::Optional,
+ cl::desc("Exclude lines matching this regex from coverage totals"),
+ cl::init("LCOV_EXCL_LINE"), cl::cat(FilteringCategory));
+
+ cl::opt<std::string> ExcludeRegionStartRegex(
+ "exclude-region-start-regex", cl::Optional,
+ cl::desc("Start of an excluded region (lines until stop marker are "
+ "excluded from coverage totals)"),
+ cl::init("LCOV_EXCL_START"), cl::cat(FilteringCategory));
+
+ cl::opt<std::string> ExcludeRegionStopRegex(
+ "exclude-region-stop-regex", cl::Optional,
+ cl::desc("End of an excluded region"), cl::init("LCOV_EXCL_STOP"),
+ cl::cat(FilteringCategory));
+
cl::opt<double> RegionCoverageLtFilter(
"region-coverage-lt", cl::Optional,
cl::desc("Show code coverage only for functions with region coverage "
@@ -947,6 +977,33 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
FilenameFilters.push_back(std::make_unique<NameRegexCoverageFilter>(
RE, NameRegexCoverageFilter::FilterType::Include));
+ if (!ExcludeLineRegex.empty()) {
+ ViewOpts.ExcludeLineRE = std::make_shared<Regex>(ExcludeLineRegex);
+ std::string RegexError;
+ if (!ViewOpts.ExcludeLineRE->isValid(RegexError)) {
+ error("invalid --exclude-line-regex: " + RegexError);
+ return 1;
+ }
+ }
+ if (!ExcludeRegionStartRegex.empty()) {
+ ViewOpts.ExcludeRegionStartRE =
+ std::make_shared<Regex>(ExcludeRegionStartRegex);
+ std::string RegexError;
+ if (!ViewOpts.ExcludeRegionStartRE->isValid(RegexError)) {
+ error("invalid --exclude-region-start-regex: " + RegexError);
+ return 1;
+ }
+ }
+ if (!ExcludeRegionStopRegex.empty()) {
+ ViewOpts.ExcludeRegionStopRE =
+ std::make_shared<Regex>(ExcludeRegionStopRegex);
+ std::string RegexError;
+ if (!ViewOpts.ExcludeRegionStopRE->isValid(RegexError)) {
+ error("invalid --exclude-region-stop-regex: " + RegexError);
+ return 1;
+ }
+ }
+
if (!Arches.empty()) {
for (const std::string &Arch : Arches) {
if (Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) {
@@ -1288,6 +1345,8 @@ int CodeCoverageTool::doReport(int argc, const char **argv,
if (!Coverage)
return 1;
+ scanSourcesForExclusionMarkers(*Coverage);
+
CoverageReport Report(ViewOpts, *Coverage);
if (!ShowFunctionSummaries) {
if (SourceFiles.empty())
@@ -1364,6 +1423,8 @@ int CodeCoverageTool::doExport(int argc, const char **argv,
return 1;
}
+ scanSourcesForExclusionMarkers(*Coverage);
+
std::unique_ptr<CoverageExporter> Exporter;
switch (ViewOpts.Format) {
diff --git a/llvm/tools/llvm-cov/CoverageReport.cpp b/llvm/tools/llvm-cov/CoverageReport.cpp
index 9b754d613370c..b357af8cfdabb 100644
--- a/llvm/tools/llvm-cov/CoverageReport.cpp
+++ b/llvm/tools/llvm-cov/CoverageReport.cpp
@@ -496,6 +496,22 @@ std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
}
Pool.wait();
+ // Subtract excluded lines (from inline markers like LCOV_EXCL_LINE) from
+ // each file's line coverage totals. The ExcludedLines map is populated by
+ // the caller scanning source files before invoking this function.
+ for (unsigned I = 0; I < Files.size(); ++I) {
+ const auto *Excluded = Options.getExcludedLinesForFile(Files[I]);
+ if (!Excluded)
+ continue;
+ auto FileCoverage = Coverage.getCoverageForFile(Files[I]);
+ for (const auto &LCS : getLineCoverageStats(FileCoverage)) {
+ if (!LCS.isMapped())
+ continue;
+ if (Excluded->count(LCS.getLine()))
+ FileReports[I].LineCoverage.subtractLine(LCS.getExecutionCount() > 0);
+ }
+ }
+
for (const auto &FileReport : FileReports)
Totals += FileReport;
diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.h b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
index d9210676c41bf..85e24cda6d735 100644
--- a/llvm/tools/llvm-cov/CoverageSummaryInfo.h
+++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
@@ -82,6 +82,15 @@ class LineCoverageInfo {
return *this;
}
+ void subtractLine(bool WasCovered) {
+ assert(NumLines > 0 && "Cannot subtract from zero lines");
+ --NumLines;
+ if (WasCovered) {
+ assert(Covered > 0 && "Cannot subtract covered from zero");
+ --Covered;
+ }
+ }
+
void merge(const LineCoverageInfo &RHS) {
Covered = std::max(Covered, RHS.Covered);
NumLines = std::max(NumLines, RHS.NumLines);
diff --git a/llvm/tools/llvm-cov/CoverageViewOptions.h b/llvm/tools/llvm-cov/CoverageViewOptions.h
index 4826d4adb53bf..6a41d887c6c2e 100644
--- a/llvm/tools/llvm-cov/CoverageViewOptions.h
+++ b/llvm/tools/llvm-cov/CoverageViewOptions.h
@@ -10,7 +10,12 @@
#define LLVM_COV_COVERAGEVIEWOPTIONS_H
#include "RenderingSupport.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/Config/llvm-config.h"
+#include "llvm/Support/LineIterator.h"
+#include "llvm/Support/Regex.h"
+#include <memory>
#include <vector>
namespace llvm {
@@ -61,6 +66,59 @@ struct CoverageViewOptions {
float HighCovWatermark;
float LowCovWatermark;
+ /// Per-file sets of line numbers excluded from coverage via inline markers.
+ /// Populated during the pre-scan phase (before multi-threaded reporting).
+ StringMap<DenseSet<unsigned>> ExcludedLines;
+
+ /// Regexes for inline exclusion markers.
+ std::shared_ptr<Regex> ExcludeLineRE;
+ std::shared_ptr<Regex> ExcludeRegionStartRE;
+ std::shared_ptr<Regex> ExcludeRegionStopRE;
+
+ /// Get excluded lines for a file, or nullptr if none.
+ const DenseSet<unsigned> *getExcludedLinesForFile(StringRef Filename) const {
+ auto It = ExcludedLines.find(Filename);
+ if (It == ExcludedLines.end())
+ return nullptr;
+ return &It->second;
+ }
+
+ /// Scan a source buffer for exclusion markers and populate ExcludedLines.
+ /// Must be called before multi-threaded reporting (prepareFileReports).
+ void scanForExclusionMarkers(StringRef Filename, const MemoryBuffer &Buffer) {
+ if (!ExcludeLineRE && !ExcludeRegionStartRE)
+ return;
+
+ DenseSet<unsigned> Excluded;
+ bool InExcludedRegion = false;
+ unsigned LineNo = 0;
+
+ for (line_iterator LI(Buffer, /*SkipBlanks=*/false); !LI.is_at_eof();
+ ++LI) {
+ ++LineNo;
+ StringRef Line = *LI;
+
+ if (InExcludedRegion) {
+ Excluded.insert(LineNo);
+ if (ExcludeRegionStopRE && ExcludeRegionStopRE->match(Line))
+ InExcludedRegion = false;
+ continue;
+ }
+
+ if (ExcludeRegionStartRE && ExcludeRegionStartRE->match(Line)) {
+ InExcludedRegion = true;
+ Excluded.insert(LineNo);
+ continue;
+ }
+
+ if (ExcludeLineRE && ExcludeLineRE->match(Line))
+ Excluded.insert(LineNo);
+ }
+
+ if (!Excluded.empty())
+ ExcludedLines[Filename] = std::move(Excluded);
+ }
+
/// Change the output's stream color if the colors are enabled.
ColoredRawOstream colored_ostream(raw_ostream &OS,
raw_ostream::Colors Color) const {
More information about the llvm-commits
mailing list