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 
renderFunctionSummary(raw_ostream & OS,const FileCoverageSummary & Summary)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 
renderFunctions(raw_ostream & OS,const iterator_range<coverage::FunctionRecordIterator> & Functions)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 
renderLineExecutionCounts(raw_ostream & OS,const coverage::CoverageData & FileCoverage)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>
collectNestedBranches(const coverage::CoverageMapping & Coverage,ArrayRef<llvm::coverage::ExpansionRecord> Expansions,int ViewDepth=0,int SrcLine=0)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, NestedExpansions,
93                                                   ViewDepth + 1, SrcLine);
94     Branches.insert(Branches.end(), NestedExBranches.begin(),
95                     NestedExBranches.end());
96 
97     // Add branches from this level of expansion.
98     auto ExBranches = ExpansionCoverage.getBranches();
99     for (auto B : ExBranches)
100       if (B.FileID == Expansion.FileID) {
101         B.LineStart = SrcLine;
102         Branches.push_back(B);
103       }
104   }
105 
106   return Branches;
107 }
108 
sortLine(llvm::coverage::CountedRegion I,llvm::coverage::CountedRegion J)109 bool sortLine(llvm::coverage::CountedRegion I,
110               llvm::coverage::CountedRegion J) {
111   return (I.LineStart < J.LineStart) ||
112          ((I.LineStart == J.LineStart) && (I.ColumnStart < J.ColumnStart));
113 }
114 
renderBranchExecutionCounts(raw_ostream & OS,const coverage::CoverageMapping & Coverage,const coverage::CoverageData & FileCoverage)115 void renderBranchExecutionCounts(raw_ostream &OS,
116                                  const coverage::CoverageMapping &Coverage,
117                                  const coverage::CoverageData &FileCoverage) {
118   std::vector<llvm::coverage::CountedRegion> Branches =
119       FileCoverage.getBranches();
120 
121   // Recursively collect branches for all file expansions.
122   std::vector<llvm::coverage::CountedRegion> ExBranches =
123       collectNestedBranches(Coverage, FileCoverage.getExpansions());
124 
125   // Append Expansion Branches to Source Branches.
126   Branches.insert(Branches.end(), ExBranches.begin(), ExBranches.end());
127 
128   // Sort branches based on line number to ensure branches corresponding to the
129   // same source line are counted together.
130   llvm::sort(Branches, sortLine);
131 
132   auto NextBranch = Branches.begin();
133   auto EndBranch = Branches.end();
134 
135   // Branches with the same source line are enumerated individually
136   // (BranchIndex) as well as based on True/False pairs (PairIndex).
137   while (NextBranch != EndBranch) {
138     unsigned CurrentLine = NextBranch->LineStart;
139     unsigned PairIndex = 0;
140     unsigned BranchIndex = 0;
141 
142     while (NextBranch != EndBranch && CurrentLine == NextBranch->LineStart) {
143       if (!NextBranch->Folded) {
144         unsigned BC1 = NextBranch->ExecutionCount;
145         unsigned BC2 = NextBranch->FalseExecutionCount;
146         bool BranchNotExecuted = (BC1 == 0 && BC2 == 0);
147 
148         for (int I = 0; I < 2; I++, BranchIndex++) {
149           OS << "BRDA:" << CurrentLine << ',' << PairIndex << ','
150              << BranchIndex;
151           if (BranchNotExecuted)
152             OS << ',' << '-' << '\n';
153           else
154             OS << ',' << (I == 0 ? BC1 : BC2) << '\n';
155         }
156 
157         PairIndex++;
158       }
159       NextBranch++;
160     }
161   }
162 }
163 
renderLineSummary(raw_ostream & OS,const FileCoverageSummary & Summary)164 void renderLineSummary(raw_ostream &OS, const FileCoverageSummary &Summary) {
165   OS << "LF:" << Summary.LineCoverage.getNumLines() << '\n'
166      << "LH:" << Summary.LineCoverage.getCovered() << '\n';
167 }
168 
renderBranchSummary(raw_ostream & OS,const FileCoverageSummary & Summary)169 void renderBranchSummary(raw_ostream &OS, const FileCoverageSummary &Summary) {
170   OS << "BRF:" << Summary.BranchCoverage.getNumBranches() << '\n'
171      << "BFH:" << Summary.BranchCoverage.getCovered() << '\n';
172 }
173 
renderFile(raw_ostream & OS,const coverage::CoverageMapping & Coverage,const std::string & Filename,const FileCoverageSummary & FileReport,bool ExportSummaryOnly,bool SkipFunctions)174 void renderFile(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
175                 const std::string &Filename,
176                 const FileCoverageSummary &FileReport, bool ExportSummaryOnly,
177                 bool SkipFunctions) {
178   OS << "SF:" << Filename << '\n';
179 
180   if (!ExportSummaryOnly && !SkipFunctions) {
181     renderFunctions(OS, Coverage.getCoveredFunctions(Filename));
182   }
183   renderFunctionSummary(OS, FileReport);
184 
185   if (!ExportSummaryOnly) {
186     // Calculate and render detailed coverage information for given file.
187     auto FileCoverage = Coverage.getCoverageForFile(Filename);
188     renderLineExecutionCounts(OS, FileCoverage);
189     renderBranchExecutionCounts(OS, Coverage, FileCoverage);
190   }
191   renderBranchSummary(OS, FileReport);
192   renderLineSummary(OS, FileReport);
193 
194   OS << "end_of_record\n";
195 }
196 
renderFiles(raw_ostream & OS,const coverage::CoverageMapping & Coverage,ArrayRef<std::string> SourceFiles,ArrayRef<FileCoverageSummary> FileReports,bool ExportSummaryOnly,bool SkipFunctions)197 void renderFiles(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
198                  ArrayRef<std::string> SourceFiles,
199                  ArrayRef<FileCoverageSummary> FileReports,
200                  bool ExportSummaryOnly, bool SkipFunctions) {
201   for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I)
202     renderFile(OS, Coverage, SourceFiles[I], FileReports[I], ExportSummaryOnly,
203                SkipFunctions);
204 }
205 
206 } // end anonymous namespace
207 
renderRoot(const CoverageFilters & IgnoreFilters)208 void CoverageExporterLcov::renderRoot(const CoverageFilters &IgnoreFilters) {
209   std::vector<std::string> SourceFiles;
210   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
211     if (!IgnoreFilters.matchesFilename(SF))
212       SourceFiles.emplace_back(SF);
213   }
214   renderRoot(SourceFiles);
215 }
216 
renderRoot(ArrayRef<std::string> SourceFiles)217 void CoverageExporterLcov::renderRoot(ArrayRef<std::string> SourceFiles) {
218   FileCoverageSummary Totals = FileCoverageSummary("Totals");
219   auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
220                                                         SourceFiles, Options);
221   renderFiles(OS, Coverage, SourceFiles, FileReports, Options.ExportSummaryOnly,
222               Options.SkipFunctions);
223 }
224