https://github.com/mgcarrasco updated 
https://github.com/llvm/llvm-project/pull/171191

>From ae1650f76560a5dafcfe1d2344ca31e0c707e280 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <[email protected]>
Date: Mon, 8 Dec 2025 13:30:06 -0600
Subject: [PATCH 1/3] [sancov] Add -diff option to compute set difference of
 sancov files

Add a new -diff action that computes the difference between two sancov
coverage files (A - B) and writes the result to a new .sancov file.

The option takes exactly two input .sancov files and requires an
--output option to specify the output file. The output file preserves
the binary format (magic number and bitness) from the first input file.

A warning is emitted if the two input files have different bitness
(32-bit vs 64-bit), though the operation proceeds using the bitness
from file A.
---
 .../tools/sancov/diff-different-files.test    |   7 ++
 llvm/test/tools/sancov/diff-same-file.test    |   6 ++
 llvm/tools/sancov/Opts.td                     |   6 ++
 llvm/tools/sancov/sancov.cpp                  | 101 ++++++++++++++++++
 4 files changed, 120 insertions(+)
 create mode 100644 llvm/test/tools/sancov/diff-different-files.test
 create mode 100644 llvm/test/tools/sancov/diff-same-file.test

diff --git a/llvm/test/tools/sancov/diff-different-files.test 
b/llvm/test/tools/sancov/diff-different-files.test
new file mode 100644
index 0000000000000..db46593099e74
--- /dev/null
+++ b/llvm/test/tools/sancov/diff-different-files.test
@@ -0,0 +1,7 @@
+REQUIRES: x86-registered-target && host-byteorder-little-endian
+RUN: rm -f %t.out.sancov
+RUN: sancov -diff --output=%t.out.sancov %p/Inputs/test-linux_x86_64.1.sancov 
%p/Inputs/test-linux_x86_64.0.sancov
+RUN: sancov -print %t.out.sancov | FileCheck %s 
+
+CHECK: 0x4e14c2
+CHECK: 0x4e178c
diff --git a/llvm/test/tools/sancov/diff-same-file.test 
b/llvm/test/tools/sancov/diff-same-file.test
new file mode 100644
index 0000000000000..fd3b5de79d9a6
--- /dev/null
+++ b/llvm/test/tools/sancov/diff-same-file.test
@@ -0,0 +1,6 @@
+REQUIRES: x86-registered-target && host-byteorder-little-endian
+RUN: rm -f %t.out.sancov
+RUN: sancov -diff --output=%t.out.sancov %p/Inputs/test-linux_x86_64.0.sancov 
%p/Inputs/test-linux_x86_64.0.sancov
+RUN: sancov -print %t.out.sancov | FileCheck %s --allow-empty 
--check-prefix=EMPTY
+
+EMPTY-NOT: {{.}} 
diff --git a/llvm/tools/sancov/Opts.td b/llvm/tools/sancov/Opts.td
index 2e8af81b2a40d..01de87d596327 100644
--- a/llvm/tools/sancov/Opts.td
+++ b/llvm/tools/sancov/Opts.td
@@ -22,6 +22,8 @@ def : Flag<["-"], "v">, Alias<version>, HelpText<"Alias for 
--version">, Group<g
 def action_grp : OptionGroup<"Action">, HelpText<"Action (required)">;
 def print : F<"print", "Print coverage addresses">,
   Group<action_grp>;
+def diff : F<"diff", "Compute difference between two sancov files (A - B) and 
write to the new output sancov file">,
+  Group<action_grp>;
 def printCoveragePcs : F<"print-coverage-pcs", "Print coverage instrumentation 
points addresses.">,
   Group<action_grp>;
 def coveredFunctions : F<"covered-functions", "Print all covered funcions.">,
@@ -56,3 +58,7 @@ defm stripPathPrefix
 defm ignorelist
     : Eq<"ignorelist", "Ignorelist file (sanitizer ignorelist format)">,
       MetaVarName<"<string>">;
+
+defm output
+    : Eq<"output", "Output file for diff action">,
+      MetaVarName<"<string>">;
diff --git a/llvm/tools/sancov/sancov.cpp b/llvm/tools/sancov/sancov.cpp
index a0585fad024c7..78fa3ebedd817 100644
--- a/llvm/tools/sancov/sancov.cpp
+++ b/llvm/tools/sancov/sancov.cpp
@@ -92,6 +92,7 @@ class SancovOptTable : public opt::GenericOptTable {
 
 enum ActionType {
   CoveredFunctionsAction,
+  DiffAction,
   HtmlReportAction,
   MergeAction,
   NotCoveredFunctionsAction,
@@ -108,6 +109,7 @@ static bool ClSkipDeadFiles;
 static bool ClUseDefaultIgnorelist;
 static std::string ClStripPathPrefix;
 static std::string ClIgnorelist;
+static std::string ClOutputFile;
 
 static const char *const DefaultIgnorelistStr = "fun:__sanitizer_.*\n"
                                                 "src:/usr/include/.*\n"
@@ -139,6 +141,10 @@ struct RawCoverage {
   static ErrorOr<std::unique_ptr<RawCoverage>>
   read(const std::string &FileName);
 
+  // Write binary .sancov file.
+  static void write(const std::string &FileName, const RawCoverage &Coverage,
+                    FileHeader Header);
+
   std::unique_ptr<std::set<uint64_t>> Addrs;
 };
 
@@ -277,6 +283,33 @@ raw_ostream &operator<<(raw_ostream &OS, const RawCoverage 
&CoverageData) {
   return OS;
 }
 
+// Write coverage addresses in binary format.
+void RawCoverage::write(const std::string &FileName,
+                        const RawCoverage &Coverage, FileHeader Header) {
+  std::error_code EC;
+  raw_fd_ostream OS(FileName, EC, sys::fs::OF_None);
+  failIfError(EC);
+
+  OS.write(reinterpret_cast<const char *>(&Header), sizeof(Header));
+
+  switch (Header.Bitness) {
+  case Bitness64:
+    for (auto Addr : *Coverage.Addrs) {
+      uint64_t Addr64 = Addr;
+      OS.write(reinterpret_cast<const char *>(&Addr64), sizeof(Addr64));
+    }
+    break;
+  case Bitness32:
+    for (auto Addr : *Coverage.Addrs) {
+      uint32_t Addr32 = static_cast<uint32_t>(Addr);
+      OS.write(reinterpret_cast<const char *>(&Addr32), sizeof(Addr32));
+    }
+    break;
+  default:
+    fail("Unsupported bitness: " + std::to_string(Header.Bitness));
+  }
+}
+
 static raw_ostream &operator<<(raw_ostream &OS, const CoverageStats &Stats) {
   OS << "all-edges: " << Stats.AllPoints << "\n";
   OS << "cov-edges: " << Stats.CovPoints << "\n";
@@ -1015,6 +1048,59 @@ static void readAndPrintRawCoverage(const 
std::vector<std::string> &FileNames,
   }
 }
 
+static const char *bitnessToString(uint32_t Bitness) {
+  switch (Bitness) {
+  case Bitness64:
+    return "64-bit";
+  case Bitness32:
+    return "32-bit";
+  default:
+    fail("Unsupported bitness: " + std::to_string(Bitness));
+    return nullptr;
+  }
+}
+
+// Compute difference between two coverage files (A - B) and write to output
+// file.
+static void diffRawCoverage(const std::string &FileA, const std::string &FileB,
+                            const std::string &OutputFile) {
+  auto CovA = RawCoverage::read(FileA);
+  failIfError(CovA);
+
+  auto CovB = RawCoverage::read(FileB);
+  failIfError(CovB);
+
+  // Determine bitness from both files
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErrA =
+      MemoryBuffer::getFile(FileA);
+  failIfError(BufOrErrA);
+  const FileHeader *HeaderA =
+      reinterpret_cast<const FileHeader *>(BufOrErrA.get()->getBufferStart());
+
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErrB =
+      MemoryBuffer::getFile(FileB);
+  failIfError(BufOrErrB);
+  const FileHeader *HeaderB =
+      reinterpret_cast<const FileHeader *>(BufOrErrB.get()->getBufferStart());
+
+  // Warn if bitness differs
+  if (HeaderA->Bitness != HeaderB->Bitness) {
+    errs() << "WARNING: Input files have different bitness (File A: "
+           << bitnessToString(HeaderA->Bitness)
+           << ", File B: " << bitnessToString(HeaderB->Bitness)
+           << "). Using bitness from File A.\n";
+  }
+
+  // Compute A - B
+  auto DiffAddrs = std::make_unique<std::set<uint64_t>>();
+  std::set_difference(CovA.get()->Addrs->begin(), CovA.get()->Addrs->end(),
+                      CovB.get()->Addrs->begin(), CovB.get()->Addrs->end(),
+                      std::inserter(*DiffAddrs, DiffAddrs->end()));
+
+  RawCoverage DiffCov(std::move(DiffAddrs));
+  RawCoverage::write(OutputFile, DiffCov, *HeaderA);
+}
+
 static std::unique_ptr<SymbolizedCoverage>
 merge(const std::vector<std::unique_ptr<SymbolizedCoverage>> &Coverages) {
   if (Coverages.empty())
@@ -1176,6 +1262,9 @@ static void parseArgs(int Argc, char **Argv) {
     case OPT_print:
       Action = ActionType::PrintAction;
       break;
+    case OPT_diff:
+      Action = ActionType::DiffAction;
+      break;
     case OPT_printCoveragePcs:
       Action = ActionType::PrintCovPointsAction;
       break;
@@ -1209,6 +1298,7 @@ static void parseArgs(int Argc, char **Argv) {
 
   ClStripPathPrefix = Args.getLastArgValue(OPT_stripPathPrefix_EQ);
   ClIgnorelist = Args.getLastArgValue(OPT_ignorelist_EQ);
+  ClOutputFile = Args.getLastArgValue(OPT_output_EQ);
 }
 
 int sancov_main(int Argc, char **Argv, const llvm::ToolContext &) {
@@ -1223,6 +1313,16 @@ int sancov_main(int Argc, char **Argv, const 
llvm::ToolContext &) {
     readAndPrintRawCoverage(ClInputFiles, outs());
     return 0;
   }
+  if (Action == DiffAction) {
+    // -diff requires exactly 2 input files and an output file.
+    failIf(ClInputFiles.size() != 2,
+           "diff action requires exactly 2 input sancov files");
+    failIf(
+        ClOutputFile.empty(),
+        "diff action requires --output option to specify output sancov file");
+    diffRawCoverage(ClInputFiles[0], ClInputFiles[1], ClOutputFile);
+    return 0;
+  }
   if (Action == PrintCovPointsAction) {
     // -print-coverage-points doesn't need coverage files.
     for (const std::string &ObjFile : ClInputFiles) {
@@ -1257,6 +1357,7 @@ int sancov_main(int Argc, char **Argv, const 
llvm::ToolContext &) {
     errs() << "-html-report option is removed: "
               "use -symbolize & coverage-report-server.py instead\n";
     return 1;
+  case DiffAction:
   case PrintAction:
   case PrintCovPointsAction:
     llvm_unreachable("unsupported action");

>From 829db2af5acf4b83aba50d76102bdc3019fe0b85 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <[email protected]>
Date: Tue, 9 Dec 2025 05:21:40 -0600
Subject: [PATCH 2/3] [sancov] Add -union option to merge multiple sancov files

Add a new -union action that computes the union of multiple sancov
coverage files and writes the result to a new .sancov file.

The option takes one or more input .sancov files and requires an
--output option to specify the output file. The output file preserves
the binary format (magic number and bitness) from the first input file.

A warning is emitted if any input file has different bitness (32-bit
vs 64-bit) than the first file, though the operation proceeds using
the bitness from the first file.
---
 .../tools/sancov/union-different-files.test   | 11 ++++
 llvm/test/tools/sancov/union-same-file.test   | 10 +++
 llvm/tools/sancov/Opts.td                     |  4 +-
 llvm/tools/sancov/sancov.cpp                  | 62 ++++++++++++++++++-
 4 files changed, 85 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/tools/sancov/union-different-files.test
 create mode 100644 llvm/test/tools/sancov/union-same-file.test

diff --git a/llvm/test/tools/sancov/union-different-files.test 
b/llvm/test/tools/sancov/union-different-files.test
new file mode 100644
index 0000000000000..47dafc97ad3c2
--- /dev/null
+++ b/llvm/test/tools/sancov/union-different-files.test
@@ -0,0 +1,11 @@
+REQUIRES: x86-registered-target && host-byteorder-little-endian
+RUN: rm -f %t.out.sancov
+RUN: sancov -union --output=%t.out.sancov %p/Inputs/test-linux_x86_64.1.sancov 
%p/Inputs/test-linux_x86_64.0.sancov
+RUN: sancov -print %t.out.sancov | FileCheck %s 
+
+CHECK: 0x4e1472
+CHECK: 0x4e14c2
+CHECK: 0x4e1520
+CHECK: 0x4e1553
+CHECK: 0x4e1586
+CHECK: 0x4e178c
diff --git a/llvm/test/tools/sancov/union-same-file.test 
b/llvm/test/tools/sancov/union-same-file.test
new file mode 100644
index 0000000000000..e7991d24fb7ac
--- /dev/null
+++ b/llvm/test/tools/sancov/union-same-file.test
@@ -0,0 +1,10 @@
+REQUIRES: x86-registered-target && host-byteorder-little-endian
+RUN: rm -f %t.out.sancov
+RUN: sancov -union --output=%t.out.sancov %p/Inputs/test-linux_x86_64.0.sancov 
%p/Inputs/test-linux_x86_64.0.sancov
+RUN: sancov -print %t.out.sancov | FileCheck %s
+
+CHECK: 0x4e132b
+CHECK: 0x4e1472
+CHECK: 0x4e1520
+CHECK: 0x4e1553
+CHECK: 0x4e1586
diff --git a/llvm/tools/sancov/Opts.td b/llvm/tools/sancov/Opts.td
index 01de87d596327..411d08a033050 100644
--- a/llvm/tools/sancov/Opts.td
+++ b/llvm/tools/sancov/Opts.td
@@ -24,6 +24,8 @@ def print : F<"print", "Print coverage addresses">,
   Group<action_grp>;
 def diff : F<"diff", "Compute difference between two sancov files (A - B) and 
write to the new output sancov file">,
   Group<action_grp>;
+def union_files : F<"union", "Compute union of multiple sancov files and write 
to the new output sancov file">,
+  Group<action_grp>;
 def printCoveragePcs : F<"print-coverage-pcs", "Print coverage instrumentation 
points addresses.">,
   Group<action_grp>;
 def coveredFunctions : F<"covered-functions", "Print all covered funcions.">,
@@ -60,5 +62,5 @@ defm ignorelist
       MetaVarName<"<string>">;
 
 defm output
-    : Eq<"output", "Output file for diff action">,
+    : Eq<"output", "Output file for diff and union actions">,
       MetaVarName<"<string>">;
diff --git a/llvm/tools/sancov/sancov.cpp b/llvm/tools/sancov/sancov.cpp
index 78fa3ebedd817..bb5507bbbf02d 100644
--- a/llvm/tools/sancov/sancov.cpp
+++ b/llvm/tools/sancov/sancov.cpp
@@ -99,7 +99,8 @@ enum ActionType {
   PrintAction,
   PrintCovPointsAction,
   StatsAction,
-  SymbolizeAction
+  SymbolizeAction,
+  UnionAction
 };
 
 static ActionType Action;
@@ -1101,6 +1102,48 @@ static void diffRawCoverage(const std::string &FileA, 
const std::string &FileB,
   RawCoverage::write(OutputFile, DiffCov, *HeaderA);
 }
 
+// Compute union of multiple coverage files and write to output file.
+static void unionRawCoverage(const std::vector<std::string> &InputFiles,
+                             const std::string &OutputFile) {
+  failIf(InputFiles.empty(), "union action requires at least one input file");
+
+  // Read the first file to get the header and initial coverage
+  auto UnionCov = RawCoverage::read(InputFiles[0]);
+  failIfError(UnionCov);
+
+  // Get header from first file
+  ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
+      MemoryBuffer::getFile(InputFiles[0]);
+  failIfError(BufOrErr);
+  const FileHeader *FirstHeader =
+      reinterpret_cast<const FileHeader *>(BufOrErr.get()->getBufferStart());
+  FileHeader Header = *FirstHeader;
+
+  for (size_t I = 1; I < InputFiles.size(); ++I) {
+    auto Cov = RawCoverage::read(InputFiles[I]);
+    failIfError(Cov);
+
+    // Check bitness of current file
+    ErrorOr<std::unique_ptr<MemoryBuffer>> CurBufOrErr =
+        MemoryBuffer::getFile(InputFiles[I]);
+    failIfError(CurBufOrErr);
+    const FileHeader *CurHeader = reinterpret_cast<const FileHeader *>(
+        CurBufOrErr.get()->getBufferStart());
+
+    if (CurHeader->Bitness != Header.Bitness) {
+      errs() << "WARNING: Input file has different bitness (File "
+             << InputFiles[I] << ": " << bitnessToString(CurHeader->Bitness)
+             << ", First file: " << bitnessToString(Header.Bitness)
+             << "). Using bitness from first file.\n";
+    }
+
+    UnionCov.get()->Addrs->insert(Cov.get()->Addrs->begin(),
+                                  Cov.get()->Addrs->end());
+  }
+
+  RawCoverage::write(OutputFile, *UnionCov.get(), Header);
+}
+
 static std::unique_ptr<SymbolizedCoverage>
 merge(const std::vector<std::unique_ptr<SymbolizedCoverage>> &Coverages) {
   if (Coverages.empty())
@@ -1239,6 +1282,9 @@ static void parseArgs(int Argc, char **Argv) {
         "  Depending on chosen action the tool expects different input 
files:\n"
         "    -print-coverage-pcs     - coverage-instrumented binary files\n"
         "    -print-coverage         - .sancov files\n"
+        "    -diff                   - two .sancov files & --output option\n"
+        "    -union                  - one or more .sancov files & --output "
+        "option\n"
         "    <other actions>         - .sancov files & corresponding binary "
         "files, .symcov files\n");
     std::exit(0);
@@ -1265,6 +1311,9 @@ static void parseArgs(int Argc, char **Argv) {
     case OPT_diff:
       Action = ActionType::DiffAction;
       break;
+    case OPT_union_files:
+      Action = ActionType::UnionAction;
+      break;
     case OPT_printCoveragePcs:
       Action = ActionType::PrintCovPointsAction;
       break;
@@ -1323,6 +1372,16 @@ int sancov_main(int Argc, char **Argv, const 
llvm::ToolContext &) {
     diffRawCoverage(ClInputFiles[0], ClInputFiles[1], ClOutputFile);
     return 0;
   }
+  if (Action == UnionAction) {
+    // -union requires at least 1 input file and an output file.
+    failIf(ClInputFiles.empty(),
+           "union action requires at least one input sancov file");
+    failIf(
+        ClOutputFile.empty(),
+        "union action requires --output option to specify output sancov file");
+    unionRawCoverage(ClInputFiles, ClOutputFile);
+    return 0;
+  }
   if (Action == PrintCovPointsAction) {
     // -print-coverage-points doesn't need coverage files.
     for (const std::string &ObjFile : ClInputFiles) {
@@ -1358,6 +1417,7 @@ int sancov_main(int Argc, char **Argv, const 
llvm::ToolContext &) {
               "use -symbolize & coverage-report-server.py instead\n";
     return 1;
   case DiffAction:
+  case UnionAction:
   case PrintAction:
   case PrintCovPointsAction:
     llvm_unreachable("unsupported action");

>From 9210908ccddc7954f01d241856632ca7e4d934b0 Mon Sep 17 00:00:00 2001
From: Manuel Carrasco <[email protected]>
Date: Mon, 8 Dec 2025 13:32:59 -0600
Subject: [PATCH 3/3] [sancov] Refreshed CLI for sancov in docs.

---
 clang/docs/SanitizerCoverage.rst | 45 +++++++++++++++++++++++---------
 1 file changed, 33 insertions(+), 12 deletions(-)

diff --git a/clang/docs/SanitizerCoverage.rst b/clang/docs/SanitizerCoverage.rst
index 23720e542e4e9..4ab2d09366f4f 100644
--- a/clang/docs/SanitizerCoverage.rst
+++ b/clang/docs/SanitizerCoverage.rst
@@ -563,18 +563,39 @@ Sancov matches these files using module names and 
binaries file names.
 
 .. code-block:: console
 
-    USAGE: sancov [options] <action> (<binary file>|<.sancov file>)...
-
-    Action (required)
-      -print                    - Print coverage addresses
-      -covered-functions        - Print all covered functions.
-      -not-covered-functions    - Print all not covered functions.
-      -symbolize                - Symbolizes the report.
-
-    Options
-      -blocklist=<string>         - Blocklist file (sanitizer blocklist 
format).
-      -demangle                   - Print demangled function name.
-      -strip_path_prefix=<string> - Strip this prefix from file paths in 
reports
+    USAGE: sancov [options] <action> <binary files...> <.sancov files...> 
<.symcov files...>
+
+    Action (required):
+      -covered-functions     Print all covered funcions.
+      -diff                  Compute difference between two sancov files (A - 
B) and write to the new output sancov file
+      -html-report           REMOVED. Use -symbolize & 
coverage-report-server.py.
+      -merge                 Merges reports.
+      -not-covered-functions Print all not covered funcions.
+      -print-coverage-pcs    Print coverage instrumentation points addresses.
+      -print-coverage-stats  Print coverage statistics.
+      -print                 Print coverage addresses
+      -symbolize             Produces a symbolized JSON report from binary 
report.
+      -union                 Compute union of multiple sancov files and write 
to the new output sancov file
+
+    Generic Options:
+      -help    Display this help
+      -h       Alias for --help
+      -version Display the version
+      -v       Alias for --version
+
+    OPTIONS:
+      -demangle=0          Alias for --no-demangle
+      -demangle            Demangle function names
+      -ignorelist=<string> Ignorelist file (sanitizer ignorelist format)
+      -no-demangle         Do not demangle function names
+      -no-skip-dead-files  List dead source files in reports
+      -output=<string>     Output file for diff and union actions
+      -skip-dead-files=0   Alias for --no-skip-dead-files
+      -skip-dead-files     Do not list dead source files in reports
+      -strip_path_prefix=<string>
+                          Strip this prefix from files paths in reports
+      -use_default_ignorelist=0
+                          Alias for --no-use_default_ignore_list
 
 
 Coverage Reports

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to