[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 14:11:06 PDT 2026
https://github.com/makslevental updated https://github.com/llvm/llvm-project/pull/203724
>From 8e3c10faaa2eeddf0de74b5ba31853adc4339203 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 -> 317 bytes
.../report-custom-region.cpp | 18 ++
.../report-custom-region.profdata | Bin 0 -> 840 bytes
.../report-custom.covmapping | Bin 0 -> 317 bytes
.../exclude-markers-custom/report-custom.cpp | 16 ++
.../report-custom.profdata | Bin 0 -> 840 bytes
.../report-region.covmapping | Bin 0 -> 317 bytes
.../exclude-markers-region/report-region.cpp | 18 ++
.../report-region.profdata | Bin 0 -> 840 bytes
.../Inputs/exclude-markers/report.covmapping | Bin 0 -> 309 bytes
.../Inputs/exclude-markers/report.cpp | 16 ++
.../Inputs/exclude-markers/report.profdata | Bin 0 -> 840 bytes
llvm/test/tools/llvm-cov/exclude-markers.test | 222 ++++++++++++++++++
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, 435 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..f1cddcff855eec24d9b39717ddafc33630747c8d
GIT binary patch
literal 317
zcmd1FDa%dHFUw_QfB^Xqtg;O8QO0Tc`ALjGIw`TJ3`m=#mF6XvG3F*_=6OI>GB8*`
zS!_U>$&^7_za+OnKeZw`r!*y1H#e~;JGH1-w>ZDFC^=Q4D77HJs6;opw74WcSGOoN
zJu^Q~FS($=efRCRZ at 8{os{+lKxCCOL`xLLhr4uX{G6DfBD<h*QGZP~tI|H`@D-#2d
zVrAfA=xu0d;9zCoWn_W6A;sw#qlS>?M@}doL?O&!U}R+DVPpb|r8v#rDt9d-9;Z4k
dUa-1fS9_nErA)mIQYQg23<yxn<Ktmr0sySUM$-TQ
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..b256d628beffe8f879c5512a919e208722892279
GIT binary patch
literal 317
zcmd1FDa%dHFUw_QfB^Xqtg;O8QO0Tc`ALjGIw`TJ3`m=#mF6XvG3F*_=D9#sGB6lI
zS!_U>NsmEVza+OnKeZw`r!*y1H#e~;JGH1-w>ZDFC^=QID77HJs6;opw74WcS1-As
z0IJu0_wBcDxUO5P0{Ih{Km^y>G2ZPurzOk?1gxx#jH1j;jEw9I+zPBr3_yyNfrp{D
zp`n3;m4TO$1!_);(=$d5A<d7RP(Fx4n8U!x$i~CS1Qbhgn!Q!-T1Grhb)39lb-%9m
XJ~vC5dK;up0%RBvpqR(Q!@>jrWxPXf
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..d1207b74c67b961bc5e5f0a63b212105d16c7c2a
GIT binary patch
literal 317
zcmd1FDa%dHFUw_QfB^Xqtg;O8QO0Tc`ALjGIw`TJ3`m=#mF6XvG3F*_=D9#sGB6lI
zS!_U>NsmEVza+OnKeZw`r!*y1H#e~;JGH1-w>ZDFC^=QID77HJs6 at 9YH9a#wPcONk
z0IJu0_wBcDxUO5P0{Ih{Km>gwCTVl7UG$t02v}Je8AX|y7#Z0axD{BL7=RQj0}n%Q
zLqh`xD+4bh3)Gwxr)P{BLYf~rp?na9Fo%JWk&TCu2`HB0G<&PuwTyV2>bQ8p>V94A
XeQuUA^)^VI1jsNTKrxSxhlL3Mmi9zq
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..e4259c2d1eee5bea22784c15c4bc4313e94ce0a0
GIT binary patch
literal 309
zcmd1FDa%dHFUw_QfB^Xytg;O8QO0Tc`ALjGIw`TJ3`m=#mF6XvG3F*_=Gj11GB9XC
zS!_U>Nu5Djza+OnKeZw`r!*y1H#e~;JGH1-w>ZDFC^?m at D77HJs6;Qhpa7!DefRCR
zZ at 8{os{*+bmq5gH#Alg5UZ=~<2n4LGjEthpOpJ`|4BQH=ObkGZm4Sz$x1phdgO!1o
zkp-fKA;sw#qlS>?M@}doL?O&!U}R+DVPpb|r8v#rDt9d-9;Z4^Ua-1fS9_nErA)mI
RQYQg23<yxn<Kbap0swHWK3M<&
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..fd421d8a0098e
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/exclude-markers.test
@@ -0,0 +1,222 @@
+// Tests for --exclude-line-regex and --exclude-region-start/stop-regex.
+//
+// Test inputs were generated with:
+// clang++ -fprofile-instr-generate -fcoverage-mapping \
+// -mllvm -enable-name-compression=false -c -o report.o report.cpp
+// llvm-cov convert-for-testing report.o -o report.covmapping
+// clang++ -fprofile-instr-generate -fcoverage-mapping \
+// -mllvm -enable-name-compression=false -o report report.cpp
+// LLVM_PROFILE_FILE=report.profraw ./report
+// llvm-profdata merge --sparse report.profraw -o report.profdata
+//
+// -mllvm -enable-name-compression=false ensures the profraw and covmapping
+// files do not use zlib, so they are portable to builds without zlib support.
+
+// ===== 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