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
