xref: /freebsd/contrib/llvm-project/llvm/tools/llvm-cov/SourceCoverageViewText.cpp (revision 30cafaa9611d82525ab32ec32304b93835a5124a)
1  //===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
2  //
3  // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4  // See https://llvm.org/LICENSE.txt for license information.
5  // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6  //
7  //===----------------------------------------------------------------------===//
8  ///
9  /// \file This file implements the text-based coverage renderer.
10  ///
11  //===----------------------------------------------------------------------===//
12  
13  #include "SourceCoverageViewText.h"
14  #include "CoverageReport.h"
15  #include "llvm/ADT/SmallString.h"
16  #include "llvm/ADT/StringExtras.h"
17  #include "llvm/Support/FileSystem.h"
18  #include "llvm/Support/Format.h"
19  #include "llvm/Support/Path.h"
20  #include <optional>
21  
22  using namespace llvm;
23  
24  Expected<CoveragePrinter::OwnedStream>
25  CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {
26    return createOutputStream(Path, "txt", InToplevel);
27  }
28  
29  void CoveragePrinterText::closeViewFile(OwnedStream OS) {
30    OS->operator<<('\n');
31  }
32  
33  Error CoveragePrinterText::createIndexFile(
34      ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
35      const CoverageFiltersMatchAll &Filters) {
36    auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true);
37    if (Error E = OSOrErr.takeError())
38      return E;
39    auto OS = std::move(OSOrErr.get());
40    raw_ostream &OSRef = *OS.get();
41  
42    CoverageReport Report(Opts, Coverage);
43    Report.renderFileReports(OSRef, SourceFiles, Filters);
44  
45    Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"
46                                                   << Opts.getLLVMVersionString();
47  
48    return Error::success();
49  }
50  
51  struct CoveragePrinterTextDirectory::Reporter : public DirectoryCoverageReport {
52    CoveragePrinterTextDirectory &Printer;
53  
54    Reporter(CoveragePrinterTextDirectory &Printer,
55             const coverage::CoverageMapping &Coverage,
56             const CoverageFiltersMatchAll &Filters)
57        : DirectoryCoverageReport(Printer.Opts, Coverage, Filters),
58          Printer(Printer) {}
59  
60    Error generateSubDirectoryReport(SubFileReports &&SubFiles,
61                                     SubDirReports &&SubDirs,
62                                     FileCoverageSummary &&SubTotals) override {
63      auto &LCPath = SubTotals.Name;
64      assert(Options.hasOutputDirectory() &&
65             "No output directory for index file");
66  
67      SmallString<128> OSPath = LCPath;
68      sys::path::append(OSPath, "index");
69      auto OSOrErr = Printer.createOutputStream(OSPath, "txt",
70                                                /*InToplevel=*/false);
71      if (auto E = OSOrErr.takeError())
72        return E;
73      auto OS = std::move(OSOrErr.get());
74      raw_ostream &OSRef = *OS.get();
75  
76      std::vector<FileCoverageSummary> Reports;
77      for (auto &&SubDir : SubDirs)
78        Reports.push_back(std::move(SubDir.second.first));
79      for (auto &&SubFile : SubFiles)
80        Reports.push_back(std::move(SubFile.second));
81  
82      CoverageReport Report(Options, Coverage);
83      Report.renderFileReports(OSRef, Reports, SubTotals, Filters.empty());
84  
85      Options.colored_ostream(OSRef, raw_ostream::CYAN)
86          << "\n"
87          << Options.getLLVMVersionString();
88  
89      return Error::success();
90    }
91  };
92  
93  Error CoveragePrinterTextDirectory::createIndexFile(
94      ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
95      const CoverageFiltersMatchAll &Filters) {
96    if (SourceFiles.size() <= 1)
97      return CoveragePrinterText::createIndexFile(SourceFiles, Coverage, Filters);
98  
99    Reporter Report(*this, Coverage, Filters);
100    auto TotalsOrErr = Report.prepareDirectoryReports(SourceFiles);
101    if (auto E = TotalsOrErr.takeError())
102      return E;
103    auto &LCPath = TotalsOrErr->Name;
104  
105    auto TopIndexFilePath =
106        getOutputPath("index", "txt", /*InToplevel=*/true, /*Relative=*/false);
107    auto LCPIndexFilePath =
108        getOutputPath((LCPath + "index").str(), "txt", /*InToplevel=*/false,
109                      /*Relative=*/false);
110    return errorCodeToError(
111        sys::fs::copy_file(LCPIndexFilePath, TopIndexFilePath));
112  }
113  
114  namespace {
115  
116  static const unsigned LineCoverageColumnWidth = 7;
117  static const unsigned LineNumberColumnWidth = 5;
118  
119  /// Get the width of the leading columns.
120  unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {
121    return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
122           (Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
123  }
124  
125  /// The width of the line that is used to divide between the view and
126  /// the subviews.
127  unsigned getDividerWidth(const CoverageViewOptions &Opts) {
128    return getCombinedColumnWidth(Opts) + 4;
129  }
130  
131  } // anonymous namespace
132  
133  void SourceCoverageViewText::renderViewHeader(raw_ostream &) {}
134  
135  void SourceCoverageViewText::renderViewFooter(raw_ostream &) {}
136  
137  void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {
138    getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()
139                                                        << ":\n";
140  }
141  
142  void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,
143                                                unsigned ViewDepth) {
144    for (unsigned I = 0; I < ViewDepth; ++I)
145      OS << "  |";
146  }
147  
148  void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}
149  
150  void SourceCoverageViewText::renderViewDivider(raw_ostream &OS,
151                                                 unsigned ViewDepth) {
152    assert(ViewDepth != 0 && "Cannot render divider at top level");
153    renderLinePrefix(OS, ViewDepth - 1);
154    OS.indent(2);
155    unsigned Length = getDividerWidth(getOptions());
156    for (unsigned I = 0; I < Length; ++I)
157      OS << '-';
158    OS << '\n';
159  }
160  
161  void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L,
162                                          const LineCoverageStats &LCS,
163                                          unsigned ExpansionCol,
164                                          unsigned ViewDepth) {
165    StringRef Line = L.Line;
166    unsigned LineNumber = L.LineNo;
167    auto *WrappedSegment = LCS.getWrappedSegment();
168    CoverageSegmentArray Segments = LCS.getLineSegments();
169  
170    std::optional<raw_ostream::Colors> Highlight;
171    SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
172  
173    // The first segment overlaps from a previous line, so we treat it specially.
174    if (WrappedSegment && !WrappedSegment->IsGapRegion &&
175        WrappedSegment->HasCount && WrappedSegment->Count == 0)
176      Highlight = raw_ostream::RED;
177  
178    // Output each segment of the line, possibly highlighted.
179    unsigned Col = 1;
180    for (const auto *S : Segments) {
181      unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
182      colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
183                      getOptions().Colors && Highlight, /*Bold=*/false,
184                      /*BG=*/true)
185          << Line.substr(Col - 1, End - Col);
186      if (getOptions().Debug && Highlight)
187        HighlightedRanges.push_back(std::make_pair(Col, End));
188      Col = End;
189      if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) &&
190          S->HasCount && S->Count == 0)
191        Highlight = raw_ostream::RED;
192      else if (Col == ExpansionCol)
193        Highlight = raw_ostream::CYAN;
194      else
195        Highlight = std::nullopt;
196    }
197  
198    // Show the rest of the line.
199    colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
200                    getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true)
201        << Line.substr(Col - 1, Line.size() - Col + 1);
202    OS << '\n';
203  
204    if (getOptions().Debug) {
205      for (const auto &Range : HighlightedRanges)
206        errs() << "Highlighted line " << LineNumber << ", " << Range.first
207               << " -> " << Range.second << '\n';
208      if (Highlight)
209        errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
210    }
211  }
212  
213  void SourceCoverageViewText::renderLineCoverageColumn(
214      raw_ostream &OS, const LineCoverageStats &Line) {
215    if (!Line.isMapped()) {
216      OS.indent(LineCoverageColumnWidth) << '|';
217      return;
218    }
219    std::string C = formatCount(Line.getExecutionCount());
220    OS.indent(LineCoverageColumnWidth - C.size());
221    colored_ostream(OS, raw_ostream::MAGENTA,
222                    Line.hasMultipleRegions() && getOptions().Colors)
223        << C;
224    OS << '|';
225  }
226  
227  void SourceCoverageViewText::renderLineNumberColumn(raw_ostream &OS,
228                                                      unsigned LineNo) {
229    SmallString<32> Buffer;
230    raw_svector_ostream BufferOS(Buffer);
231    BufferOS << LineNo;
232    auto Str = BufferOS.str();
233    // Trim and align to the right.
234    Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
235    OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
236  }
237  
238  void SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS,
239                                                   const LineCoverageStats &Line,
240                                                   unsigned ViewDepth) {
241    renderLinePrefix(OS, ViewDepth);
242    OS.indent(getCombinedColumnWidth(getOptions()));
243  
244    CoverageSegmentArray Segments = Line.getLineSegments();
245  
246    // Just consider the segments which start *and* end on this line.
247    if (Segments.size() > 1)
248      Segments = Segments.drop_back();
249  
250    unsigned PrevColumn = 1;
251    for (const auto *S : Segments) {
252      if (!S->IsRegionEntry)
253        continue;
254      if (S->Count == Line.getExecutionCount())
255        continue;
256      // Skip to the new region.
257      if (S->Col > PrevColumn)
258        OS.indent(S->Col - PrevColumn);
259      PrevColumn = S->Col + 1;
260      std::string C = formatCount(S->Count);
261      PrevColumn += C.size();
262      OS << '^' << C;
263  
264      if (getOptions().Debug)
265        errs() << "Marker at " << S->Line << ":" << S->Col << " = "
266              << formatCount(S->Count) << "\n";
267    }
268    OS << '\n';
269  }
270  
271  void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L,
272                                                   const LineCoverageStats &LCS,
273                                                   unsigned ExpansionCol,
274                                                   unsigned ViewDepth) {
275    renderLinePrefix(OS, ViewDepth);
276    OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));
277    renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
278  }
279  
280  void SourceCoverageViewText::renderExpansionView(raw_ostream &OS,
281                                                   ExpansionView &ESV,
282                                                   unsigned ViewDepth) {
283    // Render the child subview.
284    if (getOptions().Debug)
285      errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol()
286             << " -> " << ESV.getEndCol() << '\n';
287    ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
288                    /*ShowTitle=*/false, ViewDepth + 1);
289  }
290  
291  void SourceCoverageViewText::renderBranchView(raw_ostream &OS, BranchView &BRV,
292                                                unsigned ViewDepth) {
293    // Render the child subview.
294    if (getOptions().Debug)
295      errs() << "Branch at line " << BRV.getLine() << '\n';
296  
297    for (const auto &R : BRV.Regions) {
298      double TruePercent = 0.0;
299      double FalsePercent = 0.0;
300      // FIXME: It may overflow when the data is too large, but I have not
301      // encountered it in actual use, and not sure whether to use __uint128_t.
302      uint64_t Total = R.ExecutionCount + R.FalseExecutionCount;
303  
304      if (!getOptions().ShowBranchCounts && Total != 0) {
305        TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0;
306        FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0;
307      }
308  
309      renderLinePrefix(OS, ViewDepth);
310      OS << "  Branch (" << R.LineStart << ":" << R.ColumnStart << "): [";
311  
312      if (R.Folded) {
313        OS << "Folded - Ignored]\n";
314        continue;
315      }
316  
317      colored_ostream(OS, raw_ostream::RED,
318                      getOptions().Colors && !R.ExecutionCount,
319                      /*Bold=*/false, /*BG=*/true)
320          << "True";
321  
322      if (getOptions().ShowBranchCounts)
323        OS << ": " << formatCount(R.ExecutionCount) << ", ";
324      else
325        OS << ": " << format("%0.2f", TruePercent) << "%, ";
326  
327      colored_ostream(OS, raw_ostream::RED,
328                      getOptions().Colors && !R.FalseExecutionCount,
329                      /*Bold=*/false, /*BG=*/true)
330          << "False";
331  
332      if (getOptions().ShowBranchCounts)
333        OS << ": " << formatCount(R.FalseExecutionCount);
334      else
335        OS << ": " << format("%0.2f", FalsePercent) << "%";
336      OS << "]\n";
337    }
338  }
339  
340  void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV,
341                                              unsigned ViewDepth) {
342    for (auto &Record : MRV.Records) {
343      renderLinePrefix(OS, ViewDepth);
344      OS << "---> MC/DC Decision Region (";
345      // Display Line + Column information.
346      const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion();
347      OS << DecisionRegion.LineStart << ":";
348      OS << DecisionRegion.ColumnStart << ") to (";
349      OS << DecisionRegion.LineEnd << ":";
350      OS << DecisionRegion.ColumnEnd << ")\n";
351      renderLinePrefix(OS, ViewDepth);
352      OS << "\n";
353  
354      // Display MC/DC Information.
355      renderLinePrefix(OS, ViewDepth);
356      OS << "  Number of Conditions: " << Record.getNumConditions() << "\n";
357      for (unsigned i = 0; i < Record.getNumConditions(); i++) {
358        renderLinePrefix(OS, ViewDepth);
359        OS << "     " << Record.getConditionHeaderString(i);
360      }
361      renderLinePrefix(OS, ViewDepth);
362      OS << "\n";
363      renderLinePrefix(OS, ViewDepth);
364      OS << "  Executed MC/DC Test Vectors:\n";
365      renderLinePrefix(OS, ViewDepth);
366      OS << "\n";
367      renderLinePrefix(OS, ViewDepth);
368      OS << "     ";
369      OS << Record.getTestVectorHeaderString();
370      for (unsigned i = 0; i < Record.getNumTestVectors(); i++) {
371        renderLinePrefix(OS, ViewDepth);
372        OS << Record.getTestVectorString(i);
373      }
374      renderLinePrefix(OS, ViewDepth);
375      OS << "\n";
376      for (unsigned i = 0; i < Record.getNumConditions(); i++) {
377        renderLinePrefix(OS, ViewDepth);
378        OS << Record.getConditionCoverageString(i);
379      }
380      renderLinePrefix(OS, ViewDepth);
381      OS << "  MC/DC Coverage for Decision: ";
382      colored_ostream(OS, raw_ostream::RED,
383                      getOptions().Colors && Record.getPercentCovered() < 100.0,
384                      /*Bold=*/false, /*BG=*/true)
385          << format("%0.2f", Record.getPercentCovered()) << "%";
386      OS << "\n";
387      renderLinePrefix(OS, ViewDepth);
388      OS << "\n";
389    }
390  }
391  
392  void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,
393                                                       InstantiationView &ISV,
394                                                       unsigned ViewDepth) {
395    renderLinePrefix(OS, ViewDepth);
396    OS << ' ';
397    if (!ISV.View)
398      getOptions().colored_ostream(OS, raw_ostream::RED)
399          << "Unexecuted instantiation: " << ISV.FunctionName << "\n";
400    else
401      ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
402                      /*ShowTitle=*/false, ViewDepth);
403  }
404  
405  void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {
406    if (getOptions().hasProjectTitle())
407      getOptions().colored_ostream(OS, raw_ostream::CYAN)
408          << getOptions().ProjectTitle << "\n";
409  
410    getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";
411  
412    if (getOptions().hasCreatedTime())
413      getOptions().colored_ostream(OS, raw_ostream::CYAN)
414          << getOptions().CreatedTimeStr << "\n";
415  }
416  
417  void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned) {}
418