1 //===- SourceCoverageView.cpp - Code coverage view for source code --------===//
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
7 //===----------------------------------------------------------------------===//
9 /// \file This class implements rendering for code coverage of source code.
11 //===----------------------------------------------------------------------===//
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"
24 void CoveragePrinter::StreamDestructor::operator()(raw_ostream
*OS
) const {
30 std::string
CoveragePrinter::getOutputPath(StringRef Path
, StringRef Extension
,
32 bool Relative
) const {
33 assert(!Extension
.empty() && "The file extension may not be empty");
35 SmallString
<256> FullPath
;
38 FullPath
.append(Opts
.ShowOutputDirectory
);
41 sys::path::append(FullPath
, getCoverageDir());
43 SmallString
<256> ParentPath
= sys::path::parent_path(Path
);
44 sys::path::remove_dots(ParentPath
, /*remove_dot_dot=*/true);
45 sys::path::append(FullPath
, sys::path::relative_path(ParentPath
));
47 auto PathFilename
= (sys::path::filename(Path
) + "." + Extension
).str();
48 sys::path::append(FullPath
, PathFilename
);
49 sys::path::native(FullPath
);
51 return std::string(FullPath
);
54 Expected
<CoveragePrinter::OwnedStream
>
55 CoveragePrinter::createOutputStream(StringRef Path
, StringRef Extension
,
56 bool InToplevel
) const {
57 if (!Opts
.hasOutputDirectory())
58 return OwnedStream(&outs());
60 std::string FullPath
= getOutputPath(Path
, Extension
, InToplevel
, false);
62 auto ParentDir
= sys::path::parent_path(FullPath
);
63 if (auto E
= sys::fs::create_directories(ParentDir
))
64 return errorCodeToError(E
);
67 raw_ostream
*RawStream
=
68 new raw_fd_ostream(FullPath
, E
, sys::fs::FA_Read
| sys::fs::FA_Write
);
69 auto OS
= CoveragePrinter::OwnedStream(RawStream
);
71 return errorCodeToError(E
);
75 std::unique_ptr
<CoveragePrinter
>
76 CoveragePrinter::create(const CoverageViewOptions
&Opts
) {
77 switch (Opts
.Format
) {
78 case CoverageViewOptions::OutputFormat::Text
:
79 if (Opts
.ShowDirectoryCoverage
)
80 return std::make_unique
<CoveragePrinterTextDirectory
>(Opts
);
81 return std::make_unique
<CoveragePrinterText
>(Opts
);
82 case CoverageViewOptions::OutputFormat::HTML
:
83 if (Opts
.ShowDirectoryCoverage
)
84 return std::make_unique
<CoveragePrinterHTMLDirectory
>(Opts
);
85 return std::make_unique
<CoveragePrinterHTML
>(Opts
);
86 case CoverageViewOptions::OutputFormat::Lcov
:
87 // Unreachable because CodeCoverage.cpp should terminate with an error
88 // before we get here.
89 llvm_unreachable("Lcov format is not supported!");
91 llvm_unreachable("Unknown coverage output format!");
94 unsigned SourceCoverageView::getFirstUncoveredLineNo() {
95 const auto MinSegIt
= find_if(CoverageInfo
, [](const CoverageSegment
&S
) {
96 return S
.HasCount
&& S
.Count
== 0;
99 // There is no uncovered line, return zero.
100 if (MinSegIt
== CoverageInfo
.end())
103 return (*MinSegIt
).Line
;
106 std::string
SourceCoverageView::formatCount(uint64_t N
) {
107 std::string Number
= utostr(N
);
108 int Len
= Number
.size();
111 int IntLen
= Len
% 3 == 0 ? 3 : Len
% 3;
112 std::string
Result(Number
.data(), IntLen
);
114 Result
.push_back('.');
115 Result
+= Number
.substr(IntLen
, 3 - IntLen
);
117 Result
.push_back(" kMGTPEZY"[(Len
- 1) / 3]);
121 bool SourceCoverageView::shouldRenderRegionMarkers(
122 const LineCoverageStats
&LCS
) const {
123 if (!getOptions().ShowRegionMarkers
)
126 CoverageSegmentArray Segments
= LCS
.getLineSegments();
127 if (Segments
.empty())
129 for (unsigned I
= 0, E
= Segments
.size() - 1; I
< E
; ++I
) {
130 const auto *CurSeg
= Segments
[I
];
131 if (!CurSeg
->IsRegionEntry
|| CurSeg
->Count
== LCS
.getExecutionCount())
133 if (!CurSeg
->HasCount
) // don't show tooltips for SkippedRegions
140 bool SourceCoverageView::hasSubViews() const {
141 return !ExpansionSubViews
.empty() || !InstantiationSubViews
.empty() ||
142 !BranchSubViews
.empty() || !MCDCSubViews
.empty();
145 std::unique_ptr
<SourceCoverageView
>
146 SourceCoverageView::create(StringRef SourceName
, const MemoryBuffer
&File
,
147 const CoverageViewOptions
&Options
,
148 CoverageData
&&CoverageInfo
) {
149 switch (Options
.Format
) {
150 case CoverageViewOptions::OutputFormat::Text
:
151 return std::make_unique
<SourceCoverageViewText
>(
152 SourceName
, File
, Options
, std::move(CoverageInfo
));
153 case CoverageViewOptions::OutputFormat::HTML
:
154 return std::make_unique
<SourceCoverageViewHTML
>(
155 SourceName
, File
, Options
, std::move(CoverageInfo
));
156 case CoverageViewOptions::OutputFormat::Lcov
:
157 // Unreachable because CodeCoverage.cpp should terminate with an error
158 // before we get here.
159 llvm_unreachable("Lcov format is not supported!");
161 llvm_unreachable("Unknown coverage output format!");
164 std::string
SourceCoverageView::getSourceName() const {
165 SmallString
<128> SourceText(SourceName
);
166 sys::path::remove_dots(SourceText
, /*remove_dot_dot=*/true);
167 sys::path::native(SourceText
);
168 return std::string(SourceText
);
171 void SourceCoverageView::addExpansion(
172 const CounterMappingRegion
&Region
,
173 std::unique_ptr
<SourceCoverageView
> View
) {
174 ExpansionSubViews
.emplace_back(Region
, std::move(View
));
177 void SourceCoverageView::addBranch(unsigned Line
,
178 SmallVector
<CountedRegion
, 0> Regions
) {
179 BranchSubViews
.emplace_back(Line
, std::move(Regions
));
182 void SourceCoverageView::addMCDCRecord(unsigned Line
,
183 SmallVector
<MCDCRecord
, 0> Records
) {
184 MCDCSubViews
.emplace_back(Line
, std::move(Records
));
187 void SourceCoverageView::addInstantiation(
188 StringRef FunctionName
, unsigned Line
,
189 std::unique_ptr
<SourceCoverageView
> View
) {
190 InstantiationSubViews
.emplace_back(FunctionName
, Line
, std::move(View
));
193 void SourceCoverageView::print(raw_ostream
&OS
, bool WholeFile
,
194 bool ShowSourceName
, bool ShowTitle
,
195 unsigned ViewDepth
) {
197 renderTitle(OS
, "Coverage Report");
199 renderViewHeader(OS
);
202 renderSourceName(OS
, WholeFile
);
204 renderTableHeader(OS
, ViewDepth
);
206 // We need the expansions, instantiations, and branches sorted so we can go
207 // through them while we iterate lines.
208 llvm::stable_sort(ExpansionSubViews
);
209 llvm::stable_sort(InstantiationSubViews
);
210 llvm::stable_sort(BranchSubViews
);
211 llvm::stable_sort(MCDCSubViews
);
212 auto NextESV
= ExpansionSubViews
.begin();
213 auto EndESV
= ExpansionSubViews
.end();
214 auto NextISV
= InstantiationSubViews
.begin();
215 auto EndISV
= InstantiationSubViews
.end();
216 auto NextBRV
= BranchSubViews
.begin();
217 auto EndBRV
= BranchSubViews
.end();
218 auto NextMSV
= MCDCSubViews
.begin();
219 auto EndMSV
= MCDCSubViews
.end();
221 // Get the coverage information for the file.
222 auto StartSegment
= CoverageInfo
.begin();
223 auto EndSegment
= CoverageInfo
.end();
224 LineCoverageIterator LCI
{CoverageInfo
, 1};
225 LineCoverageIterator LCIEnd
= LCI
.getEnd();
227 unsigned FirstLine
= StartSegment
!= EndSegment
? StartSegment
->Line
: 0;
228 for (line_iterator
LI(File
, /*SkipBlanks=*/false); !LI
.is_at_eof();
230 // If we aren't rendering the whole file, we need to filter out the prologue
235 else if (LI
.line_number() < FirstLine
)
239 renderLinePrefix(OS
, ViewDepth
);
240 if (getOptions().ShowLineNumbers
)
241 renderLineNumberColumn(OS
, LI
.line_number());
243 if (getOptions().ShowLineStats
)
244 renderLineCoverageColumn(OS
, *LCI
);
246 // If there are expansion subviews, we want to highlight the first one.
247 unsigned ExpansionColumn
= 0;
248 if (NextESV
!= EndESV
&& NextESV
->getLine() == LI
.line_number() &&
250 ExpansionColumn
= NextESV
->getStartCol();
252 // Display the source code for the current line.
253 renderLine(OS
, {*LI
, LI
.line_number()}, *LCI
, ExpansionColumn
, ViewDepth
);
255 // Show the region markers.
256 if (shouldRenderRegionMarkers(*LCI
))
257 renderRegionMarkers(OS
, *LCI
, ViewDepth
);
259 // Show the expansions, instantiations, and branches for this line.
260 bool RenderedSubView
= false;
261 for (; NextESV
!= EndESV
&& NextESV
->getLine() == LI
.line_number();
263 renderViewDivider(OS
, ViewDepth
+ 1);
265 // Re-render the current line and highlight the expansion range for
267 if (RenderedSubView
) {
268 ExpansionColumn
= NextESV
->getStartCol();
269 renderExpansionSite(OS
, {*LI
, LI
.line_number()}, *LCI
, ExpansionColumn
,
271 renderViewDivider(OS
, ViewDepth
+ 1);
274 renderExpansionView(OS
, *NextESV
, ViewDepth
+ 1);
275 RenderedSubView
= true;
277 for (; NextISV
!= EndISV
&& NextISV
->Line
== LI
.line_number(); ++NextISV
) {
278 renderViewDivider(OS
, ViewDepth
+ 1);
279 renderInstantiationView(OS
, *NextISV
, ViewDepth
+ 1);
280 RenderedSubView
= true;
282 for (; NextBRV
!= EndBRV
&& NextBRV
->Line
== LI
.line_number(); ++NextBRV
) {
283 renderViewDivider(OS
, ViewDepth
+ 1);
284 renderBranchView(OS
, *NextBRV
, ViewDepth
+ 1);
285 RenderedSubView
= true;
287 for (; NextMSV
!= EndMSV
&& NextMSV
->Line
== LI
.line_number(); ++NextMSV
) {
288 renderViewDivider(OS
, ViewDepth
+ 1);
289 renderMCDCView(OS
, *NextMSV
, ViewDepth
+ 1);
290 RenderedSubView
= true;
293 renderViewDivider(OS
, ViewDepth
+ 1);
294 renderLineSuffix(OS
, ViewDepth
);
297 renderViewFooter(OS
);