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
.str());
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())
138 bool SourceCoverageView::hasSubViews() const {
139 return !ExpansionSubViews
.empty() || !InstantiationSubViews
.empty() ||
140 !BranchSubViews
.empty();
143 std::unique_ptr
<SourceCoverageView
>
144 SourceCoverageView::create(StringRef SourceName
, const MemoryBuffer
&File
,
145 const CoverageViewOptions
&Options
,
146 CoverageData
&&CoverageInfo
) {
147 switch (Options
.Format
) {
148 case CoverageViewOptions::OutputFormat::Text
:
149 return std::make_unique
<SourceCoverageViewText
>(
150 SourceName
, File
, Options
, std::move(CoverageInfo
));
151 case CoverageViewOptions::OutputFormat::HTML
:
152 return std::make_unique
<SourceCoverageViewHTML
>(
153 SourceName
, File
, Options
, std::move(CoverageInfo
));
154 case CoverageViewOptions::OutputFormat::Lcov
:
155 // Unreachable because CodeCoverage.cpp should terminate with an error
156 // before we get here.
157 llvm_unreachable("Lcov format is not supported!");
159 llvm_unreachable("Unknown coverage output format!");
162 std::string
SourceCoverageView::getSourceName() const {
163 SmallString
<128> SourceText(SourceName
);
164 sys::path::remove_dots(SourceText
, /*remove_dot_dot=*/true);
165 sys::path::native(SourceText
);
166 return std::string(SourceText
.str());
169 void SourceCoverageView::addExpansion(
170 const CounterMappingRegion
&Region
,
171 std::unique_ptr
<SourceCoverageView
> View
) {
172 ExpansionSubViews
.emplace_back(Region
, std::move(View
));
175 void SourceCoverageView::addBranch(unsigned Line
,
176 ArrayRef
<CountedRegion
> Regions
,
177 std::unique_ptr
<SourceCoverageView
> View
) {
178 BranchSubViews
.emplace_back(Line
, Regions
, std::move(View
));
181 void SourceCoverageView::addInstantiation(
182 StringRef FunctionName
, unsigned Line
,
183 std::unique_ptr
<SourceCoverageView
> View
) {
184 InstantiationSubViews
.emplace_back(FunctionName
, Line
, std::move(View
));
187 void SourceCoverageView::print(raw_ostream
&OS
, bool WholeFile
,
188 bool ShowSourceName
, bool ShowTitle
,
189 unsigned ViewDepth
) {
191 renderTitle(OS
, "Coverage Report");
193 renderViewHeader(OS
);
196 renderSourceName(OS
, WholeFile
);
198 renderTableHeader(OS
, (ViewDepth
> 0) ? 0 : getFirstUncoveredLineNo(),
201 // We need the expansions, instantiations, and branches sorted so we can go
202 // through them while we iterate lines.
203 llvm::stable_sort(ExpansionSubViews
);
204 llvm::stable_sort(InstantiationSubViews
);
205 llvm::stable_sort(BranchSubViews
);
206 auto NextESV
= ExpansionSubViews
.begin();
207 auto EndESV
= ExpansionSubViews
.end();
208 auto NextISV
= InstantiationSubViews
.begin();
209 auto EndISV
= InstantiationSubViews
.end();
210 auto NextBRV
= BranchSubViews
.begin();
211 auto EndBRV
= BranchSubViews
.end();
213 // Get the coverage information for the file.
214 auto StartSegment
= CoverageInfo
.begin();
215 auto EndSegment
= CoverageInfo
.end();
216 LineCoverageIterator LCI
{CoverageInfo
, 1};
217 LineCoverageIterator LCIEnd
= LCI
.getEnd();
219 unsigned FirstLine
= StartSegment
!= EndSegment
? StartSegment
->Line
: 0;
220 for (line_iterator
LI(File
, /*SkipBlanks=*/false); !LI
.is_at_eof();
222 // If we aren't rendering the whole file, we need to filter out the prologue
227 else if (LI
.line_number() < FirstLine
)
231 renderLinePrefix(OS
, ViewDepth
);
232 if (getOptions().ShowLineNumbers
)
233 renderLineNumberColumn(OS
, LI
.line_number());
235 if (getOptions().ShowLineStats
)
236 renderLineCoverageColumn(OS
, *LCI
);
238 // If there are expansion subviews, we want to highlight the first one.
239 unsigned ExpansionColumn
= 0;
240 if (NextESV
!= EndESV
&& NextESV
->getLine() == LI
.line_number() &&
242 ExpansionColumn
= NextESV
->getStartCol();
244 // Display the source code for the current line.
245 renderLine(OS
, {*LI
, LI
.line_number()}, *LCI
, ExpansionColumn
, ViewDepth
);
247 // Show the region markers.
248 if (shouldRenderRegionMarkers(*LCI
))
249 renderRegionMarkers(OS
, *LCI
, ViewDepth
);
251 // Show the expansions, instantiations, and branches for this line.
252 bool RenderedSubView
= false;
253 for (; NextESV
!= EndESV
&& NextESV
->getLine() == LI
.line_number();
255 renderViewDivider(OS
, ViewDepth
+ 1);
257 // Re-render the current line and highlight the expansion range for
259 if (RenderedSubView
) {
260 ExpansionColumn
= NextESV
->getStartCol();
261 renderExpansionSite(OS
, {*LI
, LI
.line_number()}, *LCI
, ExpansionColumn
,
263 renderViewDivider(OS
, ViewDepth
+ 1);
266 renderExpansionView(OS
, *NextESV
, ViewDepth
+ 1);
267 RenderedSubView
= true;
269 for (; NextISV
!= EndISV
&& NextISV
->Line
== LI
.line_number(); ++NextISV
) {
270 renderViewDivider(OS
, ViewDepth
+ 1);
271 renderInstantiationView(OS
, *NextISV
, ViewDepth
+ 1);
272 RenderedSubView
= true;
274 for (; NextBRV
!= EndBRV
&& NextBRV
->Line
== LI
.line_number(); ++NextBRV
) {
275 renderViewDivider(OS
, ViewDepth
+ 1);
276 renderBranchView(OS
, *NextBRV
, ViewDepth
+ 1);
277 RenderedSubView
= true;
280 renderViewDivider(OS
, ViewDepth
+ 1);
281 renderLineSuffix(OS
, ViewDepth
);
284 renderViewFooter(OS
);