[llvm] [mlir] [llvm-cov] Add inline exclusion marker support for coverage reporting (PR #203723)

Maksim Levental via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 13 12:42:32 PDT 2026


https://github.com/makslevental created https://github.com/llvm/llvm-project/pull/203723

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>

>From bd6c6fa2345f5976d8f495a0eb051a8949cb8bd5 Mon Sep 17 00:00:00 2001
From: Maksim Levental <maksim.levental at gmail.com>
Date: Fri, 12 Jun 2026 23:27:02 -0700
Subject: [PATCH 1/6] [mlir-tblgen] Render enum keyword alternatives in
 generated attr/type docs

When mlir-tblgen generates documentation for AttrDefs/TypeDefs that have
EnumParameter fields, it previously rendered the raw C++ type (e.g.
`::mlir::ns::MyEnum`) in the syntax block. This was unhelpful for
users who need to know the valid keyword values.

This patch:
1. Adds an `EnumInfo enum = enumInfo;` field to the `EnumParameter`
   TableGen class, persisting the enum record for tooling to inspect.
2. Modifies `emitAttrOrTypeDefAssemblyFormat` in OpDocGen.cpp to detect
   EnumParameter fields and render their cases as backtick-quoted
   alternatives (e.g. `` `read` | `read_write` ``).
3. Adds a test case to gen-dialect-doc.td verifying the new behavior.

Before:
  #my_dialect.my_attr<
    int32_t,   # index
    ::mlir::ns::MyEnum,   # access
  >

After:
  #my_dialect.my_attr<
    int32_t,   # index
    `read` | `read_write`,   # access
  >

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply at anthropic.com>
---
 mlir/include/mlir/IR/EnumAttr.td         |  3 +++
 mlir/test/mlir-tblgen/gen-dialect-doc.td | 25 ++++++++++++++++++++++++
 mlir/tools/mlir-tblgen/OpDocGen.cpp      | 24 ++++++++++++++++++++++-
 3 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/mlir/include/mlir/IR/EnumAttr.td b/mlir/include/mlir/IR/EnumAttr.td
index 6eef5075fe18a..5f3384e95ba79 100644
--- a/mlir/include/mlir/IR/EnumAttr.td
+++ b/mlir/include/mlir/IR/EnumAttr.td
@@ -503,6 +503,9 @@ class I64BitEnumAttr<string name, string summary,
 class EnumParameter<EnumInfo enumInfo>
     : AttrParameter<enumInfo.cppNamespace # "::" # enumInfo.className,
                     "an enum of type " # enumInfo.className> {
+  // Store the enum info so that tooling (e.g. doc generation) can inspect the
+  // enum cases without re-parsing the C++ type string.
+  EnumInfo enum = enumInfo;
   let parser = !if(!isa<EnumAttrInfo>(enumInfo),
     !cast<EnumAttrInfo>(enumInfo).parameterParser, ?);
   let printer = !if(!isa<EnumAttrInfo>(enumInfo),
diff --git a/mlir/test/mlir-tblgen/gen-dialect-doc.td b/mlir/test/mlir-tblgen/gen-dialect-doc.td
index 72916704369ee..c373f3fb6801b 100644
--- a/mlir/test/mlir-tblgen/gen-dialect-doc.td
+++ b/mlir/test/mlir-tblgen/gen-dialect-doc.td
@@ -67,6 +67,24 @@ def TestAttrDefParams : AttrDef<Test_Dialect, "TestAttrDefParams"> {
   let assemblyFormat = "`<` $value `>`";
 }
 
+def TestEnumForParam :
+    I32EnumAttr<"TestEnumForParam",
+        "enum for param test", [
+        I32EnumAttrCase<"Alpha", 0, "alpha">,
+        I32EnumAttrCase<"Beta", 1, "beta">]> {
+  let genSpecializedAttr = 0;
+  let cppNamespace = "NS";
+}
+
+def TestAttrWithEnum : AttrDef<Test_Dialect, "TestAttrWithEnum"> {
+  let mnemonic = "with_enum";
+  let parameters = (ins
+    "int":$value,
+    EnumParameter<TestEnumForParam>:$mode
+  );
+  let assemblyFormat = "`<` $value `,` $mode `>`";
+}
+
 def TestTypeDef : TypeDef<Test_Dialect, "TestTypeDef"> {
   let mnemonic = "test_type_def";
 }
@@ -140,6 +158,13 @@ def TestEnum :
 // CHECK: Syntax:
 // CHECK: #test.test_attr_def_params
 
+// CHECK: TestAttrWithEnumAttr
+// CHECK: Syntax:
+// CHECK:      #test.with_enum<
+// CHECK-NEXT:   int,   # value
+// CHECK-NEXT:   `alpha` | `beta`   # mode
+// CHECK-NEXT: >
+
 // CHECK: ## Type constraints
 // CHECK: ### type summary
 // CHECK: type description
diff --git a/mlir/tools/mlir-tblgen/OpDocGen.cpp b/mlir/tools/mlir-tblgen/OpDocGen.cpp
index 5e3cf302ed3ea..7c050dbb08a3b 100644
--- a/mlir/tools/mlir-tblgen/OpDocGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDocGen.cpp
@@ -384,6 +384,25 @@ static void emitTypeDoc(const Type &type, raw_ostream &os) {
 // TypeDef Documentation
 //===----------------------------------------------------------------------===//
 
+/// If \p param is an EnumParameter, return a string listing the enum's keyword
+/// alternatives (e.g. "`read` | `read_write`"). Otherwise return std::nullopt.
+static std::optional<std::string>
+getEnumParameterDocSyntax(const AttrOrTypeParameter &param) {
+  const auto *paramDef = dyn_cast<DefInit>(param.getDef());
+  if (!paramDef || !paramDef->getDef()->isSubClassOf("EnumParameter"))
+    return std::nullopt;
+  const Record *enumRec = paramDef->getDef()->getValueAsDef("enum");
+  EnumInfo enumInfo(enumRec);
+  std::vector<EnumCase> cases = enumInfo.getAllCases();
+  std::string result;
+  for (const auto &caseIt : llvm::enumerate(cases)) {
+    if (caseIt.index() > 0)
+      result += " | ";
+    result += (llvm::Twine("`") + caseIt.value().getStr() + "`").str();
+  }
+  return result;
+}
+
 static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef &def,
                                             raw_ostream &os) {
   ArrayRef<AttrOrTypeParameter> parameters = def.getParameters();
@@ -399,7 +418,10 @@ static void emitAttrOrTypeDefAssemblyFormat(const AttrOrTypeDef &def,
      << "<\n";
   for (const auto &it : llvm::enumerate(parameters)) {
     const AttrOrTypeParameter &param = it.value();
-    os << "  " << param.getSyntax();
+    if (auto enumSyntax = getEnumParameterDocSyntax(param))
+      os << "  " << *enumSyntax;
+    else
+      os << "  " << param.getSyntax();
     if (it.index() < (parameters.size() - 1))
       os << ",";
     os << "   # " << param.getName() << "\n";

>From f29b6c0f70c616fd0096e1115b520c20cd8e29d6 Mon Sep 17 00:00:00 2001
From: Maksim Levental <maksim.levental at gmail.com>
Date: Sat, 13 Jun 2026 06:27:21 -0700
Subject: [PATCH 2/6] Update mlir/tools/mlir-tblgen/OpDocGen.cpp

Co-authored-by: Federico Bruzzone <federico.bruzzone.i at gmail.com>
---
 mlir/tools/mlir-tblgen/OpDocGen.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mlir/tools/mlir-tblgen/OpDocGen.cpp b/mlir/tools/mlir-tblgen/OpDocGen.cpp
index 7c050dbb08a3b..d91b134764b28 100644
--- a/mlir/tools/mlir-tblgen/OpDocGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDocGen.cpp
@@ -384,7 +384,7 @@ static void emitTypeDoc(const Type &type, raw_ostream &os) {
 // TypeDef Documentation
 //===----------------------------------------------------------------------===//
 
-/// If \p param is an EnumParameter, return a string listing the enum's keyword
+/// If `param` is an EnumParameter, return a string listing the enum's keyword
 /// alternatives (e.g. "`read` | `read_write`"). Otherwise return std::nullopt.
 static std::optional<std::string>
 getEnumParameterDocSyntax(const AttrOrTypeParameter &param) {

>From 47001d7786004f3383537c7643d6fe64e2b51db6 Mon Sep 17 00:00:00 2001
From: makslevental <m_levental at apple.com>
Date: Sat, 13 Jun 2026 08:53:46 -0700
Subject: [PATCH 3/6] address comment

---
 mlir/tools/mlir-tblgen/OpDocGen.cpp | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/mlir/tools/mlir-tblgen/OpDocGen.cpp b/mlir/tools/mlir-tblgen/OpDocGen.cpp
index d91b134764b28..971039d31ed34 100644
--- a/mlir/tools/mlir-tblgen/OpDocGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDocGen.cpp
@@ -395,11 +395,9 @@ getEnumParameterDocSyntax(const AttrOrTypeParameter &param) {
   EnumInfo enumInfo(enumRec);
   std::vector<EnumCase> cases = enumInfo.getAllCases();
   std::string result;
-  for (const auto &caseIt : llvm::enumerate(cases)) {
-    if (caseIt.index() > 0)
-      result += " | ";
-    result += (llvm::Twine("`") + caseIt.value().getStr() + "`").str();
-  }
+  llvm::interleave(
+      cases, [&](const EnumCase &c) { result += "`" + c.getStr().str() + "`"; },
+      [&] { result += " | "; });
   return result;
 }
 

>From 65bab0af8c05007b3522148ffed8af40ca6c4587 Mon Sep 17 00:00:00 2001
From: makslevental <m_levental at apple.com>
Date: Sat, 13 Jun 2026 08:56:25 -0700
Subject: [PATCH 4/6] remove unncessary comment

---
 mlir/include/mlir/IR/EnumAttr.td | 2 --
 1 file changed, 2 deletions(-)

diff --git a/mlir/include/mlir/IR/EnumAttr.td b/mlir/include/mlir/IR/EnumAttr.td
index 5f3384e95ba79..4b7ea55d089d6 100644
--- a/mlir/include/mlir/IR/EnumAttr.td
+++ b/mlir/include/mlir/IR/EnumAttr.td
@@ -503,8 +503,6 @@ class I64BitEnumAttr<string name, string summary,
 class EnumParameter<EnumInfo enumInfo>
     : AttrParameter<enumInfo.cppNamespace # "::" # enumInfo.className,
                     "an enum of type " # enumInfo.className> {
-  // Store the enum info so that tooling (e.g. doc generation) can inspect the
-  // enum cases without re-parsing the C++ type string.
   EnumInfo enum = enumInfo;
   let parser = !if(!isa<EnumAttrInfo>(enumInfo),
     !cast<EnumAttrInfo>(enumInfo).parameterParser, ?);

>From 81435fc4e5eda29a6299729736f9a8019e8ca3c9 Mon Sep 17 00:00:00 2001
From: makslevental <m_levental at apple.com>
Date: Sat, 13 Jun 2026 09:03:50 -0700
Subject: [PATCH 5/6] add another test

---
 mlir/test/mlir-tblgen/gen-dialect-doc.td | 27 ++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/mlir/test/mlir-tblgen/gen-dialect-doc.td b/mlir/test/mlir-tblgen/gen-dialect-doc.td
index c373f3fb6801b..b279b518b85a9 100644
--- a/mlir/test/mlir-tblgen/gen-dialect-doc.td
+++ b/mlir/test/mlir-tblgen/gen-dialect-doc.td
@@ -85,6 +85,26 @@ def TestAttrWithEnum : AttrDef<Test_Dialect, "TestAttrWithEnum"> {
   let assemblyFormat = "`<` $value `,` $mode `>`";
 }
 
+def TestEnumWithAttrWrapper :
+    I32EnumAttr<"TestEnumWithWrapper",
+        "enum with attr wrapper", [
+        I32EnumAttrCase<"Red", 0, "red">,
+        I32EnumAttrCase<"Green", 1, "green">,
+        I32EnumAttrCase<"Blue", 2, "blue">]> {
+  let genSpecializedAttr = 0;
+  let cppNamespace = "NS";
+}
+def TestEnumWithWrapperAttr : EnumAttr<Test_Dialect, TestEnumWithAttrWrapper, "color">;
+
+def TestAttrWithWrappedEnum : AttrDef<Test_Dialect, "TestAttrWithWrappedEnum"> {
+  let mnemonic = "with_wrapped_enum";
+  let parameters = (ins
+    "int":$id,
+    EnumParameter<TestEnumWithAttrWrapper>:$color
+  );
+  let assemblyFormat = "`<` $id `,` $color `>`";
+}
+
 def TestTypeDef : TypeDef<Test_Dialect, "TestTypeDef"> {
   let mnemonic = "test_type_def";
 }
@@ -165,6 +185,13 @@ def TestEnum :
 // CHECK-NEXT:   `alpha` | `beta`   # mode
 // CHECK-NEXT: >
 
+// CHECK: TestAttrWithWrappedEnumAttr
+// CHECK: Syntax:
+// CHECK:      #test.with_wrapped_enum<
+// CHECK-NEXT:   int,   # id
+// CHECK-NEXT:   `red` | `green` | `blue`   # color
+// CHECK-NEXT: >
+
 // CHECK: ## Type constraints
 // CHECK: ### type summary
 // CHECK: type description

>From 0f0ab35b1a56d74a3ed24c45940f3cf793f7271d 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 6/6] [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 | 181 ++++++++++++++++++
 llvm/tools/llvm-cov/CodeCoverage.cpp          |  45 ++++-
 llvm/tools/llvm-cov/CoverageReport.cpp        |  16 ++
 llvm/tools/llvm-cov/CoverageSummaryInfo.h     |   9 +
 llvm/tools/llvm-cov/CoverageViewOptions.h     |  59 ++++++
 17 files changed, 377 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 100755 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 100755 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 100755
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 100755
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..a9f33ab2ca396
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/exclude-markers.test
@@ -0,0 +1,181 @@
+// 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
diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp
index e19101ac76045..2fc4f18b55509 100644
--- a/llvm/tools/llvm-cov/CodeCoverage.cpp
+++ b/llvm/tools/llvm-cov/CodeCoverage.cpp
@@ -284,6 +284,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 +300,9 @@ 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::attachExpansionSubViews(
@@ -740,6 +743,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 +966,16 @@ 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);
+    if (!ExcludeRegionStartRegex.empty())
+      ViewOpts.ExcludeRegionStartRE =
+          std::make_shared<Regex>(ExcludeRegionStartRegex);
+    if (!ExcludeRegionStopRegex.empty())
+      ViewOpts.ExcludeRegionStopRE =
+          std::make_shared<Regex>(ExcludeRegionStopRegex);
+
     if (!Arches.empty()) {
       for (const std::string &Arch : Arches) {
         if (Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) {
@@ -1288,6 +1317,13 @@ int CodeCoverageTool::doReport(int argc, const char **argv,
   if (!Coverage)
     return 1;
 
+  // Pre-scan source files for exclusion markers (getSourceFile handles path
+  // remapping and triggers scanForExclusionMarkers).
+  if (ViewOpts.ExcludeLineRE || ViewOpts.ExcludeRegionStartRE) {
+    for (StringRef SF : Coverage->getUniqueSourceFiles())
+      getSourceFile(SF);
+  }
+
   CoverageReport Report(ViewOpts, *Coverage);
   if (!ShowFunctionSummaries) {
     if (SourceFiles.empty())
@@ -1364,6 +1400,13 @@ int CodeCoverageTool::doExport(int argc, const char **argv,
     return 1;
   }
 
+  // Pre-scan source files for exclusion markers (getSourceFile handles path
+  // remapping and triggers scanForExclusionMarkers).
+  if (ViewOpts.ExcludeLineRE || ViewOpts.ExcludeRegionStartRE) {
+    for (StringRef SF : Coverage->getUniqueSourceFiles())
+      getSourceFile(SF);
+  }
+
   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..16af86f037192 100644
--- a/llvm/tools/llvm-cov/CoverageViewOptions.h
+++ b/llvm/tools/llvm-cov/CoverageViewOptions.h
@@ -10,7 +10,13 @@
 #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/MemoryBuffer.h"
+#include "llvm/Support/Regex.h"
+#include <memory>
 #include <vector>
 
 namespace llvm {
@@ -61,6 +67,59 @@ struct CoverageViewOptions {
   float HighCovWatermark;
   float LowCovWatermark;
 
+  /// Per-file sets of line numbers excluded from coverage via inline markers.
+  /// Mutable because it's lazily populated as source files are loaded.
+  mutable 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.
+  void scanForExclusionMarkers(StringRef Filename,
+                               const MemoryBuffer &Buffer) const {
+    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