diff options
Diffstat (limited to 'tools/llvm-cov')
-rw-r--r-- | tools/llvm-cov/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tools/llvm-cov/CodeCoverage.cpp | 363 | ||||
-rw-r--r-- | tools/llvm-cov/CoverageFilters.h | 2 | ||||
-rw-r--r-- | tools/llvm-cov/CoverageReport.cpp | 2 | ||||
-rw-r--r-- | tools/llvm-cov/CoverageReport.h | 2 | ||||
-rw-r--r-- | tools/llvm-cov/CoverageSummaryInfo.h | 8 | ||||
-rw-r--r-- | tools/llvm-cov/CoverageViewOptions.h | 15 | ||||
-rw-r--r-- | tools/llvm-cov/LLVMBuild.txt | 2 | ||||
-rw-r--r-- | tools/llvm-cov/Makefile | 17 | ||||
-rw-r--r-- | tools/llvm-cov/RenderingSupport.h | 3 | ||||
-rw-r--r-- | tools/llvm-cov/SourceCoverageView.cpp | 273 | ||||
-rw-r--r-- | tools/llvm-cov/SourceCoverageView.h | 230 | ||||
-rw-r--r-- | tools/llvm-cov/SourceCoverageViewHTML.cpp | 436 | ||||
-rw-r--r-- | tools/llvm-cov/SourceCoverageViewHTML.h | 83 | ||||
-rw-r--r-- | tools/llvm-cov/SourceCoverageViewText.cpp | 213 | ||||
-rw-r--r-- | tools/llvm-cov/SourceCoverageViewText.h | 83 | ||||
-rw-r--r-- | tools/llvm-cov/TestingSupport.cpp | 26 | ||||
-rw-r--r-- | tools/llvm-cov/gcov.cpp | 8 | ||||
-rw-r--r-- | tools/llvm-cov/llvm-cov.cpp | 8 |
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 += "&"; + else if (C == '<') + Result += "<"; + else if (C == '>') + Result += ">"; + else if (C == '\"') + Result += """; + 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); |