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 | |
47 | namespace { |
48 | |
49 | void renderFunctionSummary(raw_ostream &OS, |
50 | const FileCoverageSummary &Summary) { |
51 | OS << "FNF:" << Summary.FunctionCoverage.getNumFunctions() << '\n' |
52 | << "FNH:" << Summary.FunctionCoverage.getExecuted() << '\n'; |
53 | } |
54 | |
55 | void renderFunctions( |
56 | raw_ostream &OS, |
57 | const iterator_range<coverage::FunctionRecordIterator> &Functions) { |
58 | for (const auto &F : Functions) { |
59 | auto StartLine = F.CountedRegions.front().LineStart; |
60 | OS << "FN:" << StartLine << ',' << F.Name << '\n'; |
61 | } |
62 | for (const auto &F : Functions) |
63 | OS << "FNDA:" << F.ExecutionCount << ',' << F.Name << '\n'; |
64 | } |
65 | |
66 | void renderLineExecutionCounts(raw_ostream &OS, |
67 | const coverage::CoverageData &FileCoverage) { |
68 | coverage::LineCoverageIterator LCI{FileCoverage, 1}; |
69 | coverage::LineCoverageIterator LCIEnd = LCI.getEnd(); |
70 | for (; LCI != LCIEnd; ++LCI) { |
71 | const coverage::LineCoverageStats &LCS = *LCI; |
72 | if (LCS.isMapped()) { |
73 | OS << "DA:" << LCS.getLine() << ',' << LCS.getExecutionCount() << '\n'; |
74 | } |
75 | } |
76 | } |
77 | |
78 | std::vector<llvm::coverage::CountedRegion> |
79 | collectNestedBranches(const coverage::CoverageMapping &Coverage, |
80 | ArrayRef<llvm::coverage::ExpansionRecord> Expansions, |
81 | int ViewDepth = 0, int SrcLine = 0) { |
82 | std::vector<llvm::coverage::CountedRegion> Branches; |
83 | for (const auto &Expansion : Expansions) { |
84 | auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion); |
85 | |
86 | // If we're at the top level, set the corresponding source line. |
87 | if (ViewDepth == 0) |
88 | SrcLine = Expansion.Region.LineStart; |
89 | |
90 | // Recursively collect branches from nested expansions. |
91 | auto NestedExpansions = ExpansionCoverage.getExpansions(); |
92 | auto NestedExBranches = collectNestedBranches(Coverage, Expansions: NestedExpansions, |
93 | ViewDepth: ViewDepth + 1, SrcLine); |
94 | append_range(C&: Branches, R&: NestedExBranches); |
95 | |
96 | // Add branches from this level of expansion. |
97 | auto ExBranches = ExpansionCoverage.getBranches(); |
98 | for (auto B : ExBranches) |
99 | if (B.FileID == Expansion.FileID) { |
100 | B.LineStart = SrcLine; |
101 | Branches.push_back(x: B); |
102 | } |
103 | } |
104 | |
105 | return Branches; |
106 | } |
107 | |
108 | bool sortLine(llvm::coverage::CountedRegion I, |
109 | llvm::coverage::CountedRegion J) { |
110 | return (I.LineStart < J.LineStart) || |
111 | ((I.LineStart == J.LineStart) && (I.ColumnStart < J.ColumnStart)); |
112 | } |
113 | |
114 | void renderBranchExecutionCounts(raw_ostream &OS, |
115 | const coverage::CoverageMapping &Coverage, |
116 | const coverage::CoverageData &FileCoverage) { |
117 | std::vector<llvm::coverage::CountedRegion> Branches = |
118 | FileCoverage.getBranches(); |
119 | |
120 | // Recursively collect branches for all file expansions. |
121 | std::vector<llvm::coverage::CountedRegion> ExBranches = |
122 | collectNestedBranches(Coverage, Expansions: FileCoverage.getExpansions()); |
123 | |
124 | // Append Expansion Branches to Source Branches. |
125 | append_range(C&: Branches, R&: ExBranches); |
126 | |
127 | // Sort branches based on line number to ensure branches corresponding to the |
128 | // same source line are counted together. |
129 | llvm::sort(C&: Branches, Comp: sortLine); |
130 | |
131 | auto NextBranch = Branches.begin(); |
132 | auto EndBranch = Branches.end(); |
133 | |
134 | // Branches with the same source line are enumerated individually |
135 | // (BranchIndex) as well as based on True/False pairs (PairIndex). |
136 | while (NextBranch != EndBranch) { |
137 | unsigned CurrentLine = NextBranch->LineStart; |
138 | unsigned PairIndex = 0; |
139 | unsigned BranchIndex = 0; |
140 | |
141 | while (NextBranch != EndBranch && CurrentLine == NextBranch->LineStart) { |
142 | if (!NextBranch->Folded) { |
143 | unsigned BC1 = NextBranch->ExecutionCount; |
144 | unsigned BC2 = NextBranch->FalseExecutionCount; |
145 | bool BranchNotExecuted = (BC1 == 0 && BC2 == 0); |
146 | |
147 | for (int I = 0; I < 2; I++, BranchIndex++) { |
148 | OS << "BRDA:" << CurrentLine << ',' << PairIndex << ',' |
149 | << BranchIndex; |
150 | if (BranchNotExecuted) |
151 | OS << ',' << '-' << '\n'; |
152 | else |
153 | OS << ',' << (I == 0 ? BC1 : BC2) << '\n'; |
154 | } |
155 | |
156 | PairIndex++; |
157 | } |
158 | NextBranch++; |
159 | } |
160 | } |
161 | } |
162 | |
163 | void renderLineSummary(raw_ostream &OS, const FileCoverageSummary &Summary) { |
164 | OS << "LF:" << Summary.LineCoverage.getNumLines() << '\n' |
165 | << "LH:" << Summary.LineCoverage.getCovered() << '\n'; |
166 | } |
167 | |
168 | void renderBranchSummary(raw_ostream &OS, const FileCoverageSummary &Summary) { |
169 | OS << "BRF:" << Summary.BranchCoverage.getNumBranches() << '\n' |
170 | << "BRH:" << Summary.BranchCoverage.getCovered() << '\n'; |
171 | } |
172 | |
173 | void renderFile(raw_ostream &OS, const coverage::CoverageMapping &Coverage, |
174 | const std::string &Filename, |
175 | const FileCoverageSummary &FileReport, bool ExportSummaryOnly, |
176 | bool SkipFunctions, bool SkipBranches) { |
177 | OS << "SF:" << Filename << '\n'; |
178 | |
179 | if (!ExportSummaryOnly && !SkipFunctions) { |
180 | renderFunctions(OS, Functions: Coverage.getCoveredFunctions(Filename)); |
181 | } |
182 | renderFunctionSummary(OS, Summary: FileReport); |
183 | |
184 | if (!ExportSummaryOnly) { |
185 | // Calculate and render detailed coverage information for given file. |
186 | auto FileCoverage = Coverage.getCoverageForFile(Filename); |
187 | renderLineExecutionCounts(OS, FileCoverage); |
188 | if (!SkipBranches) |
189 | renderBranchExecutionCounts(OS, Coverage, FileCoverage); |
190 | } |
191 | if (!SkipBranches) |
192 | renderBranchSummary(OS, Summary: FileReport); |
193 | renderLineSummary(OS, Summary: FileReport); |
194 | |
195 | OS << "end_of_record\n" ; |
196 | } |
197 | |
198 | void renderFiles(raw_ostream &OS, const coverage::CoverageMapping &Coverage, |
199 | ArrayRef<std::string> SourceFiles, |
200 | ArrayRef<FileCoverageSummary> FileReports, |
201 | bool ExportSummaryOnly, bool SkipFunctions, |
202 | bool SkipBranches) { |
203 | for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) |
204 | renderFile(OS, Coverage, Filename: SourceFiles[I], FileReport: FileReports[I], ExportSummaryOnly, |
205 | SkipFunctions, SkipBranches); |
206 | } |
207 | |
208 | } // end anonymous namespace |
209 | |
210 | void CoverageExporterLcov::renderRoot(const CoverageFilters &IgnoreFilters) { |
211 | std::vector<std::string> SourceFiles; |
212 | for (StringRef SF : Coverage.getUniqueSourceFiles()) { |
213 | if (!IgnoreFilters.matchesFilename(Filename: SF)) |
214 | SourceFiles.emplace_back(args&: SF); |
215 | } |
216 | renderRoot(SourceFiles); |
217 | } |
218 | |
219 | void CoverageExporterLcov::renderRoot(ArrayRef<std::string> SourceFiles) { |
220 | FileCoverageSummary Totals = FileCoverageSummary("Totals" ); |
221 | auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals, |
222 | Files: SourceFiles, Options); |
223 | renderFiles(OS, Coverage, SourceFiles, FileReports, ExportSummaryOnly: Options.ExportSummaryOnly, |
224 | SkipFunctions: Options.SkipFunctions, SkipBranches: Options.SkipBranches); |
225 | } |
226 | |