1//===- SourceCoverageView.cpp - Code coverage view for source code --------===//
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 class implements rendering for code coverage of source code.
10///
11//===----------------------------------------------------------------------===//
12
13#include "SourceCoverageView.h"
14#include "SourceCoverageViewHTML.h"
15#include "SourceCoverageViewText.h"
16#include "llvm/ADT/SmallString.h"
17#include "llvm/ADT/StringExtras.h"
18#include "llvm/Support/FileSystem.h"
19#include "llvm/Support/LineIterator.h"
20#include "llvm/Support/Path.h"
21
22using namespace llvm;
23
24ExpansionView::ExpansionView(const CounterMappingRegion &Region,
25 std::unique_ptr<SourceCoverageView> View)
26 : Region(Region), View(std::move(View)) {}
27
28ExpansionView::ExpansionView(ExpansionView &&RHS)
29 : Region(std::move(RHS.Region)), View(std::move(RHS.View)) {}
30
31ExpansionView &ExpansionView::operator=(ExpansionView &&RHS) {
32 Region = std::move(RHS.Region);
33 View = std::move(RHS.View);
34 return *this;
35}
36
37InstantiationView::InstantiationView(StringRef FunctionName, unsigned Line,
38 std::unique_ptr<SourceCoverageView> View)
39 : FunctionName(FunctionName), Line(Line), View(std::move(View)) {}
40
41void CoveragePrinter::StreamDestructor::operator()(raw_ostream *OS) const {
42 if (OS == &outs())
43 return;
44 delete OS;
45}
46
47std::string CoveragePrinter::getOutputPath(StringRef Path, StringRef Extension,
48 bool InToplevel,
49 bool Relative) const {
50 assert(!Extension.empty() && "The file extension may not be empty");
51
52 SmallString<256> FullPath;
53
54 if (!Relative)
55 FullPath.append(RHS: Opts.ShowOutputDirectory);
56
57 if (!InToplevel)
58 sys::path::append(path&: FullPath, a: getCoverageDir());
59
60 SmallString<256> ParentPath = sys::path::parent_path(path: Path);
61 sys::path::remove_dots(path&: ParentPath, /*remove_dot_dot=*/true);
62 sys::path::append(path&: FullPath, a: sys::path::relative_path(path: ParentPath));
63
64 auto PathFilename = (sys::path::filename(path: Path) + "." + Extension).str();
65 sys::path::append(path&: FullPath, a: PathFilename);
66 sys::path::native(path&: FullPath);
67
68 return std::string(FullPath);
69}
70
71Expected<CoveragePrinter::OwnedStream>
72CoveragePrinter::createOutputStream(StringRef Path, StringRef Extension,
73 bool InToplevel) const {
74 if (!Opts.hasOutputDirectory())
75 return OwnedStream(&outs());
76
77 std::string FullPath = getOutputPath(Path, Extension, InToplevel, Relative: false);
78
79 auto ParentDir = sys::path::parent_path(path: FullPath);
80 if (auto E = sys::fs::create_directories(path: ParentDir))
81 return errorCodeToError(EC: E);
82
83 std::error_code E;
84 raw_ostream *RawStream =
85 new raw_fd_ostream(FullPath, E, sys::fs::FA_Read | sys::fs::FA_Write);
86 auto OS = CoveragePrinter::OwnedStream(RawStream);
87 if (E)
88 return errorCodeToError(EC: E);
89 return std::move(OS);
90}
91
92std::unique_ptr<CoveragePrinter>
93CoveragePrinter::create(const CoverageViewOptions &Opts) {
94 switch (Opts.Format) {
95 case CoverageViewOptions::OutputFormat::Text:
96 if (Opts.ShowDirectoryCoverage)
97 return std::make_unique<CoveragePrinterTextDirectory>(args: Opts);
98 return std::make_unique<CoveragePrinterText>(args: Opts);
99 case CoverageViewOptions::OutputFormat::HTML:
100 if (Opts.ShowDirectoryCoverage)
101 return std::make_unique<CoveragePrinterHTMLDirectory>(args: Opts);
102 return std::make_unique<CoveragePrinterHTML>(args: Opts);
103 case CoverageViewOptions::OutputFormat::Lcov:
104 // Unreachable because CodeCoverage.cpp should terminate with an error
105 // before we get here.
106 llvm_unreachable("Lcov format is not supported!");
107 }
108 llvm_unreachable("Unknown coverage output format!");
109}
110
111unsigned SourceCoverageView::getFirstUncoveredLineNo() {
112 const auto MinSegIt = find_if(Range&: CoverageInfo, P: [](const CoverageSegment &S) {
113 return S.HasCount && S.Count == 0;
114 });
115
116 // There is no uncovered line, return zero.
117 if (MinSegIt == CoverageInfo.end())
118 return 0;
119
120 return (*MinSegIt).Line;
121}
122
123std::string SourceCoverageView::formatCount(uint64_t N) {
124 std::string Number = utostr(X: N);
125 int Len = Number.size();
126 if (Len <= 3)
127 return Number;
128 int IntLen = Len % 3 == 0 ? 3 : Len % 3;
129 std::string Result(Number.data(), IntLen);
130 if (IntLen != 3) {
131 Result.push_back(c: '.');
132 Result += Number.substr(pos: IntLen, n: 3 - IntLen);
133 }
134 Result.push_back(c: " kMGTPEZY"[(Len - 1) / 3]);
135 return Result;
136}
137
138bool SourceCoverageView::shouldRenderRegionMarkers(
139 const LineCoverageStats &LCS) const {
140 if (!getOptions().ShowRegionMarkers)
141 return false;
142
143 CoverageSegmentArray Segments = LCS.getLineSegments();
144 if (Segments.empty())
145 return false;
146 for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {
147 const auto *CurSeg = Segments[I];
148 if (!CurSeg->IsRegionEntry || CurSeg->Count == LCS.getExecutionCount())
149 continue;
150 if (!CurSeg->HasCount) // don't show tooltips for SkippedRegions
151 continue;
152 return true;
153 }
154 return false;
155}
156
157bool SourceCoverageView::hasSubViews() const {
158 return !ExpansionSubViews.empty() || !InstantiationSubViews.empty() ||
159 !BranchSubViews.empty() || !MCDCSubViews.empty();
160}
161
162std::unique_ptr<SourceCoverageView>
163SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File,
164 const CoverageViewOptions &Options,
165 CoverageData &&CoverageInfo) {
166 switch (Options.Format) {
167 case CoverageViewOptions::OutputFormat::Text:
168 return std::make_unique<SourceCoverageViewText>(
169 args&: SourceName, args: File, args: Options, args: std::move(CoverageInfo));
170 case CoverageViewOptions::OutputFormat::HTML:
171 return std::make_unique<SourceCoverageViewHTML>(
172 args&: SourceName, args: File, args: Options, args: std::move(CoverageInfo));
173 case CoverageViewOptions::OutputFormat::Lcov:
174 // Unreachable because CodeCoverage.cpp should terminate with an error
175 // before we get here.
176 llvm_unreachable("Lcov format is not supported!");
177 }
178 llvm_unreachable("Unknown coverage output format!");
179}
180
181std::string SourceCoverageView::getSourceName() const {
182 SmallString<128> SourceText(SourceName);
183 sys::path::remove_dots(path&: SourceText, /*remove_dot_dot=*/true);
184 sys::path::native(path&: SourceText);
185 return std::string(SourceText);
186}
187
188void SourceCoverageView::addExpansion(
189 const CounterMappingRegion &Region,
190 std::unique_ptr<SourceCoverageView> View) {
191 ExpansionSubViews.emplace_back(args: Region, args: std::move(View));
192}
193
194void SourceCoverageView::addBranch(unsigned Line,
195 SmallVector<CountedRegion, 0> Regions) {
196 BranchSubViews.emplace_back(Args&: Line, Args: std::move(Regions));
197}
198
199void SourceCoverageView::addMCDCRecord(unsigned Line,
200 SmallVector<MCDCRecord, 0> Records) {
201 MCDCSubViews.emplace_back(Args&: Line, Args: std::move(Records));
202}
203
204void SourceCoverageView::addInstantiation(
205 StringRef FunctionName, unsigned Line,
206 std::unique_ptr<SourceCoverageView> View) {
207 InstantiationSubViews.emplace_back(args&: FunctionName, args&: Line, args: std::move(View));
208}
209
210void SourceCoverageView::print(raw_ostream &OS, bool WholeFile,
211 bool ShowSourceName, bool ShowTitle,
212 unsigned ViewDepth) {
213 if (ShowTitle)
214 renderTitle(OS, CellText: "Coverage Report");
215
216 renderViewHeader(OS);
217
218 if (ShowSourceName)
219 renderSourceName(OS, WholeFile);
220
221 renderTableHeader(OS, IndentLevel: ViewDepth);
222
223 // We need the expansions, instantiations, and branches sorted so we can go
224 // through them while we iterate lines.
225 llvm::stable_sort(Range&: ExpansionSubViews);
226 llvm::stable_sort(Range&: InstantiationSubViews);
227 llvm::stable_sort(Range&: BranchSubViews);
228 llvm::stable_sort(Range&: MCDCSubViews);
229 auto NextESV = ExpansionSubViews.begin();
230 auto EndESV = ExpansionSubViews.end();
231 auto NextISV = InstantiationSubViews.begin();
232 auto EndISV = InstantiationSubViews.end();
233 auto NextBRV = BranchSubViews.begin();
234 auto EndBRV = BranchSubViews.end();
235 auto NextMSV = MCDCSubViews.begin();
236 auto EndMSV = MCDCSubViews.end();
237
238 // Get the coverage information for the file.
239 auto StartSegment = CoverageInfo.begin();
240 auto EndSegment = CoverageInfo.end();
241 LineCoverageIterator LCI{CoverageInfo, 1};
242 LineCoverageIterator LCIEnd = LCI.getEnd();
243
244 unsigned FirstLine = StartSegment != EndSegment ? StartSegment->Line : 0;
245 for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof();
246 ++LI, ++LCI) {
247 // If we aren't rendering the whole file, we need to filter out the prologue
248 // and epilogue.
249 if (!WholeFile) {
250 if (LCI == LCIEnd)
251 break;
252 else if (LI.line_number() < FirstLine)
253 continue;
254 }
255
256 renderLinePrefix(OS, ViewDepth);
257 if (getOptions().ShowLineNumbers)
258 renderLineNumberColumn(OS, LineNo: LI.line_number());
259
260 if (getOptions().ShowLineStats)
261 renderLineCoverageColumn(OS, Line: *LCI);
262
263 // If there are expansion subviews, we want to highlight the first one.
264 unsigned ExpansionColumn = 0;
265 if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
266 getOptions().Colors)
267 ExpansionColumn = NextESV->getStartCol();
268
269 // Display the source code for the current line.
270 renderLine(OS, L: {*LI, LI.line_number()}, LCS: *LCI, ExpansionCol: ExpansionColumn, ViewDepth);
271
272 // Show the region markers.
273 if (shouldRenderRegionMarkers(LCS: *LCI))
274 renderRegionMarkers(OS, Line: *LCI, ViewDepth);
275
276 // Show the expansions, instantiations, and branches for this line.
277 bool RenderedSubView = false;
278 for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
279 ++NextESV) {
280 renderViewDivider(OS, ViewDepth: ViewDepth + 1);
281
282 // Re-render the current line and highlight the expansion range for
283 // this subview.
284 if (RenderedSubView) {
285 ExpansionColumn = NextESV->getStartCol();
286 renderExpansionSite(OS, L: {*LI, LI.line_number()}, LCS: *LCI, ExpansionCol: ExpansionColumn,
287 ViewDepth);
288 renderViewDivider(OS, ViewDepth: ViewDepth + 1);
289 }
290
291 renderExpansionView(OS, ESV&: *NextESV, ViewDepth: ViewDepth + 1);
292 RenderedSubView = true;
293 }
294 for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
295 renderViewDivider(OS, ViewDepth: ViewDepth + 1);
296 renderInstantiationView(OS, ISV&: *NextISV, ViewDepth: ViewDepth + 1);
297 RenderedSubView = true;
298 }
299 for (; NextBRV != EndBRV && NextBRV->Line == LI.line_number(); ++NextBRV) {
300 renderViewDivider(OS, ViewDepth: ViewDepth + 1);
301 renderBranchView(OS, BRV&: *NextBRV, ViewDepth: ViewDepth + 1);
302 RenderedSubView = true;
303 }
304 for (; NextMSV != EndMSV && NextMSV->Line == LI.line_number(); ++NextMSV) {
305 renderViewDivider(OS, ViewDepth: ViewDepth + 1);
306 renderMCDCView(OS, BRV&: *NextMSV, ViewDepth: ViewDepth + 1);
307 RenderedSubView = true;
308 }
309 if (RenderedSubView)
310 renderViewDivider(OS, ViewDepth: ViewDepth + 1);
311 renderLineSuffix(OS, ViewDepth);
312 }
313
314 renderViewFooter(OS);
315}
316