1 | //===- CoverageExporterLcov.cpp - Code coverage export --------------------===// |
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 | // This file implements export of code coverage data to lcov trace file format. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | //===----------------------------------------------------------------------===// |
14 | // |
15 | // The trace file code coverage export follows the following format (see also |
16 | // https://linux.die.net/man/1/geninfo). Each quoted string appears on its own |
17 | // line; the indentation shown here is only for documentation purposes. |
18 | // |
19 | // - for each source file: |
20 | // - "SF:<absolute path to source file>" |
21 | // - for each function: |
22 | // - "FN:<line number of function start>,<function name>" |
23 | // - for each function: |
24 | // - "FNDA:<execution count>,<function name>" |
25 | // - "FNF:<number of functions found>" |
26 | // - "FNH:<number of functions hit>" |
27 | // - for each instrumented line: |
28 | // - "DA:<line number>,<execution count>[,<checksum>] |
29 | // - for each branch: |
30 | // - "BRDA:<line number>,<branch pair id>,<branch id>,<count>" |
31 | // - "BRF:<number of branches found>" |
32 | // - "BRH:<number of branches hit>" |
33 | // - "LH:<number of lines with non-zero execution count>" |
34 | // - "LF:<number of instrumented lines>" |
35 | // - "end_of_record" |
36 | // |
37 | // If the user is exporting summary information only, then the FN, FNDA, and DA |
38 | // lines will not be present. |
39 | // |
40 | //===----------------------------------------------------------------------===// |
41 | |
42 | #include "CoverageExporterLcov.h" |
43 | #include "CoverageReport.h" |
44 | |
45 | using namespace llvm; |
46 | using namespace coverage; |
47 | |
48 | namespace { |
49 | |
50 | struct NestedCountedRegion : public coverage::CountedRegion { |
51 | // Contains the path to default and expanded branches. |
52 | // Size is 1 for default branches and greater 1 for expanded branches. |
53 | std::vector<LineColPair> NestedPath; |
54 | // Contains the original index of this element used to keep the original order |
55 | // in case of equal nested path. |
56 | unsigned Position; |
57 | // Indicates whether this item should be ignored at rendering. |
58 | bool Ignore = false; |
59 | |
60 | NestedCountedRegion(llvm::coverage::CountedRegion Region, |
61 | std::vector<LineColPair> NestedPath, unsigned Position) |
62 | : llvm::coverage::CountedRegion(std::move(Region)), |
63 | NestedPath(std::move(NestedPath)), Position(Position) {} |
64 | |
65 | // Returns the root line of the branch. |
66 | unsigned getEffectiveLine() const { return NestedPath.front().first; } |
67 | }; |
68 | |
69 | void renderFunctionSummary(raw_ostream &OS, |
70 | const FileCoverageSummary &Summary) { |
71 | OS << "FNF:" << Summary.FunctionCoverage.getNumFunctions() << '\n' |
72 | << "FNH:" << Summary.FunctionCoverage.getExecuted() << '\n'; |
73 | } |
74 | |
75 | void renderFunctions( |
76 | raw_ostream &OS, |
77 | const iterator_range<coverage::FunctionRecordIterator> &Functions) { |
78 | for (const auto &F : Functions) { |
79 | auto StartLine = F.CountedRegions.front().LineStart; |
80 | OS << "FN:" << StartLine << ',' << F.Name << '\n'; |
81 | } |
82 | for (const auto &F : Functions) |
83 | OS << "FNDA:" << F.ExecutionCount << ',' << F.Name << '\n'; |
84 | } |
85 | |
86 | void renderLineExecutionCounts(raw_ostream &OS, |
87 | const coverage::CoverageData &FileCoverage) { |
88 | coverage::LineCoverageIterator LCI{FileCoverage, 1}; |
89 | coverage::LineCoverageIterator LCIEnd = LCI.getEnd(); |
90 | for (; LCI != LCIEnd; ++LCI) { |
91 | const coverage::LineCoverageStats &LCS = *LCI; |
92 | if (LCS.isMapped()) { |
93 | OS << "DA:" << LCS.getLine() << ',' << LCS.getExecutionCount() << '\n'; |
94 | } |
95 | } |
96 | } |
97 | |
98 | std::vector<NestedCountedRegion> |
99 | collectNestedBranches(const coverage::CoverageMapping &Coverage, |
100 | ArrayRef<llvm::coverage::ExpansionRecord> Expansions, |
101 | std::vector<LineColPair> &NestedPath, |
102 | unsigned &PositionCounter) { |
103 | std::vector<NestedCountedRegion> Branches; |
104 | for (const auto &Expansion : Expansions) { |
105 | auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion); |
106 | |
107 | // Track the path to the nested expansions. |
108 | NestedPath.push_back(x: Expansion.Region.startLoc()); |
109 | |
110 | // Recursively collect branches from nested expansions. |
111 | auto NestedExpansions = ExpansionCoverage.getExpansions(); |
112 | auto NestedExBranches = collectNestedBranches(Coverage, Expansions: NestedExpansions, |
113 | NestedPath, PositionCounter); |
114 | append_range(C&: Branches, R&: NestedExBranches); |
115 | |
116 | // Add branches from this level of expansion. |
117 | auto ExBranches = ExpansionCoverage.getBranches(); |
118 | for (auto &B : ExBranches) |
119 | if (B.FileID == Expansion.FileID) { |
120 | Branches.push_back( |
121 | x: NestedCountedRegion(B, NestedPath, PositionCounter++)); |
122 | } |
123 | |
124 | NestedPath.pop_back(); |
125 | } |
126 | |
127 | return Branches; |
128 | } |
129 | |
130 | void appendNestedCountedRegions(const std::vector<CountedRegion> &Src, |
131 | std::vector<NestedCountedRegion> &Dst) { |
132 | auto Unfolded = make_filter_range(Range: Src, Pred: [](auto &Region) { |
133 | return !Region.TrueFolded || !Region.FalseFolded; |
134 | }); |
135 | Dst.reserve(n: Dst.size() + Src.size()); |
136 | unsigned PositionCounter = Dst.size(); |
137 | std::transform(first: Unfolded.begin(), last: Unfolded.end(), result: std::back_inserter(x&: Dst), |
138 | unary_op: [=, &PositionCounter](auto &Region) { |
139 | return NestedCountedRegion(Region, {Region.startLoc()}, |
140 | PositionCounter++); |
141 | }); |
142 | } |
143 | |
144 | void appendNestedCountedRegions(const std::vector<NestedCountedRegion> &Src, |
145 | std::vector<NestedCountedRegion> &Dst) { |
146 | auto Unfolded = make_filter_range(Range: Src, Pred: [](auto &NestedRegion) { |
147 | return !NestedRegion.TrueFolded || !NestedRegion.FalseFolded; |
148 | }); |
149 | Dst.reserve(n: Dst.size() + Src.size()); |
150 | std::copy(first: Unfolded.begin(), last: Unfolded.end(), result: std::back_inserter(x&: Dst)); |
151 | } |
152 | |
153 | bool sortNested(const NestedCountedRegion &I, const NestedCountedRegion &J) { |
154 | // This sorts each element by line and column. |
155 | // Implies that all elements are first sorted by getEffectiveLine(). |
156 | // Use original position if NestedPath is equal. |
157 | return std::tie(args: I.NestedPath, args: I.Position) < |
158 | std::tie(args: J.NestedPath, args: J.Position); |
159 | } |
160 | |
161 | void combineInstanceCounts(std::vector<NestedCountedRegion> &Branches) { |
162 | auto NextBranch = Branches.begin(); |
163 | auto EndBranch = Branches.end(); |
164 | |
165 | while (NextBranch != EndBranch) { |
166 | auto SumBranch = NextBranch++; |
167 | |
168 | // Ensure that only branches with the same NestedPath are summed up. |
169 | while (NextBranch != EndBranch && |
170 | SumBranch->NestedPath == NextBranch->NestedPath) { |
171 | SumBranch->ExecutionCount += NextBranch->ExecutionCount; |
172 | SumBranch->FalseExecutionCount += NextBranch->FalseExecutionCount; |
173 | // Mark this branch as ignored. |
174 | NextBranch->Ignore = true; |
175 | |
176 | NextBranch++; |
177 | } |
178 | } |
179 | } |
180 | |
181 | void renderBranchExecutionCounts(raw_ostream &OS, |
182 | const coverage::CoverageMapping &Coverage, |
183 | const coverage::CoverageData &FileCoverage, |
184 | bool UnifyInstances) { |
185 | |
186 | std::vector<NestedCountedRegion> Branches; |
187 | |
188 | appendNestedCountedRegions(Src: FileCoverage.getBranches(), Dst&: Branches); |
189 | |
190 | // Recursively collect branches for all file expansions. |
191 | std::vector<LineColPair> NestedPath; |
192 | unsigned PositionCounter = 0; |
193 | std::vector<NestedCountedRegion> ExBranches = collectNestedBranches( |
194 | Coverage, Expansions: FileCoverage.getExpansions(), NestedPath, PositionCounter); |
195 | |
196 | // Append Expansion Branches to Source Branches. |
197 | appendNestedCountedRegions(Src: ExBranches, Dst&: Branches); |
198 | |
199 | // Sort branches based on line number to ensure branches corresponding to the |
200 | // same source line are counted together. |
201 | llvm::sort(C&: Branches, Comp: sortNested); |
202 | |
203 | if (UnifyInstances) { |
204 | combineInstanceCounts(Branches); |
205 | } |
206 | |
207 | auto NextBranch = Branches.begin(); |
208 | auto EndBranch = Branches.end(); |
209 | |
210 | // Branches with the same source line are enumerated individually |
211 | // (BranchIndex) as well as based on True/False pairs (PairIndex). |
212 | while (NextBranch != EndBranch) { |
213 | unsigned CurrentLine = NextBranch->getEffectiveLine(); |
214 | unsigned PairIndex = 0; |
215 | unsigned BranchIndex = 0; |
216 | |
217 | while (NextBranch != EndBranch && |
218 | CurrentLine == NextBranch->getEffectiveLine()) { |
219 | if (!NextBranch->Ignore) { |
220 | unsigned BC1 = NextBranch->ExecutionCount; |
221 | unsigned BC2 = NextBranch->FalseExecutionCount; |
222 | bool BranchNotExecuted = (BC1 == 0 && BC2 == 0); |
223 | |
224 | for (int I = 0; I < 2; I++, BranchIndex++) { |
225 | OS << "BRDA:" << CurrentLine << ',' << PairIndex << ',' |
226 | << BranchIndex; |
227 | if (BranchNotExecuted) |
228 | OS << ',' << '-' << '\n'; |
229 | else |
230 | OS << ',' << (I == 0 ? BC1 : BC2) << '\n'; |
231 | } |
232 | |
233 | PairIndex++; |
234 | } |
235 | NextBranch++; |
236 | } |
237 | } |
238 | } |
239 | |
240 | void renderLineSummary(raw_ostream &OS, const FileCoverageSummary &Summary) { |
241 | OS << "LF:" << Summary.LineCoverage.getNumLines() << '\n' |
242 | << "LH:" << Summary.LineCoverage.getCovered() << '\n'; |
243 | } |
244 | |
245 | void renderBranchSummary(raw_ostream &OS, const FileCoverageSummary &Summary) { |
246 | OS << "BRF:" << Summary.BranchCoverage.getNumBranches() << '\n' |
247 | << "BRH:" << Summary.BranchCoverage.getCovered() << '\n'; |
248 | } |
249 | |
250 | void renderFile(raw_ostream &OS, const coverage::CoverageMapping &Coverage, |
251 | const std::string &Filename, |
252 | const FileCoverageSummary &FileReport, bool ExportSummaryOnly, |
253 | bool SkipFunctions, bool SkipBranches, bool UnifyInstances) { |
254 | OS << "SF:" << Filename << '\n'; |
255 | |
256 | if (!ExportSummaryOnly && !SkipFunctions) { |
257 | renderFunctions(OS, Functions: Coverage.getCoveredFunctions(Filename)); |
258 | } |
259 | renderFunctionSummary(OS, Summary: FileReport); |
260 | |
261 | if (!ExportSummaryOnly) { |
262 | // Calculate and render detailed coverage information for given file. |
263 | auto FileCoverage = Coverage.getCoverageForFile(Filename); |
264 | renderLineExecutionCounts(OS, FileCoverage); |
265 | if (!SkipBranches) |
266 | renderBranchExecutionCounts(OS, Coverage, FileCoverage, UnifyInstances); |
267 | } |
268 | if (!SkipBranches) |
269 | renderBranchSummary(OS, Summary: FileReport); |
270 | renderLineSummary(OS, Summary: FileReport); |
271 | |
272 | OS << "end_of_record\n" ; |
273 | } |
274 | |
275 | void renderFiles(raw_ostream &OS, const coverage::CoverageMapping &Coverage, |
276 | ArrayRef<std::string> SourceFiles, |
277 | ArrayRef<FileCoverageSummary> FileReports, |
278 | bool ExportSummaryOnly, bool SkipFunctions, bool SkipBranches, |
279 | bool UnifyInstances) { |
280 | for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) |
281 | renderFile(OS, Coverage, Filename: SourceFiles[I], FileReport: FileReports[I], ExportSummaryOnly, |
282 | SkipFunctions, SkipBranches, UnifyInstances); |
283 | } |
284 | |
285 | } // end anonymous namespace |
286 | |
287 | void CoverageExporterLcov::renderRoot(const CoverageFilters &IgnoreFilters) { |
288 | std::vector<std::string> SourceFiles; |
289 | for (StringRef SF : Coverage.getUniqueSourceFiles()) { |
290 | if (!IgnoreFilters.matchesFilename(Filename: SF)) |
291 | SourceFiles.emplace_back(args&: SF); |
292 | } |
293 | renderRoot(SourceFiles); |
294 | } |
295 | |
296 | void CoverageExporterLcov::renderRoot(ArrayRef<std::string> SourceFiles) { |
297 | FileCoverageSummary Totals = FileCoverageSummary("Totals" ); |
298 | auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals, |
299 | Files: SourceFiles, Options); |
300 | renderFiles(OS, Coverage, SourceFiles, FileReports, ExportSummaryOnly: Options.ExportSummaryOnly, |
301 | SkipFunctions: Options.SkipFunctions, SkipBranches: Options.SkipBranches, |
302 | UnifyInstances: Options.UnifyFunctionInstantiations); |
303 | } |
304 | |