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 return std::make_unique
<CoveragePrinterText
>(Opts
);
80 case CoverageViewOptions::OutputFormat::HTML
:
81 return std::make_unique
<CoveragePrinterHTML
>(Opts
);
82 case CoverageViewOptions::OutputFormat::Lcov
:
83 // Unreachable because CodeCoverage.cpp should terminate with an error
84 // before we get here.
85 llvm_unreachable("Lcov format is not supported!");
87 llvm_unreachable("Unknown coverage output format!");
90 unsigned SourceCoverageView::getFirstUncoveredLineNo() {
91 const auto MinSegIt
= find_if(CoverageInfo
, [](const CoverageSegment
&S
) {
92 return S
.HasCount
&& S
.Count
== 0;
95 // There is no uncovered line, return zero.
96 if (MinSegIt
== CoverageInfo
.end())
99 return (*MinSegIt
).Line
;
102 std::string
SourceCoverageView::formatCount(uint64_t N
) {
103 std::string Number
= utostr(N
);
104 int Len
= Number
.size();
107 int IntLen
= Len
% 3 == 0 ? 3 : Len
% 3;
108 std::string
Result(Number
.data(), IntLen
);
110 Result
.push_back('.');
111 Result
+= Number
.substr(IntLen
, 3 - IntLen
);
113 Result
.push_back(" kMGTPEZY"[(Len
- 1) / 3]);
117 bool SourceCoverageView::shouldRenderRegionMarkers(
118 const LineCoverageStats
&LCS
) const {
119 if (!getOptions().ShowRegionMarkers
)
122 CoverageSegmentArray Segments
= LCS
.getLineSegments();
123 if (Segments
.empty())
125 for (unsigned I
= 0, E
= Segments
.size() - 1; I
< E
; ++I
) {
126 const auto *CurSeg
= Segments
[I
];
127 if (!CurSeg
->IsRegionEntry
|| CurSeg
->Count
== LCS
.getExecutionCount())
134 bool SourceCoverageView::hasSubViews() const {
135 return !ExpansionSubViews
.empty() || !InstantiationSubViews
.empty() ||
136 !BranchSubViews
.empty();
139 std::unique_ptr
<SourceCoverageView
>
140 SourceCoverageView::create(StringRef SourceName
, const MemoryBuffer
&File
,
141 const CoverageViewOptions
&Options
,
142 CoverageData
&&CoverageInfo
) {
143 switch (Options
.Format
) {
144 case CoverageViewOptions::OutputFormat::Text
:
145 return std::make_unique
<SourceCoverageViewText
>(
146 SourceName
, File
, Options
, std::move(CoverageInfo
));
147 case CoverageViewOptions::OutputFormat::HTML
:
148 return std::make_unique
<SourceCoverageViewHTML
>(
149 SourceName
, File
, Options
, std::move(CoverageInfo
));
150 case CoverageViewOptions::OutputFormat::Lcov
:
151 // Unreachable because CodeCoverage.cpp should terminate with an error
152 // before we get here.
153 llvm_unreachable("Lcov format is not supported!");
155 llvm_unreachable("Unknown coverage output format!");
158 std::string
SourceCoverageView::getSourceName() const {
159 SmallString
<128> SourceText(SourceName
);
160 sys::path::remove_dots(SourceText
, /*remove_dot_dot=*/true);
161 sys::path::native(SourceText
);
162 return std::string(SourceText
.str());
165 void SourceCoverageView::addExpansion(
166 const CounterMappingRegion
&Region
,
167 std::unique_ptr
<SourceCoverageView
> View
) {
168 ExpansionSubViews
.emplace_back(Region
, std::move(View
));
171 void SourceCoverageView::addBranch(unsigned Line
,
172 ArrayRef
<CountedRegion
> Regions
,
173 std::unique_ptr
<SourceCoverageView
> View
) {
174 BranchSubViews
.emplace_back(Line
, Regions
, std::move(View
));
177 void SourceCoverageView::addInstantiation(
178 StringRef FunctionName
, unsigned Line
,
179 std::unique_ptr
<SourceCoverageView
> View
) {
180 InstantiationSubViews
.emplace_back(FunctionName
, Line
, std::move(View
));
183 void SourceCoverageView::print(raw_ostream
&OS
, bool WholeFile
,
184 bool ShowSourceName
, bool ShowTitle
,
185 unsigned ViewDepth
) {
187 renderTitle(OS
, "Coverage Report");
189 renderViewHeader(OS
);
192 renderSourceName(OS
, WholeFile
);
194 renderTableHeader(OS
, (ViewDepth
> 0) ? 0 : getFirstUncoveredLineNo(),
197 // We need the expansions, instantiations, and branches sorted so we can go
198 // through them while we iterate lines.
199 llvm::stable_sort(ExpansionSubViews
);
200 llvm::stable_sort(InstantiationSubViews
);
201 llvm::stable_sort(BranchSubViews
);
202 auto NextESV
= ExpansionSubViews
.begin();
203 auto EndESV
= ExpansionSubViews
.end();
204 auto NextISV
= InstantiationSubViews
.begin();
205 auto EndISV
= InstantiationSubViews
.end();
206 auto NextBRV
= BranchSubViews
.begin();
207 auto EndBRV
= BranchSubViews
.end();
209 // Get the coverage information for the file.
210 auto StartSegment
= CoverageInfo
.begin();
211 auto EndSegment
= CoverageInfo
.end();
212 LineCoverageIterator LCI
{CoverageInfo
, 1};
213 LineCoverageIterator LCIEnd
= LCI
.getEnd();
215 unsigned FirstLine
= StartSegment
!= EndSegment
? StartSegment
->Line
: 0;
216 for (line_iterator
LI(File
, /*SkipBlanks=*/false); !LI
.is_at_eof();
218 // If we aren't rendering the whole file, we need to filter out the prologue
223 else if (LI
.line_number() < FirstLine
)
227 renderLinePrefix(OS
, ViewDepth
);
228 if (getOptions().ShowLineNumbers
)
229 renderLineNumberColumn(OS
, LI
.line_number());
231 if (getOptions().ShowLineStats
)
232 renderLineCoverageColumn(OS
, *LCI
);
234 // If there are expansion subviews, we want to highlight the first one.
235 unsigned ExpansionColumn
= 0;
236 if (NextESV
!= EndESV
&& NextESV
->getLine() == LI
.line_number() &&
238 ExpansionColumn
= NextESV
->getStartCol();
240 // Display the source code for the current line.
241 renderLine(OS
, {*LI
, LI
.line_number()}, *LCI
, ExpansionColumn
, ViewDepth
);
243 // Show the region markers.
244 if (shouldRenderRegionMarkers(*LCI
))
245 renderRegionMarkers(OS
, *LCI
, ViewDepth
);
247 // Show the expansions, instantiations, and branches for this line.
248 bool RenderedSubView
= false;
249 for (; NextESV
!= EndESV
&& NextESV
->getLine() == LI
.line_number();
251 renderViewDivider(OS
, ViewDepth
+ 1);
253 // Re-render the current line and highlight the expansion range for
255 if (RenderedSubView
) {
256 ExpansionColumn
= NextESV
->getStartCol();
257 renderExpansionSite(OS
, {*LI
, LI
.line_number()}, *LCI
, ExpansionColumn
,
259 renderViewDivider(OS
, ViewDepth
+ 1);
262 renderExpansionView(OS
, *NextESV
, ViewDepth
+ 1);
263 RenderedSubView
= true;
265 for (; NextISV
!= EndISV
&& NextISV
->Line
== LI
.line_number(); ++NextISV
) {
266 renderViewDivider(OS
, ViewDepth
+ 1);
267 renderInstantiationView(OS
, *NextISV
, ViewDepth
+ 1);
268 RenderedSubView
= true;
270 for (; NextBRV
!= EndBRV
&& NextBRV
->Line
== LI
.line_number(); ++NextBRV
) {
271 renderViewDivider(OS
, ViewDepth
+ 1);
272 renderBranchView(OS
, *NextBRV
, ViewDepth
+ 1);
273 RenderedSubView
= true;
276 renderViewDivider(OS
, ViewDepth
+ 1);
277 renderLineSuffix(OS
, ViewDepth
);
280 renderViewFooter(OS
);