| 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 | |