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
45using namespace llvm;
46
47namespace {
48
49void renderFunctionSummary(raw_ostream &OS,
50 const FileCoverageSummary &Summary) {
51 OS << "FNF:" << Summary.FunctionCoverage.getNumFunctions() << '\n'
52 << "FNH:" << Summary.FunctionCoverage.getExecuted() << '\n';
53}
54
55void 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
66void 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
78std::vector<llvm::coverage::CountedRegion>
79collectNestedBranches(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
108bool 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
114void 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
163void renderLineSummary(raw_ostream &OS, const FileCoverageSummary &Summary) {
164 OS << "LF:" << Summary.LineCoverage.getNumLines() << '\n'
165 << "LH:" << Summary.LineCoverage.getCovered() << '\n';
166}
167
168void renderBranchSummary(raw_ostream &OS, const FileCoverageSummary &Summary) {
169 OS << "BRF:" << Summary.BranchCoverage.getNumBranches() << '\n'
170 << "BRH:" << Summary.BranchCoverage.getCovered() << '\n';
171}
172
173void 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
198void 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
210void 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
219void 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