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; +} | 
