summaryrefslogtreecommitdiff
path: root/tools/llvm-cov
diff options
context:
space:
mode:
Diffstat (limited to 'tools/llvm-cov')
-rw-r--r--tools/llvm-cov/CMakeLists.txt4
-rw-r--r--tools/llvm-cov/CodeCoverage.cpp363
-rw-r--r--tools/llvm-cov/CoverageFilters.h2
-rw-r--r--tools/llvm-cov/CoverageReport.cpp2
-rw-r--r--tools/llvm-cov/CoverageReport.h2
-rw-r--r--tools/llvm-cov/CoverageSummaryInfo.h8
-rw-r--r--tools/llvm-cov/CoverageViewOptions.h15
-rw-r--r--tools/llvm-cov/LLVMBuild.txt2
-rw-r--r--tools/llvm-cov/Makefile17
-rw-r--r--tools/llvm-cov/RenderingSupport.h3
-rw-r--r--tools/llvm-cov/SourceCoverageView.cpp273
-rw-r--r--tools/llvm-cov/SourceCoverageView.h230
-rw-r--r--tools/llvm-cov/SourceCoverageViewHTML.cpp436
-rw-r--r--tools/llvm-cov/SourceCoverageViewHTML.h83
-rw-r--r--tools/llvm-cov/SourceCoverageViewText.cpp213
-rw-r--r--tools/llvm-cov/SourceCoverageViewText.h83
-rw-r--r--tools/llvm-cov/TestingSupport.cpp26
-rw-r--r--tools/llvm-cov/gcov.cpp8
-rw-r--r--tools/llvm-cov/llvm-cov.cpp8
19 files changed, 1460 insertions, 318 deletions
diff --git a/tools/llvm-cov/CMakeLists.txt b/tools/llvm-cov/CMakeLists.txt
index 193218a6639f2..e22828e11effe 100644
--- a/tools/llvm-cov/CMakeLists.txt
+++ b/tools/llvm-cov/CMakeLists.txt
@@ -1,4 +1,4 @@
-set(LLVM_LINK_COMPONENTS core support object profiledata)
+set(LLVM_LINK_COMPONENTS core support object coverage profiledata)
add_llvm_tool(llvm-cov
llvm-cov.cpp
@@ -8,5 +8,7 @@ add_llvm_tool(llvm-cov
CoverageReport.cpp
CoverageSummaryInfo.cpp
SourceCoverageView.cpp
+ SourceCoverageViewHTML.cpp
+ SourceCoverageViewText.cpp
TestingSupport.cpp
)
diff --git a/tools/llvm-cov/CodeCoverage.cpp b/tools/llvm-cov/CodeCoverage.cpp
index 8dc4d665f23ca..0a4d1a67d6105 100644
--- a/tools/llvm-cov/CodeCoverage.cpp
+++ b/tools/llvm-cov/CodeCoverage.cpp
@@ -13,24 +13,25 @@
//
//===----------------------------------------------------------------------===//
-#include "RenderingSupport.h"
#include "CoverageFilters.h"
#include "CoverageReport.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/CoverageMapping.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/ManagedStatic.h"
+#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
-#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Process.h"
-#include "llvm/Support/Signals.h"
+#include "llvm/Support/Program.h"
+#include "llvm/Support/ThreadPool.h"
+#include "llvm/Support/ToolOutputFile.h"
#include <functional>
#include <system_error>
@@ -51,28 +52,47 @@ public:
/// \brief Print the error message to the error output stream.
void error(const Twine &Message, StringRef Whence = "");
+ /// \brief Record (but do not print) an error message in a thread-safe way.
+ void deferError(const Twine &Message, StringRef Whence = "");
+
+ /// \brief Record (but do not print) a warning message in a thread-safe way.
+ void deferWarning(const Twine &Message, StringRef Whence = "");
+
+ /// \brief Print (and then clear) all deferred error and warning messages.
+ void consumeDeferredMessages();
+
+ /// \brief Append a reference to a private copy of \p Path into SourceFiles.
+ void addCollectedPath(const std::string &Path);
+
/// \brief Return a memory buffer for the given source file.
ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
/// \brief Create source views for the expansions of the view.
void attachExpansionSubViews(SourceCoverageView &View,
ArrayRef<ExpansionRecord> Expansions,
- CoverageMapping &Coverage);
+ const CoverageMapping &Coverage);
/// \brief Create the source view of a particular function.
std::unique_ptr<SourceCoverageView>
- createFunctionView(const FunctionRecord &Function, CoverageMapping &Coverage);
+ createFunctionView(const FunctionRecord &Function,
+ const CoverageMapping &Coverage);
/// \brief Create the main source view of a particular source file.
std::unique_ptr<SourceCoverageView>
- createSourceFileView(StringRef SourceFile, CoverageMapping &Coverage);
+ createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage);
- /// \brief Load the coverage mapping data. Return true if an error occured.
+ /// \brief Load the coverage mapping data. Return nullptr if an error occured.
std::unique_ptr<CoverageMapping> load();
+ /// \brief If a demangler is available, demangle all symbol names.
+ void demangleSymbols(const CoverageMapping &Coverage);
+
+ /// \brief Demangle \p Sym if possible. Otherwise, just return \p Sym.
+ StringRef getSymbolForHumans(StringRef Sym) const;
+
int run(Command Cmd, int argc, const char **argv);
- typedef std::function<int(int, const char **)> CommandLineParserType;
+ typedef llvm::function_ref<int(int, const char **)> CommandLineParserType;
int show(int argc, const char **argv,
CommandLineParserType commandLineParser);
@@ -84,25 +104,69 @@ public:
CoverageViewOptions ViewOpts;
std::string PGOFilename;
CoverageFiltersMatchAll Filters;
- std::vector<std::string> SourceFiles;
- std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>>
- LoadedSourceFiles;
+ std::vector<StringRef> SourceFiles;
bool CompareFilenamesOnly;
StringMap<std::string> RemappedFilenames;
std::string CoverageArch;
+
+private:
+ /// A cache for demangled symbol names.
+ StringMap<std::string> DemangledNames;
+
+ /// File paths (absolute, or otherwise) to input source files.
+ std::vector<std::string> CollectedPaths;
+
+ /// Errors and warnings which have not been printed.
+ std::mutex DeferredMessagesLock;
+ std::vector<std::string> DeferredMessages;
+
+ /// A container for input source file buffers.
+ std::mutex LoadedSourceFilesLock;
+ std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>>
+ LoadedSourceFiles;
};
}
-void CodeCoverageTool::error(const Twine &Message, StringRef Whence) {
- errs() << "error: ";
+static std::string getErrorString(const Twine &Message, StringRef Whence,
+ bool Warning) {
+ std::string Str = (Warning ? "warning" : "error");
+ Str += ": ";
if (!Whence.empty())
- errs() << Whence << ": ";
- errs() << Message << "\n";
+ Str += Whence.str() + ": ";
+ Str += Message.str() + "\n";
+ return Str;
+}
+
+void CodeCoverageTool::error(const Twine &Message, StringRef Whence) {
+ errs() << getErrorString(Message, Whence, false);
+}
+
+void CodeCoverageTool::deferError(const Twine &Message, StringRef Whence) {
+ std::unique_lock<std::mutex> Guard{DeferredMessagesLock};
+ DeferredMessages.emplace_back(getErrorString(Message, Whence, false));
+}
+
+void CodeCoverageTool::deferWarning(const Twine &Message, StringRef Whence) {
+ std::unique_lock<std::mutex> Guard{DeferredMessagesLock};
+ DeferredMessages.emplace_back(getErrorString(Message, Whence, true));
+}
+
+void CodeCoverageTool::consumeDeferredMessages() {
+ std::unique_lock<std::mutex> Guard{DeferredMessagesLock};
+ for (const std::string &Message : DeferredMessages)
+ ViewOpts.colored_ostream(errs(), raw_ostream::RED) << Message;
+ DeferredMessages.clear();
+}
+
+void CodeCoverageTool::addCollectedPath(const std::string &Path) {
+ CollectedPaths.push_back(Path);
+ SourceFiles.emplace_back(CollectedPaths.back());
}
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())
@@ -113,17 +177,16 @@ CodeCoverageTool::getSourceFile(StringRef SourceFile) {
return *Files.second;
auto Buffer = MemoryBuffer::getFile(SourceFile);
if (auto EC = Buffer.getError()) {
- error(EC.message(), SourceFile);
+ deferError(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,
- CoverageMapping &Coverage) {
+void CodeCoverageTool::attachExpansionSubViews(
+ SourceCoverageView &View, ArrayRef<ExpansionRecord> Expansions,
+ const CoverageMapping &Coverage) {
if (!ViewOpts.ShowExpandedRegions)
return;
for (const auto &Expansion : Expansions) {
@@ -135,8 +198,9 @@ CodeCoverageTool::attachExpansionSubViews(SourceCoverageView &View,
continue;
auto SubViewExpansions = ExpansionCoverage.getExpansions();
- auto SubView = llvm::make_unique<SourceCoverageView>(
- SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage));
+ auto SubView =
+ SourceCoverageView::create(Expansion.Function.Name, SourceBuffer.get(),
+ ViewOpts, std::move(ExpansionCoverage));
attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
View.addExpansion(Expansion.Region, std::move(SubView));
}
@@ -144,7 +208,7 @@ CodeCoverageTool::attachExpansionSubViews(SourceCoverageView &View,
std::unique_ptr<SourceCoverageView>
CodeCoverageTool::createFunctionView(const FunctionRecord &Function,
- CoverageMapping &Coverage) {
+ const CoverageMapping &Coverage) {
auto FunctionCoverage = Coverage.getCoverageForFunction(Function);
if (FunctionCoverage.empty())
return nullptr;
@@ -153,8 +217,9 @@ CodeCoverageTool::createFunctionView(const FunctionRecord &Function,
return nullptr;
auto Expansions = FunctionCoverage.getExpansions();
- auto View = llvm::make_unique<SourceCoverageView>(
- SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage));
+ auto View = SourceCoverageView::create(getSymbolForHumans(Function.Name),
+ SourceBuffer.get(), ViewOpts,
+ std::move(FunctionCoverage));
attachExpansionSubViews(*View, Expansions, Coverage);
return View;
@@ -162,7 +227,7 @@ CodeCoverageTool::createFunctionView(const FunctionRecord &Function,
std::unique_ptr<SourceCoverageView>
CodeCoverageTool::createSourceFileView(StringRef SourceFile,
- CoverageMapping &Coverage) {
+ const CoverageMapping &Coverage) {
auto SourceBuffer = getSourceFile(SourceFile);
if (!SourceBuffer)
return nullptr;
@@ -171,15 +236,16 @@ CodeCoverageTool::createSourceFileView(StringRef SourceFile,
return nullptr;
auto Expansions = FileCoverage.getExpansions();
- auto View = llvm::make_unique<SourceCoverageView>(
- SourceBuffer.get(), ViewOpts, std::move(FileCoverage));
+ auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(),
+ ViewOpts, std::move(FileCoverage));
attachExpansionSubViews(*View, Expansions, Coverage);
- for (auto Function : Coverage.getInstantiations(SourceFile)) {
+ for (const auto *Function : Coverage.getInstantiations(SourceFile)) {
auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
auto SubViewExpansions = SubViewCoverage.getExpansions();
- auto SubView = llvm::make_unique<SourceCoverageView>(
- SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
+ auto SubView = SourceCoverageView::create(
+ getSymbolForHumans(Function->Name), SourceBuffer.get(), ViewOpts,
+ std::move(SubViewCoverage));
attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
if (SubView) {
@@ -210,10 +276,9 @@ std::unique_ptr<CoverageMapping> CodeCoverageTool::load() {
errs() << "warning: profile data may be out of date - object is newer\n";
auto CoverageOrErr = CoverageMapping::load(ObjectFilename, PGOFilename,
CoverageArch);
- if (std::error_code EC = CoverageOrErr.getError()) {
+ if (Error E = CoverageOrErr.takeError()) {
colored_ostream(errs(), raw_ostream::RED)
- << "error: Failed to load coverage: " << EC.message();
- errs() << "\n";
+ << "error: Failed to load coverage: " << toString(std::move(E)) << "\n";
return nullptr;
}
auto Coverage = std::move(CoverageOrErr.get());
@@ -237,15 +302,95 @@ std::unique_ptr<CoverageMapping> CodeCoverageTool::load() {
}
}
+ demangleSymbols(*Coverage);
+
return Coverage;
}
-int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
- // Print a stack trace if we signal out.
- sys::PrintStackTraceOnErrorSignal();
- PrettyStackTraceProgram X(argc, argv);
- llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
+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;
+ }
+ tool_output_file 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;
+ }
+ tool_output_file OutputTOF{OutputPath, OutputFD};
+ OutputTOF.os().close();
+
+ // Invoke the demangler.
+ std::vector<const char *> ArgsV;
+ for (const std::string &Arg : ViewOpts.DemanglerOpts)
+ ArgsV.push_back(Arg.c_str());
+ ArgsV.push_back(nullptr);
+ StringRef InputPathRef = InputPath.str();
+ StringRef OutputPathRef = OutputPath.str();
+ StringRef StderrRef;
+ const StringRef *Redirects[] = {&InputPathRef, &OutputPathRef, &StderrRef};
+ std::string ErrMsg;
+ int RC = sys::ExecuteAndWait(ViewOpts.DemanglerOpts[0], ArgsV.data(),
+ /*env=*/nullptr, 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())
+ DemangledNames[Function.Name] = Symbols[I++];
+}
+
+StringRef CodeCoverageTool::getSymbolForHumans(StringRef Sym) const {
+ const auto DemangledName = DemangledNames.find(Sym);
+ if (DemangledName == DemangledNames.end())
+ return Sym;
+ return DemangledName->getValue();
+}
+
+int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
cl::opt<std::string, true> ObjectFilename(
cl::Positional, cl::Required, cl::location(this->ObjectFilename),
cl::desc("Covered executable or object file."));
@@ -264,6 +409,15 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
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"),
+ clEnumValEnd),
+ cl::init(CoverageViewOptions::OutputFormat::Text));
+
cl::opt<bool> FilenameEquivalence(
"filename-equivalence", cl::Optional,
cl::desc("Treat source files as equivalent to paths in the coverage data "
@@ -310,14 +464,39 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
"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>"));
+
auto commandLineParser = [&, this](int argc, const char **argv) -> int {
cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
ViewOpts.Debug = DebugDump;
CompareFilenamesOnly = FilenameEquivalence;
- ViewOpts.Colors = UseColor == cl::BOU_UNSET
- ? sys::Process::StandardOutHasColors()
- : UseColor == cl::BOU_TRUE;
+ 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)
+ error("Color output cannot be disabled when generating html.");
+ ViewOpts.Colors = true;
+ break;
+ }
+
+ // If a demangler is supplied, check if it exists and register it.
+ if (DemanglerOpts.size()) {
+ 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);
+ }
// Create the function filters
if (!NameFilters.empty() || !NameRegexFilters.empty()) {
@@ -363,7 +542,7 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
errs() << "error: " << File << ": " << EC.message();
return 1;
}
- SourceFiles.push_back(Path.str());
+ addCollectedPath(Path.str());
}
return 0;
};
@@ -406,6 +585,12 @@ int CodeCoverageTool::show(int argc, const char **argv,
cl::desc("Show function instantiations"),
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));
+
auto Err = commandLineParser(argc, argv);
if (Err)
return Err;
@@ -417,30 +602,46 @@ int CodeCoverageTool::show(int argc, const char **argv,
ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
ViewOpts.ShowExpandedRegions = ShowExpansions;
ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
+ ViewOpts.ShowOutputDirectory = ShowOutputDirectory;
+
+ if (ViewOpts.hasOutputDirectory()) {
+ if (auto E = sys::fs::create_directories(ViewOpts.ShowOutputDirectory)) {
+ error("Could not create output directory!", E.message());
+ return 1;
+ }
+ }
auto Coverage = load();
if (!Coverage)
return 1;
+ auto Printer = CoveragePrinter::create(ViewOpts);
+
if (!Filters.empty()) {
- // Show functions
+ auto OSOrErr = Printer->createViewFile("functions", /*InToplevel=*/true);
+ if (Error E = OSOrErr.takeError()) {
+ error("Could not create view file!", toString(std::move(E)));
+ return 1;
+ }
+ auto OS = std::move(OSOrErr.get());
+
+ // Show functions.
for (const auto &Function : Coverage->getCoveredFunctions()) {
if (!Filters.matches(Function))
continue;
auto mainView = createFunctionView(Function, *Coverage);
if (!mainView) {
- ViewOpts.colored_ostream(outs(), raw_ostream::RED)
- << "warning: Could not read coverage for '" << Function.Name;
- outs() << "\n";
+ ViewOpts.colored_ostream(errs(), raw_ostream::RED)
+ << "warning: Could not read coverage for '" << Function.Name << "'."
+ << "\n";
continue;
}
- ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name
- << ":";
- outs() << "\n";
- mainView->render(outs(), /*WholeFile=*/false);
- outs() << "\n";
+
+ mainView->print(*OS.get(), /*WholeFile=*/false, /*ShowSourceName=*/true);
}
+
+ Printer->closeViewFile(std::move(OS));
return 0;
}
@@ -448,28 +649,49 @@ int CodeCoverageTool::show(int argc, const char **argv,
bool ShowFilenames = SourceFiles.size() != 1;
if (SourceFiles.empty())
- // Get the source files from the function coverage mapping
+ // Get the source files from the function coverage mapping.
for (StringRef Filename : Coverage->getUniqueSourceFiles())
SourceFiles.push_back(Filename);
- for (const auto &SourceFile : SourceFiles) {
- auto mainView = createSourceFileView(SourceFile, *Coverage);
- if (!mainView) {
- ViewOpts.colored_ostream(outs(), raw_ostream::RED)
- << "warning: The file '" << SourceFile << "' isn't covered.";
- outs() << "\n";
- continue;
+ // Create an index out of the source files.
+ if (ViewOpts.hasOutputDirectory()) {
+ if (Error E = Printer->createIndexFile(SourceFiles)) {
+ error("Could not create index file!", toString(std::move(E)));
+ return 1;
}
+ }
- if (ShowFilenames) {
- ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":";
- outs() << "\n";
- }
- mainView->render(outs(), /*Wholefile=*/true);
- if (SourceFiles.size() > 1)
- outs() << "\n";
+ // In -output-dir mode, it's safe to use multiple threads to print files.
+ unsigned ThreadCount = 1;
+ if (ViewOpts.hasOutputDirectory())
+ ThreadCount = std::thread::hardware_concurrency();
+ ThreadPool Pool(ThreadCount);
+
+ for (StringRef SourceFile : SourceFiles) {
+ Pool.async([this, SourceFile, &Coverage, &Printer, ShowFilenames] {
+ auto View = createSourceFileView(SourceFile, *Coverage);
+ if (!View) {
+ deferWarning("The file '" + SourceFile.str() + "' isn't covered.");
+ return;
+ }
+
+ auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false);
+ if (Error E = OSOrErr.takeError()) {
+ deferError("Could not create view file!", toString(std::move(E)));
+ return;
+ }
+ auto OS = std::move(OSOrErr.get());
+
+ View->print(*OS.get(), /*Wholefile=*/true,
+ /*ShowSourceName=*/ShowFilenames);
+ Printer->closeViewFile(std::move(OS));
+ });
}
+ Pool.wait();
+
+ consumeDeferredMessages();
+
return 0;
}
@@ -479,6 +701,9 @@ int CodeCoverageTool::report(int argc, const char **argv,
if (Err)
return Err;
+ if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML)
+ error("HTML output for summary reports is not yet supported.");
+
auto Coverage = load();
if (!Coverage)
return 1;
diff --git a/tools/llvm-cov/CoverageFilters.h b/tools/llvm-cov/CoverageFilters.h
index dc5dc98807b1a..756c4b47872c1 100644
--- a/tools/llvm-cov/CoverageFilters.h
+++ b/tools/llvm-cov/CoverageFilters.h
@@ -14,7 +14,7 @@
#ifndef LLVM_COV_COVERAGEFILTERS_H
#define LLVM_COV_COVERAGEFILTERS_H
-#include "llvm/ProfileData/CoverageMapping.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
#include <memory>
#include <vector>
diff --git a/tools/llvm-cov/CoverageReport.cpp b/tools/llvm-cov/CoverageReport.cpp
index ed01a2e154f1b..10e53b3f1f723 100644
--- a/tools/llvm-cov/CoverageReport.cpp
+++ b/tools/llvm-cov/CoverageReport.cpp
@@ -171,7 +171,7 @@ void CoverageReport::render(const FunctionCoverageSummary &Function,
OS << "\n";
}
-void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
+void CoverageReport::renderFunctionReports(ArrayRef<StringRef> Files,
raw_ostream &OS) {
adjustColumnWidths(Coverage.get());
bool isFirst = true;
diff --git a/tools/llvm-cov/CoverageReport.h b/tools/llvm-cov/CoverageReport.h
index 7ec3df90b8f92..bb3d734b52a54 100644
--- a/tools/llvm-cov/CoverageReport.h
+++ b/tools/llvm-cov/CoverageReport.h
@@ -32,7 +32,7 @@ public:
std::unique_ptr<coverage::CoverageMapping> Coverage)
: Options(Options), Coverage(std::move(Coverage)) {}
- void renderFunctionReports(ArrayRef<std::string> Files, raw_ostream &OS);
+ void renderFunctionReports(ArrayRef<StringRef> Files, raw_ostream &OS);
void renderFileReports(raw_ostream &OS);
};
diff --git a/tools/llvm-cov/CoverageSummaryInfo.h b/tools/llvm-cov/CoverageSummaryInfo.h
index c393b00d32a43..822742b635e96 100644
--- a/tools/llvm-cov/CoverageSummaryInfo.h
+++ b/tools/llvm-cov/CoverageSummaryInfo.h
@@ -15,7 +15,7 @@
#ifndef LLVM_COV_COVERAGESUMMARYINFO_H
#define LLVM_COV_COVERAGESUMMARYINFO_H
-#include "llvm/ProfileData/CoverageMapping.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
#include "llvm/Support/raw_ostream.h"
namespace llvm {
@@ -47,6 +47,8 @@ struct RegionCoverageInfo {
bool isFullyCovered() const { return Covered == NumRegions; }
double getPercentCovered() const {
+ if (NumRegions == 0)
+ return 0.0;
return double(Covered) / double(NumRegions) * 100.0;
}
};
@@ -83,6 +85,8 @@ struct LineCoverageInfo {
bool isFullyCovered() const { return Covered == (NumLines - NonCodeLines); }
double getPercentCovered() const {
+ if (NumLines - NonCodeLines == 0)
+ return 0.0;
return double(Covered) / double(NumLines - NonCodeLines) * 100.0;
}
};
@@ -109,6 +113,8 @@ struct FunctionCoverageInfo {
bool isFullyCovered() const { return Executed == NumFunctions; }
double getPercentCovered() const {
+ if (NumFunctions == 0)
+ return 0.0;
return double(Executed) / double(NumFunctions) * 100.0;
}
};
diff --git a/tools/llvm-cov/CoverageViewOptions.h b/tools/llvm-cov/CoverageViewOptions.h
index 1208fad791766..350c264a28764 100644
--- a/tools/llvm-cov/CoverageViewOptions.h
+++ b/tools/llvm-cov/CoverageViewOptions.h
@@ -11,11 +11,17 @@
#define LLVM_COV_COVERAGEVIEWOPTIONS_H
#include "RenderingSupport.h"
+#include <vector>
namespace llvm {
/// \brief The options for displaying the code coverage information.
struct CoverageViewOptions {
+ enum class OutputFormat {
+ Text,
+ HTML
+ };
+
bool Debug;
bool Colors;
bool ShowLineNumbers;
@@ -25,12 +31,21 @@ struct CoverageViewOptions {
bool ShowExpandedRegions;
bool ShowFunctionInstantiations;
bool ShowFullFilenames;
+ OutputFormat Format;
+ std::string ShowOutputDirectory;
+ std::vector<std::string> DemanglerOpts;
/// \brief 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);
}
+
+ /// \brief Check if an output directory has been specified.
+ bool hasOutputDirectory() const { return !ShowOutputDirectory.empty(); }
+
+ /// \brief Check if a demangler has been specified.
+ bool hasDemangler() const { return !DemanglerOpts.empty(); }
};
}
diff --git a/tools/llvm-cov/LLVMBuild.txt b/tools/llvm-cov/LLVMBuild.txt
index d6eb74de0d4bf..33f51fb2ed5a9 100644
--- a/tools/llvm-cov/LLVMBuild.txt
+++ b/tools/llvm-cov/LLVMBuild.txt
@@ -19,4 +19,4 @@
type = Tool
name = llvm-cov
parent = Tools
-required_libraries = ProfileData Support Instrumentation
+required_libraries = Coverage Support Instrumentation
diff --git a/tools/llvm-cov/Makefile b/tools/llvm-cov/Makefile
deleted file mode 100644
index 6e32b4d233d7e..0000000000000
--- a/tools/llvm-cov/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-##===- tools/llvm-cov/Makefile -----------------------------*- Makefile -*-===##
-#
-# The LLVM Compiler Infrastructure
-#
-# This file is distributed under the University of Illinois Open Source
-# License. See LICENSE.TXT for details.
-#
-##===----------------------------------------------------------------------===##
-
-LEVEL := ../..
-TOOLNAME := llvm-cov
-LINK_COMPONENTS := core support profiledata object
-
-# This tool has no plugins, optimize startup time.
-TOOL_NO_EXPORTS := 1
-
-include $(LEVEL)/Makefile.common
diff --git a/tools/llvm-cov/RenderingSupport.h b/tools/llvm-cov/RenderingSupport.h
index 3ef155d3fd4bb..aa70fbc23e3c0 100644
--- a/tools/llvm-cov/RenderingSupport.h
+++ b/tools/llvm-cov/RenderingSupport.h
@@ -55,6 +55,7 @@ inline ColoredRawOstream colored_ostream(raw_ostream &OS,
OS.changeColor(Color, Bold, BG);
return ColoredRawOstream(OS, IsColorUsed);
}
-}
+
+} // namespace llvm
#endif // LLVM_COV_RENDERINGSUPPORT_H
diff --git a/tools/llvm-cov/SourceCoverageView.cpp b/tools/llvm-cov/SourceCoverageView.cpp
index 58c8a67952946..baf7c148bb800 100644
--- a/tools/llvm-cov/SourceCoverageView.cpp
+++ b/tools/llvm-cov/SourceCoverageView.cpp
@@ -6,80 +6,82 @@
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
-//
-// This class implements rendering for code coverage of source code.
-//
+///
+/// \file This class implements rendering for code coverage of source code.
+///
//===----------------------------------------------------------------------===//
#include "SourceCoverageView.h"
-#include "llvm/ADT/Optional.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 SourceCoverageView::renderLine(
- raw_ostream &OS, StringRef Line, int64_t LineNumber,
- const coverage::CoverageSegment *WrappedSegment,
- ArrayRef<const coverage::CoverageSegment *> Segments,
- unsigned ExpansionCol) {
- 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->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,
- Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
- << Line.substr(Col - 1, End - Col);
- if (Options.Debug && Highlight)
- HighlightedRanges.push_back(std::make_pair(Col, End));
- Col = End;
- if (Col == ExpansionCol)
- Highlight = raw_ostream::CYAN;
- else if (S->HasCount && S->Count == 0)
- Highlight = raw_ostream::RED;
- else
- Highlight = None;
- }
+void CoveragePrinter::StreamDestructor::operator()(raw_ostream *OS) const {
+ if (OS == &outs())
+ return;
+ delete OS;
+}
- // Show the rest of the line
- colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
- Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
- << Line.substr(Col - 1, Line.size() - Col + 1);
- OS << "\n";
-
- if (Options.Debug) {
- for (const auto &Range : HighlightedRanges)
- errs() << "Highlighted line " << LineNumber << ", " << Range.first
- << " -> " << Range.second << "\n";
- if (Highlight)
- errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
- }
+std::string CoveragePrinter::getOutputPath(StringRef Path, StringRef Extension,
+ bool InToplevel, bool Relative) {
+ assert(Extension.size() && "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);
+
+ return FullPath.str();
}
-void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
- for (unsigned I = 0; I < Level; ++I)
- OS << " |";
+Expected<CoveragePrinter::OwnedStream>
+CoveragePrinter::createOutputStream(StringRef Path, StringRef Extension,
+ bool InToplevel) {
+ 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::F_RW);
+ auto OS = CoveragePrinter::OwnedStream(RawStream);
+ if (E)
+ return errorCodeToError(E);
+ return std::move(OS);
}
-void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
- raw_ostream &OS) {
- assert(Level != 0 && "Cannot render divider at top level");
- renderIndent(OS, Level - 1);
- OS.indent(2);
- for (unsigned I = 0; I < Length; ++I)
- OS << "-";
+std::unique_ptr<CoveragePrinter>
+CoveragePrinter::create(const CoverageViewOptions &Opts) {
+ switch (Opts.Format) {
+ case CoverageViewOptions::OutputFormat::Text:
+ return llvm::make_unique<CoveragePrinterText>(Opts);
+ case CoverageViewOptions::OutputFormat::HTML:
+ return llvm::make_unique<CoveragePrinterHTML>(Opts);
+ }
+ llvm_unreachable("Unknown coverage output format!");
}
-/// Format a count using engineering notation with 3 significant digits.
-static std::string formatCount(uint64_t N) {
+std::string SourceCoverageView::formatCount(uint64_t N) {
std::string Number = utostr(N);
int Len = Number.size();
if (Len <= 3)
@@ -94,63 +96,49 @@ static std::string formatCount(uint64_t N) {
return Result;
}
-void
-SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
- const LineCoverageInfo &Line) {
- if (!Line.isMapped()) {
- OS.indent(LineCoverageColumnWidth) << '|';
- return;
- }
- std::string C = formatCount(Line.ExecutionCount);
- OS.indent(LineCoverageColumnWidth - C.size());
- colored_ostream(OS, raw_ostream::MAGENTA,
- Line.hasMultipleRegions() && Options.Colors)
- << C;
- OS << '|';
+bool SourceCoverageView::shouldRenderRegionMarkers(
+ bool LineHasMultipleRegions) const {
+ return getOptions().ShowRegionMarkers &&
+ (!getOptions().ShowLineStatsOrRegionMarkers || LineHasMultipleRegions);
}
-void SourceCoverageView::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 << '|';
+bool SourceCoverageView::hasSubViews() const {
+ return !ExpansionSubViews.empty() || !InstantiationSubViews.empty();
}
-void SourceCoverageView::renderRegionMarkers(
- raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments) {
- unsigned PrevColumn = 1;
- for (const auto *S : Segments) {
- if (!S->IsRegionEntry)
- 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;
+std::unique_ptr<SourceCoverageView>
+SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File,
+ const CoverageViewOptions &Options,
+ coverage::CoverageData &&CoverageInfo) {
+ switch (Options.Format) {
+ case CoverageViewOptions::OutputFormat::Text:
+ return llvm::make_unique<SourceCoverageViewText>(SourceName, File, Options,
+ std::move(CoverageInfo));
+ case CoverageViewOptions::OutputFormat::HTML:
+ return llvm::make_unique<SourceCoverageViewHTML>(SourceName, File, Options,
+ std::move(CoverageInfo));
}
- OS << "\n";
+ llvm_unreachable("Unknown coverage output format!");
+}
+
+void SourceCoverageView::addExpansion(
+ const coverage::CounterMappingRegion &Region,
+ std::unique_ptr<SourceCoverageView> View) {
+ ExpansionSubViews.emplace_back(Region, std::move(View));
+}
- if (Options.Debug)
- for (const auto *S : Segments)
- errs() << "Marker at " << S->Line << ":" << S->Col << " = "
- << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
+void SourceCoverageView::addInstantiation(
+ StringRef FunctionName, unsigned Line,
+ std::unique_ptr<SourceCoverageView> View) {
+ InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
}
-void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
- unsigned IndentLevel) {
- // The width of the leading columns
- unsigned CombinedColumnWidth =
- (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
- (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
- // The width of the line that is used to divide between the view and the
- // subviews.
- unsigned DividerWidth = CombinedColumnWidth + 4;
+void SourceCoverageView::print(raw_ostream &OS, bool WholeFile,
+ bool ShowSourceName, unsigned ViewDepth) {
+ if (ShowSourceName)
+ renderSourceName(OS);
+
+ renderViewHeader(OS);
// We need the expansions and instantiations sorted so we can go through them
// while we iterate lines.
@@ -186,79 +174,60 @@ void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
LineSegments.push_back(&*NextSegment++);
// Calculate a count to be for the line as a whole.
- LineCoverageInfo LineCount;
+ LineCoverageStats LineCount;
if (WrappedSegment && WrappedSegment->HasCount)
LineCount.addRegionCount(WrappedSegment->Count);
for (const auto *S : LineSegments)
if (S->HasCount && S->IsRegionEntry)
- LineCount.addRegionStartCount(S->Count);
+ LineCount.addRegionStartCount(S->Count);
- // Render the line prefix.
- renderIndent(OS, IndentLevel);
- if (Options.ShowLineStats)
+ renderLinePrefix(OS, ViewDepth);
+ if (getOptions().ShowLineStats)
renderLineCoverageColumn(OS, LineCount);
- if (Options.ShowLineNumbers)
+ if (getOptions().ShowLineNumbers)
renderLineNumberColumn(OS, LI.line_number());
// If there are expansion subviews, we want to highlight the first one.
unsigned ExpansionColumn = 0;
if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
- Options.Colors)
+ getOptions().Colors)
ExpansionColumn = NextESV->getStartCol();
// Display the source code for the current line.
- renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
- ExpansionColumn);
+ renderLine(OS, {*LI, LI.line_number()}, WrappedSegment, LineSegments,
+ ExpansionColumn, ViewDepth);
// Show the region markers.
- if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
- LineCount.hasMultipleRegions()) &&
- !LineSegments.empty()) {
- renderIndent(OS, IndentLevel);
- OS.indent(CombinedColumnWidth);
- renderRegionMarkers(OS, LineSegments);
- }
+ if (shouldRenderRegionMarkers(LineCount.hasMultipleRegions()))
+ renderRegionMarkers(OS, LineSegments, ViewDepth);
// Show the expansions and instantiations for this line.
- unsigned NestedIndent = IndentLevel + 1;
bool RenderedSubView = false;
for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
++NextESV) {
- renderViewDivider(NestedIndent, DividerWidth, OS);
- OS << "\n";
+ renderViewDivider(OS, ViewDepth + 1);
+
+ // Re-render the current line and highlight the expansion range for
+ // this subview.
if (RenderedSubView) {
- // Re-render the current line and highlight the expansion range for
- // this subview.
ExpansionColumn = NextESV->getStartCol();
- renderIndent(OS, IndentLevel);
- OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
- renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
- ExpansionColumn);
- renderViewDivider(NestedIndent, DividerWidth, OS);
- OS << "\n";
+ renderExpansionSite(OS, {*LI, LI.line_number()}, WrappedSegment,
+ LineSegments, ExpansionColumn, ViewDepth);
+ renderViewDivider(OS, ViewDepth + 1);
}
- // Render the child subview
- if (Options.Debug)
- errs() << "Expansion at line " << NextESV->getLine() << ", "
- << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
- << "\n";
- NextESV->View->render(OS, false, NestedIndent);
+
+ renderExpansionView(OS, *NextESV, ViewDepth + 1);
RenderedSubView = true;
}
for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
- renderViewDivider(NestedIndent, DividerWidth, OS);
- OS << "\n";
- renderIndent(OS, NestedIndent);
- OS << ' ';
- Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
- << ":";
- OS << "\n";
- NextISV->View->render(OS, false, NestedIndent);
+ renderViewDivider(OS, ViewDepth + 1);
+ renderInstantiationView(OS, *NextISV, ViewDepth + 1);
RenderedSubView = true;
}
- if (RenderedSubView) {
- renderViewDivider(NestedIndent, DividerWidth, OS);
- OS << "\n";
- }
+ if (RenderedSubView)
+ renderViewDivider(OS, ViewDepth + 1);
+ renderLineSuffix(OS, ViewDepth);
}
+
+ renderViewFooter(OS);
}
diff --git a/tools/llvm-cov/SourceCoverageView.h b/tools/llvm-cov/SourceCoverageView.h
index 9e6fe5f350012..feef959f86bf1 100644
--- a/tools/llvm-cov/SourceCoverageView.h
+++ b/tools/llvm-cov/SourceCoverageView.h
@@ -6,16 +6,16 @@
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
-//
-// This class implements rendering for code coverage of source code.
-//
+///
+/// \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 "llvm/ProfileData/CoverageMapping.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
#include "llvm/Support/MemoryBuffer.h"
#include <vector>
@@ -23,7 +23,7 @@ namespace llvm {
class SourceCoverageView;
-/// \brief A view that represents a macro or include expansion
+/// \brief A view that represents a macro or include expansion.
struct ExpansionView {
coverage::CounterMappingRegion Region;
std::unique_ptr<SourceCoverageView> View;
@@ -48,7 +48,7 @@ struct ExpansionView {
}
};
-/// \brief A view that represents a function instantiation
+/// \brief A view that represents a function instantiation.
struct InstantiationView {
StringRef FunctionName;
unsigned Line;
@@ -73,87 +73,211 @@ struct InstantiationView {
}
};
-/// \brief A code coverage view of a specific source file.
-/// It can have embedded coverage views.
-class SourceCoverageView {
-private:
- /// \brief Coverage information for a single line.
- struct LineCoverageInfo {
- uint64_t ExecutionCount;
- unsigned RegionCount;
- bool Mapped;
+/// \brief Coverage statistics for a single line.
+struct LineCoverageStats {
+ uint64_t ExecutionCount;
+ unsigned RegionCount;
+ bool Mapped;
+
+ LineCoverageStats() : ExecutionCount(0), RegionCount(0), Mapped(false) {}
- LineCoverageInfo() : ExecutionCount(0), RegionCount(0), Mapped(false) {}
+ bool isMapped() const { return Mapped; }
- bool isMapped() const { return Mapped; }
+ bool hasMultipleRegions() const { return RegionCount > 1; }
- bool hasMultipleRegions() const { return RegionCount > 1; }
+ void addRegionStartCount(uint64_t Count) {
+ // The max of all region starts is the most interesting value.
+ addRegionCount(RegionCount ? std::max(ExecutionCount, Count) : Count);
+ ++RegionCount;
+ }
+
+ void addRegionCount(uint64_t Count) {
+ Mapped = true;
+ ExecutionCount = Count;
+ }
+};
- void addRegionStartCount(uint64_t Count) {
- // The max of all region starts is the most interesting value.
- addRegionCount(RegionCount ? std::max(ExecutionCount, Count) : Count);
- ++RegionCount;
- }
+/// \brief A file manager that handles format-aware file creation.
+class CoveragePrinter {
+ const CoverageViewOptions &Opts;
- void addRegionCount(uint64_t Count) {
- Mapped = true;
- ExecutionCount = Count;
- }
+public:
+ struct StreamDestructor {
+ void operator()(raw_ostream *OS) const;
};
+ using OwnedStream = std::unique_ptr<raw_ostream, StreamDestructor>;
+
+protected:
+ CoveragePrinter(const CoverageViewOptions &Opts) : Opts(Opts) {}
+
+ /// \brief 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);
+
+ /// \brief 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);
+
+ /// \brief 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
+ /// @{
+
+ /// \brief Create a file to print a coverage view into.
+ virtual Expected<OwnedStream> createViewFile(StringRef Path,
+ bool InToplevel) = 0;
+
+ /// \brief Close a file which has been used to print a coverage view.
+ virtual void closeViewFile(OwnedStream OS) = 0;
+
+ /// \brief Create an index which lists reports for the given source files.
+ virtual Error createIndexFile(ArrayRef<StringRef> SourceFiles) = 0;
+
+ /// @}
+};
+
+/// \brief 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.
coverage::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;
- /// \brief Render a source line with highlighting.
- void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
- const coverage::CoverageSegment *WrappedSegment,
- ArrayRef<const coverage::CoverageSegment *> Segments,
- unsigned ExpansionCol);
+protected:
+ struct LineRef {
+ StringRef Line;
+ int64_t LineNo;
+
+ LineRef(StringRef Line, int64_t LineNo) : Line(Line), LineNo(LineNo) {}
+ };
+
+ using CoverageSegmentArray = ArrayRef<const coverage::CoverageSegment *>;
+
+ /// @name Rendering Interface
+ /// @{
- void renderIndent(raw_ostream &OS, unsigned Level);
+ /// \brief Render a header for the view.
+ virtual void renderViewHeader(raw_ostream &OS) = 0;
- void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
+ /// \brief Render a footer for the view.
+ virtual void renderViewFooter(raw_ostream &OS) = 0;
+
+ /// \brief Render the source name for the view.
+ virtual void renderSourceName(raw_ostream &OS) = 0;
+
+ /// \brief Render the line prefix at the given \p ViewDepth.
+ virtual void renderLinePrefix(raw_ostream &OS, unsigned ViewDepth) = 0;
+
+ /// \brief Render the line suffix at the given \p ViewDepth.
+ virtual void renderLineSuffix(raw_ostream &OS, unsigned ViewDepth) = 0;
+
+ /// \brief Render a view divider at the given \p ViewDepth.
+ virtual void renderViewDivider(raw_ostream &OS, unsigned ViewDepth) = 0;
+
+ /// \brief Render a source line with highlighting.
+ virtual void renderLine(raw_ostream &OS, LineRef L,
+ const coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol,
+ unsigned ViewDepth) = 0;
/// \brief Render the line's execution count column.
- void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line);
+ virtual void renderLineCoverageColumn(raw_ostream &OS,
+ const LineCoverageStats &Line) = 0;
/// \brief Render the line number column.
- void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo);
+ virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0;
/// \brief Render all the region's execution counts on a line.
- void
- renderRegionMarkers(raw_ostream &OS,
- ArrayRef<const coverage::CoverageSegment *> Segments);
+ virtual void renderRegionMarkers(raw_ostream &OS,
+ CoverageSegmentArray Segments,
+ unsigned ViewDepth) = 0;
- static const unsigned LineCoverageColumnWidth = 7;
- static const unsigned LineNumberColumnWidth = 5;
+ /// \brief Render the site of an expansion.
+ virtual void
+ renderExpansionSite(raw_ostream &OS, LineRef L,
+ const coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol,
+ unsigned ViewDepth) = 0;
-public:
- SourceCoverageView(const MemoryBuffer &File,
+ /// \brief Render an expansion view and any nested views.
+ virtual void renderExpansionView(raw_ostream &OS, ExpansionView &ESV,
+ unsigned ViewDepth) = 0;
+
+ /// \brief Render an instantiation view and any nested views.
+ virtual void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV,
+ unsigned ViewDepth) = 0;
+
+ /// @}
+
+ /// \brief Format a count using engineering notation with 3 significant
+ /// digits.
+ static std::string formatCount(uint64_t N);
+
+ /// \brief Check if region marker output is expected for a line.
+ bool shouldRenderRegionMarkers(bool LineHasMultipleRegions) const;
+
+ /// \brief Check if there are any sub-views attached to this view.
+ bool hasSubViews() const;
+
+ SourceCoverageView(StringRef SourceName, const MemoryBuffer &File,
const CoverageViewOptions &Options,
coverage::CoverageData &&CoverageInfo)
- : File(File), Options(Options), CoverageInfo(std::move(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,
+ coverage::CoverageData &&CoverageInfo);
+
+ virtual ~SourceCoverageView() {}
+
+ StringRef getSourceName() const { return SourceName; }
const CoverageViewOptions &getOptions() const { return Options; }
/// \brief Add an expansion subview to this view.
void addExpansion(const coverage::CounterMappingRegion &Region,
- std::unique_ptr<SourceCoverageView> View) {
- ExpansionSubViews.emplace_back(Region, std::move(View));
- }
+ std::unique_ptr<SourceCoverageView> View);
/// \brief Add a function instantiation subview to this view.
void addInstantiation(StringRef FunctionName, unsigned Line,
- std::unique_ptr<SourceCoverageView> View) {
- InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
- }
+ std::unique_ptr<SourceCoverageView> View);
- /// \brief Print the code coverage information for a specific
- /// portion of a source file to the output stream.
- void render(raw_ostream &OS, bool WholeFile, unsigned IndentLevel = 0);
+ /// \brief 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,
+ unsigned ViewDepth = 0);
};
} // namespace llvm
diff --git a/tools/llvm-cov/SourceCoverageViewHTML.cpp b/tools/llvm-cov/SourceCoverageViewHTML.cpp
new file mode 100644
index 0000000000000..81963e5c54474
--- /dev/null
+++ b/tools/llvm-cov/SourceCoverageViewHTML.cpp
@@ -0,0 +1,436 @@
+//===- SourceCoverageViewHTML.cpp - A html code coverage view -------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file implements the html coverage renderer.
+///
+//===----------------------------------------------------------------------===//
+
+#include "SourceCoverageViewHTML.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Path.h"
+
+using namespace llvm;
+
+namespace {
+
+const char *BeginHeader =
+ "<head>"
+ "<meta name='viewport' content='width=device-width,initial-scale=1'>"
+ "<meta charset='UTF-8'>";
+
+const char *CSSForCoverage =
+ "<style>"
+R"(
+
+.red {
+ background-color: #FFD0D0;
+}
+.cyan {
+ background-color: cyan;
+}
+.black {
+ background-color: black;
+ color: white;
+}
+.green {
+ background-color: #98FFA6;
+ color: white;
+}
+.magenta {
+ background-color: #F998FF;
+ color: white;
+}
+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;
+}
+.centered {
+ display: table;
+ margin-left: auto;
+ 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;
+}
+.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 5px;
+ border-collapse: collapse;
+ border-right: solid 1px #eee;
+ border-left: solid 1px #eee;
+}
+td:first-child {
+ border-left: none;
+}
+td:last-child {
+ border-right: none;
+}
+
+)"
+ "</style>";
+
+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>";
+
+void emitPrelude(raw_ostream &OS) {
+ OS << "<!doctype html>"
+ "<html>"
+ << BeginHeader << CSSForCoverage << EndHeader << "<body>"
+ << BeginCenteredDiv;
+}
+
+void emitEpilog(raw_ostream &OS) {
+ OS << EndCenteredDiv << "</body>"
+ "</html>";
+}
+
+// Return a string with the special characters in \p Str escaped.
+std::string escape(StringRef Str) {
+ std::string Result;
+ for (char C : Str) {
+ if (C == '&')
+ Result += "&amp;";
+ else if (C == '<')
+ Result += "&lt;";
+ else if (C == '>')
+ Result += "&gt;";
+ else if (C == '\"')
+ Result += "&quot;";
+ else
+ Result += C;
+ }
+ return Result;
+}
+
+// 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 != "")
+ 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) {
+ return "<a href='" + Link + "'>" + Str + "</a>";
+}
+
+} // 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());
+ emitPrelude(*OS.get());
+ return std::move(OS);
+}
+
+void CoveragePrinterHTML::closeViewFile(OwnedStream OS) {
+ emitEpilog(*OS.get());
+}
+
+Error CoveragePrinterHTML::createIndexFile(ArrayRef<StringRef> SourceFiles) {
+ 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();
+
+ // Emit a table containing links to reports for each file in the covmapping.
+ emitPrelude(OSRef);
+ OSRef << BeginSourceNameDiv << "Index" << EndSourceNameDiv;
+ OSRef << BeginTable;
+ for (StringRef SF : SourceFiles) {
+ std::string LinkText = escape(sys::path::relative_path(SF));
+ std::string LinkTarget =
+ escape(getOutputPath(SF, "html", /*InToplevel=*/false));
+ OSRef << tag("tr", tag("td", tag("pre", a(LinkTarget, LinkText), "code")));
+ }
+ OSRef << EndTable;
+ emitEpilog(OSRef);
+
+ return Error::success();
+}
+
+void SourceCoverageViewHTML::renderViewHeader(raw_ostream &OS) {
+ OS << BeginTable;
+}
+
+void SourceCoverageViewHTML::renderViewFooter(raw_ostream &OS) {
+ OS << EndTable;
+}
+
+void SourceCoverageViewHTML::renderSourceName(raw_ostream &OS) {
+ OS << BeginSourceNameDiv << tag("pre", escape(getSourceName()))
+ << 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 coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned) {
+ StringRef Line = L.Line;
+
+ // 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;
+
+ unsigned LCol = 1;
+ auto Snip = [&](unsigned Start, unsigned Len) {
+ assert(Start + Len <= Line.size() && "Snippet extends past the EOL");
+ 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) {
+ assert(LCol == Segments[I - 1]->Col && "Snippet start position is wrong");
+ 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);
+ assert(LCol == Line.size() + 1 && "Final snippet doesn't reach the EOL");
+
+ // 2. Escape all of the snippets.
+
+ for (unsigned I = 0, E = Snippets.size(); I < E; ++I)
+ Snippets[I] = escape(Snippets[I]);
+
+ // 3. Use \p WrappedSegment to set the highlight for snippets 0 and 1. Use
+ // segment 1 to set the highlight for snippet 2, segment 2 to set the
+ // highlight for snippet 3, and so on.
+
+ Optional<std::string> Color;
+ auto Highlight = [&](const std::string &Snippet) {
+ return tag("span", Snippet, Color.getValue());
+ };
+
+ auto CheckIfUncovered = [](const coverage::CoverageSegment *S) {
+ return S && S->HasCount && S->Count == 0;
+ };
+
+ if (CheckIfUncovered(WrappedSegment) ||
+ CheckIfUncovered(Segments.empty() ? nullptr : Segments.front())) {
+ Color = "red";
+ Snippets[0] = Highlight(Snippets[0]);
+ Snippets[1] = Highlight(Snippets[1]);
+ }
+
+ for (unsigned I = 1, E = Segments.size(); I < E; ++I) {
+ const auto *CurSeg = Segments[I];
+ if (CurSeg->Col == ExpansionCol)
+ Color = "cyan";
+ else if (CheckIfUncovered(CurSeg))
+ Color = "red";
+ else
+ Color = None;
+
+ if (Color.hasValue())
+ Snippets[I + 1] = Highlight(Snippets[I + 1]);
+ }
+
+ // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate
+ // sub-line region count tooltips if needed.
+
+ bool HasMultipleRegions = [&] {
+ unsigned RegionCount = 0;
+ for (const auto *S : Segments)
+ if (S->HasCount && S->IsRegionEntry)
+ if (++RegionCount > 1)
+ return true;
+ return false;
+ }();
+
+ if (shouldRenderRegionMarkers(HasMultipleRegions)) {
+ for (unsigned I = 0, E = Segments.size(); I < E; ++I) {
+ const auto *CurSeg = Segments[I];
+ if (!CurSeg->IsRegionEntry || !CurSeg->HasCount)
+ continue;
+
+ Snippets[I + 1] =
+ tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count),
+ "tooltip-content"),
+ "tooltip");
+ }
+ }
+
+ 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.ExecutionCount));
+ std::string CoverageClass =
+ (Line.ExecutionCount > 0) ? "covered-line" : "uncovered-line";
+ OS << tag("td", Count, CoverageClass);
+}
+
+void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
+ unsigned LineNo) {
+ OS << tag("td", tag("pre", utostr(uint64_t(LineNo))), "line-number");
+}
+
+void SourceCoverageViewHTML::renderRegionMarkers(raw_ostream &,
+ CoverageSegmentArray,
+ unsigned) {
+ // Region markers are rendered in-line using tooltips.
+}
+
+void SourceCoverageViewHTML::renderExpansionSite(
+ raw_ostream &OS, LineRef L, const coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned ViewDepth) {
+ // Render the line containing the expansion site. No extra formatting needed.
+ renderLine(OS, L, WrappedSegment, Segments, ExpansionCol, ViewDepth);
+}
+
+void SourceCoverageViewHTML::renderExpansionView(raw_ostream &OS,
+ ExpansionView &ESV,
+ unsigned ViewDepth) {
+ OS << BeginExpansionDiv;
+ ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
+ ViewDepth + 1);
+ OS << EndExpansionDiv;
+}
+
+void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS,
+ InstantiationView &ISV,
+ unsigned ViewDepth) {
+ OS << BeginExpansionDiv;
+ ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, ViewDepth);
+ OS << EndExpansionDiv;
+}
diff --git a/tools/llvm-cov/SourceCoverageViewHTML.h b/tools/llvm-cov/SourceCoverageViewHTML.h
new file mode 100644
index 0000000000000..50ecf2bf89970
--- /dev/null
+++ b/tools/llvm-cov/SourceCoverageViewHTML.h
@@ -0,0 +1,83 @@
+//===- SourceCoverageViewHTML.h - A html code coverage view ---------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \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 {
+
+/// \brief 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<StringRef> SourceFiles) override;
+
+ CoveragePrinterHTML(const CoverageViewOptions &Opts)
+ : CoveragePrinter(Opts) {}
+};
+
+/// \brief 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) 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 coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol,
+ unsigned ViewDepth) override;
+
+ void renderExpansionSite(raw_ostream &OS, LineRef L,
+ const coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, 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, CoverageSegmentArray Segments,
+ unsigned ViewDepth) 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/tools/llvm-cov/SourceCoverageViewText.cpp b/tools/llvm-cov/SourceCoverageViewText.cpp
new file mode 100644
index 0000000000000..ae9d6daed08f7
--- /dev/null
+++ b/tools/llvm-cov/SourceCoverageViewText.cpp
@@ -0,0 +1,213 @@
+//===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file implements the text-based coverage renderer.
+///
+//===----------------------------------------------------------------------===//
+
+#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<StringRef> SourceFiles) {
+ 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();
+
+ for (StringRef SF : SourceFiles)
+ OSRef << getOutputPath(SF, "txt", /*InToplevel=*/false) << '\n';
+
+ return Error::success();
+}
+
+namespace {
+
+static const unsigned LineCoverageColumnWidth = 7;
+static const unsigned LineNumberColumnWidth = 5;
+
+/// \brief Get the width of the leading columns.
+unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {
+ return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
+ (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
+}
+
+/// \brief 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) {
+ 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 coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned ViewDepth) {
+ StringRef Line = L.Line;
+ unsigned LineNumber = L.LineNo;
+
+ 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->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 (Col == ExpansionCol)
+ Highlight = raw_ostream::CYAN;
+ else if (S->HasCount && S->Count == 0)
+ Highlight = raw_ostream::RED;
+ 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.ExecutionCount);
+ 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, CoverageSegmentArray Segments, unsigned ViewDepth) {
+ renderLinePrefix(OS, ViewDepth);
+ OS.indent(getCombinedColumnWidth(getOptions()));
+
+ unsigned PrevColumn = 1;
+ for (const auto *S : Segments) {
+ if (!S->IsRegionEntry)
+ 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;
+ }
+ OS << '\n';
+
+ if (getOptions().Debug)
+ for (const auto *S : Segments)
+ errs() << "Marker at " << S->Line << ":" << S->Col << " = "
+ << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
+}
+
+void SourceCoverageViewText::renderExpansionSite(
+ raw_ostream &OS, LineRef L, const coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol, unsigned ViewDepth) {
+ renderLinePrefix(OS, ViewDepth);
+ OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));
+ renderLine(OS, L, WrappedSegment, Segments, 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,
+ ViewDepth + 1);
+}
+
+void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,
+ InstantiationView &ISV,
+ unsigned ViewDepth) {
+ renderLinePrefix(OS, ViewDepth);
+ OS << ' ';
+ ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true, ViewDepth);
+}
diff --git a/tools/llvm-cov/SourceCoverageViewText.h b/tools/llvm-cov/SourceCoverageViewText.h
new file mode 100644
index 0000000000000..b2331247b37b7
--- /dev/null
+++ b/tools/llvm-cov/SourceCoverageViewText.h
@@ -0,0 +1,83 @@
+//===- SourceCoverageViewText.h - A text-based code coverage view ---------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \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 {
+
+/// \brief 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<StringRef> SourceFiles) override;
+
+ CoveragePrinterText(const CoverageViewOptions &Opts)
+ : CoveragePrinter(Opts) {}
+};
+
+/// \brief 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) 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 coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, unsigned ExpansionCol,
+ unsigned ViewDepth) override;
+
+ void renderExpansionSite(raw_ostream &OS, LineRef L,
+ const coverage::CoverageSegment *WrappedSegment,
+ CoverageSegmentArray Segments, 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, CoverageSegmentArray Segments,
+ unsigned ViewDepth) override;
+
+public:
+ SourceCoverageViewText(StringRef SourceName, const MemoryBuffer &File,
+ const CoverageViewOptions &Options,
+ coverage::CoverageData &&CoverageInfo)
+ : SourceCoverageView(SourceName, File, Options, std::move(CoverageInfo)) {
+ }
+};
+
+} // namespace llvm
+
+#endif // LLVM_COV_SOURCECOVERAGEVIEWTEXT_H
diff --git a/tools/llvm-cov/TestingSupport.cpp b/tools/llvm-cov/TestingSupport.cpp
index 6959897482ca2..72768f4fd583f 100644
--- a/tools/llvm-cov/TestingSupport.cpp
+++ b/tools/llvm-cov/TestingSupport.cpp
@@ -8,11 +8,9 @@
//===----------------------------------------------------------------------===//
#include "llvm/Object/ObjectFile.h"
+#include "llvm/ProfileData/InstrProf.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/LEB128.h"
-#include "llvm/Support/ManagedStatic.h"
-#include "llvm/Support/PrettyStackTrace.h"
-#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"
#include <functional>
#include <system_error>
@@ -21,10 +19,6 @@ using namespace llvm;
using namespace object;
int convertForTestingMain(int argc, const char *argv[]) {
- sys::PrintStackTraceOnErrorSignal();
- PrettyStackTraceProgram X(argc, argv);
- llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
-
cl::opt<std::string> InputSourceFile(cl::Positional, cl::Required,
cl::desc("<Source file>"));
@@ -36,8 +30,12 @@ int convertForTestingMain(int argc, const char *argv[]) {
cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
auto ObjErr = llvm::object::ObjectFile::createObjectFile(InputSourceFile);
- if (auto Err = ObjErr.getError()) {
- errs() << "error: " << Err.message() << "\n";
+ 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();
@@ -54,9 +52,9 @@ int convertForTestingMain(int argc, const char *argv[]) {
StringRef Name;
if (Section.getName(Name))
return 1;
- if (Name == "__llvm_prf_names") {
+ if (Name == llvm::getInstrProfNameSectionName(false)) {
ProfileNames = Section;
- } else if (Name == "__llvm_covmap") {
+ } else if (Name == llvm::getInstrProfCoverageSectionName(false)) {
CoverageMapping = Section;
} else
continue;
@@ -84,7 +82,11 @@ int convertForTestingMain(int argc, const char *argv[]) {
OS << "llvmcovmtestdata";
encodeULEB128(ProfileNamesData.size(), OS);
encodeULEB128(ProfileNamesAddress, OS);
- OS << ProfileNamesData << CoverageMappingData;
+ OS << ProfileNamesData;
+ // Coverage mapping data is expected to have an alignment of 8.
+ for (unsigned Pad = OffsetToAlignment(OS.tell(), 8); Pad; --Pad)
+ OS.write(uint8_t(0));
+ OS << CoverageMappingData;
return 0;
}
diff --git a/tools/llvm-cov/gcov.cpp b/tools/llvm-cov/gcov.cpp
index a5343fa29afc3..4652fed2a384e 100644
--- a/tools/llvm-cov/gcov.cpp
+++ b/tools/llvm-cov/gcov.cpp
@@ -16,10 +16,7 @@
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/GCOV.h"
-#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Path.h"
-#include "llvm/Support/PrettyStackTrace.h"
-#include "llvm/Support/Signals.h"
#include <system_error>
using namespace llvm;
@@ -85,11 +82,6 @@ static void reportCoverage(StringRef SourceFile, StringRef ObjectDir,
}
int gcovMain(int argc, const char *argv[]) {
- // Print a stack trace if we signal out.
- sys::PrintStackTraceOnErrorSignal();
- PrettyStackTraceProgram X(argc, argv);
- llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
-
cl::list<std::string> SourceFiles(cl::Positional, cl::OneOrMore,
cl::desc("SOURCEFILE"));
diff --git a/tools/llvm-cov/llvm-cov.cpp b/tools/llvm-cov/llvm-cov.cpp
index 8c5acaef63b25..ba60cd91da906 100644
--- a/tools/llvm-cov/llvm-cov.cpp
+++ b/tools/llvm-cov/llvm-cov.cpp
@@ -14,8 +14,11 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Process.h"
+#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
@@ -51,6 +54,11 @@ static int versionMain(int argc, const char *argv[]) {
}
int main(int argc, const char **argv) {
+ // Print a stack trace if we signal out.
+ sys::PrintStackTraceOnErrorSignal(argv[0]);
+ PrettyStackTraceProgram X(argc, argv);
+ llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
+
// 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);