summaryrefslogtreecommitdiff
path: root/tools/llvm-cov/CodeCoverage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/llvm-cov/CodeCoverage.cpp')
-rw-r--r--tools/llvm-cov/CodeCoverage.cpp363
1 files changed, 294 insertions, 69 deletions
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;