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                                                unsigned) {}
419