summaryrefslogtreecommitdiff
path: root/llvm/tools/llvm-cov
diff options
context:
space:
mode:
Diffstat (limited to 'llvm/tools/llvm-cov')
-rw-r--r--llvm/tools/llvm-cov/CodeCoverage.cpp1077
-rw-r--r--llvm/tools/llvm-cov/CoverageExporter.h51
-rw-r--r--llvm/tools/llvm-cov/CoverageExporterJson.cpp229
-rw-r--r--llvm/tools/llvm-cov/CoverageExporterJson.h35
-rw-r--r--llvm/tools/llvm-cov/CoverageExporterLcov.cpp124
-rw-r--r--llvm/tools/llvm-cov/CoverageExporterLcov.h35
-rw-r--r--llvm/tools/llvm-cov/CoverageFilters.cpp85
-rw-r--r--llvm/tools/llvm-cov/CoverageFilters.h157
-rw-r--r--llvm/tools/llvm-cov/CoverageReport.cpp449
-rw-r--r--llvm/tools/llvm-cov/CoverageReport.h69
-rw-r--r--llvm/tools/llvm-cov/CoverageSummaryInfo.cpp70
-rw-r--r--llvm/tools/llvm-cov/CoverageSummaryInfo.h218
-rw-r--r--llvm/tools/llvm-cov/CoverageViewOptions.h74
-rw-r--r--llvm/tools/llvm-cov/RenderingSupport.h60
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageView.cpp266
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageView.h266
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp697
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewHTML.h97
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewText.cpp251
-rw-r--r--llvm/tools/llvm-cov/SourceCoverageViewText.h88
-rw-r--r--llvm/tools/llvm-cov/TestingSupport.cpp108
-rw-r--r--llvm/tools/llvm-cov/gcov.cpp150
-rw-r--r--llvm/tools/llvm-cov/llvm-cov.cpp95
23 files changed, 4751 insertions, 0 deletions
diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp
new file mode 100644
index 000000000000..7151cfb032f3
--- /dev/null
+++ b/llvm/tools/llvm-cov/CodeCoverage.cpp
@@ -0,0 +1,1077 @@
+//===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// The 'CodeCoverageTool' class implements a command line tool to analyze and
+// report coverage information using the profiling instrumentation and code
+// coverage mapping.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoverageExporterJson.h"
+#include "CoverageExporterLcov.h"
+#include "CoverageFilters.h"
+#include "CoverageReport.h"
+#include "CoverageSummaryInfo.h"
+#include "CoverageViewOptions.h"
+#include "RenderingSupport.h"
+#include "SourceCoverageView.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
+#include "llvm/ProfileData/InstrProfReader.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/Program.h"
+#include "llvm/Support/ScopedPrinter.h"
+#include "llvm/Support/ThreadPool.h"
+#include "llvm/Support/Threading.h"
+#include "llvm/Support/ToolOutputFile.h"
+
+#include <functional>
+#include <map>
+#include <system_error>
+
+using namespace llvm;
+using namespace coverage;
+
+void exportCoverageDataToJson(const coverage::CoverageMapping &CoverageMapping,
+ const CoverageViewOptions &Options,
+ raw_ostream &OS);
+
+namespace {
+/// The implementation of the coverage tool.
+class CodeCoverageTool {
+public:
+ enum Command {
+ /// The show command.
+ Show,
+ /// The report command.
+ Report,
+ /// The export command.
+ Export
+ };
+
+ int run(Command Cmd, int argc, const char **argv);
+
+private:
+ /// Print the error message to the error output stream.
+ void error(const Twine &Message, StringRef Whence = "");
+
+ /// Print the warning message to the error output stream.
+ void warning(const Twine &Message, StringRef Whence = "");
+
+ /// Convert \p Path into an absolute path and append it to the list
+ /// of collected paths.
+ void addCollectedPath(const std::string &Path);
+
+ /// If \p Path is a regular file, collect the path. If it's a
+ /// directory, recursively collect all of the paths within the directory.
+ void collectPaths(const std::string &Path);
+
+ /// Return a memory buffer for the given source file.
+ ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
+
+ /// Create source views for the expansions of the view.
+ void attachExpansionSubViews(SourceCoverageView &View,
+ ArrayRef<ExpansionRecord> Expansions,
+ const CoverageMapping &Coverage);
+
+ /// Create the source view of a particular function.
+ std::unique_ptr<SourceCoverageView>
+ createFunctionView(const FunctionRecord &Function,
+ const CoverageMapping &Coverage);
+
+ /// Create the main source view of a particular source file.
+ std::unique_ptr<SourceCoverageView>
+ createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage);
+
+ /// Load the coverage mapping data. Return nullptr if an error occurred.
+ std::unique_ptr<CoverageMapping> load();
+
+ /// Create a mapping from files in the Coverage data to local copies
+ /// (path-equivalence).
+ void remapPathNames(const CoverageMapping &Coverage);
+
+ /// Remove input source files which aren't mapped by \p Coverage.
+ void removeUnmappedInputs(const CoverageMapping &Coverage);
+
+ /// If a demangler is available, demangle all symbol names.
+ void demangleSymbols(const CoverageMapping &Coverage);
+
+ /// Write out a source file view to the filesystem.
+ void writeSourceFileView(StringRef SourceFile, CoverageMapping *Coverage,
+ CoveragePrinter *Printer, bool ShowFilenames);
+
+ typedef llvm::function_ref<int(int, const char **)> CommandLineParserType;
+
+ int doShow(int argc, const char **argv,
+ CommandLineParserType commandLineParser);
+
+ int doReport(int argc, const char **argv,
+ CommandLineParserType commandLineParser);
+
+ int doExport(int argc, const char **argv,
+ CommandLineParserType commandLineParser);
+
+ std::vector<StringRef> ObjectFilenames;
+ CoverageViewOptions ViewOpts;
+ CoverageFiltersMatchAll Filters;
+ CoverageFilters IgnoreFilenameFilters;
+
+ /// The path to the indexed profile.
+ std::string PGOFilename;
+
+ /// A list of input source files.
+ std::vector<std::string> SourceFiles;
+
+ /// In -path-equivalence mode, this maps the absolute paths from the coverage
+ /// mapping data to the input source files.
+ StringMap<std::string> RemappedFilenames;
+
+ /// The coverage data path to be remapped from, and the source path to be
+ /// remapped to, when using -path-equivalence.
+ Optional<std::pair<std::string, std::string>> PathRemapping;
+
+ /// The architecture the coverage mapping data targets.
+ std::vector<StringRef> CoverageArches;
+
+ /// A cache for demangled symbols.
+ DemangleCache DC;
+
+ /// A lock which guards printing to stderr.
+ std::mutex ErrsLock;
+
+ /// A container for input source file buffers.
+ std::mutex LoadedSourceFilesLock;
+ std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>>
+ LoadedSourceFiles;
+
+ /// Whitelist from -name-whitelist to be used for filtering.
+ std::unique_ptr<SpecialCaseList> NameWhitelist;
+};
+}
+
+static std::string getErrorString(const Twine &Message, StringRef Whence,
+ bool Warning) {
+ std::string Str = (Warning ? "warning" : "error");
+ Str += ": ";
+ if (!Whence.empty())
+ Str += Whence.str() + ": ";
+ Str += Message.str() + "\n";
+ return Str;
+}
+
+void CodeCoverageTool::error(const Twine &Message, StringRef Whence) {
+ std::unique_lock<std::mutex> Guard{ErrsLock};
+ ViewOpts.colored_ostream(errs(), raw_ostream::RED)
+ << getErrorString(Message, Whence, false);
+}
+
+void CodeCoverageTool::warning(const Twine &Message, StringRef Whence) {
+ std::unique_lock<std::mutex> Guard{ErrsLock};
+ ViewOpts.colored_ostream(errs(), raw_ostream::RED)
+ << getErrorString(Message, Whence, true);
+}
+
+void CodeCoverageTool::addCollectedPath(const std::string &Path) {
+ SmallString<128> EffectivePath(Path);
+ if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) {
+ error(EC.message(), Path);
+ return;
+ }
+ sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true);
+ if (!IgnoreFilenameFilters.matchesFilename(EffectivePath))
+ SourceFiles.emplace_back(EffectivePath.str());
+}
+
+void CodeCoverageTool::collectPaths(const std::string &Path) {
+ llvm::sys::fs::file_status Status;
+ llvm::sys::fs::status(Path, Status);
+ if (!llvm::sys::fs::exists(Status)) {
+ if (PathRemapping)
+ addCollectedPath(Path);
+ else
+ warning("Source file doesn't exist, proceeded by ignoring it.", Path);
+ return;
+ }
+
+ if (llvm::sys::fs::is_regular_file(Status)) {
+ addCollectedPath(Path);
+ return;
+ }
+
+ if (llvm::sys::fs::is_directory(Status)) {
+ std::error_code EC;
+ for (llvm::sys::fs::recursive_directory_iterator F(Path, EC), E;
+ F != E; F.increment(EC)) {
+
+ auto Status = F->status();
+ if (!Status) {
+ warning(Status.getError().message(), F->path());
+ continue;
+ }
+
+ if (Status->type() == llvm::sys::fs::file_type::regular_file)
+ addCollectedPath(F->path());
+ }
+ }
+}
+
+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};
+ if (!RemappedFilenames.empty()) {
+ auto Loc = RemappedFilenames.find(SourceFile);
+ if (Loc != RemappedFilenames.end())
+ SourceFile = Loc->second;
+ }
+ for (const auto &Files : LoadedSourceFiles)
+ if (sys::fs::equivalent(SourceFile, Files.first))
+ return *Files.second;
+ auto Buffer = MemoryBuffer::getFile(SourceFile);
+ if (auto EC = Buffer.getError()) {
+ error(EC.message(), SourceFile);
+ return EC;
+ }
+ LoadedSourceFiles.emplace_back(SourceFile, std::move(Buffer.get()));
+ return *LoadedSourceFiles.back().second;
+}
+
+void CodeCoverageTool::attachExpansionSubViews(
+ SourceCoverageView &View, ArrayRef<ExpansionRecord> Expansions,
+ const CoverageMapping &Coverage) {
+ if (!ViewOpts.ShowExpandedRegions)
+ return;
+ for (const auto &Expansion : Expansions) {
+ auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);
+ if (ExpansionCoverage.empty())
+ continue;
+ auto SourceBuffer = getSourceFile(ExpansionCoverage.getFilename());
+ if (!SourceBuffer)
+ continue;
+
+ auto SubViewExpansions = ExpansionCoverage.getExpansions();
+ auto SubView =
+ SourceCoverageView::create(Expansion.Function.Name, SourceBuffer.get(),
+ ViewOpts, std::move(ExpansionCoverage));
+ attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
+ View.addExpansion(Expansion.Region, std::move(SubView));
+ }
+}
+
+std::unique_ptr<SourceCoverageView>
+CodeCoverageTool::createFunctionView(const FunctionRecord &Function,
+ const CoverageMapping &Coverage) {
+ auto FunctionCoverage = Coverage.getCoverageForFunction(Function);
+ if (FunctionCoverage.empty())
+ return nullptr;
+ auto SourceBuffer = getSourceFile(FunctionCoverage.getFilename());
+ if (!SourceBuffer)
+ return nullptr;
+
+ auto Expansions = FunctionCoverage.getExpansions();
+ auto View = SourceCoverageView::create(DC.demangle(Function.Name),
+ SourceBuffer.get(), ViewOpts,
+ std::move(FunctionCoverage));
+ attachExpansionSubViews(*View, Expansions, Coverage);
+
+ return View;
+}
+
+std::unique_ptr<SourceCoverageView>
+CodeCoverageTool::createSourceFileView(StringRef SourceFile,
+ const CoverageMapping &Coverage) {
+ auto SourceBuffer = getSourceFile(SourceFile);
+ if (!SourceBuffer)
+ return nullptr;
+ auto FileCoverage = Coverage.getCoverageForFile(SourceFile);
+ if (FileCoverage.empty())
+ return nullptr;
+
+ auto Expansions = FileCoverage.getExpansions();
+ auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(),
+ ViewOpts, std::move(FileCoverage));
+ attachExpansionSubViews(*View, Expansions, Coverage);
+ if (!ViewOpts.ShowFunctionInstantiations)
+ return View;
+
+ for (const auto &Group : Coverage.getInstantiationGroups(SourceFile)) {
+ // Skip functions which have a single instantiation.
+ if (Group.size() < 2)
+ continue;
+
+ for (const FunctionRecord *Function : Group.getInstantiations()) {
+ std::unique_ptr<SourceCoverageView> SubView{nullptr};
+
+ StringRef Funcname = DC.demangle(Function->Name);
+
+ if (Function->ExecutionCount > 0) {
+ auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
+ auto SubViewExpansions = SubViewCoverage.getExpansions();
+ SubView = SourceCoverageView::create(
+ Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
+ attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
+ }
+
+ unsigned FileID = Function->CountedRegions.front().FileID;
+ unsigned Line = 0;
+ for (const auto &CR : Function->CountedRegions)
+ if (CR.FileID == FileID)
+ Line = std::max(CR.LineEnd, Line);
+ View->addInstantiation(Funcname, Line, std::move(SubView));
+ }
+ }
+ return View;
+}
+
+static bool modifiedTimeGT(StringRef LHS, StringRef RHS) {
+ sys::fs::file_status Status;
+ if (sys::fs::status(LHS, Status))
+ return false;
+ auto LHSTime = Status.getLastModificationTime();
+ if (sys::fs::status(RHS, Status))
+ return false;
+ auto RHSTime = Status.getLastModificationTime();
+ return LHSTime > RHSTime;
+}
+
+std::unique_ptr<CoverageMapping> CodeCoverageTool::load() {
+ for (StringRef ObjectFilename : ObjectFilenames)
+ if (modifiedTimeGT(ObjectFilename, PGOFilename))
+ warning("profile data may be out of date - object is newer",
+ ObjectFilename);
+ auto CoverageOrErr =
+ CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArches);
+ if (Error E = CoverageOrErr.takeError()) {
+ error("Failed to load coverage: " + toString(std::move(E)),
+ join(ObjectFilenames.begin(), ObjectFilenames.end(), ", "));
+ return nullptr;
+ }
+ auto Coverage = std::move(CoverageOrErr.get());
+ unsigned Mismatched = Coverage->getMismatchedCount();
+ if (Mismatched) {
+ warning(Twine(Mismatched) + " functions have mismatched data");
+
+ if (ViewOpts.Debug) {
+ for (const auto &HashMismatch : Coverage->getHashMismatches())
+ errs() << "hash-mismatch: "
+ << "No profile record found for '" << HashMismatch.first << "'"
+ << " with hash = 0x" << Twine::utohexstr(HashMismatch.second)
+ << '\n';
+ }
+ }
+
+ remapPathNames(*Coverage);
+
+ if (!SourceFiles.empty())
+ removeUnmappedInputs(*Coverage);
+
+ demangleSymbols(*Coverage);
+
+ return Coverage;
+}
+
+void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) {
+ if (!PathRemapping)
+ return;
+
+ // Convert remapping paths to native paths with trailing seperators.
+ auto nativeWithTrailing = [](StringRef Path) -> std::string {
+ if (Path.empty())
+ return "";
+ SmallString<128> NativePath;
+ sys::path::native(Path, NativePath);
+ if (!sys::path::is_separator(NativePath.back()))
+ NativePath += sys::path::get_separator();
+ return NativePath.c_str();
+ };
+ std::string RemapFrom = nativeWithTrailing(PathRemapping->first);
+ std::string RemapTo = nativeWithTrailing(PathRemapping->second);
+
+ // Create a mapping from coverage data file paths to local paths.
+ for (StringRef Filename : Coverage.getUniqueSourceFiles()) {
+ SmallString<128> NativeFilename;
+ sys::path::native(Filename, NativeFilename);
+ if (NativeFilename.startswith(RemapFrom)) {
+ RemappedFilenames[Filename] =
+ RemapTo + NativeFilename.substr(RemapFrom.size()).str();
+ }
+ }
+
+ // Convert input files from local paths to coverage data file paths.
+ StringMap<std::string> InvRemappedFilenames;
+ for (const auto &RemappedFilename : RemappedFilenames)
+ InvRemappedFilenames[RemappedFilename.getValue()] = RemappedFilename.getKey();
+
+ for (std::string &Filename : SourceFiles) {
+ SmallString<128> NativeFilename;
+ sys::path::native(Filename, NativeFilename);
+ auto CovFileName = InvRemappedFilenames.find(NativeFilename);
+ if (CovFileName != InvRemappedFilenames.end())
+ Filename = CovFileName->second;
+ }
+}
+
+void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping &Coverage) {
+ std::vector<StringRef> CoveredFiles = Coverage.getUniqueSourceFiles();
+
+ auto UncoveredFilesIt = SourceFiles.end();
+ // The user may have specified source files which aren't in the coverage
+ // mapping. Filter these files away.
+ UncoveredFilesIt = std::remove_if(
+ SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) {
+ return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(),
+ SF);
+ });
+
+ SourceFiles.erase(UncoveredFilesIt, SourceFiles.end());
+}
+
+void CodeCoverageTool::demangleSymbols(const CoverageMapping &Coverage) {
+ if (!ViewOpts.hasDemangler())
+ return;
+
+ // Pass function names to the demangler in a temporary file.
+ int InputFD;
+ SmallString<256> InputPath;
+ std::error_code EC =
+ sys::fs::createTemporaryFile("demangle-in", "list", InputFD, InputPath);
+ if (EC) {
+ error(InputPath, EC.message());
+ return;
+ }
+ ToolOutputFile InputTOF{InputPath, InputFD};
+
+ unsigned NumSymbols = 0;
+ for (const auto &Function : Coverage.getCoveredFunctions()) {
+ InputTOF.os() << Function.Name << '\n';
+ ++NumSymbols;
+ }
+ InputTOF.os().close();
+
+ // Use another temporary file to store the demangler's output.
+ int OutputFD;
+ SmallString<256> OutputPath;
+ EC = sys::fs::createTemporaryFile("demangle-out", "list", OutputFD,
+ OutputPath);
+ if (EC) {
+ error(OutputPath, EC.message());
+ return;
+ }
+ ToolOutputFile OutputTOF{OutputPath, OutputFD};
+ OutputTOF.os().close();
+
+ // Invoke the demangler.
+ std::vector<StringRef> ArgsV;
+ for (StringRef Arg : ViewOpts.DemanglerOpts)
+ ArgsV.push_back(Arg);
+ Optional<StringRef> Redirects[] = {InputPath.str(), OutputPath.str(), {""}};
+ std::string ErrMsg;
+ int RC = sys::ExecuteAndWait(ViewOpts.DemanglerOpts[0], ArgsV,
+ /*env=*/None, Redirects, /*secondsToWait=*/0,
+ /*memoryLimit=*/0, &ErrMsg);
+ if (RC) {
+ error(ErrMsg, ViewOpts.DemanglerOpts[0]);
+ return;
+ }
+
+ // Parse the demangler's output.
+ auto BufOrError = MemoryBuffer::getFile(OutputPath);
+ if (!BufOrError) {
+ error(OutputPath, BufOrError.getError().message());
+ return;
+ }
+
+ std::unique_ptr<MemoryBuffer> DemanglerBuf = std::move(*BufOrError);
+
+ SmallVector<StringRef, 8> Symbols;
+ StringRef DemanglerData = DemanglerBuf->getBuffer();
+ DemanglerData.split(Symbols, '\n', /*MaxSplit=*/NumSymbols,
+ /*KeepEmpty=*/false);
+ if (Symbols.size() != NumSymbols) {
+ error("Demangler did not provide expected number of symbols");
+ return;
+ }
+
+ // Cache the demangled names.
+ unsigned I = 0;
+ for (const auto &Function : Coverage.getCoveredFunctions())
+ // On Windows, lines in the demangler's output file end with "\r\n".
+ // Splitting by '\n' keeps '\r's, so cut them now.
+ DC.DemangledNames[Function.Name] = Symbols[I++].rtrim();
+}
+
+void CodeCoverageTool::writeSourceFileView(StringRef SourceFile,
+ CoverageMapping *Coverage,
+ CoveragePrinter *Printer,
+ bool ShowFilenames) {
+ auto View = createSourceFileView(SourceFile, *Coverage);
+ if (!View) {
+ warning("The file '" + SourceFile + "' isn't covered.");
+ return;
+ }
+
+ auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false);
+ if (Error E = OSOrErr.takeError()) {
+ error("Could not create view file!", toString(std::move(E)));
+ return;
+ }
+ auto OS = std::move(OSOrErr.get());
+
+ View->print(*OS.get(), /*Wholefile=*/true,
+ /*ShowSourceName=*/ShowFilenames,
+ /*ShowTitle=*/ViewOpts.hasOutputDirectory());
+ Printer->closeViewFile(std::move(OS));
+}
+
+int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
+ cl::opt<std::string> CovFilename(
+ cl::Positional, cl::desc("Covered executable or object file."));
+
+ cl::list<std::string> CovFilenames(
+ "object", cl::desc("Coverage executable or object file"), cl::ZeroOrMore,
+ cl::CommaSeparated);
+
+ cl::list<std::string> InputSourceFiles(
+ cl::Positional, cl::desc("<Source files>"), cl::ZeroOrMore);
+
+ cl::opt<bool> DebugDumpCollectedPaths(
+ "dump-collected-paths", cl::Optional, cl::Hidden,
+ cl::desc("Show the collected paths to source files"));
+
+ cl::opt<std::string, true> PGOFilename(
+ "instr-profile", cl::Required, cl::location(this->PGOFilename),
+ cl::desc(
+ "File with the profile data obtained after an instrumented run"));
+
+ cl::list<std::string> Arches(
+ "arch", cl::desc("architectures of the coverage mapping binaries"));
+
+ cl::opt<bool> DebugDump("dump", cl::Optional,
+ cl::desc("Show internal debug dump"));
+
+ cl::opt<CoverageViewOptions::OutputFormat> Format(
+ "format", cl::desc("Output format for line-based coverage reports"),
+ cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text",
+ "Text output"),
+ clEnumValN(CoverageViewOptions::OutputFormat::HTML, "html",
+ "HTML output"),
+ clEnumValN(CoverageViewOptions::OutputFormat::Lcov, "lcov",
+ "lcov tracefile output")),
+ cl::init(CoverageViewOptions::OutputFormat::Text));
+
+ cl::opt<std::string> PathRemap(
+ "path-equivalence", cl::Optional,
+ cl::desc("<from>,<to> Map coverage data paths to local source file "
+ "paths"));
+
+ cl::OptionCategory FilteringCategory("Function filtering options");
+
+ cl::list<std::string> NameFilters(
+ "name", cl::Optional,
+ cl::desc("Show code coverage only for functions with the given name"),
+ cl::ZeroOrMore, cl::cat(FilteringCategory));
+
+ cl::list<std::string> NameFilterFiles(
+ "name-whitelist", cl::Optional,
+ cl::desc("Show code coverage only for functions listed in the given "
+ "file"),
+ cl::ZeroOrMore, cl::cat(FilteringCategory));
+
+ cl::list<std::string> NameRegexFilters(
+ "name-regex", cl::Optional,
+ cl::desc("Show code coverage only for functions that match the given "
+ "regular expression"),
+ cl::ZeroOrMore, cl::cat(FilteringCategory));
+
+ cl::list<std::string> IgnoreFilenameRegexFilters(
+ "ignore-filename-regex", cl::Optional,
+ cl::desc("Skip source code files with file paths that match the given "
+ "regular expression"),
+ cl::ZeroOrMore, cl::cat(FilteringCategory));
+
+ cl::opt<double> RegionCoverageLtFilter(
+ "region-coverage-lt", cl::Optional,
+ cl::desc("Show code coverage only for functions with region coverage "
+ "less than the given threshold"),
+ cl::cat(FilteringCategory));
+
+ cl::opt<double> RegionCoverageGtFilter(
+ "region-coverage-gt", cl::Optional,
+ cl::desc("Show code coverage only for functions with region coverage "
+ "greater than the given threshold"),
+ cl::cat(FilteringCategory));
+
+ cl::opt<double> LineCoverageLtFilter(
+ "line-coverage-lt", cl::Optional,
+ cl::desc("Show code coverage only for functions with line coverage less "
+ "than the given threshold"),
+ cl::cat(FilteringCategory));
+
+ cl::opt<double> LineCoverageGtFilter(
+ "line-coverage-gt", cl::Optional,
+ cl::desc("Show code coverage only for functions with line coverage "
+ "greater than the given threshold"),
+ cl::cat(FilteringCategory));
+
+ cl::opt<cl::boolOrDefault> UseColor(
+ "use-color", cl::desc("Emit colored output (default=autodetect)"),
+ cl::init(cl::BOU_UNSET));
+
+ cl::list<std::string> DemanglerOpts(
+ "Xdemangler", cl::desc("<demangler-path>|<demangler-option>"));
+
+ cl::opt<bool> RegionSummary(
+ "show-region-summary", cl::Optional,
+ cl::desc("Show region statistics in summary table"),
+ cl::init(true));
+
+ cl::opt<bool> InstantiationSummary(
+ "show-instantiation-summary", cl::Optional,
+ cl::desc("Show instantiation statistics in summary table"));
+
+ cl::opt<bool> SummaryOnly(
+ "summary-only", cl::Optional,
+ cl::desc("Export only summary information for each source file"));
+
+ cl::opt<unsigned> NumThreads(
+ "num-threads", cl::init(0),
+ cl::desc("Number of merge threads to use (default: autodetect)"));
+ cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"),
+ cl::aliasopt(NumThreads));
+
+ auto commandLineParser = [&, this](int argc, const char **argv) -> int {
+ cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
+ ViewOpts.Debug = DebugDump;
+
+ if (!CovFilename.empty())
+ ObjectFilenames.emplace_back(CovFilename);
+ for (const std::string &Filename : CovFilenames)
+ ObjectFilenames.emplace_back(Filename);
+ if (ObjectFilenames.empty()) {
+ errs() << "No filenames specified!\n";
+ ::exit(1);
+ }
+
+ ViewOpts.Format = Format;
+ switch (ViewOpts.Format) {
+ case CoverageViewOptions::OutputFormat::Text:
+ ViewOpts.Colors = UseColor == cl::BOU_UNSET
+ ? sys::Process::StandardOutHasColors()
+ : UseColor == cl::BOU_TRUE;
+ break;
+ case CoverageViewOptions::OutputFormat::HTML:
+ if (UseColor == cl::BOU_FALSE)
+ errs() << "Color output cannot be disabled when generating html.\n";
+ ViewOpts.Colors = true;
+ break;
+ case CoverageViewOptions::OutputFormat::Lcov:
+ if (UseColor == cl::BOU_TRUE)
+ errs() << "Color output cannot be enabled when generating lcov.\n";
+ ViewOpts.Colors = false;
+ break;
+ }
+
+ // If path-equivalence was given and is a comma seperated pair then set
+ // PathRemapping.
+ auto EquivPair = StringRef(PathRemap).split(',');
+ if (!(EquivPair.first.empty() && EquivPair.second.empty()))
+ PathRemapping = EquivPair;
+
+ // If a demangler is supplied, check if it exists and register it.
+ if (!DemanglerOpts.empty()) {
+ auto DemanglerPathOrErr = sys::findProgramByName(DemanglerOpts[0]);
+ if (!DemanglerPathOrErr) {
+ error("Could not find the demangler!",
+ DemanglerPathOrErr.getError().message());
+ return 1;
+ }
+ DemanglerOpts[0] = *DemanglerPathOrErr;
+ ViewOpts.DemanglerOpts.swap(DemanglerOpts);
+ }
+
+ // Read in -name-whitelist files.
+ if (!NameFilterFiles.empty()) {
+ std::string SpecialCaseListErr;
+ NameWhitelist =
+ SpecialCaseList::create(NameFilterFiles, SpecialCaseListErr);
+ if (!NameWhitelist)
+ error(SpecialCaseListErr);
+ }
+
+ // Create the function filters
+ if (!NameFilters.empty() || NameWhitelist || !NameRegexFilters.empty()) {
+ auto NameFilterer = std::make_unique<CoverageFilters>();
+ for (const auto &Name : NameFilters)
+ NameFilterer->push_back(std::make_unique<NameCoverageFilter>(Name));
+ if (NameWhitelist)
+ NameFilterer->push_back(
+ std::make_unique<NameWhitelistCoverageFilter>(*NameWhitelist));
+ for (const auto &Regex : NameRegexFilters)
+ NameFilterer->push_back(
+ std::make_unique<NameRegexCoverageFilter>(Regex));
+ Filters.push_back(std::move(NameFilterer));
+ }
+
+ if (RegionCoverageLtFilter.getNumOccurrences() ||
+ RegionCoverageGtFilter.getNumOccurrences() ||
+ LineCoverageLtFilter.getNumOccurrences() ||
+ LineCoverageGtFilter.getNumOccurrences()) {
+ auto StatFilterer = std::make_unique<CoverageFilters>();
+ if (RegionCoverageLtFilter.getNumOccurrences())
+ StatFilterer->push_back(std::make_unique<RegionCoverageFilter>(
+ RegionCoverageFilter::LessThan, RegionCoverageLtFilter));
+ if (RegionCoverageGtFilter.getNumOccurrences())
+ StatFilterer->push_back(std::make_unique<RegionCoverageFilter>(
+ RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter));
+ if (LineCoverageLtFilter.getNumOccurrences())
+ StatFilterer->push_back(std::make_unique<LineCoverageFilter>(
+ LineCoverageFilter::LessThan, LineCoverageLtFilter));
+ if (LineCoverageGtFilter.getNumOccurrences())
+ StatFilterer->push_back(std::make_unique<LineCoverageFilter>(
+ RegionCoverageFilter::GreaterThan, LineCoverageGtFilter));
+ Filters.push_back(std::move(StatFilterer));
+ }
+
+ // Create the ignore filename filters.
+ for (const auto &RE : IgnoreFilenameRegexFilters)
+ IgnoreFilenameFilters.push_back(
+ std::make_unique<NameRegexCoverageFilter>(RE));
+
+ if (!Arches.empty()) {
+ for (const std::string &Arch : Arches) {
+ if (Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) {
+ error("Unknown architecture: " + Arch);
+ return 1;
+ }
+ CoverageArches.emplace_back(Arch);
+ }
+ if (CoverageArches.size() != ObjectFilenames.size()) {
+ error("Number of architectures doesn't match the number of objects");
+ return 1;
+ }
+ }
+
+ // IgnoreFilenameFilters are applied even when InputSourceFiles specified.
+ for (const std::string &File : InputSourceFiles)
+ collectPaths(File);
+
+ if (DebugDumpCollectedPaths) {
+ for (const std::string &SF : SourceFiles)
+ outs() << SF << '\n';
+ ::exit(0);
+ }
+
+ ViewOpts.ShowRegionSummary = RegionSummary;
+ ViewOpts.ShowInstantiationSummary = InstantiationSummary;
+ ViewOpts.ExportSummaryOnly = SummaryOnly;
+ ViewOpts.NumThreads = NumThreads;
+
+ return 0;
+ };
+
+ switch (Cmd) {
+ case Show:
+ return doShow(argc, argv, commandLineParser);
+ case Report:
+ return doReport(argc, argv, commandLineParser);
+ case Export:
+ return doExport(argc, argv, commandLineParser);
+ }
+ return 0;
+}
+
+int CodeCoverageTool::doShow(int argc, const char **argv,
+ CommandLineParserType commandLineParser) {
+
+ cl::OptionCategory ViewCategory("Viewing options");
+
+ cl::opt<bool> ShowLineExecutionCounts(
+ "show-line-counts", cl::Optional,
+ cl::desc("Show the execution counts for each line"), cl::init(true),
+ cl::cat(ViewCategory));
+
+ cl::opt<bool> ShowRegions(
+ "show-regions", cl::Optional,
+ cl::desc("Show the execution counts for each region"),
+ cl::cat(ViewCategory));
+
+ cl::opt<bool> ShowBestLineRegionsCounts(
+ "show-line-counts-or-regions", cl::Optional,
+ cl::desc("Show the execution counts for each line, or the execution "
+ "counts for each region on lines that have multiple regions"),
+ cl::cat(ViewCategory));
+
+ cl::opt<bool> ShowExpansions("show-expansions", cl::Optional,
+ cl::desc("Show expanded source regions"),
+ cl::cat(ViewCategory));
+
+ cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,
+ cl::desc("Show function instantiations"),
+ cl::init(true), cl::cat(ViewCategory));
+
+ cl::opt<std::string> ShowOutputDirectory(
+ "output-dir", cl::init(""),
+ cl::desc("Directory in which coverage information is written out"));
+ cl::alias ShowOutputDirectoryA("o", cl::desc("Alias for --output-dir"),
+ cl::aliasopt(ShowOutputDirectory));
+
+ cl::opt<uint32_t> TabSize(
+ "tab-size", cl::init(2),
+ cl::desc(
+ "Set tab expansion size for html coverage reports (default = 2)"));
+
+ cl::opt<std::string> ProjectTitle(
+ "project-title", cl::Optional,
+ cl::desc("Set project title for the coverage report"));
+
+ auto Err = commandLineParser(argc, argv);
+ if (Err)
+ return Err;
+
+ if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) {
+ error("Lcov format should be used with 'llvm-cov export'.");
+ return 1;
+ }
+
+ ViewOpts.ShowLineNumbers = true;
+ ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 ||
+ !ShowRegions || ShowBestLineRegionsCounts;
+ ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts;
+ ViewOpts.ShowExpandedRegions = ShowExpansions;
+ ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
+ ViewOpts.ShowOutputDirectory = ShowOutputDirectory;
+ ViewOpts.TabSize = TabSize;
+ ViewOpts.ProjectTitle = ProjectTitle;
+
+ if (ViewOpts.hasOutputDirectory()) {
+ if (auto E = sys::fs::create_directories(ViewOpts.ShowOutputDirectory)) {
+ error("Could not create output directory!", E.message());
+ return 1;
+ }
+ }
+
+ sys::fs::file_status Status;
+ if (sys::fs::status(PGOFilename, Status)) {
+ error("profdata file error: can not get the file status. \n");
+ return 1;
+ }
+
+ auto ModifiedTime = Status.getLastModificationTime();
+ std::string ModifiedTimeStr = to_string(ModifiedTime);
+ size_t found = ModifiedTimeStr.rfind(':');
+ ViewOpts.CreatedTimeStr = (found != std::string::npos)
+ ? "Created: " + ModifiedTimeStr.substr(0, found)
+ : "Created: " + ModifiedTimeStr;
+
+ auto Coverage = load();
+ if (!Coverage)
+ return 1;
+
+ auto Printer = CoveragePrinter::create(ViewOpts);
+
+ if (SourceFiles.empty())
+ // Get the source files from the function coverage mapping.
+ for (StringRef Filename : Coverage->getUniqueSourceFiles()) {
+ if (!IgnoreFilenameFilters.matchesFilename(Filename))
+ SourceFiles.push_back(Filename);
+ }
+
+ // Create an index out of the source files.
+ if (ViewOpts.hasOutputDirectory()) {
+ if (Error E = Printer->createIndexFile(SourceFiles, *Coverage, Filters)) {
+ error("Could not create index file!", toString(std::move(E)));
+ return 1;
+ }
+ }
+
+ if (!Filters.empty()) {
+ // Build the map of filenames to functions.
+ std::map<llvm::StringRef, std::vector<const FunctionRecord *>>
+ FilenameFunctionMap;
+ for (const auto &SourceFile : SourceFiles)
+ for (const auto &Function : Coverage->getCoveredFunctions(SourceFile))
+ if (Filters.matches(*Coverage.get(), Function))
+ FilenameFunctionMap[SourceFile].push_back(&Function);
+
+ // Only print filter matching functions for each file.
+ for (const auto &FileFunc : FilenameFunctionMap) {
+ StringRef File = FileFunc.first;
+ const auto &Functions = FileFunc.second;
+
+ auto OSOrErr = Printer->createViewFile(File, /*InToplevel=*/false);
+ if (Error E = OSOrErr.takeError()) {
+ error("Could not create view file!", toString(std::move(E)));
+ return 1;
+ }
+ auto OS = std::move(OSOrErr.get());
+
+ bool ShowTitle = ViewOpts.hasOutputDirectory();
+ for (const auto *Function : Functions) {
+ auto FunctionView = createFunctionView(*Function, *Coverage);
+ if (!FunctionView) {
+ warning("Could not read coverage for '" + Function->Name + "'.");
+ continue;
+ }
+ FunctionView->print(*OS.get(), /*WholeFile=*/false,
+ /*ShowSourceName=*/true, ShowTitle);
+ ShowTitle = false;
+ }
+
+ Printer->closeViewFile(std::move(OS));
+ }
+ return 0;
+ }
+
+ // Show files
+ bool ShowFilenames =
+ (SourceFiles.size() != 1) || ViewOpts.hasOutputDirectory() ||
+ (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML);
+
+ auto NumThreads = ViewOpts.NumThreads;
+
+ // If NumThreads is not specified, auto-detect a good default.
+ if (NumThreads == 0)
+ NumThreads =
+ std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(),
+ unsigned(SourceFiles.size())));
+
+ if (!ViewOpts.hasOutputDirectory() || NumThreads == 1) {
+ for (const std::string &SourceFile : SourceFiles)
+ writeSourceFileView(SourceFile, Coverage.get(), Printer.get(),
+ ShowFilenames);
+ } else {
+ // In -output-dir mode, it's safe to use multiple threads to print files.
+ ThreadPool Pool(NumThreads);
+ for (const std::string &SourceFile : SourceFiles)
+ Pool.async(&CodeCoverageTool::writeSourceFileView, this, SourceFile,
+ Coverage.get(), Printer.get(), ShowFilenames);
+ Pool.wait();
+ }
+
+ return 0;
+}
+
+int CodeCoverageTool::doReport(int argc, const char **argv,
+ CommandLineParserType commandLineParser) {
+ cl::opt<bool> ShowFunctionSummaries(
+ "show-functions", cl::Optional, cl::init(false),
+ cl::desc("Show coverage summaries for each function"));
+
+ auto Err = commandLineParser(argc, argv);
+ if (Err)
+ return Err;
+
+ if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) {
+ error("HTML output for summary reports is not yet supported.");
+ return 1;
+ } else if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov) {
+ error("Lcov format should be used with 'llvm-cov export'.");
+ return 1;
+ }
+
+ auto Coverage = load();
+ if (!Coverage)
+ return 1;
+
+ CoverageReport Report(ViewOpts, *Coverage.get());
+ if (!ShowFunctionSummaries) {
+ if (SourceFiles.empty())
+ Report.renderFileReports(llvm::outs(), IgnoreFilenameFilters);
+ else
+ Report.renderFileReports(llvm::outs(), SourceFiles);
+ } else {
+ if (SourceFiles.empty()) {
+ error("Source files must be specified when -show-functions=true is "
+ "specified");
+ return 1;
+ }
+
+ Report.renderFunctionReports(SourceFiles, DC, llvm::outs());
+ }
+ return 0;
+}
+
+int CodeCoverageTool::doExport(int argc, const char **argv,
+ CommandLineParserType commandLineParser) {
+
+ cl::OptionCategory ExportCategory("Exporting options");
+
+ cl::opt<bool> SkipExpansions("skip-expansions", cl::Optional,
+ cl::desc("Don't export expanded source regions"),
+ cl::cat(ExportCategory));
+
+ cl::opt<bool> SkipFunctions("skip-functions", cl::Optional,
+ cl::desc("Don't export per-function data"),
+ cl::cat(ExportCategory));
+
+ auto Err = commandLineParser(argc, argv);
+ if (Err)
+ return Err;
+
+ ViewOpts.SkipExpansions = SkipExpansions;
+ ViewOpts.SkipFunctions = SkipFunctions;
+
+ if (ViewOpts.Format != CoverageViewOptions::OutputFormat::Text &&
+ ViewOpts.Format != CoverageViewOptions::OutputFormat::Lcov) {
+ error("Coverage data can only be exported as textual JSON or an "
+ "lcov tracefile.");
+ return 1;
+ }
+
+ auto Coverage = load();
+ if (!Coverage) {
+ error("Could not load coverage information");
+ return 1;
+ }
+
+ std::unique_ptr<CoverageExporter> Exporter;
+
+ switch (ViewOpts.Format) {
+ case CoverageViewOptions::OutputFormat::Text:
+ Exporter = std::make_unique<CoverageExporterJson>(*Coverage.get(),
+ ViewOpts, outs());
+ break;
+ case CoverageViewOptions::OutputFormat::HTML:
+ // Unreachable because we should have gracefully terminated with an error
+ // above.
+ llvm_unreachable("Export in HTML is not supported!");
+ case CoverageViewOptions::OutputFormat::Lcov:
+ Exporter = std::make_unique<CoverageExporterLcov>(*Coverage.get(),
+ ViewOpts, outs());
+ break;
+ }
+
+ if (SourceFiles.empty())
+ Exporter->renderRoot(IgnoreFilenameFilters);
+ else
+ Exporter->renderRoot(SourceFiles);
+
+ return 0;
+}
+
+int showMain(int argc, const char *argv[]) {
+ CodeCoverageTool Tool;
+ return Tool.run(CodeCoverageTool::Show, argc, argv);
+}
+
+int reportMain(int argc, const char *argv[]) {
+ CodeCoverageTool Tool;
+ return Tool.run(CodeCoverageTool::Report, argc, argv);
+}
+
+int exportMain(int argc, const char *argv[]) {
+ CodeCoverageTool Tool;
+ return Tool.run(CodeCoverageTool::Export, argc, argv);
+}
diff --git a/llvm/tools/llvm-cov/CoverageExporter.h b/llvm/tools/llvm-cov/CoverageExporter.h
new file mode 100644
index 000000000000..751e55dc0916
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageExporter.h
@@ -0,0 +1,51 @@
+//===- CoverageExporter.h - Code coverage exporter ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This class defines a code coverage exporter interface.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGEEXPORTER_H
+#define LLVM_COV_COVERAGEEXPORTER_H
+
+#include "CoverageFilters.h"
+#include "CoverageSummaryInfo.h"
+#include "CoverageViewOptions.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
+
+namespace llvm {
+
+/// Exports the code coverage information.
+class CoverageExporter {
+protected:
+ /// The full CoverageMapping object to export.
+ const coverage::CoverageMapping &Coverage;
+
+ /// The options passed to the tool.
+ const CoverageViewOptions &Options;
+
+ /// Output stream to print to.
+ raw_ostream &OS;
+
+ CoverageExporter(const coverage::CoverageMapping &CoverageMapping,
+ const CoverageViewOptions &Options, raw_ostream &OS)
+ : Coverage(CoverageMapping), Options(Options), OS(OS) {}
+
+public:
+ virtual ~CoverageExporter(){};
+
+ /// Render the CoverageMapping object.
+ virtual void renderRoot(const CoverageFilters &IgnoreFilters) = 0;
+
+ /// Render the CoverageMapping object for specified source files.
+ virtual void renderRoot(ArrayRef<std::string> SourceFiles) = 0;
+};
+
+} // end namespace llvm
+
+#endif // LLVM_COV_COVERAGEEXPORTER_H
diff --git a/llvm/tools/llvm-cov/CoverageExporterJson.cpp b/llvm/tools/llvm-cov/CoverageExporterJson.cpp
new file mode 100644
index 000000000000..181d428ed9d8
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageExporterJson.cpp
@@ -0,0 +1,229 @@
+//===- CoverageExporterJson.cpp - Code coverage export --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements export of code coverage data to JSON.
+//
+//===----------------------------------------------------------------------===//
+
+//===----------------------------------------------------------------------===//
+//
+// The json code coverage export follows the following format
+// Root: dict => Root Element containing metadata
+// -- Data: array => Homogeneous array of one or more export objects
+// -- Export: dict => Json representation of one CoverageMapping
+// -- Files: array => List of objects describing coverage for files
+// -- File: dict => Coverage for a single file
+// -- Segments: array => List of Segments contained in the file
+// -- Segment: dict => Describes a segment of the file with a counter
+// -- Expansions: array => List of expansion records
+// -- Expansion: dict => Object that descibes a single expansion
+// -- CountedRegion: dict => The region to be expanded
+// -- TargetRegions: array => List of Regions in the expansion
+// -- CountedRegion: dict => Single Region in the expansion
+// -- Summary: dict => Object summarizing the coverage for this file
+// -- LineCoverage: dict => Object summarizing line coverage
+// -- FunctionCoverage: dict => Object summarizing function coverage
+// -- RegionCoverage: dict => Object summarizing region coverage
+// -- Functions: array => List of objects describing coverage for functions
+// -- Function: dict => Coverage info for a single function
+// -- Filenames: array => List of filenames that the function relates to
+// -- Summary: dict => Object summarizing the coverage for the entire binary
+// -- LineCoverage: dict => Object summarizing line coverage
+// -- FunctionCoverage: dict => Object summarizing function coverage
+// -- InstantiationCoverage: dict => Object summarizing inst. coverage
+// -- RegionCoverage: dict => Object summarizing region coverage
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoverageExporterJson.h"
+#include "CoverageReport.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/ThreadPool.h"
+#include "llvm/Support/Threading.h"
+#include <algorithm>
+#include <mutex>
+#include <utility>
+
+/// The semantic version combined as a string.
+#define LLVM_COVERAGE_EXPORT_JSON_STR "2.0.0"
+
+/// Unique type identifier for JSON coverage export.
+#define LLVM_COVERAGE_EXPORT_JSON_TYPE_STR "llvm.coverage.json.export"
+
+using namespace llvm;
+
+namespace {
+
+json::Array renderSegment(const coverage::CoverageSegment &Segment) {
+ return json::Array({Segment.Line, Segment.Col, int64_t(Segment.Count),
+ Segment.HasCount, Segment.IsRegionEntry});
+}
+
+json::Array renderRegion(const coverage::CountedRegion &Region) {
+ return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd,
+ Region.ColumnEnd, int64_t(Region.ExecutionCount),
+ Region.FileID, Region.ExpandedFileID,
+ int64_t(Region.Kind)});
+}
+
+json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) {
+ json::Array RegionArray;
+ for (const auto &Region : Regions)
+ RegionArray.push_back(renderRegion(Region));
+ return RegionArray;
+}
+
+json::Object renderExpansion(const coverage::ExpansionRecord &Expansion) {
+ return json::Object(
+ {{"filenames", json::Array(Expansion.Function.Filenames)},
+ // Mark the beginning and end of this expansion in the source file.
+ {"source_region", renderRegion(Expansion.Region)},
+ // Enumerate the coverage information for the expansion.
+ {"target_regions", renderRegions(Expansion.Function.CountedRegions)}});
+}
+
+json::Object renderSummary(const FileCoverageSummary &Summary) {
+ return json::Object(
+ {{"lines",
+ json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())},
+ {"covered", int64_t(Summary.LineCoverage.getCovered())},
+ {"percent", Summary.LineCoverage.getPercentCovered()}})},
+ {"functions",
+ json::Object(
+ {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())},
+ {"covered", int64_t(Summary.FunctionCoverage.getExecuted())},
+ {"percent", Summary.FunctionCoverage.getPercentCovered()}})},
+ {"instantiations",
+ json::Object(
+ {{"count",
+ int64_t(Summary.InstantiationCoverage.getNumFunctions())},
+ {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())},
+ {"percent", Summary.InstantiationCoverage.getPercentCovered()}})},
+ {"regions",
+ json::Object(
+ {{"count", int64_t(Summary.RegionCoverage.getNumRegions())},
+ {"covered", int64_t(Summary.RegionCoverage.getCovered())},
+ {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() -
+ Summary.RegionCoverage.getCovered())},
+ {"percent", Summary.RegionCoverage.getPercentCovered()}})}});
+}
+
+json::Array renderFileExpansions(const coverage::CoverageData &FileCoverage,
+ const FileCoverageSummary &FileReport) {
+ json::Array ExpansionArray;
+ for (const auto &Expansion : FileCoverage.getExpansions())
+ ExpansionArray.push_back(renderExpansion(Expansion));
+ return ExpansionArray;
+}
+
+json::Array renderFileSegments(const coverage::CoverageData &FileCoverage,
+ const FileCoverageSummary &FileReport) {
+ json::Array SegmentArray;
+ for (const auto &Segment : FileCoverage)
+ SegmentArray.push_back(renderSegment(Segment));
+ return SegmentArray;
+}
+
+json::Object renderFile(const coverage::CoverageMapping &Coverage,
+ const std::string &Filename,
+ const FileCoverageSummary &FileReport,
+ const CoverageViewOptions &Options) {
+ json::Object File({{"filename", Filename}});
+ if (!Options.ExportSummaryOnly) {
+ // Calculate and render detailed coverage information for given file.
+ auto FileCoverage = Coverage.getCoverageForFile(Filename);
+ File["segments"] = renderFileSegments(FileCoverage, FileReport);
+ if (!Options.SkipExpansions) {
+ File["expansions"] = renderFileExpansions(FileCoverage, FileReport);
+ }
+ }
+ File["summary"] = renderSummary(FileReport);
+ return File;
+}
+
+json::Array renderFiles(const coverage::CoverageMapping &Coverage,
+ ArrayRef<std::string> SourceFiles,
+ ArrayRef<FileCoverageSummary> FileReports,
+ const CoverageViewOptions &Options) {
+ auto NumThreads = Options.NumThreads;
+ if (NumThreads == 0) {
+ NumThreads = std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(),
+ unsigned(SourceFiles.size())));
+ }
+ ThreadPool Pool(NumThreads);
+ json::Array FileArray;
+ std::mutex FileArrayMutex;
+
+ for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) {
+ auto &SourceFile = SourceFiles[I];
+ auto &FileReport = FileReports[I];
+ Pool.async([&] {
+ auto File = renderFile(Coverage, SourceFile, FileReport, Options);
+ {
+ std::lock_guard<std::mutex> Lock(FileArrayMutex);
+ FileArray.push_back(std::move(File));
+ }
+ });
+ }
+ Pool.wait();
+ return FileArray;
+}
+
+json::Array renderFunctions(
+ const iterator_range<coverage::FunctionRecordIterator> &Functions) {
+ json::Array FunctionArray;
+ for (const auto &F : Functions)
+ FunctionArray.push_back(
+ json::Object({{"name", F.Name},
+ {"count", int64_t(F.ExecutionCount)},
+ {"regions", renderRegions(F.CountedRegions)},
+ {"filenames", json::Array(F.Filenames)}}));
+ return FunctionArray;
+}
+
+} // end anonymous namespace
+
+void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) {
+ std::vector<std::string> SourceFiles;
+ for (StringRef SF : Coverage.getUniqueSourceFiles()) {
+ if (!IgnoreFilters.matchesFilename(SF))
+ SourceFiles.emplace_back(SF);
+ }
+ renderRoot(SourceFiles);
+}
+
+void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) {
+ FileCoverageSummary Totals = FileCoverageSummary("Totals");
+ auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
+ SourceFiles, Options);
+ auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options);
+ // Sort files in order of their names.
+ std::sort(Files.begin(), Files.end(),
+ [](const json::Value &A, const json::Value &B) {
+ const json::Object *ObjA = A.getAsObject();
+ const json::Object *ObjB = B.getAsObject();
+ assert(ObjA != nullptr && "Value A was not an Object");
+ assert(ObjB != nullptr && "Value B was not an Object");
+ const StringRef FilenameA = ObjA->getString("filename").getValue();
+ const StringRef FilenameB = ObjB->getString("filename").getValue();
+ return FilenameA.compare(FilenameB) < 0;
+ });
+ auto Export = json::Object(
+ {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}});
+ // Skip functions-level information if necessary.
+ if (!Options.ExportSummaryOnly && !Options.SkipFunctions)
+ Export["functions"] = renderFunctions(Coverage.getCoveredFunctions());
+
+ auto ExportArray = json::Array({std::move(Export)});
+
+ OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR},
+ {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR},
+ {"data", std::move(ExportArray)}});
+}
diff --git a/llvm/tools/llvm-cov/CoverageExporterJson.h b/llvm/tools/llvm-cov/CoverageExporterJson.h
new file mode 100644
index 000000000000..c19475005552
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageExporterJson.h
@@ -0,0 +1,35 @@
+//===- CoverageExporterJson.h - Code coverage JSON exporter ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This class implements a code coverage exporter for JSON format.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGEEXPORTERJSON_H
+#define LLVM_COV_COVERAGEEXPORTERJSON_H
+
+#include "CoverageExporter.h"
+
+namespace llvm {
+
+class CoverageExporterJson : public CoverageExporter {
+public:
+ CoverageExporterJson(const coverage::CoverageMapping &CoverageMapping,
+ const CoverageViewOptions &Options, raw_ostream &OS)
+ : CoverageExporter(CoverageMapping, Options, OS) {}
+
+ /// Render the CoverageMapping object.
+ void renderRoot(const CoverageFilters &IgnoreFilters) override;
+
+ /// Render the CoverageMapping object for specified source files.
+ void renderRoot(ArrayRef<std::string> SourceFiles) override;
+};
+
+} // end namespace llvm
+
+#endif // LLVM_COV_COVERAGEEXPORTERJSON_H
diff --git a/llvm/tools/llvm-cov/CoverageExporterLcov.cpp b/llvm/tools/llvm-cov/CoverageExporterLcov.cpp
new file mode 100644
index 000000000000..d9b0c3b0d7a8
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageExporterLcov.cpp
@@ -0,0 +1,124 @@
+//===- CoverageExporterLcov.cpp - Code coverage export --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements export of code coverage data to lcov trace file format.
+//
+//===----------------------------------------------------------------------===//
+
+//===----------------------------------------------------------------------===//
+//
+// The trace file code coverage export follows the following format (see also
+// https://linux.die.net/man/1/geninfo). Each quoted string appears on its own
+// line; the indentation shown here is only for documentation purposes.
+//
+// - for each source file:
+// - "SF:<absolute path to source file>"
+// - for each function:
+// - "FN:<line number of function start>,<function name>"
+// - for each function:
+// - "FNDA:<execution count>,<function name>"
+// - "FNF:<number of functions found>"
+// - "FNH:<number of functions hit>"
+// - for each instrumented line:
+// - "DA:<line number>,<execution count>[,<checksum>]
+// - "LH:<number of lines with non-zero execution count>"
+// - "LF:<nubmer of instrumented lines>"
+// - "end_of_record"
+//
+// If the user is exporting summary information only, then the FN, FNDA, and DA
+// lines will not be present.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoverageExporterLcov.h"
+#include "CoverageReport.h"
+
+using namespace llvm;
+
+namespace {
+
+void renderFunctionSummary(raw_ostream &OS,
+ const FileCoverageSummary &Summary) {
+ OS << "FNF:" << Summary.FunctionCoverage.getNumFunctions() << '\n'
+ << "FNH:" << Summary.FunctionCoverage.getExecuted() << '\n';
+}
+
+void renderFunctions(
+ raw_ostream &OS,
+ const iterator_range<coverage::FunctionRecordIterator> &Functions) {
+ for (const auto &F : Functions) {
+ auto StartLine = F.CountedRegions.front().LineStart;
+ OS << "FN:" << StartLine << ',' << F.Name << '\n';
+ }
+ for (const auto &F : Functions)
+ OS << "FNDA:" << F.ExecutionCount << ',' << F.Name << '\n';
+}
+
+void renderLineExecutionCounts(raw_ostream &OS,
+ const coverage::CoverageData &FileCoverage) {
+ coverage::LineCoverageIterator LCI{FileCoverage, 1};
+ coverage::LineCoverageIterator LCIEnd = LCI.getEnd();
+ for (; LCI != LCIEnd; ++LCI) {
+ const coverage::LineCoverageStats &LCS = *LCI;
+ if (LCS.isMapped()) {
+ OS << "DA:" << LCS.getLine() << ',' << LCS.getExecutionCount() << '\n';
+ }
+ }
+}
+
+void renderLineSummary(raw_ostream &OS, const FileCoverageSummary &Summary) {
+ OS << "LF:" << Summary.LineCoverage.getNumLines() << '\n'
+ << "LH:" << Summary.LineCoverage.getCovered() << '\n';
+}
+
+void renderFile(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
+ const std::string &Filename,
+ const FileCoverageSummary &FileReport, bool ExportSummaryOnly) {
+ OS << "SF:" << Filename << '\n';
+
+ if (!ExportSummaryOnly) {
+ renderFunctions(OS, Coverage.getCoveredFunctions(Filename));
+ }
+ renderFunctionSummary(OS, FileReport);
+
+ if (!ExportSummaryOnly) {
+ // Calculate and render detailed coverage information for given file.
+ auto FileCoverage = Coverage.getCoverageForFile(Filename);
+ renderLineExecutionCounts(OS, FileCoverage);
+ }
+ renderLineSummary(OS, FileReport);
+
+ OS << "end_of_record\n";
+}
+
+void renderFiles(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
+ ArrayRef<std::string> SourceFiles,
+ ArrayRef<FileCoverageSummary> FileReports,
+ bool ExportSummaryOnly) {
+ for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I)
+ renderFile(OS, Coverage, SourceFiles[I], FileReports[I], ExportSummaryOnly);
+}
+
+} // end anonymous namespace
+
+void CoverageExporterLcov::renderRoot(const CoverageFilters &IgnoreFilters) {
+ std::vector<std::string> SourceFiles;
+ for (StringRef SF : Coverage.getUniqueSourceFiles()) {
+ if (!IgnoreFilters.matchesFilename(SF))
+ SourceFiles.emplace_back(SF);
+ }
+ renderRoot(SourceFiles);
+}
+
+void CoverageExporterLcov::renderRoot(ArrayRef<std::string> SourceFiles) {
+ FileCoverageSummary Totals = FileCoverageSummary("Totals");
+ auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
+ SourceFiles, Options);
+ renderFiles(OS, Coverage, SourceFiles, FileReports,
+ Options.ExportSummaryOnly);
+}
diff --git a/llvm/tools/llvm-cov/CoverageExporterLcov.h b/llvm/tools/llvm-cov/CoverageExporterLcov.h
new file mode 100644
index 000000000000..e8a260bf4937
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageExporterLcov.h
@@ -0,0 +1,35 @@
+//===- CoverageExporterLcov.h - Code coverage lcov exporter ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This class implements a code coverage exporter for lcov trace file format.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGEEXPORTERLCOV_H
+#define LLVM_COV_COVERAGEEXPORTERLCOV_H
+
+#include "CoverageExporter.h"
+
+namespace llvm {
+
+class CoverageExporterLcov : public CoverageExporter {
+public:
+ CoverageExporterLcov(const coverage::CoverageMapping &CoverageMapping,
+ const CoverageViewOptions &Options, raw_ostream &OS)
+ : CoverageExporter(CoverageMapping, Options, OS) {}
+
+ /// Render the CoverageMapping object.
+ void renderRoot(const CoverageFilters &IgnoreFilters) override;
+
+ /// Render the CoverageMapping object for specified source files.
+ void renderRoot(ArrayRef<std::string> SourceFiles) override;
+};
+
+} // end namespace llvm
+
+#endif // LLVM_COV_COVERAGEEXPORTERLCOV_H
diff --git a/llvm/tools/llvm-cov/CoverageFilters.cpp b/llvm/tools/llvm-cov/CoverageFilters.cpp
new file mode 100644
index 000000000000..ca241e386e87
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageFilters.cpp
@@ -0,0 +1,85 @@
+//===- CoverageFilters.cpp - Function coverage mapping filters ------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// These classes provide filtering for function coverage mapping records.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoverageFilters.h"
+#include "CoverageSummaryInfo.h"
+#include "llvm/Support/Regex.h"
+
+using namespace llvm;
+
+bool NameCoverageFilter::matches(
+ const coverage::CoverageMapping &,
+ const coverage::FunctionRecord &Function) const {
+ StringRef FuncName = Function.Name;
+ return FuncName.find(Name) != StringRef::npos;
+}
+
+bool NameRegexCoverageFilter::matches(
+ const coverage::CoverageMapping &,
+ const coverage::FunctionRecord &Function) const {
+ return llvm::Regex(Regex).match(Function.Name);
+}
+
+bool NameRegexCoverageFilter::matchesFilename(StringRef Filename) const {
+ return llvm::Regex(Regex).match(Filename);
+}
+
+bool NameWhitelistCoverageFilter::matches(
+ const coverage::CoverageMapping &,
+ const coverage::FunctionRecord &Function) const {
+ return Whitelist.inSection("llvmcov", "whitelist_fun", Function.Name);
+}
+
+bool RegionCoverageFilter::matches(
+ const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const {
+ return PassesThreshold(FunctionCoverageSummary::get(CM, Function)
+ .RegionCoverage.getPercentCovered());
+}
+
+bool LineCoverageFilter::matches(
+ const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const {
+ return PassesThreshold(FunctionCoverageSummary::get(CM, Function)
+ .LineCoverage.getPercentCovered());
+}
+
+void CoverageFilters::push_back(std::unique_ptr<CoverageFilter> Filter) {
+ Filters.push_back(std::move(Filter));
+}
+
+bool CoverageFilters::matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const {
+ for (const auto &Filter : Filters) {
+ if (Filter->matches(CM, Function))
+ return true;
+ }
+ return false;
+}
+
+bool CoverageFilters::matchesFilename(StringRef Filename) const {
+ for (const auto &Filter : Filters) {
+ if (Filter->matchesFilename(Filename))
+ return true;
+ }
+ return false;
+}
+
+bool CoverageFiltersMatchAll::matches(
+ const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const {
+ for (const auto &Filter : Filters) {
+ if (!Filter->matches(CM, Function))
+ return false;
+ }
+ return true;
+}
diff --git a/llvm/tools/llvm-cov/CoverageFilters.h b/llvm/tools/llvm-cov/CoverageFilters.h
new file mode 100644
index 000000000000..ce56e1607111
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageFilters.h
@@ -0,0 +1,157 @@
+//===- CoverageFilters.h - Function coverage mapping filters --------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// These classes provide filtering for function coverage mapping records.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGEFILTERS_H
+#define LLVM_COV_COVERAGEFILTERS_H
+
+#include "CoverageSummaryInfo.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
+#include "llvm/Support/SpecialCaseList.h"
+#include <memory>
+#include <vector>
+
+namespace llvm {
+
+/// Matches specific functions that pass the requirement of this filter.
+class CoverageFilter {
+public:
+ virtual ~CoverageFilter() {}
+
+ /// Return true if the function passes the requirements of this filter.
+ virtual bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const {
+ return true;
+ }
+
+ /// Return true if the filename passes the requirements of this filter.
+ virtual bool matchesFilename(StringRef Filename) const {
+ return true;
+ }
+};
+
+/// Matches functions that contain a specific string in their name.
+class NameCoverageFilter : public CoverageFilter {
+ StringRef Name;
+
+public:
+ NameCoverageFilter(StringRef Name) : Name(Name) {}
+
+ bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const override;
+};
+
+/// Matches functions whose name matches a certain regular expression.
+class NameRegexCoverageFilter : public CoverageFilter {
+ StringRef Regex;
+
+public:
+ NameRegexCoverageFilter(StringRef Regex) : Regex(Regex) {}
+
+ bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const override;
+
+ bool matchesFilename(StringRef Filename) const override;
+};
+
+/// Matches functions whose name appears in a SpecialCaseList in the
+/// whitelist_fun section.
+class NameWhitelistCoverageFilter : public CoverageFilter {
+ const SpecialCaseList &Whitelist;
+
+public:
+ NameWhitelistCoverageFilter(const SpecialCaseList &Whitelist)
+ : Whitelist(Whitelist) {}
+
+ bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const override;
+};
+
+/// Matches numbers that pass a certain threshold.
+template <typename T> class StatisticThresholdFilter {
+public:
+ enum Operation { LessThan, GreaterThan };
+
+protected:
+ Operation Op;
+ T Threshold;
+
+ StatisticThresholdFilter(Operation Op, T Threshold)
+ : Op(Op), Threshold(Threshold) {}
+
+ /// Return true if the given number is less than
+ /// or greater than the certain threshold.
+ bool PassesThreshold(T Value) const {
+ switch (Op) {
+ case LessThan:
+ return Value < Threshold;
+ case GreaterThan:
+ return Value > Threshold;
+ }
+ return false;
+ }
+};
+
+/// Matches functions whose region coverage percentage
+/// is above/below a certain percentage.
+class RegionCoverageFilter : public CoverageFilter,
+ public StatisticThresholdFilter<double> {
+public:
+ RegionCoverageFilter(Operation Op, double Threshold)
+ : StatisticThresholdFilter(Op, Threshold) {}
+
+ bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const override;
+};
+
+/// Matches functions whose line coverage percentage
+/// is above/below a certain percentage.
+class LineCoverageFilter : public CoverageFilter,
+ public StatisticThresholdFilter<double> {
+public:
+ LineCoverageFilter(Operation Op, double Threshold)
+ : StatisticThresholdFilter(Op, Threshold) {}
+
+ bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const override;
+};
+
+/// A collection of filters.
+/// Matches functions that match any filters contained
+/// in an instance of this class.
+class CoverageFilters : public CoverageFilter {
+protected:
+ std::vector<std::unique_ptr<CoverageFilter>> Filters;
+
+public:
+ /// Append a filter to this collection.
+ void push_back(std::unique_ptr<CoverageFilter> Filter);
+
+ bool empty() const { return Filters.empty(); }
+
+ bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const override;
+
+ bool matchesFilename(StringRef Filename) const override;
+};
+
+/// A collection of filters.
+/// Matches functions that match all of the filters contained
+/// in an instance of this class.
+class CoverageFiltersMatchAll : public CoverageFilters {
+public:
+ bool matches(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) const override;
+};
+
+} // namespace llvm
+
+#endif // LLVM_COV_COVERAGEFILTERS_H
diff --git a/llvm/tools/llvm-cov/CoverageReport.cpp b/llvm/tools/llvm-cov/CoverageReport.cpp
new file mode 100644
index 000000000000..82259542c597
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageReport.cpp
@@ -0,0 +1,449 @@
+//===- CoverageReport.cpp - Code coverage report -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This class implements rendering of a code coverage report.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoverageReport.h"
+#include "RenderingSupport.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/ThreadPool.h"
+#include "llvm/Support/Threading.h"
+#include <numeric>
+
+using namespace llvm;
+
+namespace {
+
+/// Helper struct which prints trimmed and aligned columns.
+struct Column {
+ enum TrimKind { NoTrim, WidthTrim, RightTrim };
+
+ enum AlignmentKind { LeftAlignment, RightAlignment };
+
+ StringRef Str;
+ unsigned Width;
+ TrimKind Trim;
+ AlignmentKind Alignment;
+
+ Column(StringRef Str, unsigned Width)
+ : Str(Str), Width(Width), Trim(WidthTrim), Alignment(LeftAlignment) {}
+
+ Column &set(TrimKind Value) {
+ Trim = Value;
+ return *this;
+ }
+
+ Column &set(AlignmentKind Value) {
+ Alignment = Value;
+ return *this;
+ }
+
+ void render(raw_ostream &OS) const {
+ if (Str.size() <= Width) {
+ if (Alignment == RightAlignment) {
+ OS.indent(Width - Str.size());
+ OS << Str;
+ return;
+ }
+ OS << Str;
+ OS.indent(Width - Str.size());
+ return;
+ }
+
+ switch (Trim) {
+ case NoTrim:
+ OS << Str;
+ break;
+ case WidthTrim:
+ OS << Str.substr(0, Width);
+ break;
+ case RightTrim:
+ OS << Str.substr(0, Width - 3) << "...";
+ break;
+ }
+ }
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const Column &Value) {
+ Value.render(OS);
+ return OS;
+}
+
+Column column(StringRef Str, unsigned Width) { return Column(Str, Width); }
+
+template <typename T>
+Column column(StringRef Str, unsigned Width, const T &Value) {
+ return Column(Str, Width).set(Value);
+}
+
+// Specify the default column widths.
+size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10,
+ 16, 16, 10, 12, 18, 10};
+size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8};
+
+/// Adjust column widths to fit long file paths and function names.
+void adjustColumnWidths(ArrayRef<StringRef> Files,
+ ArrayRef<StringRef> Functions) {
+ for (StringRef Filename : Files)
+ FileReportColumns[0] = std::max(FileReportColumns[0], Filename.size());
+ for (StringRef Funcname : Functions)
+ FunctionReportColumns[0] =
+ std::max(FunctionReportColumns[0], Funcname.size());
+}
+
+/// Prints a horizontal divider long enough to cover the given column
+/// widths.
+void renderDivider(ArrayRef<size_t> ColumnWidths, raw_ostream &OS) {
+ size_t Length = std::accumulate(ColumnWidths.begin(), ColumnWidths.end(), 0);
+ for (size_t I = 0; I < Length; ++I)
+ OS << '-';
+}
+
+/// Return the color which correponds to the coverage percentage of a
+/// certain metric.
+template <typename T>
+raw_ostream::Colors determineCoveragePercentageColor(const T &Info) {
+ if (Info.isFullyCovered())
+ return raw_ostream::GREEN;
+ return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW
+ : raw_ostream::RED;
+}
+
+/// Get the number of redundant path components in each path in \p Paths.
+unsigned getNumRedundantPathComponents(ArrayRef<std::string> Paths) {
+ // To start, set the number of redundant path components to the maximum
+ // possible value.
+ SmallVector<StringRef, 8> FirstPathComponents{sys::path::begin(Paths[0]),
+ sys::path::end(Paths[0])};
+ unsigned NumRedundant = FirstPathComponents.size();
+
+ for (unsigned I = 1, E = Paths.size(); NumRedundant > 0 && I < E; ++I) {
+ StringRef Path = Paths[I];
+ for (const auto &Component :
+ enumerate(make_range(sys::path::begin(Path), sys::path::end(Path)))) {
+ // Do not increase the number of redundant components: that would remove
+ // useful parts of already-visited paths.
+ if (Component.index() >= NumRedundant)
+ break;
+
+ // Lower the number of redundant components when there's a mismatch
+ // between the first path, and the path under consideration.
+ if (FirstPathComponents[Component.index()] != Component.value()) {
+ NumRedundant = Component.index();
+ break;
+ }
+ }
+ }
+
+ return NumRedundant;
+}
+
+/// Determine the length of the longest redundant prefix of the paths in
+/// \p Paths.
+unsigned getRedundantPrefixLen(ArrayRef<std::string> Paths) {
+ // If there's at most one path, no path components are redundant.
+ if (Paths.size() <= 1)
+ return 0;
+
+ unsigned PrefixLen = 0;
+ unsigned NumRedundant = getNumRedundantPathComponents(Paths);
+ auto Component = sys::path::begin(Paths[0]);
+ for (unsigned I = 0; I < NumRedundant; ++I) {
+ auto LastComponent = Component;
+ ++Component;
+ PrefixLen += Component - LastComponent;
+ }
+ return PrefixLen;
+}
+
+} // end anonymous namespace
+
+namespace llvm {
+
+void CoverageReport::render(const FileCoverageSummary &File,
+ raw_ostream &OS) const {
+ auto FileCoverageColor =
+ determineCoveragePercentageColor(File.RegionCoverage);
+ auto FuncCoverageColor =
+ determineCoveragePercentageColor(File.FunctionCoverage);
+ auto InstantiationCoverageColor =
+ determineCoveragePercentageColor(File.InstantiationCoverage);
+ auto LineCoverageColor = determineCoveragePercentageColor(File.LineCoverage);
+ SmallString<256> FileName = File.Name;
+ sys::path::remove_dots(FileName, /*remove_dot_dots=*/true);
+ sys::path::native(FileName);
+ OS << column(FileName, FileReportColumns[0], Column::NoTrim);
+
+ if (Options.ShowRegionSummary) {
+ OS << format("%*u", FileReportColumns[1],
+ (unsigned)File.RegionCoverage.getNumRegions());
+ Options.colored_ostream(OS, FileCoverageColor)
+ << format("%*u", FileReportColumns[2],
+ (unsigned)(File.RegionCoverage.getNumRegions() -
+ File.RegionCoverage.getCovered()));
+ if (File.RegionCoverage.getNumRegions())
+ Options.colored_ostream(OS, FileCoverageColor)
+ << format("%*.2f", FileReportColumns[3] - 1,
+ File.RegionCoverage.getPercentCovered())
+ << '%';
+ else
+ OS << column("-", FileReportColumns[3], Column::RightAlignment);
+ }
+
+ OS << format("%*u", FileReportColumns[4],
+ (unsigned)File.FunctionCoverage.getNumFunctions());
+ OS << format("%*u", FileReportColumns[5],
+ (unsigned)(File.FunctionCoverage.getNumFunctions() -
+ File.FunctionCoverage.getExecuted()));
+ if (File.FunctionCoverage.getNumFunctions())
+ Options.colored_ostream(OS, FuncCoverageColor)
+ << format("%*.2f", FileReportColumns[6] - 1,
+ File.FunctionCoverage.getPercentCovered())
+ << '%';
+ else
+ OS << column("-", FileReportColumns[6], Column::RightAlignment);
+
+ if (Options.ShowInstantiationSummary) {
+ OS << format("%*u", FileReportColumns[7],
+ (unsigned)File.InstantiationCoverage.getNumFunctions());
+ OS << format("%*u", FileReportColumns[8],
+ (unsigned)(File.InstantiationCoverage.getNumFunctions() -
+ File.InstantiationCoverage.getExecuted()));
+ if (File.InstantiationCoverage.getNumFunctions())
+ Options.colored_ostream(OS, InstantiationCoverageColor)
+ << format("%*.2f", FileReportColumns[9] - 1,
+ File.InstantiationCoverage.getPercentCovered())
+ << '%';
+ else
+ OS << column("-", FileReportColumns[9], Column::RightAlignment);
+ }
+
+ OS << format("%*u", FileReportColumns[10],
+ (unsigned)File.LineCoverage.getNumLines());
+ Options.colored_ostream(OS, LineCoverageColor) << format(
+ "%*u", FileReportColumns[11], (unsigned)(File.LineCoverage.getNumLines() -
+ File.LineCoverage.getCovered()));
+ if (File.LineCoverage.getNumLines())
+ Options.colored_ostream(OS, LineCoverageColor)
+ << format("%*.2f", FileReportColumns[12] - 1,
+ File.LineCoverage.getPercentCovered())
+ << '%';
+ else
+ OS << column("-", FileReportColumns[12], Column::RightAlignment);
+ OS << "\n";
+}
+
+void CoverageReport::render(const FunctionCoverageSummary &Function,
+ const DemangleCache &DC,
+ raw_ostream &OS) const {
+ auto FuncCoverageColor =
+ determineCoveragePercentageColor(Function.RegionCoverage);
+ auto LineCoverageColor =
+ determineCoveragePercentageColor(Function.LineCoverage);
+ OS << column(DC.demangle(Function.Name), FunctionReportColumns[0],
+ Column::RightTrim)
+ << format("%*u", FunctionReportColumns[1],
+ (unsigned)Function.RegionCoverage.getNumRegions());
+ Options.colored_ostream(OS, FuncCoverageColor)
+ << format("%*u", FunctionReportColumns[2],
+ (unsigned)(Function.RegionCoverage.getNumRegions() -
+ Function.RegionCoverage.getCovered()));
+ Options.colored_ostream(
+ OS, determineCoveragePercentageColor(Function.RegionCoverage))
+ << format("%*.2f", FunctionReportColumns[3] - 1,
+ Function.RegionCoverage.getPercentCovered())
+ << '%';
+ OS << format("%*u", FunctionReportColumns[4],
+ (unsigned)Function.LineCoverage.getNumLines());
+ Options.colored_ostream(OS, LineCoverageColor)
+ << format("%*u", FunctionReportColumns[5],
+ (unsigned)(Function.LineCoverage.getNumLines() -
+ Function.LineCoverage.getCovered()));
+ Options.colored_ostream(
+ OS, determineCoveragePercentageColor(Function.LineCoverage))
+ << format("%*.2f", FunctionReportColumns[6] - 1,
+ Function.LineCoverage.getPercentCovered())
+ << '%';
+ OS << "\n";
+}
+
+void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
+ const DemangleCache &DC,
+ raw_ostream &OS) {
+ bool isFirst = true;
+ for (StringRef Filename : Files) {
+ auto Functions = Coverage.getCoveredFunctions(Filename);
+
+ if (isFirst)
+ isFirst = false;
+ else
+ OS << "\n";
+
+ std::vector<StringRef> Funcnames;
+ for (const auto &F : Functions)
+ Funcnames.emplace_back(DC.demangle(F.Name));
+ adjustColumnWidths({}, Funcnames);
+
+ OS << "File '" << Filename << "':\n";
+ OS << column("Name", FunctionReportColumns[0])
+ << column("Regions", FunctionReportColumns[1], Column::RightAlignment)
+ << column("Miss", FunctionReportColumns[2], Column::RightAlignment)
+ << column("Cover", FunctionReportColumns[3], Column::RightAlignment)
+ << column("Lines", FunctionReportColumns[4], Column::RightAlignment)
+ << column("Miss", FunctionReportColumns[5], Column::RightAlignment)
+ << column("Cover", FunctionReportColumns[6], Column::RightAlignment);
+ OS << "\n";
+ renderDivider(FunctionReportColumns, OS);
+ OS << "\n";
+ FunctionCoverageSummary Totals("TOTAL");
+ for (const auto &F : Functions) {
+ auto Function = FunctionCoverageSummary::get(Coverage, F);
+ ++Totals.ExecutionCount;
+ Totals.RegionCoverage += Function.RegionCoverage;
+ Totals.LineCoverage += Function.LineCoverage;
+ render(Function, DC, OS);
+ }
+ if (Totals.ExecutionCount) {
+ renderDivider(FunctionReportColumns, OS);
+ OS << "\n";
+ render(Totals, DC, OS);
+ }
+ }
+}
+
+void CoverageReport::prepareSingleFileReport(const StringRef Filename,
+ const coverage::CoverageMapping *Coverage,
+ const CoverageViewOptions &Options, const unsigned LCP,
+ FileCoverageSummary *FileReport, const CoverageFilter *Filters) {
+ for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
+ std::vector<FunctionCoverageSummary> InstantiationSummaries;
+ for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
+ if (!Filters->matches(*Coverage, *F))
+ continue;
+ auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
+ FileReport->addInstantiation(InstantiationSummary);
+ InstantiationSummaries.push_back(InstantiationSummary);
+ }
+ if (InstantiationSummaries.empty())
+ continue;
+
+ auto GroupSummary =
+ FunctionCoverageSummary::get(Group, InstantiationSummaries);
+
+ if (Options.Debug)
+ outs() << "InstantiationGroup: " << GroupSummary.Name << " with "
+ << "size = " << Group.size() << "\n";
+
+ FileReport->addFunction(GroupSummary);
+ }
+}
+
+std::vector<FileCoverageSummary> CoverageReport::prepareFileReports(
+ const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals,
+ ArrayRef<std::string> Files, const CoverageViewOptions &Options,
+ const CoverageFilter &Filters) {
+ unsigned LCP = getRedundantPrefixLen(Files);
+ auto NumThreads = Options.NumThreads;
+
+ // If NumThreads is not specified, auto-detect a good default.
+ if (NumThreads == 0)
+ NumThreads =
+ std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(),
+ unsigned(Files.size())));
+
+ ThreadPool Pool(NumThreads);
+
+ std::vector<FileCoverageSummary> FileReports;
+ FileReports.reserve(Files.size());
+
+ for (StringRef Filename : Files) {
+ FileReports.emplace_back(Filename.drop_front(LCP));
+ Pool.async(&CoverageReport::prepareSingleFileReport, Filename,
+ &Coverage, Options, LCP, &FileReports.back(), &Filters);
+ }
+ Pool.wait();
+
+ for (const auto &FileReport : FileReports)
+ Totals += FileReport;
+
+ return FileReports;
+}
+
+void CoverageReport::renderFileReports(
+ raw_ostream &OS, const CoverageFilters &IgnoreFilenameFilters) const {
+ std::vector<std::string> UniqueSourceFiles;
+ for (StringRef SF : Coverage.getUniqueSourceFiles()) {
+ // Apply ignore source files filters.
+ if (!IgnoreFilenameFilters.matchesFilename(SF))
+ UniqueSourceFiles.emplace_back(SF.str());
+ }
+ renderFileReports(OS, UniqueSourceFiles);
+}
+
+void CoverageReport::renderFileReports(
+ raw_ostream &OS, ArrayRef<std::string> Files) const {
+ renderFileReports(OS, Files, CoverageFiltersMatchAll());
+}
+
+void CoverageReport::renderFileReports(
+ raw_ostream &OS, ArrayRef<std::string> Files,
+ const CoverageFiltersMatchAll &Filters) const {
+ FileCoverageSummary Totals("TOTAL");
+ auto FileReports =
+ prepareFileReports(Coverage, Totals, Files, Options, Filters);
+
+ std::vector<StringRef> Filenames;
+ for (const FileCoverageSummary &FCS : FileReports)
+ Filenames.emplace_back(FCS.Name);
+ adjustColumnWidths(Filenames, {});
+
+ OS << column("Filename", FileReportColumns[0]);
+ if (Options.ShowRegionSummary)
+ OS << column("Regions", FileReportColumns[1], Column::RightAlignment)
+ << column("Missed Regions", FileReportColumns[2], Column::RightAlignment)
+ << column("Cover", FileReportColumns[3], Column::RightAlignment);
+ OS << column("Functions", FileReportColumns[4], Column::RightAlignment)
+ << column("Missed Functions", FileReportColumns[5], Column::RightAlignment)
+ << column("Executed", FileReportColumns[6], Column::RightAlignment);
+ if (Options.ShowInstantiationSummary)
+ OS << column("Instantiations", FileReportColumns[7], Column::RightAlignment)
+ << column("Missed Insts.", FileReportColumns[8], Column::RightAlignment)
+ << column("Executed", FileReportColumns[9], Column::RightAlignment);
+ OS << column("Lines", FileReportColumns[10], Column::RightAlignment)
+ << column("Missed Lines", FileReportColumns[11], Column::RightAlignment)
+ << column("Cover", FileReportColumns[12], Column::RightAlignment) << "\n";
+ renderDivider(FileReportColumns, OS);
+ OS << "\n";
+
+ bool EmptyFiles = false;
+ for (const FileCoverageSummary &FCS : FileReports) {
+ if (FCS.FunctionCoverage.getNumFunctions())
+ render(FCS, OS);
+ else
+ EmptyFiles = true;
+ }
+
+ if (EmptyFiles && Filters.empty()) {
+ OS << "\n"
+ << "Files which contain no functions:\n";
+
+ for (const FileCoverageSummary &FCS : FileReports)
+ if (!FCS.FunctionCoverage.getNumFunctions())
+ render(FCS, OS);
+ }
+
+ renderDivider(FileReportColumns, OS);
+ OS << "\n";
+ render(Totals, OS);
+}
+
+} // end namespace llvm
diff --git a/llvm/tools/llvm-cov/CoverageReport.h b/llvm/tools/llvm-cov/CoverageReport.h
new file mode 100644
index 000000000000..f9a092f510b5
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageReport.h
@@ -0,0 +1,69 @@
+//===- CoverageReport.h - Code coverage report ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This class implements rendering of a code coverage report.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGEREPORT_H
+#define LLVM_COV_COVERAGEREPORT_H
+
+#include "CoverageFilters.h"
+#include "CoverageSummaryInfo.h"
+#include "CoverageViewOptions.h"
+
+namespace llvm {
+
+/// Displays the code coverage report.
+class CoverageReport {
+ const CoverageViewOptions &Options;
+ const coverage::CoverageMapping &Coverage;
+
+ void render(const FileCoverageSummary &File, raw_ostream &OS) const;
+ void render(const FunctionCoverageSummary &Function, const DemangleCache &DC,
+ raw_ostream &OS) const;
+
+public:
+ CoverageReport(const CoverageViewOptions &Options,
+ const coverage::CoverageMapping &Coverage)
+ : Options(Options), Coverage(Coverage) {}
+
+ void renderFunctionReports(ArrayRef<std::string> Files,
+ const DemangleCache &DC, raw_ostream &OS);
+
+ /// Prepare file reports for the files specified in \p Files.
+ static std::vector<FileCoverageSummary>
+ prepareFileReports(const coverage::CoverageMapping &Coverage,
+ FileCoverageSummary &Totals, ArrayRef<std::string> Files,
+ const CoverageViewOptions &Options,
+ const CoverageFilter &Filters = CoverageFiltersMatchAll());
+
+ static void
+ prepareSingleFileReport(const StringRef Filename,
+ const coverage::CoverageMapping *Coverage,
+ const CoverageViewOptions &Options,
+ const unsigned LCP,
+ FileCoverageSummary *FileReport,
+ const CoverageFilter *Filters);
+
+ /// Render file reports for every unique file in the coverage mapping.
+ void renderFileReports(raw_ostream &OS,
+ const CoverageFilters &IgnoreFilenameFilters) const;
+
+ /// Render file reports for the files specified in \p Files.
+ void renderFileReports(raw_ostream &OS, ArrayRef<std::string> Files) const;
+
+ /// Render file reports for the files specified in \p Files and the functions
+ /// in \p Filters.
+ void renderFileReports(raw_ostream &OS, ArrayRef<std::string> Files,
+ const CoverageFiltersMatchAll &Filters) const;
+};
+
+} // end namespace llvm
+
+#endif // LLVM_COV_COVERAGEREPORT_H
diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp
new file mode 100644
index 000000000000..1029f7784040
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp
@@ -0,0 +1,70 @@
+//===- CoverageSummaryInfo.cpp - Coverage summary for function/file -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// These structures are used to represent code coverage metrics
+// for functions/files.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoverageSummaryInfo.h"
+
+using namespace llvm;
+using namespace coverage;
+
+FunctionCoverageSummary
+FunctionCoverageSummary::get(const CoverageMapping &CM,
+ const coverage::FunctionRecord &Function) {
+ // Compute the region coverage.
+ size_t NumCodeRegions = 0, CoveredRegions = 0;
+ for (auto &CR : Function.CountedRegions) {
+ if (CR.Kind != CounterMappingRegion::CodeRegion)
+ continue;
+ ++NumCodeRegions;
+ if (CR.ExecutionCount != 0)
+ ++CoveredRegions;
+ }
+
+ // Compute the line coverage
+ size_t NumLines = 0, CoveredLines = 0;
+ CoverageData CD = CM.getCoverageForFunction(Function);
+ for (const auto &LCS : getLineCoverageStats(CD)) {
+ if (!LCS.isMapped())
+ continue;
+ ++NumLines;
+ if (LCS.getExecutionCount())
+ ++CoveredLines;
+ }
+
+ return FunctionCoverageSummary(
+ Function.Name, Function.ExecutionCount,
+ RegionCoverageInfo(CoveredRegions, NumCodeRegions),
+ LineCoverageInfo(CoveredLines, NumLines));
+}
+
+FunctionCoverageSummary
+FunctionCoverageSummary::get(const InstantiationGroup &Group,
+ ArrayRef<FunctionCoverageSummary> Summaries) {
+ std::string Name;
+ if (Group.hasName()) {
+ Name = Group.getName();
+ } else {
+ llvm::raw_string_ostream OS(Name);
+ OS << "Definition at line " << Group.getLine() << ", column "
+ << Group.getColumn();
+ }
+
+ FunctionCoverageSummary Summary(Name);
+ Summary.ExecutionCount = Group.getTotalExecutionCount();
+ Summary.RegionCoverage = Summaries[0].RegionCoverage;
+ Summary.LineCoverage = Summaries[0].LineCoverage;
+ for (const auto &FCS : Summaries.drop_front()) {
+ Summary.RegionCoverage.merge(FCS.RegionCoverage);
+ Summary.LineCoverage.merge(FCS.LineCoverage);
+ }
+ return Summary;
+}
diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.h b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
new file mode 100644
index 000000000000..97beacb26d07
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
@@ -0,0 +1,218 @@
+//===- CoverageSummaryInfo.h - Coverage summary for function/file ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// These structures are used to represent code coverage metrics
+// for functions/files.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGESUMMARYINFO_H
+#define LLVM_COV_COVERAGESUMMARYINFO_H
+
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace llvm {
+
+/// Provides information about region coverage for a function/file.
+class RegionCoverageInfo {
+ /// The number of regions that were executed at least once.
+ size_t Covered;
+
+ /// The total number of regions in a function/file.
+ size_t NumRegions;
+
+public:
+ RegionCoverageInfo() : Covered(0), NumRegions(0) {}
+
+ RegionCoverageInfo(size_t Covered, size_t NumRegions)
+ : Covered(Covered), NumRegions(NumRegions) {
+ assert(Covered <= NumRegions && "Covered regions over-counted");
+ }
+
+ RegionCoverageInfo &operator+=(const RegionCoverageInfo &RHS) {
+ Covered += RHS.Covered;
+ NumRegions += RHS.NumRegions;
+ return *this;
+ }
+
+ void merge(const RegionCoverageInfo &RHS) {
+ Covered = std::max(Covered, RHS.Covered);
+ NumRegions = std::max(NumRegions, RHS.NumRegions);
+ }
+
+ size_t getCovered() const { return Covered; }
+
+ size_t getNumRegions() const { return NumRegions; }
+
+ bool isFullyCovered() const { return Covered == NumRegions; }
+
+ double getPercentCovered() const {
+ assert(Covered <= NumRegions && "Covered regions over-counted");
+ if (NumRegions == 0)
+ return 0.0;
+ return double(Covered) / double(NumRegions) * 100.0;
+ }
+};
+
+/// Provides information about line coverage for a function/file.
+class LineCoverageInfo {
+ /// The number of lines that were executed at least once.
+ size_t Covered;
+
+ /// The total number of lines in a function/file.
+ size_t NumLines;
+
+public:
+ LineCoverageInfo() : Covered(0), NumLines(0) {}
+
+ LineCoverageInfo(size_t Covered, size_t NumLines)
+ : Covered(Covered), NumLines(NumLines) {
+ assert(Covered <= NumLines && "Covered lines over-counted");
+ }
+
+ LineCoverageInfo &operator+=(const LineCoverageInfo &RHS) {
+ Covered += RHS.Covered;
+ NumLines += RHS.NumLines;
+ return *this;
+ }
+
+ void merge(const LineCoverageInfo &RHS) {
+ Covered = std::max(Covered, RHS.Covered);
+ NumLines = std::max(NumLines, RHS.NumLines);
+ }
+
+ size_t getCovered() const { return Covered; }
+
+ size_t getNumLines() const { return NumLines; }
+
+ bool isFullyCovered() const { return Covered == NumLines; }
+
+ double getPercentCovered() const {
+ assert(Covered <= NumLines && "Covered lines over-counted");
+ if (NumLines == 0)
+ return 0.0;
+ return double(Covered) / double(NumLines) * 100.0;
+ }
+};
+
+/// Provides information about function coverage for a file.
+class FunctionCoverageInfo {
+ /// The number of functions that were executed.
+ size_t Executed;
+
+ /// The total number of functions in this file.
+ size_t NumFunctions;
+
+public:
+ FunctionCoverageInfo() : Executed(0), NumFunctions(0) {}
+
+ FunctionCoverageInfo(size_t Executed, size_t NumFunctions)
+ : Executed(Executed), NumFunctions(NumFunctions) {}
+
+ FunctionCoverageInfo &operator+=(const FunctionCoverageInfo &RHS) {
+ Executed += RHS.Executed;
+ NumFunctions += RHS.NumFunctions;
+ return *this;
+ }
+
+ void addFunction(bool Covered) {
+ if (Covered)
+ ++Executed;
+ ++NumFunctions;
+ }
+
+ size_t getExecuted() const { return Executed; }
+
+ size_t getNumFunctions() const { return NumFunctions; }
+
+ bool isFullyCovered() const { return Executed == NumFunctions; }
+
+ double getPercentCovered() const {
+ assert(Executed <= NumFunctions && "Covered functions over-counted");
+ if (NumFunctions == 0)
+ return 0.0;
+ return double(Executed) / double(NumFunctions) * 100.0;
+ }
+};
+
+/// A summary of function's code coverage.
+struct FunctionCoverageSummary {
+ std::string Name;
+ uint64_t ExecutionCount;
+ RegionCoverageInfo RegionCoverage;
+ LineCoverageInfo LineCoverage;
+
+ FunctionCoverageSummary(const std::string &Name)
+ : Name(Name), ExecutionCount(0), RegionCoverage(), LineCoverage() {}
+
+ FunctionCoverageSummary(const std::string &Name, uint64_t ExecutionCount,
+ const RegionCoverageInfo &RegionCoverage,
+ const LineCoverageInfo &LineCoverage)
+ : Name(Name), ExecutionCount(ExecutionCount),
+ RegionCoverage(RegionCoverage), LineCoverage(LineCoverage) {}
+
+ /// Compute the code coverage summary for the given function coverage
+ /// mapping record.
+ static FunctionCoverageSummary get(const coverage::CoverageMapping &CM,
+ const coverage::FunctionRecord &Function);
+
+ /// Compute the code coverage summary for an instantiation group \p Group,
+ /// given a list of summaries for each instantiation in \p Summaries.
+ static FunctionCoverageSummary
+ get(const coverage::InstantiationGroup &Group,
+ ArrayRef<FunctionCoverageSummary> Summaries);
+};
+
+/// A summary of file's code coverage.
+struct FileCoverageSummary {
+ StringRef Name;
+ RegionCoverageInfo RegionCoverage;
+ LineCoverageInfo LineCoverage;
+ FunctionCoverageInfo FunctionCoverage;
+ FunctionCoverageInfo InstantiationCoverage;
+
+ FileCoverageSummary(StringRef Name)
+ : Name(Name), RegionCoverage(), LineCoverage(), FunctionCoverage(),
+ InstantiationCoverage() {}
+
+ FileCoverageSummary &operator+=(const FileCoverageSummary &RHS) {
+ RegionCoverage += RHS.RegionCoverage;
+ LineCoverage += RHS.LineCoverage;
+ FunctionCoverage += RHS.FunctionCoverage;
+ InstantiationCoverage += RHS.InstantiationCoverage;
+ return *this;
+ }
+
+ void addFunction(const FunctionCoverageSummary &Function) {
+ RegionCoverage += Function.RegionCoverage;
+ LineCoverage += Function.LineCoverage;
+ FunctionCoverage.addFunction(/*Covered=*/Function.ExecutionCount > 0);
+ }
+
+ void addInstantiation(const FunctionCoverageSummary &Function) {
+ InstantiationCoverage.addFunction(/*Covered=*/Function.ExecutionCount > 0);
+ }
+};
+
+/// A cache for demangled symbols.
+struct DemangleCache {
+ StringMap<std::string> DemangledNames;
+
+ /// Demangle \p Sym if possible. Otherwise, just return \p Sym.
+ StringRef demangle(StringRef Sym) const {
+ const auto DemangledName = DemangledNames.find(Sym);
+ if (DemangledName == DemangledNames.end())
+ return Sym;
+ return DemangledName->getValue();
+ }
+};
+
+} // namespace llvm
+
+#endif // LLVM_COV_COVERAGESUMMARYINFO_H
diff --git a/llvm/tools/llvm-cov/CoverageViewOptions.h b/llvm/tools/llvm-cov/CoverageViewOptions.h
new file mode 100644
index 000000000000..dde0c692ab05
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageViewOptions.h
@@ -0,0 +1,74 @@
+//===- CoverageViewOptions.h - Code coverage display options -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGEVIEWOPTIONS_H
+#define LLVM_COV_COVERAGEVIEWOPTIONS_H
+
+#include "llvm/Config/llvm-config.h"
+#include "RenderingSupport.h"
+#include <vector>
+
+namespace llvm {
+
+/// The options for displaying the code coverage information.
+struct CoverageViewOptions {
+ enum class OutputFormat {
+ Text,
+ HTML,
+ Lcov
+ };
+
+ bool Debug;
+ bool Colors;
+ bool ShowLineNumbers;
+ bool ShowLineStats;
+ bool ShowRegionMarkers;
+ bool ShowExpandedRegions;
+ bool ShowFunctionInstantiations;
+ bool ShowFullFilenames;
+ bool ShowRegionSummary;
+ bool ShowInstantiationSummary;
+ bool ExportSummaryOnly;
+ bool SkipExpansions;
+ bool SkipFunctions;
+ OutputFormat Format;
+ std::string ShowOutputDirectory;
+ std::vector<std::string> DemanglerOpts;
+ uint32_t TabSize;
+ std::string ProjectTitle;
+ std::string CreatedTimeStr;
+ unsigned NumThreads;
+
+ /// Change the output's stream color if the colors are enabled.
+ ColoredRawOstream colored_ostream(raw_ostream &OS,
+ raw_ostream::Colors Color) const {
+ return llvm::colored_ostream(OS, Color, Colors);
+ }
+
+ /// Check if an output directory has been specified.
+ bool hasOutputDirectory() const { return !ShowOutputDirectory.empty(); }
+
+ /// Check if a demangler has been specified.
+ bool hasDemangler() const { return !DemanglerOpts.empty(); }
+
+ /// Check if a project title has been specified.
+ bool hasProjectTitle() const { return !ProjectTitle.empty(); }
+
+ /// Check if the created time of the profile data file is available.
+ bool hasCreatedTime() const { return !CreatedTimeStr.empty(); }
+
+ /// Get the LLVM version string.
+ std::string getLLVMVersionString() const {
+ std::string VersionString = "Generated by llvm-cov -- llvm version ";
+ VersionString += LLVM_VERSION_STRING;
+ return VersionString;
+ }
+};
+}
+
+#endif // LLVM_COV_COVERAGEVIEWOPTIONS_H
diff --git a/llvm/tools/llvm-cov/RenderingSupport.h b/llvm/tools/llvm-cov/RenderingSupport.h
new file mode 100644
index 000000000000..0674fbac9a3c
--- /dev/null
+++ b/llvm/tools/llvm-cov/RenderingSupport.h
@@ -0,0 +1,60 @@
+//===- RenderingSupport.h - output stream rendering support functions ----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_RENDERINGSUPPORT_H
+#define LLVM_COV_RENDERINGSUPPORT_H
+
+#include "llvm/Support/raw_ostream.h"
+#include <utility>
+
+namespace llvm {
+
+/// A helper class that resets the output stream's color if needed
+/// when destroyed.
+class ColoredRawOstream {
+ ColoredRawOstream(const ColoredRawOstream &OS) = delete;
+
+public:
+ raw_ostream &OS;
+ bool IsColorUsed;
+
+ ColoredRawOstream(raw_ostream &OS, bool IsColorUsed)
+ : OS(OS), IsColorUsed(IsColorUsed) {}
+
+ ColoredRawOstream(ColoredRawOstream &&Other)
+ : OS(Other.OS), IsColorUsed(Other.IsColorUsed) {
+ // Reset the other IsColorUsed so that the other object won't reset the
+ // color when destroyed.
+ Other.IsColorUsed = false;
+ }
+
+ ~ColoredRawOstream() {
+ if (IsColorUsed)
+ OS.resetColor();
+ }
+};
+
+template <typename T>
+inline raw_ostream &operator<<(const ColoredRawOstream &OS, T &&Value) {
+ return OS.OS << std::forward<T>(Value);
+}
+
+/// Change the color of the output stream if the `IsColorUsed` flag
+/// is true. Returns an object that resets the color when destroyed.
+inline ColoredRawOstream colored_ostream(raw_ostream &OS,
+ raw_ostream::Colors Color,
+ bool IsColorUsed = true,
+ bool Bold = false, bool BG = false) {
+ if (IsColorUsed)
+ OS.changeColor(Color, Bold, BG);
+ return ColoredRawOstream(OS, IsColorUsed);
+}
+
+} // namespace llvm
+
+#endif // LLVM_COV_RENDERINGSUPPORT_H
diff --git a/llvm/tools/llvm-cov/SourceCoverageView.cpp b/llvm/tools/llvm-cov/SourceCoverageView.cpp
new file mode 100644
index 000000000000..0e20ea63cd6f
--- /dev/null
+++ b/llvm/tools/llvm-cov/SourceCoverageView.cpp
@@ -0,0 +1,266 @@
+//===- SourceCoverageView.cpp - Code coverage view for source code --------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This class implements rendering for code coverage of source code.
+///
+//===----------------------------------------------------------------------===//
+
+#include "SourceCoverageView.h"
+#include "SourceCoverageViewHTML.h"
+#include "SourceCoverageViewText.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/LineIterator.h"
+#include "llvm/Support/Path.h"
+
+using namespace llvm;
+
+void CoveragePrinter::StreamDestructor::operator()(raw_ostream *OS) const {
+ if (OS == &outs())
+ return;
+ delete OS;
+}
+
+std::string CoveragePrinter::getOutputPath(StringRef Path, StringRef Extension,
+ bool InToplevel,
+ bool Relative) const {
+ assert(!Extension.empty() && "The file extension may not be empty");
+
+ SmallString<256> FullPath;
+
+ if (!Relative)
+ FullPath.append(Opts.ShowOutputDirectory);
+
+ if (!InToplevel)
+ sys::path::append(FullPath, getCoverageDir());
+
+ SmallString<256> ParentPath = sys::path::parent_path(Path);
+ sys::path::remove_dots(ParentPath, /*remove_dot_dots=*/true);
+ sys::path::append(FullPath, sys::path::relative_path(ParentPath));
+
+ auto PathFilename = (sys::path::filename(Path) + "." + Extension).str();
+ sys::path::append(FullPath, PathFilename);
+ sys::path::native(FullPath);
+
+ return FullPath.str();
+}
+
+Expected<CoveragePrinter::OwnedStream>
+CoveragePrinter::createOutputStream(StringRef Path, StringRef Extension,
+ bool InToplevel) const {
+ if (!Opts.hasOutputDirectory())
+ return OwnedStream(&outs());
+
+ std::string FullPath = getOutputPath(Path, Extension, InToplevel, false);
+
+ auto ParentDir = sys::path::parent_path(FullPath);
+ if (auto E = sys::fs::create_directories(ParentDir))
+ return errorCodeToError(E);
+
+ std::error_code E;
+ raw_ostream *RawStream =
+ new raw_fd_ostream(FullPath, E, sys::fs::FA_Read | sys::fs::FA_Write);
+ auto OS = CoveragePrinter::OwnedStream(RawStream);
+ if (E)
+ return errorCodeToError(E);
+ return std::move(OS);
+}
+
+std::unique_ptr<CoveragePrinter>
+CoveragePrinter::create(const CoverageViewOptions &Opts) {
+ switch (Opts.Format) {
+ case CoverageViewOptions::OutputFormat::Text:
+ return std::make_unique<CoveragePrinterText>(Opts);
+ case CoverageViewOptions::OutputFormat::HTML:
+ return std::make_unique<CoveragePrinterHTML>(Opts);
+ case CoverageViewOptions::OutputFormat::Lcov:
+ // Unreachable because CodeCoverage.cpp should terminate with an error
+ // before we get here.
+ llvm_unreachable("Lcov format is not supported!");
+ }
+ llvm_unreachable("Unknown coverage output format!");
+}
+
+unsigned SourceCoverageView::getFirstUncoveredLineNo() {
+ const auto MinSegIt = find_if(CoverageInfo, [](const CoverageSegment &S) {
+ return S.HasCount && S.Count == 0;
+ });
+
+ // There is no uncovered line, return zero.
+ if (MinSegIt == CoverageInfo.end())
+ return 0;
+
+ return (*MinSegIt).Line;
+}
+
+std::string SourceCoverageView::formatCount(uint64_t N) {
+ std::string Number = utostr(N);
+ int Len = Number.size();
+ if (Len <= 3)
+ return Number;
+ int IntLen = Len % 3 == 0 ? 3 : Len % 3;
+ std::string Result(Number.data(), IntLen);
+ if (IntLen != 3) {
+ Result.push_back('.');
+ Result += Number.substr(IntLen, 3 - IntLen);
+ }
+ Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
+ return Result;
+}
+
+bool SourceCoverageView::shouldRenderRegionMarkers(
+ const LineCoverageStats &LCS) const {
+ if (!getOptions().ShowRegionMarkers)
+ return false;
+
+ CoverageSegmentArray Segments = LCS.getLineSegments();
+ if (Segments.empty())
+ return false;
+ for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {
+ const auto *CurSeg = Segments[I];
+ if (!CurSeg->IsRegionEntry || CurSeg->Count == LCS.getExecutionCount())
+ continue;
+ return true;
+ }
+ return false;
+}
+
+bool SourceCoverageView::hasSubViews() const {
+ return !ExpansionSubViews.empty() || !InstantiationSubViews.empty();
+}
+
+std::unique_ptr<SourceCoverageView>
+SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File,
+ const CoverageViewOptions &Options,
+ CoverageData &&CoverageInfo) {
+ switch (Options.Format) {
+ case CoverageViewOptions::OutputFormat::Text:
+ return std::make_unique<SourceCoverageViewText>(
+ SourceName, File, Options, std::move(CoverageInfo));
+ case CoverageViewOptions::OutputFormat::HTML:
+ return std::make_unique<SourceCoverageViewHTML>(
+ SourceName, File, Options, std::move(CoverageInfo));
+ case CoverageViewOptions::OutputFormat::Lcov:
+ // Unreachable because CodeCoverage.cpp should terminate with an error
+ // before we get here.
+ llvm_unreachable("Lcov format is not supported!");
+ }
+ llvm_unreachable("Unknown coverage output format!");
+}
+
+std::string SourceCoverageView::getSourceName() const {
+ SmallString<128> SourceText(SourceName);
+ sys::path::remove_dots(SourceText, /*remove_dot_dots=*/true);
+ sys::path::native(SourceText);
+ return SourceText.str();
+}
+
+void SourceCoverageView::addExpansion(
+ const CounterMappingRegion &Region,
+ std::unique_ptr<SourceCoverageView> View) {
+ ExpansionSubViews.emplace_back(Region, std::move(View));
+}
+
+void SourceCoverageView::addInstantiation(
+ StringRef FunctionName, unsigned Line,
+ std::unique_ptr<SourceCoverageView> View) {
+ InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
+}
+
+void SourceCoverageView::print(raw_ostream &OS, bool WholeFile,
+ bool ShowSourceName, bool ShowTitle,
+ unsigned ViewDepth) {
+ if (ShowTitle)
+ renderTitle(OS, "Coverage Report");
+
+ renderViewHeader(OS);
+
+ if (ShowSourceName)
+ renderSourceName(OS, WholeFile);
+
+ renderTableHeader(OS, (ViewDepth > 0) ? 0 : getFirstUncoveredLineNo(),
+ ViewDepth);
+
+ // We need the expansions and instantiations sorted so we can go through them
+ // while we iterate lines.
+ llvm::stable_sort(ExpansionSubViews);
+ llvm::stable_sort(InstantiationSubViews);
+ auto NextESV = ExpansionSubViews.begin();
+ auto EndESV = ExpansionSubViews.end();
+ auto NextISV = InstantiationSubViews.begin();
+ auto EndISV = InstantiationSubViews.end();
+
+ // Get the coverage information for the file.
+ auto StartSegment = CoverageInfo.begin();
+ auto EndSegment = CoverageInfo.end();
+ LineCoverageIterator LCI{CoverageInfo, 1};
+ LineCoverageIterator LCIEnd = LCI.getEnd();
+
+ unsigned FirstLine = StartSegment != EndSegment ? StartSegment->Line : 0;
+ for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof();
+ ++LI, ++LCI) {
+ // If we aren't rendering the whole file, we need to filter out the prologue
+ // and epilogue.
+ if (!WholeFile) {
+ if (LCI == LCIEnd)
+ break;
+ else if (LI.line_number() < FirstLine)
+ continue;
+ }
+
+ renderLinePrefix(OS, ViewDepth);
+ if (getOptions().ShowLineNumbers)
+ renderLineNumberColumn(OS, LI.line_number());
+
+ if (getOptions().ShowLineStats)
+ renderLineCoverageColumn(OS, *LCI);
+
+ // If there are expansion subviews, we want to highlight the first one.
+ unsigned ExpansionColumn = 0;
+ if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
+ getOptions().Colors)
+ ExpansionColumn = NextESV->getStartCol();
+
+ // Display the source code for the current line.
+ renderLine(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn, ViewDepth);
+
+ // Show the region markers.
+ if (shouldRenderRegionMarkers(*LCI))
+ renderRegionMarkers(OS, *LCI, ViewDepth);
+
+ // Show the expansions and instantiations for this line.
+ bool RenderedSubView = false;
+ for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
+ ++NextESV) {
+ renderViewDivider(OS, ViewDepth + 1);
+
+ // Re-render the current line and highlight the expansion range for
+ // this subview.
+ if (RenderedSubView) {
+ ExpansionColumn = NextESV->getStartCol();
+ renderExpansionSite(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn,
+ ViewDepth);
+ renderViewDivider(OS, ViewDepth + 1);
+ }
+
+ renderExpansionView(OS, *NextESV, ViewDepth + 1);
+ RenderedSubView = true;
+ }
+ for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
+ renderViewDivider(OS, ViewDepth + 1);
+ renderInstantiationView(OS, *NextISV, ViewDepth + 1);
+ RenderedSubView = true;
+ }
+ if (RenderedSubView)
+ renderViewDivider(OS, ViewDepth + 1);
+ renderLineSuffix(OS, ViewDepth);
+ }
+
+ renderViewFooter(OS);
+}
diff --git a/llvm/tools/llvm-cov/SourceCoverageView.h b/llvm/tools/llvm-cov/SourceCoverageView.h
new file mode 100644
index 000000000000..9ae928443651
--- /dev/null
+++ b/llvm/tools/llvm-cov/SourceCoverageView.h
@@ -0,0 +1,266 @@
+//===- SourceCoverageView.h - Code coverage view for source code ----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This class implements rendering for code coverage of source code.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_SOURCECOVERAGEVIEW_H
+#define LLVM_COV_SOURCECOVERAGEVIEW_H
+
+#include "CoverageViewOptions.h"
+#include "CoverageSummaryInfo.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include <vector>
+
+namespace llvm {
+
+using namespace coverage;
+
+class CoverageFiltersMatchAll;
+class SourceCoverageView;
+
+/// A view that represents a macro or include expansion.
+struct ExpansionView {
+ CounterMappingRegion Region;
+ std::unique_ptr<SourceCoverageView> View;
+
+ ExpansionView(const CounterMappingRegion &Region,
+ std::unique_ptr<SourceCoverageView> View)
+ : Region(Region), View(std::move(View)) {}
+ ExpansionView(ExpansionView &&RHS)
+ : Region(std::move(RHS.Region)), View(std::move(RHS.View)) {}
+ ExpansionView &operator=(ExpansionView &&RHS) {
+ Region = std::move(RHS.Region);
+ View = std::move(RHS.View);
+ return *this;
+ }
+
+ unsigned getLine() const { return Region.LineStart; }
+ unsigned getStartCol() const { return Region.ColumnStart; }
+ unsigned getEndCol() const { return Region.ColumnEnd; }
+
+ friend bool operator<(const ExpansionView &LHS, const ExpansionView &RHS) {
+ return LHS.Region.startLoc() < RHS.Region.startLoc();
+ }
+};
+
+/// A view that represents a function instantiation.
+struct InstantiationView {
+ StringRef FunctionName;
+ unsigned Line;
+ std::unique_ptr<SourceCoverageView> View;
+
+ InstantiationView(StringRef FunctionName, unsigned Line,
+ std::unique_ptr<SourceCoverageView> View)
+ : FunctionName(FunctionName), Line(Line), View(std::move(View)) {}
+
+ friend bool operator<(const InstantiationView &LHS,
+ const InstantiationView &RHS) {
+ return LHS.Line < RHS.Line;
+ }
+};
+
+/// A file manager that handles format-aware file creation.
+class CoveragePrinter {
+public:
+ struct StreamDestructor {
+ void operator()(raw_ostream *OS) const;
+ };
+
+ using OwnedStream = std::unique_ptr<raw_ostream, StreamDestructor>;
+
+protected:
+ const CoverageViewOptions &Opts;
+
+ CoveragePrinter(const CoverageViewOptions &Opts) : Opts(Opts) {}
+
+ /// Return `OutputDir/ToplevelDir/Path.Extension`. If \p InToplevel is
+ /// false, skip the ToplevelDir component. If \p Relative is false, skip the
+ /// OutputDir component.
+ std::string getOutputPath(StringRef Path, StringRef Extension,
+ bool InToplevel, bool Relative = true) const;
+
+ /// If directory output is enabled, create a file in that directory
+ /// at the path given by getOutputPath(). Otherwise, return stdout.
+ Expected<OwnedStream> createOutputStream(StringRef Path, StringRef Extension,
+ bool InToplevel) const;
+
+ /// Return the sub-directory name for file coverage reports.
+ static StringRef getCoverageDir() { return "coverage"; }
+
+public:
+ static std::unique_ptr<CoveragePrinter>
+ create(const CoverageViewOptions &Opts);
+
+ virtual ~CoveragePrinter() {}
+
+ /// @name File Creation Interface
+ /// @{
+
+ /// Create a file to print a coverage view into.
+ virtual Expected<OwnedStream> createViewFile(StringRef Path,
+ bool InToplevel) = 0;
+
+ /// Close a file which has been used to print a coverage view.
+ virtual void closeViewFile(OwnedStream OS) = 0;
+
+ /// Create an index which lists reports for the given source files.
+ virtual Error createIndexFile(ArrayRef<std::string> SourceFiles,
+ const CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) = 0;
+
+ /// @}
+};
+
+/// A code coverage view of a source file or function.
+///
+/// A source coverage view and its nested sub-views form a file-oriented
+/// representation of code coverage data. This view can be printed out by a
+/// renderer which implements the Rendering Interface.
+class SourceCoverageView {
+ /// A function or file name.
+ StringRef SourceName;
+
+ /// A memory buffer backing the source on display.
+ const MemoryBuffer &File;
+
+ /// Various options to guide the coverage renderer.
+ const CoverageViewOptions &Options;
+
+ /// Complete coverage information about the source on display.
+ CoverageData CoverageInfo;
+
+ /// A container for all expansions (e.g macros) in the source on display.
+ std::vector<ExpansionView> ExpansionSubViews;
+
+ /// A container for all instantiations (e.g template functions) in the source
+ /// on display.
+ std::vector<InstantiationView> InstantiationSubViews;
+
+ /// Get the first uncovered line number for the source file.
+ unsigned getFirstUncoveredLineNo();
+
+protected:
+ struct LineRef {
+ StringRef Line;
+ int64_t LineNo;
+
+ LineRef(StringRef Line, int64_t LineNo) : Line(Line), LineNo(LineNo) {}
+ };
+
+ using CoverageSegmentArray = ArrayRef<const CoverageSegment *>;
+
+ /// @name Rendering Interface
+ /// @{
+
+ /// Render a header for the view.
+ virtual void renderViewHeader(raw_ostream &OS) = 0;
+
+ /// Render a footer for the view.
+ virtual void renderViewFooter(raw_ostream &OS) = 0;
+
+ /// Render the source name for the view.
+ virtual void renderSourceName(raw_ostream &OS, bool WholeFile) = 0;
+
+ /// Render the line prefix at the given \p ViewDepth.
+ virtual void renderLinePrefix(raw_ostream &OS, unsigned ViewDepth) = 0;
+
+ /// Render the line suffix at the given \p ViewDepth.
+ virtual void renderLineSuffix(raw_ostream &OS, unsigned ViewDepth) = 0;
+
+ /// Render a view divider at the given \p ViewDepth.
+ virtual void renderViewDivider(raw_ostream &OS, unsigned ViewDepth) = 0;
+
+ /// Render a source line with highlighting.
+ virtual void renderLine(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS, unsigned ExpansionCol,
+ unsigned ViewDepth) = 0;
+
+ /// Render the line's execution count column.
+ virtual void renderLineCoverageColumn(raw_ostream &OS,
+ const LineCoverageStats &Line) = 0;
+
+ /// Render the line number column.
+ virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0;
+
+ /// Render all the region's execution counts on a line.
+ virtual void renderRegionMarkers(raw_ostream &OS,
+ const LineCoverageStats &Line,
+ unsigned ViewDepth) = 0;
+
+ /// Render the site of an expansion.
+ virtual void renderExpansionSite(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS,
+ unsigned ExpansionCol,
+ unsigned ViewDepth) = 0;
+
+ /// Render an expansion view and any nested views.
+ virtual void renderExpansionView(raw_ostream &OS, ExpansionView &ESV,
+ unsigned ViewDepth) = 0;
+
+ /// Render an instantiation view and any nested views.
+ virtual void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV,
+ unsigned ViewDepth) = 0;
+
+ /// Render \p Title, a project title if one is available, and the
+ /// created time.
+ virtual void renderTitle(raw_ostream &OS, StringRef CellText) = 0;
+
+ /// Render the table header for a given source file.
+ virtual void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo,
+ unsigned IndentLevel) = 0;
+
+ /// @}
+
+ /// Format a count using engineering notation with 3 significant
+ /// digits.
+ static std::string formatCount(uint64_t N);
+
+ /// Check if region marker output is expected for a line.
+ bool shouldRenderRegionMarkers(const LineCoverageStats &LCS) const;
+
+ /// Check if there are any sub-views attached to this view.
+ bool hasSubViews() const;
+
+ SourceCoverageView(StringRef SourceName, const MemoryBuffer &File,
+ const CoverageViewOptions &Options,
+ CoverageData &&CoverageInfo)
+ : SourceName(SourceName), File(File), Options(Options),
+ CoverageInfo(std::move(CoverageInfo)) {}
+
+public:
+ static std::unique_ptr<SourceCoverageView>
+ create(StringRef SourceName, const MemoryBuffer &File,
+ const CoverageViewOptions &Options, CoverageData &&CoverageInfo);
+
+ virtual ~SourceCoverageView() {}
+
+ /// Return the source name formatted for the host OS.
+ std::string getSourceName() const;
+
+ const CoverageViewOptions &getOptions() const { return Options; }
+
+ /// Add an expansion subview to this view.
+ void addExpansion(const CounterMappingRegion &Region,
+ std::unique_ptr<SourceCoverageView> View);
+
+ /// Add a function instantiation subview to this view.
+ void addInstantiation(StringRef FunctionName, unsigned Line,
+ std::unique_ptr<SourceCoverageView> View);
+
+ /// Print the code coverage information for a specific portion of a
+ /// source file to the output stream.
+ void print(raw_ostream &OS, bool WholeFile, bool ShowSourceName,
+ bool ShowTitle, unsigned ViewDepth = 0);
+};
+
+} // namespace llvm
+
+#endif // LLVM_COV_SOURCECOVERAGEVIEW_H
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
new file mode 100644
index 000000000000..e3332245f9c8
--- /dev/null
+++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp
@@ -0,0 +1,697 @@
+//===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file implements the html coverage renderer.
+///
+//===----------------------------------------------------------------------===//
+
+#include "CoverageReport.h"
+#include "SourceCoverageViewHTML.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/Path.h"
+
+using namespace llvm;
+
+namespace {
+
+// Return a string with the special characters in \p Str escaped.
+std::string escape(StringRef Str, const CoverageViewOptions &Opts) {
+ std::string TabExpandedResult;
+ unsigned ColNum = 0; // Record the column number.
+ for (char C : Str) {
+ if (C == '\t') {
+ // Replace '\t' with up to TabSize spaces.
+ unsigned NumSpaces = Opts.TabSize - (ColNum % Opts.TabSize);
+ for (unsigned I = 0; I < NumSpaces; ++I)
+ TabExpandedResult += ' ';
+ ColNum += NumSpaces;
+ } else {
+ TabExpandedResult += C;
+ if (C == '\n' || C == '\r')
+ ColNum = 0;
+ else
+ ++ColNum;
+ }
+ }
+ std::string EscapedHTML;
+ {
+ raw_string_ostream OS{EscapedHTML};
+ printHTMLEscaped(TabExpandedResult, OS);
+ }
+ return EscapedHTML;
+}
+
+// Create a \p Name tag around \p Str, and optionally set its \p ClassName.
+std::string tag(const std::string &Name, const std::string &Str,
+ const std::string &ClassName = "") {
+ std::string Tag = "<" + Name;
+ if (!ClassName.empty())
+ Tag += " class='" + ClassName + "'";
+ return Tag + ">" + Str + "</" + Name + ">";
+}
+
+// Create an anchor to \p Link with the label \p Str.
+std::string a(const std::string &Link, const std::string &Str,
+ const std::string &TargetName = "") {
+ std::string Name = TargetName.empty() ? "" : ("name='" + TargetName + "' ");
+ return "<a " + Name + "href='" + Link + "'>" + Str + "</a>";
+}
+
+const char *BeginHeader =
+ "<head>"
+ "<meta name='viewport' content='width=device-width,initial-scale=1'>"
+ "<meta charset='UTF-8'>";
+
+const char *CSSForCoverage =
+ R"(.red {
+ background-color: #ffd0d0;
+}
+.cyan {
+ background-color: cyan;
+}
+body {
+ font-family: -apple-system, sans-serif;
+}
+pre {
+ margin-top: 0px !important;
+ margin-bottom: 0px !important;
+}
+.source-name-title {
+ padding: 5px 10px;
+ border-bottom: 1px solid #dbdbdb;
+ background-color: #eee;
+ line-height: 35px;
+}
+.centered {
+ display: table;
+ margin-left: left;
+ margin-right: auto;
+ border: 1px solid #dbdbdb;
+ border-radius: 3px;
+}
+.expansion-view {
+ background-color: rgba(0, 0, 0, 0);
+ margin-left: 0px;
+ margin-top: 5px;
+ margin-right: 5px;
+ margin-bottom: 5px;
+ border: 1px solid #dbdbdb;
+ border-radius: 3px;
+}
+table {
+ border-collapse: collapse;
+}
+.light-row {
+ background: #ffffff;
+ border: 1px solid #dbdbdb;
+}
+.light-row-bold {
+ background: #ffffff;
+ border: 1px solid #dbdbdb;
+ font-weight: bold;
+}
+.column-entry {
+ text-align: left;
+}
+.column-entry-bold {
+ font-weight: bold;
+ text-align: left;
+}
+.column-entry-yellow {
+ text-align: left;
+ background-color: #ffffd0;
+}
+.column-entry-yellow:hover {
+ background-color: #fffff0;
+}
+.column-entry-red {
+ text-align: left;
+ background-color: #ffd0d0;
+}
+.column-entry-red:hover {
+ background-color: #fff0f0;
+}
+.column-entry-green {
+ text-align: left;
+ background-color: #d0ffd0;
+}
+.column-entry-green:hover {
+ background-color: #f0fff0;
+}
+.line-number {
+ text-align: right;
+ color: #aaa;
+}
+.covered-line {
+ text-align: right;
+ color: #0080ff;
+}
+.uncovered-line {
+ text-align: right;
+ color: #ff3300;
+}
+.tooltip {
+ position: relative;
+ display: inline;
+ background-color: #b3e6ff;
+ text-decoration: none;
+}
+.tooltip span.tooltip-content {
+ position: absolute;
+ width: 100px;
+ margin-left: -50px;
+ color: #FFFFFF;
+ background: #000000;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+ visibility: hidden;
+ border-radius: 6px;
+}
+.tooltip span.tooltip-content:after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -8px;
+ width: 0; height: 0;
+ border-top: 8px solid #000000;
+ border-right: 8px solid transparent;
+ border-left: 8px solid transparent;
+}
+:hover.tooltip span.tooltip-content {
+ visibility: visible;
+ opacity: 0.8;
+ bottom: 30px;
+ left: 50%;
+ z-index: 999;
+}
+th, td {
+ vertical-align: top;
+ padding: 2px 8px;
+ border-collapse: collapse;
+ border-right: solid 1px #eee;
+ border-left: solid 1px #eee;
+ text-align: left;
+}
+td pre {
+ display: inline-block;
+}
+td:first-child {
+ border-left: none;
+}
+td:last-child {
+ border-right: none;
+}
+tr:hover {
+ background-color: #f0f0f0;
+}
+)";
+
+const char *EndHeader = "</head>";
+
+const char *BeginCenteredDiv = "<div class='centered'>";
+
+const char *EndCenteredDiv = "</div>";
+
+const char *BeginSourceNameDiv = "<div class='source-name-title'>";
+
+const char *EndSourceNameDiv = "</div>";
+
+const char *BeginCodeTD = "<td class='code'>";
+
+const char *EndCodeTD = "</td>";
+
+const char *BeginPre = "<pre>";
+
+const char *EndPre = "</pre>";
+
+const char *BeginExpansionDiv = "<div class='expansion-view'>";
+
+const char *EndExpansionDiv = "</div>";
+
+const char *BeginTable = "<table>";
+
+const char *EndTable = "</table>";
+
+const char *ProjectTitleTag = "h1";
+
+const char *ReportTitleTag = "h2";
+
+const char *CreatedTimeTag = "h4";
+
+std::string getPathToStyle(StringRef ViewPath) {
+ std::string PathToStyle = "";
+ std::string PathSep = sys::path::get_separator();
+ unsigned NumSeps = ViewPath.count(PathSep);
+ for (unsigned I = 0, E = NumSeps; I < E; ++I)
+ PathToStyle += ".." + PathSep;
+ return PathToStyle + "style.css";
+}
+
+void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
+ const std::string &PathToStyle = "") {
+ OS << "<!doctype html>"
+ "<html>"
+ << BeginHeader;
+
+ // Link to a stylesheet if one is available. Otherwise, use the default style.
+ if (PathToStyle.empty())
+ OS << "<style>" << CSSForCoverage << "</style>";
+ else
+ OS << "<link rel='stylesheet' type='text/css' href='"
+ << escape(PathToStyle, Opts) << "'>";
+
+ OS << EndHeader << "<body>";
+}
+
+void emitEpilog(raw_ostream &OS) {
+ OS << "</body>"
+ << "</html>";
+}
+
+} // anonymous namespace
+
+Expected<CoveragePrinter::OwnedStream>
+CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
+ auto OSOrErr = createOutputStream(Path, "html", InToplevel);
+ if (!OSOrErr)
+ return OSOrErr;
+
+ OwnedStream OS = std::move(OSOrErr.get());
+
+ if (!Opts.hasOutputDirectory()) {
+ emitPrelude(*OS.get(), Opts);
+ } else {
+ std::string ViewPath = getOutputPath(Path, "html", InToplevel);
+ emitPrelude(*OS.get(), Opts, getPathToStyle(ViewPath));
+ }
+
+ return std::move(OS);
+}
+
+void CoveragePrinterHTML::closeViewFile(OwnedStream OS) {
+ emitEpilog(*OS.get());
+}
+
+/// Emit column labels for the table in the index.
+static void emitColumnLabelsForIndex(raw_ostream &OS,
+ const CoverageViewOptions &Opts) {
+ SmallVector<std::string, 4> Columns;
+ Columns.emplace_back(tag("td", "Filename", "column-entry-bold"));
+ Columns.emplace_back(tag("td", "Function Coverage", "column-entry-bold"));
+ if (Opts.ShowInstantiationSummary)
+ Columns.emplace_back(
+ tag("td", "Instantiation Coverage", "column-entry-bold"));
+ Columns.emplace_back(tag("td", "Line Coverage", "column-entry-bold"));
+ if (Opts.ShowRegionSummary)
+ Columns.emplace_back(tag("td", "Region Coverage", "column-entry-bold"));
+ OS << tag("tr", join(Columns.begin(), Columns.end(), ""));
+}
+
+std::string
+CoveragePrinterHTML::buildLinkToFile(StringRef SF,
+ const FileCoverageSummary &FCS) const {
+ SmallString<128> LinkTextStr(sys::path::relative_path(FCS.Name));
+ sys::path::remove_dots(LinkTextStr, /*remove_dot_dots=*/true);
+ sys::path::native(LinkTextStr);
+ std::string LinkText = escape(LinkTextStr, Opts);
+ std::string LinkTarget =
+ escape(getOutputPath(SF, "html", /*InToplevel=*/false), Opts);
+ return a(LinkTarget, LinkText);
+}
+
+/// Render a file coverage summary (\p FCS) in a table row. If \p IsTotals is
+/// false, link the summary to \p SF.
+void CoveragePrinterHTML::emitFileSummary(raw_ostream &OS, StringRef SF,
+ const FileCoverageSummary &FCS,
+ bool IsTotals) const {
+ SmallVector<std::string, 8> Columns;
+
+ // Format a coverage triple and add the result to the list of columns.
+ auto AddCoverageTripleToColumn = [&Columns](unsigned Hit, unsigned Total,
+ float Pctg) {
+ std::string S;
+ {
+ raw_string_ostream RSO{S};
+ if (Total)
+ RSO << format("%*.2f", 7, Pctg) << "% ";
+ else
+ RSO << "- ";
+ RSO << '(' << Hit << '/' << Total << ')';
+ }
+ const char *CellClass = "column-entry-yellow";
+ if (Hit == Total)
+ CellClass = "column-entry-green";
+ else if (Pctg < 80.0)
+ CellClass = "column-entry-red";
+ Columns.emplace_back(tag("td", tag("pre", S), CellClass));
+ };
+
+ // Simplify the display file path, and wrap it in a link if requested.
+ std::string Filename;
+ if (IsTotals) {
+ Filename = SF;
+ } else {
+ Filename = buildLinkToFile(SF, FCS);
+ }
+
+ Columns.emplace_back(tag("td", tag("pre", Filename)));
+ AddCoverageTripleToColumn(FCS.FunctionCoverage.getExecuted(),
+ FCS.FunctionCoverage.getNumFunctions(),
+ FCS.FunctionCoverage.getPercentCovered());
+ if (Opts.ShowInstantiationSummary)
+ AddCoverageTripleToColumn(FCS.InstantiationCoverage.getExecuted(),
+ FCS.InstantiationCoverage.getNumFunctions(),
+ FCS.InstantiationCoverage.getPercentCovered());
+ AddCoverageTripleToColumn(FCS.LineCoverage.getCovered(),
+ FCS.LineCoverage.getNumLines(),
+ FCS.LineCoverage.getPercentCovered());
+ if (Opts.ShowRegionSummary)
+ AddCoverageTripleToColumn(FCS.RegionCoverage.getCovered(),
+ FCS.RegionCoverage.getNumRegions(),
+ FCS.RegionCoverage.getPercentCovered());
+
+ if (IsTotals)
+ OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold");
+ else
+ OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row");
+}
+
+Error CoveragePrinterHTML::createIndexFile(
+ ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) {
+ // Emit the default stylesheet.
+ auto CSSOrErr = createOutputStream("style", "css", /*InToplevel=*/true);
+ if (Error E = CSSOrErr.takeError())
+ return E;
+
+ OwnedStream CSS = std::move(CSSOrErr.get());
+ CSS->operator<<(CSSForCoverage);
+
+ // Emit a file index along with some coverage statistics.
+ auto OSOrErr = createOutputStream("index", "html", /*InToplevel=*/true);
+ if (Error E = OSOrErr.takeError())
+ return E;
+ auto OS = std::move(OSOrErr.get());
+ raw_ostream &OSRef = *OS.get();
+
+ assert(Opts.hasOutputDirectory() && "No output directory for index file");
+ emitPrelude(OSRef, Opts, getPathToStyle(""));
+
+ // Emit some basic information about the coverage report.
+ if (Opts.hasProjectTitle())
+ OSRef << tag(ProjectTitleTag, escape(Opts.ProjectTitle, Opts));
+ OSRef << tag(ReportTitleTag, "Coverage Report");
+ if (Opts.hasCreatedTime())
+ OSRef << tag(CreatedTimeTag, escape(Opts.CreatedTimeStr, Opts));
+
+ // Emit a link to some documentation.
+ OSRef << tag("p", "Click " +
+ a("http://clang.llvm.org/docs/"
+ "SourceBasedCodeCoverage.html#interpreting-reports",
+ "here") +
+ " for information about interpreting this report.");
+
+ // Emit a table containing links to reports for each file in the covmapping.
+ // Exclude files which don't contain any regions.
+ OSRef << BeginCenteredDiv << BeginTable;
+ emitColumnLabelsForIndex(OSRef, Opts);
+ FileCoverageSummary Totals("TOTALS");
+ auto FileReports = CoverageReport::prepareFileReports(
+ Coverage, Totals, SourceFiles, Opts, Filters);
+ bool EmptyFiles = false;
+ for (unsigned I = 0, E = FileReports.size(); I < E; ++I) {
+ if (FileReports[I].FunctionCoverage.getNumFunctions())
+ emitFileSummary(OSRef, SourceFiles[I], FileReports[I]);
+ else
+ EmptyFiles = true;
+ }
+ emitFileSummary(OSRef, "Totals", Totals, /*IsTotals=*/true);
+ OSRef << EndTable << EndCenteredDiv;
+
+ // Emit links to files which don't contain any functions. These are normally
+ // not very useful, but could be relevant for code which abuses the
+ // preprocessor.
+ if (EmptyFiles && Filters.empty()) {
+ OSRef << tag("p", "Files which contain no functions. (These "
+ "files contain code pulled into other files "
+ "by the preprocessor.)\n");
+ OSRef << BeginCenteredDiv << BeginTable;
+ for (unsigned I = 0, E = FileReports.size(); I < E; ++I)
+ if (!FileReports[I].FunctionCoverage.getNumFunctions()) {
+ std::string Link = buildLinkToFile(SourceFiles[I], FileReports[I]);
+ OSRef << tag("tr", tag("td", tag("pre", Link)), "light-row") << '\n';
+ }
+ OSRef << EndTable << EndCenteredDiv;
+ }
+
+ OSRef << tag("h5", escape(Opts.getLLVMVersionString(), Opts));
+ emitEpilog(OSRef);
+
+ return Error::success();
+}
+
+void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
+ OS << BeginCenteredDiv << BeginTable;
+}
+
+void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) {
+ OS << EndTable << EndCenteredDiv;
+}
+
+void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS, bool WholeFile) {
+ OS << BeginSourceNameDiv << tag("pre", escape(getSourceName(), getOptions()))
+ << EndSourceNameDiv;
+}
+
+void SourceCoverageViewHTML::renderLinePrefix(raw_ostream &OS, unsigned) {
+ OS << "<tr>";
+}
+
+void SourceCoverageViewHTML::renderLineSuffix(raw_ostream &OS, unsigned) {
+ // If this view has sub-views, renderLine() cannot close the view's cell.
+ // Take care of it here, after all sub-views have been rendered.
+ if (hasSubViews())
+ OS << EndCodeTD;
+ OS << "</tr>";
+}
+
+void SourceCoverageViewHTML::renderViewDivider(raw_ostream &, unsigned) {
+ // The table-based output makes view dividers unnecessary.
+}
+
+void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS,
+ unsigned ExpansionCol, unsigned) {
+ StringRef Line = L.Line;
+ unsigned LineNo = L.LineNo;
+
+ // Steps for handling text-escaping, highlighting, and tooltip creation:
+ //
+ // 1. Split the line into N+1 snippets, where N = |Segments|. The first
+ // snippet starts from Col=1 and ends at the start of the first segment.
+ // The last snippet starts at the last mapped column in the line and ends
+ // at the end of the line. Both are required but may be empty.
+
+ SmallVector<std::string, 8> Snippets;
+ CoverageSegmentArray Segments = LCS.getLineSegments();
+
+ unsigned LCol = 1;
+ auto Snip = [&](unsigned Start, unsigned Len) {
+ Snippets.push_back(Line.substr(Start, Len));
+ LCol += Len;
+ };
+
+ Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1));
+
+ for (unsigned I = 1, E = Segments.size(); I < E; ++I)
+ Snip(LCol - 1, Segments[I]->Col - LCol);
+
+ // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1.
+ Snip(LCol - 1, Line.size() + 1 - LCol);
+
+ // 2. Escape all of the snippets.
+
+ for (unsigned I = 0, E = Snippets.size(); I < E; ++I)
+ Snippets[I] = escape(Snippets[I], getOptions());
+
+ // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment
+ // 1 to set the highlight for snippet 2, segment 2 to set the highlight for
+ // snippet 3, and so on.
+
+ Optional<StringRef> Color;
+ SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
+ auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) {
+ if (getOptions().Debug)
+ HighlightedRanges.emplace_back(LC, RC);
+ return tag("span", Snippet, Color.getValue());
+ };
+
+ auto CheckIfUncovered = [&](const CoverageSegment *S) {
+ return S && (!S->IsGapRegion || (Color && *Color == "red")) &&
+ S->HasCount && S->Count == 0;
+ };
+
+ if (CheckIfUncovered(LCS.getWrappedSegment())) {
+ Color = "red";
+ if (!Snippets[0].empty())
+ Snippets[0] = Highlight(Snippets[0], 1, 1 + Snippets[0].size());
+ }
+
+ for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
+ const auto *CurSeg = Segments[I];
+ if (CheckIfUncovered(CurSeg))
+ Color = "red";
+ else if (CurSeg->Col == ExpansionCol)
+ Color = "cyan";
+ else
+ Color = None;
+
+ if (Color.hasValue())
+ Snippets[I + 1] = Highlight(Snippets[I + 1], CurSeg->Col,
+ CurSeg->Col + Snippets[I + 1].size());
+ }
+
+ if (Color.hasValue() && Segments.empty())
+ Snippets.back() = Highlight(Snippets.back(), 1, 1 + Snippets.back().size());
+
+ if (getOptions().Debug) {
+ for (const auto &Range : HighlightedRanges) {
+ errs() << "Highlighted line " << LineNo << ", " << Range.first << " -> ";
+ if (Range.second == 0)
+ errs() << "?";
+ else
+ errs() << Range.second;
+ errs() << "\n";
+ }
+ }
+
+ // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
+ // sub-line region count tooltips if needed.
+
+ if (shouldRenderRegionMarkers(LCS)) {
+ // Just consider the segments which start *and* end on this line.
+ for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {
+ const auto *CurSeg = Segments[I];
+ if (!CurSeg->IsRegionEntry)
+ continue;
+ if (CurSeg->Count == LCS.getExecutionCount())
+ continue;
+
+ Snippets[I + 1] =
+ tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count),
+ "tooltip-content"),
+ "tooltip");
+
+ if (getOptions().Debug)
+ errs() << "Marker at " << CurSeg->Line << ":" << CurSeg->Col << " = "
+ << formatCount(CurSeg->Count) << "\n";
+ }
+ }
+
+ OS << BeginCodeTD;
+ OS << BeginPre;
+ for (const auto &Snippet : Snippets)
+ OS << Snippet;
+ OS << EndPre;
+
+ // If there are no sub-views left to attach to this cell, end the cell.
+ // Otherwise, end it after the sub-views are rendered (renderLineSuffix()).
+ if (!hasSubViews())
+ OS << EndCodeTD;
+}
+
+void SourceCoverageViewHTML::renderLineCoverageColumn(
+ raw_ostream &OS, const LineCoverageStats &Line) {
+ std::string Count = "";
+ if (Line.isMapped())
+ Count = tag("pre", formatCount(Line.getExecutionCount()));
+ std::string CoverageClass =
+ (Line.getExecutionCount() > 0) ? "covered-line" : "uncovered-line";
+ OS << tag("td", Count, CoverageClass);
+}
+
+void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
+ unsigned LineNo) {
+ std::string LineNoStr = utostr(uint64_t(LineNo));
+ std::string TargetName = "L" + LineNoStr;
+ OS << tag("td", a("#" + TargetName, tag("pre", LineNoStr), TargetName),
+ "line-number");
+}
+
+void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &,
+ const LineCoverageStats &Line,
+ unsigned) {
+ // Region markers are rendered in-line using tooltips.
+}
+
+void SourceCoverageViewHTML::renderExpansionSite(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS,
+ unsigned ExpansionCol,
+ unsigned ViewDepth) {
+ // Render the line containing the expansion site. No extra formatting needed.
+ renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
+}
+
+void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS,
+ ExpansionView &ESV,
+ unsigned ViewDepth) {
+ OS << BeginExpansionDiv;
+ ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
+ /*ShowTitle=*/false, ViewDepth + 1);
+ OS << EndExpansionDiv;
+}
+
+void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS,
+ InstantiationView &ISV,
+ unsigned ViewDepth) {
+ OS << BeginExpansionDiv;
+ if (!ISV.View)
+ OS << BeginSourceNameDiv
+ << tag("pre",
+ escape("Unexecuted instantiation: " + ISV.FunctionName.str(),
+ getOptions()))
+ << EndSourceNameDiv;
+ else
+ ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
+ /*ShowTitle=*/false, ViewDepth);
+ OS << EndExpansionDiv;
+}
+
+void SourceCoverageViewHTML::renderTitle(raw_ostream &OS, StringRef Title) {
+ if (getOptions().hasProjectTitle())
+ OS << tag(ProjectTitleTag, escape(getOptions().ProjectTitle, getOptions()));
+ OS << tag(ReportTitleTag, escape(Title, getOptions()));
+ if (getOptions().hasCreatedTime())
+ OS << tag(CreatedTimeTag,
+ escape(getOptions().CreatedTimeStr, getOptions()));
+}
+
+void SourceCoverageViewHTML::renderTableHeader(raw_ostream &OS,
+ unsigned FirstUncoveredLineNo,
+ unsigned ViewDepth) {
+ std::string SourceLabel;
+ if (FirstUncoveredLineNo == 0) {
+ SourceLabel = tag("td", tag("pre", "Source"));
+ } else {
+ std::string LinkTarget = "#L" + utostr(uint64_t(FirstUncoveredLineNo));
+ SourceLabel =
+ tag("td", tag("pre", "Source (" +
+ a(LinkTarget, "jump to first uncovered line") +
+ ")"));
+ }
+
+ renderLinePrefix(OS, ViewDepth);
+ OS << tag("td", tag("pre", "Line")) << tag("td", tag("pre", "Count"))
+ << SourceLabel;
+ renderLineSuffix(OS, ViewDepth);
+}
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
new file mode 100644
index 000000000000..9834040008a6
--- /dev/null
+++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h
@@ -0,0 +1,97 @@
+//===- SourceCoverageViewHTML.h - A html code coverage view ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file defines the interface to the html coverage renderer.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_SOURCECOVERAGEVIEWHTML_H
+#define LLVM_COV_SOURCECOVERAGEVIEWHTML_H
+
+#include "SourceCoverageView.h"
+
+namespace llvm {
+
+using namespace coverage;
+
+struct FileCoverageSummary;
+
+/// A coverage printer for html output.
+class CoveragePrinterHTML : public CoveragePrinter {
+public:
+ Expected<OwnedStream> createViewFile(StringRef Path,
+ bool InToplevel) override;
+
+ void closeViewFile(OwnedStream OS) override;
+
+ Error createIndexFile(ArrayRef<std::string> SourceFiles,
+ const coverage::CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) override;
+
+ CoveragePrinterHTML(const CoverageViewOptions &Opts)
+ : CoveragePrinter(Opts) {}
+
+private:
+ void emitFileSummary(raw_ostream &OS, StringRef SF,
+ const FileCoverageSummary &FCS,
+ bool IsTotals = false) const;
+ std::string buildLinkToFile(StringRef SF,
+ const FileCoverageSummary &FCS) const;
+};
+
+/// A code coverage view which supports html-based rendering.
+class SourceCoverageViewHTML : public SourceCoverageView {
+ void renderViewHeader(raw_ostream &OS) override;
+
+ void renderViewFooter(raw_ostream &OS) override;
+
+ void renderSourceName(raw_ostream &OS, bool WholeFile) override;
+
+ void renderLinePrefix(raw_ostream &OS, unsigned ViewDepth) override;
+
+ void renderLineSuffix(raw_ostream &OS, unsigned ViewDepth) override;
+
+ void renderViewDivider(raw_ostream &OS, unsigned ViewDepth) override;
+
+ void renderLine(raw_ostream &OS, LineRef L, const LineCoverageStats &LCS,
+ unsigned ExpansionCol, unsigned ViewDepth) override;
+
+ void renderExpansionSite(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS, unsigned ExpansionCol,
+ unsigned ViewDepth) override;
+
+ void renderExpansionView(raw_ostream &OS, ExpansionView &ESV,
+ unsigned ViewDepth) override;
+
+ void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV,
+ unsigned ViewDepth) override;
+
+ void renderLineCoverageColumn(raw_ostream &OS,
+ const LineCoverageStats &Line) override;
+
+ void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override;
+
+ void renderRegionMarkers(raw_ostream &OS, const LineCoverageStats &Line,
+ unsigned ViewDepth) override;
+
+ void renderTitle(raw_ostream &OS, StringRef Title) override;
+
+ void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo,
+ unsigned IndentLevel) override;
+
+public:
+ SourceCoverageViewHTML(StringRef SourceName, const MemoryBuffer &File,
+ const CoverageViewOptions &Options,
+ coverage::CoverageData &&CoverageInfo)
+ : SourceCoverageView(SourceName, File, Options, std::move(CoverageInfo)) {
+ }
+};
+
+} // namespace llvm
+
+#endif // LLVM_COV_SOURCECOVERAGEVIEWHTML_H
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
new file mode 100644
index 000000000000..fcabee2ee69d
--- /dev/null
+++ b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp
@@ -0,0 +1,251 @@
+//===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file implements the text-based coverage renderer.
+///
+//===----------------------------------------------------------------------===//
+
+#include "CoverageReport.h"
+#include "SourceCoverageViewText.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+
+using namespace llvm;
+
+Expected<CoveragePrinter::OwnedStream>
+CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {
+ return createOutputStream(Path, "txt", InToplevel);
+}
+
+void CoveragePrinterText::closeViewFile(OwnedStream OS) {
+ OS->operator<<('\n');
+}
+
+Error CoveragePrinterText::createIndexFile(
+ ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) {
+ auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true);
+ if (Error E = OSOrErr.takeError())
+ return E;
+ auto OS = std::move(OSOrErr.get());
+ raw_ostream &OSRef = *OS.get();
+
+ CoverageReport Report(Opts, Coverage);
+ Report.renderFileReports(OSRef, SourceFiles, Filters);
+
+ Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"
+ << Opts.getLLVMVersionString();
+
+ return Error::success();
+}
+
+namespace {
+
+static const unsigned LineCoverageColumnWidth = 7;
+static const unsigned LineNumberColumnWidth = 5;
+
+/// Get the width of the leading columns.
+unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {
+ return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
+ (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
+}
+
+/// The width of the line that is used to divide between the view and
+/// the subviews.
+unsigned getDividerWidth(const CoverageViewOptions &Opts) {
+ return getCombinedColumnWidth(Opts) + 4;
+}
+
+} // anonymous namespace
+
+void SourceCoverageViewText::renderViewHeader(raw_ostream &) {}
+
+void SourceCoverageViewText::renderViewFooter(raw_ostream &) {}
+
+void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {
+ getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()
+ << ":\n";
+}
+
+void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,
+ unsigned ViewDepth) {
+ for (unsigned I = 0; I < ViewDepth; ++I)
+ OS << " |";
+}
+
+void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}
+
+void SourceCoverageViewText::renderViewDivider(raw_ostream &OS,
+ unsigned ViewDepth) {
+ assert(ViewDepth != 0 && "Cannot render divider at top level");
+ renderLinePrefix(OS, ViewDepth - 1);
+ OS.indent(2);
+ unsigned Length = getDividerWidth(getOptions());
+ for (unsigned I = 0; I < Length; ++I)
+ OS << '-';
+ OS << '\n';
+}
+
+void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS,
+ unsigned ExpansionCol,
+ unsigned ViewDepth) {
+ StringRef Line = L.Line;
+ unsigned LineNumber = L.LineNo;
+ auto *WrappedSegment = LCS.getWrappedSegment();
+ CoverageSegmentArray Segments = LCS.getLineSegments();
+
+ Optional<raw_ostream::Colors> Highlight;
+ SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
+
+ // The first segment overlaps from a previous line, so we treat it specially.
+ if (WrappedSegment && !WrappedSegment->IsGapRegion &&
+ WrappedSegment->HasCount && WrappedSegment->Count == 0)
+ Highlight = raw_ostream::RED;
+
+ // Output each segment of the line, possibly highlighted.
+ unsigned Col = 1;
+ for (const auto *S : Segments) {
+ unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
+ colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
+ getOptions().Colors && Highlight, /*Bold=*/false,
+ /*BG=*/true)
+ << Line.substr(Col - 1, End - Col);
+ if (getOptions().Debug && Highlight)
+ HighlightedRanges.push_back(std::make_pair(Col, End));
+ Col = End;
+ if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) &&
+ S->HasCount && S->Count == 0)
+ Highlight = raw_ostream::RED;
+ else if (Col == ExpansionCol)
+ Highlight = raw_ostream::CYAN;
+ else
+ Highlight = None;
+ }
+
+ // Show the rest of the line.
+ colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
+ getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true)
+ << Line.substr(Col - 1, Line.size() - Col + 1);
+ OS << '\n';
+
+ if (getOptions().Debug) {
+ for (const auto &Range : HighlightedRanges)
+ errs() << "Highlighted line " << LineNumber << ", " << Range.first
+ << " -> " << Range.second << '\n';
+ if (Highlight)
+ errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
+ }
+}
+
+void SourceCoverageViewText::renderLineCoverageColumn(
+ raw_ostream &OS, const LineCoverageStats &Line) {
+ if (!Line.isMapped()) {
+ OS.indent(LineCoverageColumnWidth) << '|';
+ return;
+ }
+ std::string C = formatCount(Line.getExecutionCount());
+ OS.indent(LineCoverageColumnWidth - C.size());
+ colored_ostream(OS, raw_ostream::MAGENTA,
+ Line.hasMultipleRegions() && getOptions().Colors)
+ << C;
+ OS << '|';
+}
+
+void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS,
+ unsigned LineNo) {
+ SmallString<32> Buffer;
+ raw_svector_ostream BufferOS(Buffer);
+ BufferOS << LineNo;
+ auto Str = BufferOS.str();
+ // Trim and align to the right.
+ Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
+ OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
+}
+
+void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS,
+ const LineCoverageStats &Line,
+ unsigned ViewDepth) {
+ renderLinePrefix(OS, ViewDepth);
+ OS.indent(getCombinedColumnWidth(getOptions()));
+
+ CoverageSegmentArray Segments = Line.getLineSegments();
+
+ // Just consider the segments which start *and* end on this line.
+ if (Segments.size() > 1)
+ Segments = Segments.drop_back();
+
+ unsigned PrevColumn = 1;
+ for (const auto *S : Segments) {
+ if (!S->IsRegionEntry)
+ continue;
+ if (S->Count == Line.getExecutionCount())
+ continue;
+ // Skip to the new region.
+ if (S->Col > PrevColumn)
+ OS.indent(S->Col - PrevColumn);
+ PrevColumn = S->Col + 1;
+ std::string C = formatCount(S->Count);
+ PrevColumn += C.size();
+ OS << '^' << C;
+
+ if (getOptions().Debug)
+ errs() << "Marker at " << S->Line << ":" << S->Col << " = "
+ << formatCount(S->Count) << "\n";
+ }
+ OS << '\n';
+}
+
+void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS,
+ unsigned ExpansionCol,
+ unsigned ViewDepth) {
+ renderLinePrefix(OS, ViewDepth);
+ OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));
+ renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
+}
+
+void SourceCoverageViewText::renderExpansionView(raw_ostream &OS,
+ ExpansionView &ESV,
+ unsigned ViewDepth) {
+ // Render the child subview.
+ if (getOptions().Debug)
+ errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol()
+ << " -> " << ESV.getEndCol() << '\n';
+ ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
+ /*ShowTitle=*/false, ViewDepth + 1);
+}
+
+void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,
+ InstantiationView &ISV,
+ unsigned ViewDepth) {
+ renderLinePrefix(OS, ViewDepth);
+ OS << ' ';
+ if (!ISV.View)
+ getOptions().colored_ostream(OS, raw_ostream::RED)
+ << "Unexecuted instantiation: " << ISV.FunctionName << "\n";
+ else
+ ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
+ /*ShowTitle=*/false, ViewDepth);
+}
+
+void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {
+ if (getOptions().hasProjectTitle())
+ getOptions().colored_ostream(OS, raw_ostream::CYAN)
+ << getOptions().ProjectTitle << "\n";
+
+ getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";
+
+ if (getOptions().hasCreatedTime())
+ getOptions().colored_ostream(OS, raw_ostream::CYAN)
+ << getOptions().CreatedTimeStr << "\n";
+}
+
+void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned,
+ unsigned) {}
diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.h b/llvm/tools/llvm-cov/SourceCoverageViewText.h
new file mode 100644
index 000000000000..c8c4632c3b9d
--- /dev/null
+++ b/llvm/tools/llvm-cov/SourceCoverageViewText.h
@@ -0,0 +1,88 @@
+//===- SourceCoverageViewText.h - A text-based code coverage view ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file defines the interface to the text-based coverage renderer.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_SOURCECOVERAGEVIEWTEXT_H
+#define LLVM_COV_SOURCECOVERAGEVIEWTEXT_H
+
+#include "SourceCoverageView.h"
+
+namespace llvm {
+
+using namespace coverage;
+
+/// A coverage printer for text output.
+class CoveragePrinterText : public CoveragePrinter {
+public:
+ Expected<OwnedStream> createViewFile(StringRef Path,
+ bool InToplevel) override;
+
+ void closeViewFile(OwnedStream OS) override;
+
+ Error createIndexFile(ArrayRef<std::string> SourceFiles,
+ const CoverageMapping &Coverage,
+ const CoverageFiltersMatchAll &Filters) override;
+
+ CoveragePrinterText(const CoverageViewOptions &Opts)
+ : CoveragePrinter(Opts) {}
+};
+
+/// A code coverage view which supports text-based rendering.
+class SourceCoverageViewText : public SourceCoverageView {
+ void renderViewHeader(raw_ostream &OS) override;
+
+ void renderViewFooter(raw_ostream &OS) override;
+
+ void renderSourceName(raw_ostream &OS, bool WholeFile) override;
+
+ void renderLinePrefix(raw_ostream &OS, unsigned ViewDepth) override;
+
+ void renderLineSuffix(raw_ostream &OS, unsigned ViewDepth) override;
+
+ void renderViewDivider(raw_ostream &OS, unsigned ViewDepth) override;
+
+ void renderLine(raw_ostream &OS, LineRef L, const LineCoverageStats &LCS,
+ unsigned ExpansionCol, unsigned ViewDepth) override;
+
+ void renderExpansionSite(raw_ostream &OS, LineRef L,
+ const LineCoverageStats &LCS, unsigned ExpansionCol,
+ unsigned ViewDepth) override;
+
+ void renderExpansionView(raw_ostream &OS, ExpansionView &ESV,
+ unsigned ViewDepth) override;
+
+ void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV,
+ unsigned ViewDepth) override;
+
+ void renderLineCoverageColumn(raw_ostream &OS,
+ const LineCoverageStats &Line) override;
+
+ void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override;
+
+ void renderRegionMarkers(raw_ostream &OS, const LineCoverageStats &Line,
+ unsigned ViewDepth) override;
+
+ void renderTitle(raw_ostream &OS, StringRef Title) override;
+
+ void renderTableHeader(raw_ostream &OS, unsigned FirstUncoveredLineNo,
+ unsigned IndentLevel) override;
+
+public:
+ SourceCoverageViewText(StringRef SourceName, const MemoryBuffer &File,
+ const CoverageViewOptions &Options,
+ CoverageData &&CoverageInfo)
+ : SourceCoverageView(SourceName, File, Options, std::move(CoverageInfo)) {
+ }
+};
+
+} // namespace llvm
+
+#endif // LLVM_COV_SOURCECOVERAGEVIEWTEXT_H
diff --git a/llvm/tools/llvm-cov/TestingSupport.cpp b/llvm/tools/llvm-cov/TestingSupport.cpp
new file mode 100644
index 000000000000..b99bd83157d0
--- /dev/null
+++ b/llvm/tools/llvm-cov/TestingSupport.cpp
@@ -0,0 +1,108 @@
+//===- TestingSupport.cpp - Convert objects files into test files --------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/ProfileData/InstrProf.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/LEB128.h"
+#include "llvm/Support/raw_ostream.h"
+#include <functional>
+#include <system_error>
+
+using namespace llvm;
+using namespace object;
+
+int convertForTestingMain(int argc, const char *argv[]) {
+ cl::opt<std::string> InputSourceFile(cl::Positional, cl::Required,
+ cl::desc("<Source file>"));
+
+ cl::opt<std::string> OutputFilename(
+ "o", cl::Required,
+ cl::desc(
+ "File with the profile data obtained after an instrumented run"));
+
+ cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
+
+ auto ObjErr = llvm::object::ObjectFile::createObjectFile(InputSourceFile);
+ if (!ObjErr) {
+ std::string Buf;
+ raw_string_ostream OS(Buf);
+ logAllUnhandledErrors(ObjErr.takeError(), OS);
+ OS.flush();
+ errs() << "error: " << Buf;
+ return 1;
+ }
+ ObjectFile *OF = ObjErr.get().getBinary();
+ auto BytesInAddress = OF->getBytesInAddress();
+ if (BytesInAddress != 8) {
+ errs() << "error: 64 bit binary expected\n";
+ return 1;
+ }
+
+ // Look for the sections that we are interested in.
+ int FoundSectionCount = 0;
+ SectionRef ProfileNames, CoverageMapping;
+ auto ObjFormat = OF->getTripleObjectFormat();
+ for (const auto &Section : OF->sections()) {
+ StringRef Name;
+ if (Expected<StringRef> NameOrErr = Section.getName()) {
+ Name = *NameOrErr;
+ } else {
+ consumeError(NameOrErr.takeError());
+ return 1;
+ }
+
+ if (Name == llvm::getInstrProfSectionName(IPSK_name, ObjFormat,
+ /*AddSegmentInfo=*/false)) {
+ ProfileNames = Section;
+ } else if (Name == llvm::getInstrProfSectionName(
+ IPSK_covmap, ObjFormat, /*AddSegmentInfo=*/false)) {
+ CoverageMapping = Section;
+ } else
+ continue;
+ ++FoundSectionCount;
+ }
+ if (FoundSectionCount != 2)
+ return 1;
+
+ // Get the contents of the given sections.
+ uint64_t ProfileNamesAddress = ProfileNames.getAddress();
+ StringRef CoverageMappingData;
+ StringRef ProfileNamesData;
+ if (Expected<StringRef> E = CoverageMapping.getContents())
+ CoverageMappingData = *E;
+ else {
+ consumeError(E.takeError());
+ return 1;
+ }
+ if (Expected<StringRef> E = ProfileNames.getContents())
+ ProfileNamesData = *E;
+ else {
+ consumeError(E.takeError());
+ return 1;
+ }
+
+ int FD;
+ if (auto Err = sys::fs::openFileForWrite(OutputFilename, FD)) {
+ errs() << "error: " << Err.message() << "\n";
+ return 1;
+ }
+
+ raw_fd_ostream OS(FD, true);
+ OS << "llvmcovmtestdata";
+ encodeULEB128(ProfileNamesData.size(), OS);
+ encodeULEB128(ProfileNamesAddress, OS);
+ OS << ProfileNamesData;
+ // Coverage mapping data is expected to have an alignment of 8.
+ for (unsigned Pad = offsetToAlignment(OS.tell(), Align(8)); Pad; --Pad)
+ OS.write(uint8_t(0));
+ OS << CoverageMappingData;
+
+ return 0;
+}
diff --git a/llvm/tools/llvm-cov/gcov.cpp b/llvm/tools/llvm-cov/gcov.cpp
new file mode 100644
index 000000000000..8a00ff64711f
--- /dev/null
+++ b/llvm/tools/llvm-cov/gcov.cpp
@@ -0,0 +1,150 @@
+//===- gcov.cpp - GCOV compatible LLVM coverage tool ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// llvm-cov is a command line tools to analyze and report coverage information.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ProfileData/GCOV.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include <system_error>
+using namespace llvm;
+
+static void reportCoverage(StringRef SourceFile, StringRef ObjectDir,
+ const std::string &InputGCNO,
+ const std::string &InputGCDA, bool DumpGCOV,
+ const GCOV::Options &Options) {
+ SmallString<128> CoverageFileStem(ObjectDir);
+ if (CoverageFileStem.empty()) {
+ // If no directory was specified with -o, look next to the source file.
+ CoverageFileStem = sys::path::parent_path(SourceFile);
+ sys::path::append(CoverageFileStem, sys::path::stem(SourceFile));
+ } else if (sys::fs::is_directory(ObjectDir))
+ // A directory name was given. Use it and the source file name.
+ sys::path::append(CoverageFileStem, sys::path::stem(SourceFile));
+ else
+ // A file was given. Ignore the source file and look next to this file.
+ sys::path::replace_extension(CoverageFileStem, "");
+
+ std::string GCNO = InputGCNO.empty()
+ ? std::string(CoverageFileStem.str()) + ".gcno"
+ : InputGCNO;
+ std::string GCDA = InputGCDA.empty()
+ ? std::string(CoverageFileStem.str()) + ".gcda"
+ : InputGCDA;
+ GCOVFile GF;
+
+ ErrorOr<std::unique_ptr<MemoryBuffer>> GCNO_Buff =
+ MemoryBuffer::getFileOrSTDIN(GCNO);
+ if (std::error_code EC = GCNO_Buff.getError()) {
+ errs() << GCNO << ": " << EC.message() << "\n";
+ return;
+ }
+ GCOVBuffer GCNO_GB(GCNO_Buff.get().get());
+ if (!GF.readGCNO(GCNO_GB)) {
+ errs() << "Invalid .gcno File!\n";
+ return;
+ }
+
+ ErrorOr<std::unique_ptr<MemoryBuffer>> GCDA_Buff =
+ MemoryBuffer::getFileOrSTDIN(GCDA);
+ if (std::error_code EC = GCDA_Buff.getError()) {
+ if (EC != errc::no_such_file_or_directory) {
+ errs() << GCDA << ": " << EC.message() << "\n";
+ return;
+ }
+ // Clear the filename to make it clear we didn't read anything.
+ GCDA = "-";
+ } else {
+ GCOVBuffer GCDA_GB(GCDA_Buff.get().get());
+ if (!GF.readGCDA(GCDA_GB)) {
+ errs() << "Invalid .gcda File!\n";
+ return;
+ }
+ }
+
+ if (DumpGCOV)
+ GF.print(errs());
+
+ FileInfo FI(Options);
+ GF.collectLineCounts(FI);
+ FI.print(llvm::outs(), SourceFile, GCNO, GCDA);
+}
+
+int gcovMain(int argc, const char *argv[]) {
+ cl::list<std::string> SourceFiles(cl::Positional, cl::OneOrMore,
+ cl::desc("SOURCEFILE"));
+
+ cl::opt<bool> AllBlocks("a", cl::Grouping, cl::init(false),
+ cl::desc("Display all basic blocks"));
+ cl::alias AllBlocksA("all-blocks", cl::aliasopt(AllBlocks));
+
+ cl::opt<bool> BranchProb("b", cl::Grouping, cl::init(false),
+ cl::desc("Display branch probabilities"));
+ cl::alias BranchProbA("branch-probabilities", cl::aliasopt(BranchProb));
+
+ cl::opt<bool> BranchCount("c", cl::Grouping, cl::init(false),
+ cl::desc("Display branch counts instead "
+ "of percentages (requires -b)"));
+ cl::alias BranchCountA("branch-counts", cl::aliasopt(BranchCount));
+
+ cl::opt<bool> LongNames("l", cl::Grouping, cl::init(false),
+ cl::desc("Prefix filenames with the main file"));
+ cl::alias LongNamesA("long-file-names", cl::aliasopt(LongNames));
+
+ cl::opt<bool> FuncSummary("f", cl::Grouping, cl::init(false),
+ cl::desc("Show coverage for each function"));
+ cl::alias FuncSummaryA("function-summaries", cl::aliasopt(FuncSummary));
+
+ cl::opt<bool> NoOutput("n", cl::Grouping, cl::init(false),
+ cl::desc("Do not output any .gcov files"));
+ cl::alias NoOutputA("no-output", cl::aliasopt(NoOutput));
+
+ cl::opt<std::string> ObjectDir(
+ "o", cl::value_desc("DIR|FILE"), cl::init(""),
+ cl::desc("Find objects in DIR or based on FILE's path"));
+ cl::alias ObjectDirA("object-directory", cl::aliasopt(ObjectDir));
+ cl::alias ObjectDirB("object-file", cl::aliasopt(ObjectDir));
+
+ cl::opt<bool> PreservePaths("p", cl::Grouping, cl::init(false),
+ cl::desc("Preserve path components"));
+ cl::alias PreservePathsA("preserve-paths", cl::aliasopt(PreservePaths));
+
+ cl::opt<bool> UncondBranch("u", cl::Grouping, cl::init(false),
+ cl::desc("Display unconditional branch info "
+ "(requires -b)"));
+ cl::alias UncondBranchA("unconditional-branches", cl::aliasopt(UncondBranch));
+
+ cl::opt<bool> HashFilenames("x", cl::Grouping, cl::init(false),
+ cl::desc("Hash long pathnames"));
+ cl::alias HashFilenamesA("hash-filenames", cl::aliasopt(HashFilenames));
+
+
+ cl::OptionCategory DebugCat("Internal and debugging options");
+ cl::opt<bool> DumpGCOV("dump", cl::init(false), cl::cat(DebugCat),
+ cl::desc("Dump the gcov file to stderr"));
+ cl::opt<std::string> InputGCNO("gcno", cl::cat(DebugCat), cl::init(""),
+ cl::desc("Override inferred gcno file"));
+ cl::opt<std::string> InputGCDA("gcda", cl::cat(DebugCat), cl::init(""),
+ cl::desc("Override inferred gcda file"));
+
+ cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
+
+ GCOV::Options Options(AllBlocks, BranchProb, BranchCount, FuncSummary,
+ PreservePaths, UncondBranch, LongNames, NoOutput,
+ HashFilenames);
+
+ for (const auto &SourceFile : SourceFiles)
+ reportCoverage(SourceFile, ObjectDir, InputGCNO, InputGCDA, DumpGCOV,
+ Options);
+ return 0;
+}
diff --git a/llvm/tools/llvm-cov/llvm-cov.cpp b/llvm/tools/llvm-cov/llvm-cov.cpp
new file mode 100644
index 000000000000..172ec9f3cedf
--- /dev/null
+++ b/llvm/tools/llvm-cov/llvm-cov.cpp
@@ -0,0 +1,95 @@
+//===- llvm-cov.cpp - LLVM coverage tool ----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// llvm-cov is a command line tools to analyze and report coverage information.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/ManagedStatic.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+using namespace llvm;
+
+/// The main entry point for the 'show' subcommand.
+int showMain(int argc, const char *argv[]);
+
+/// The main entry point for the 'report' subcommand.
+int reportMain(int argc, const char *argv[]);
+
+/// The main entry point for the 'export' subcommand.
+int exportMain(int argc, const char *argv[]);
+
+/// The main entry point for the 'convert-for-testing' subcommand.
+int convertForTestingMain(int argc, const char *argv[]);
+
+/// The main entry point for the gcov compatible coverage tool.
+int gcovMain(int argc, const char *argv[]);
+
+/// Top level help.
+static int helpMain(int argc, const char *argv[]) {
+ errs() << "Usage: llvm-cov {export|gcov|report|show} [OPTION]...\n\n"
+ << "Shows code coverage information.\n\n"
+ << "Subcommands:\n"
+ << " export: Export instrprof file to structured format.\n"
+ << " gcov: Work with the gcov format.\n"
+ << " report: Summarize instrprof style coverage information.\n"
+ << " show: Annotate source files using instrprof style coverage.\n";
+
+ return 0;
+}
+
+/// Top level version information.
+static int versionMain(int argc, const char *argv[]) {
+ cl::PrintVersionMessage();
+ return 0;
+}
+
+int main(int argc, const char **argv) {
+ InitLLVM X(argc, argv);
+
+ // If argv[0] is or ends with 'gcov', always be gcov compatible
+ if (sys::path::stem(argv[0]).endswith_lower("gcov"))
+ return gcovMain(argc, argv);
+
+ // Check if we are invoking a specific tool command.
+ if (argc > 1) {
+ typedef int (*MainFunction)(int, const char *[]);
+ MainFunction Func = StringSwitch<MainFunction>(argv[1])
+ .Case("convert-for-testing", convertForTestingMain)
+ .Case("export", exportMain)
+ .Case("gcov", gcovMain)
+ .Case("report", reportMain)
+ .Case("show", showMain)
+ .Cases("-h", "-help", "--help", helpMain)
+ .Cases("-version", "--version", versionMain)
+ .Default(nullptr);
+
+ if (Func) {
+ std::string Invocation = std::string(argv[0]) + " " + argv[1];
+ argv[1] = Invocation.c_str();
+ return Func(argc - 1, argv + 1);
+ }
+ }
+
+ if (argc > 1) {
+ if (sys::Process::StandardErrHasColors())
+ errs().changeColor(raw_ostream::RED);
+ errs() << "Unrecognized command: " << argv[1] << ".\n\n";
+ if (sys::Process::StandardErrHasColors())
+ errs().resetColor();
+ }
+ helpMain(argc, argv);
+ return 1;
+}