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