1//===-- Coverage.cpp - Debug info coverage metrics ------------------------===//
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#include "llvm-dwarfdump.h"
10#include "llvm/ADT/SetOperations.h"
11#include "llvm/BinaryFormat/Dwarf.h"
12#include "llvm/DebugInfo/DIContext.h"
13#include "llvm/DebugInfo/DWARF/DWARFAcceleratorTable.h"
14#include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h"
15#include "llvm/DebugInfo/DWARF/DWARFContext.h"
16#include "llvm/IR/CFG.h"
17#include "llvm/IR/DebugInfo.h"
18#include "llvm/IR/DebugInfoMetadata.h"
19#include "llvm/IR/DebugProgramInstruction.h"
20#include "llvm/IR/Instructions.h"
21#include "llvm/IR/Module.h"
22#include "llvm/IRReader/IRReader.h"
23#include "llvm/Object/ObjectFile.h"
24#include "llvm/Support/MemoryBuffer.h"
25#include "llvm/Support/SourceMgr.h"
26
27using namespace llvm;
28using namespace llvm::dwarf;
29using namespace llvm::object;
30
31/// Pair of file index and line number representing a source location.
32typedef std::pair<uint16_t, size_t> SourceLocation;
33/// Pair of subroutine name and variable name representing a local variable.
34typedef std::pair<std::string, std::string> BitcodeVarKey;
35/// Pair of file name and line number representing a source location.
36typedef std::pair<StringRef, uint32_t> BitcodeSourceLocation;
37/// Maps local variables found in the bitcode to a set of source locations.
38typedef std::map<BitcodeVarKey, std::optional<DenseSet<BitcodeSourceLocation>>>
39 BitcodeLineMap;
40
41/// Adds source locations to the line set that correspond to an address range.
42static void addLines(const DWARFDebugLine::LineTable *LineTable,
43 DenseSet<SourceLocation> &Lines, DWARFAddressRange Range) {
44 std::vector<uint32_t> Rows;
45 if (LineTable->lookupAddressRange(Address: {.Address: Range.LowPC, .SectionIndex: Range.SectionIndex},
46 Size: Range.HighPC - Range.LowPC, Result&: Rows)) {
47 for (const auto &RowI : Rows) {
48 const auto Row = LineTable->Rows[RowI];
49 // Lookup can return addresses below the LowPC - filter these out.
50 if (Row.Address.Address < Range.LowPC)
51 continue;
52
53 if (Row.Line) // Ignore zero lines.
54 Lines.insert(V: {Row.File, Row.Line});
55 }
56 }
57}
58
59/// Converts the file index of each line in the set to use our own internal
60/// file index. This is required for a reliable comparison as the DWARF index
61/// may differ across compilations.
62static DenseSet<SourceLocation>
63convertFileIndices(DenseSet<SourceLocation> Lines,
64 const DWARFDebugLine::LineTable *const LineTable,
65 DenseMap<uint16_t, uint16_t> &FileIndexMap,
66 StringMap<std::optional<uint16_t>> &FileNameMap) {
67 DenseSet<SourceLocation> ResultLines;
68 for (const auto &L : Lines) {
69 uint16_t Index;
70 const auto IndexIt = FileIndexMap.find(Val: L.first);
71 if (IndexIt != FileIndexMap.end()) {
72 Index = IndexIt->second;
73 } else {
74 std::string Name;
75 [[maybe_unused]] bool ValidIndex = LineTable->getFileNameByIndex(
76 FileIndex: L.first, CompDir: "", Kind: DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath,
77 Result&: Name);
78 assert(ValidIndex && "File index was not valid for its own line table");
79
80 auto NameIt = FileNameMap.find(Key: Name);
81 if (NameIt != FileNameMap.end() && NameIt->second) {
82 Index = *NameIt->second;
83 } else {
84 Index = FileNameMap.size();
85 FileNameMap.insert(KV: {Name, Index});
86 }
87
88 FileIndexMap.insert(KV: {L.first, Index});
89 }
90
91 ResultLines.insert(V: {Index, L.second});
92 }
93
94 return ResultLines;
95}
96
97/// Returns the set of source lines covered by a variable's debug information,
98/// computed by intersecting the variable's location ranges and the containing
99/// scope's address ranges.
100static DenseSet<SourceLocation>
101computeVariableCoverage(DWARFDie VariableDIE,
102 const DWARFDebugLine::LineTable *const LineTable,
103 DenseMap<uint16_t, uint16_t> &FileIndexMap,
104 StringMap<std::optional<uint16_t>> &FileNameMap,
105 BitcodeLineMap::value_type *DefinedLines) {
106 // The optionals below will be empty if no address ranges were found, and
107 // present (but containing an empty set) if ranges were found but contained no
108 // source locations, in order to distinguish the two cases.
109
110 auto Locations = VariableDIE.getLocations(Attr: DW_AT_location);
111 std::optional<DenseSet<SourceLocation>> Lines;
112 if (Locations) {
113 for (const auto &L : Locations.get()) {
114 if (L.Range) {
115 if (!Lines)
116 Lines = DenseSet<SourceLocation>();
117 addLines(LineTable, Lines&: *Lines, Range: L.Range.value());
118 }
119 }
120 } else {
121 // If the variable is optimized out and has no DW_AT_location, return an
122 // empty set instead of falling back to the parent scope's address ranges.
123 consumeError(Err: Locations.takeError());
124 return {};
125 }
126
127 // DW_AT_location attribute may contain overly broad address ranges, or none
128 // at all, so we also consider the parent scope's address ranges if present.
129 auto ParentRanges = VariableDIE.getParent().getAddressRanges();
130 std::optional<DenseSet<SourceLocation>> ParentLines;
131 if (ParentRanges) {
132 ParentLines = DenseSet<SourceLocation>();
133 for (const auto &R : ParentRanges.get())
134 addLines(LineTable, Lines&: *ParentLines, Range: R);
135 } else {
136 consumeError(Err: ParentRanges.takeError());
137 }
138
139 if (!Lines && ParentLines)
140 Lines = std::move(ParentLines);
141 else if (ParentLines)
142 set_intersect(S1&: *Lines, S2: *ParentLines);
143
144 auto ResultLines =
145 convertFileIndices(Lines: Lines.value_or(u: DenseSet<SourceLocation>()), LineTable,
146 FileIndexMap, FileNameMap);
147
148 if (DefinedLines) {
149 // Remove any lines where the variable does not have a defined value.
150 auto &DL = DefinedLines->second;
151 if (DL) {
152 DenseSet<SourceLocation> IndexLines;
153 for (const auto &L : *DL) {
154 auto NameIt = FileNameMap.find(Key: L.first);
155 if (NameIt != FileNameMap.end()) {
156 if (NameIt->second)
157 IndexLines.insert(V: {*NameIt->second, L.second});
158 } else {
159 // LineTable::getFileNameByIndex can return absolute paths even when
160 // relative paths are requested, so search for keys that end with this
161 // path as well.
162 for (const auto &NameEntry : FileNameMap) {
163 auto Name = NameEntry.first();
164 if (Name.find(Str: L.first, From: Name.size() - L.first.size()) !=
165 std::string_view::npos) {
166 FileNameMap.insert(KV: {L.first, NameEntry.second});
167 IndexLines.insert(V: {*NameEntry.second, L.second});
168 }
169 }
170 if (FileNameMap.find(Key: L.first) == FileNameMap.end()) {
171 assert(0 && "Files found in bitcode but not in DWARF");
172 FileNameMap.insert(KV: {L.first, std::nullopt});
173 }
174 }
175 }
176 if (!Lines)
177 assert(0 && "Source lines found in bitcode but not in DWARF");
178 else
179 set_intersect(S1&: ResultLines, S2: IndexLines);
180 }
181 }
182
183 return ResultLines;
184}
185
186/// Adds source locations to the line set that are within an inlined subroutine.
187static void getInlinedLines(DWARFDie SubroutineDIE,
188 DenseSet<SourceLocation> &Lines,
189 const DWARFDebugLine::LineTable *const LineTable) {
190 for (const auto &ChildDIE : SubroutineDIE.children()) {
191 if (ChildDIE.getTag() == DW_TAG_inlined_subroutine) {
192 auto Ranges = ChildDIE.getAddressRanges();
193 if (Ranges) {
194 for (const auto &R : Ranges.get())
195 addLines(LineTable, Lines, Range: R);
196 } else {
197 consumeError(Err: Ranges.takeError());
198 }
199 } else {
200 getInlinedLines(SubroutineDIE: ChildDIE, Lines, LineTable);
201 }
202 }
203}
204
205/// Returns the set of source lines present in the line table for a subroutine.
206static DenseSet<SourceLocation>
207computeSubroutineCoverage(DWARFDie SubroutineDIE,
208 const DWARFDebugLine::LineTable *const LineTable,
209 DenseMap<uint16_t, uint16_t> &FileIndexMap,
210 StringMap<std::optional<uint16_t>> &FileNameMap) {
211 auto Ranges = SubroutineDIE.getAddressRanges();
212 DenseSet<SourceLocation> Lines;
213 if (Ranges) {
214 for (const auto &R : Ranges.get())
215 addLines(LineTable, Lines, Range: R);
216 } else {
217 consumeError(Err: Ranges.takeError());
218 }
219
220 // Exclude lines from any subroutines inlined into this one.
221 DenseSet<SourceLocation> InlinedLines;
222 getInlinedLines(SubroutineDIE, Lines&: InlinedLines, LineTable);
223 set_subtract(S1&: Lines, S2: InlinedLines);
224
225 return convertFileIndices(Lines, LineTable, FileIndexMap, FileNameMap);
226}
227
228static const SmallVector<DWARFDie> getParentSubroutines(DWARFDie DIE) {
229 SmallVector<DWARFDie> Parents;
230 DWARFDie Parent = DIE;
231 do {
232 if (Parent.getTag() == DW_TAG_subprogram) {
233 Parents.push_back(Elt: Parent);
234 break;
235 }
236 if (Parent.getTag() == DW_TAG_inlined_subroutine)
237 Parents.push_back(Elt: Parent);
238 } while ((Parent = Parent.getParent()));
239 return Parents;
240}
241
242static bool isInScope(MDNode *Scope, const DebugLoc &Loc) {
243 MDNode *Parent = Loc.getScope();
244 while (Parent != Scope) {
245 auto *S = dyn_cast_if_present<DIScope>(Val: Parent);
246 if (!S)
247 return false;
248 Parent = S->getScope();
249 }
250 return true;
251}
252
253/// Determines whether an instruction stores to a location. For the purposes of
254/// this analysis, we consider any call-like instruction with the location as an
255/// argument to be a store to it.
256static bool isStoreToLocation(const DataLayout &DL, Instruction &I,
257 Value *Loc) {
258 std::optional<at::AssignmentInfo> Info;
259 if (StoreInst *SI = dyn_cast<StoreInst>(Val: &I)) {
260 if (SI->getPointerOperand() == Loc)
261 return true;
262 Info = at::getAssignmentInfo(DL, SI);
263 } else if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(Val: &I)) {
264 if (MI->getDest() == Loc)
265 return true;
266 Info = at::getAssignmentInfo(DL, I: MI);
267 } else if (CallBase *CI = dyn_cast<CallBase>(Val: &I)) {
268 return CI->hasArgument(V: Loc);
269 }
270 return Info && Info->Base == Loc;
271}
272
273typedef SmallDenseMap<BasicBlock *, Instruction *, 8> VarDefinitionMap;
274
275struct VarState {
276 DbgVariableRecord &DVR;
277 VarDefinitionMap Definitions;
278};
279
280/// Adds source locations to the line set for instructions in a basic block,
281/// starting with a specific instruction.
282static void addModuleLines(Instruction *I, VarState &Var,
283 DenseSet<std::pair<StringRef, uint32_t>> &Lines) {
284 auto *VarScope = Var.DVR.getVariable()->getScope();
285 do {
286 auto &Loc = I->getDebugLoc();
287 DIScope *Scope;
288 if (Loc && isInScope(Scope: VarScope, Loc) && Loc.getLine() &&
289 (Scope = dyn_cast_if_present<DIScope>(Val: Loc.getScope()))) {
290 Lines.insert(V: {Scope->getFilename(), Loc.getLine()});
291 }
292 } while ((I = I->getNextNode()));
293}
294
295/// Computes the defined lines of all variables in an IR module.
296static BitcodeLineMap processModule(Module *Mod) {
297 BitcodeLineMap Result;
298 std::vector<VarState> Vars;
299 for (auto &F : Mod->functions()) {
300 Vars.clear();
301 for (auto &BB : F) {
302 for (auto &I : BB) {
303 for (DbgVariableRecord &DVR : filterDbgVars(R: I.getDbgRecordRange())) {
304 if (DVR.isKillLocation()) {
305 assert(0 && "Variable in bitcode has been optimized out");
306 continue;
307 }
308 if (DVR.isDbgDeclare()) {
309 // For #dbg_declare, don't treat the variable as live until we find
310 // a store to it.
311 Vars.push_back(x: VarState{.DVR: DVR, .Definitions: VarDefinitionMap()});
312 } else if (DVR.isDbgValue()) {
313 // For #dbg_value, the variable is live immediately from this point.
314 if (DVR.getDebugLoc().getInlinedAt() != nullptr) {
315 assert(0 && "Variable in bitcode has been inlined");
316 continue;
317 }
318 auto Var = find_if(Range&: Vars, P: [&](auto &Var) {
319 return Var.DVR.getVariable() == DVR.getVariable();
320 });
321 if (Var != Vars.end())
322 // If a basic block contains multiple stores to a variable, use
323 // the earliest one by allowing the insertion to silently fail if
324 // the basic block is already in the map.
325 Var->Definitions.insert(KV: {&BB, &I});
326 else
327 Vars.push_back(x: VarState{.DVR: DVR, .Definitions: {{&BB, &I}}});
328 }
329 }
330 }
331 }
332
333 for (auto &BB : F) {
334 for (auto &I : BB) {
335 for (auto &Var : Vars) {
336 if (isStoreToLocation(DL: Mod->getDataLayout(), I, Loc: Var.DVR.getValue())) {
337 // The variable is live from the instruction after the store. As
338 // above, the earliest store in this basic block will be used.
339 Var.Definitions.insert(KV: {&BB, I.getNextNode()});
340 }
341 }
342 }
343 }
344
345 for (auto &Var : Vars) {
346 SmallPtrSet<BasicBlock *, 8> Visited;
347 DenseSet<std::pair<StringRef, uint32_t>> Lines;
348
349 // Visit all basic blocks that are reachable from the entry block without
350 // going through a block that stores to the variable.
351 SmallVector<BasicBlock *> BlocksToVisit{&F.getEntryBlock()};
352 while (!BlocksToVisit.empty()) {
353 BasicBlock *BB = BlocksToVisit.pop_back_val();
354 if (!Visited.insert(Ptr: BB).second)
355 continue;
356
357 auto I = Var.Definitions.find(Val: BB);
358 if (I != Var.Definitions.end()) {
359 // Block contains a definition: add all lines after it to the set
360 if (I->second != nullptr)
361 addModuleLines(I: I->second, Var, Lines);
362 } else {
363 // Block does not contain a definition: visit its successors
364 auto S = successors(BB);
365 BlocksToVisit.append(in_start: S.begin(), in_end: S.end());
366 }
367 }
368
369 // All unvisited basic blocks must only be reachable by going through a
370 // block that stores to the variable, so add lines to the set for all of
371 // their instructions.
372 for (auto &BB : F)
373 if (!Visited.count(Ptr: &BB))
374 addModuleLines(I: &*BB.begin(), Var, Lines);
375
376 BitcodeVarKey Key(F.getName(), Var.DVR.getVariable()->getName());
377 Result.emplace(args&: Key, args&: Lines);
378 }
379 }
380 return Result;
381}
382
383struct VarKey {
384 const char *const SubprogramName;
385 const char *const Name;
386 std::string DeclFile;
387 uint64_t DeclLine;
388
389 bool operator==(const VarKey &Other) const {
390 return DeclLine == Other.DeclLine &&
391 !strcmp(s1: SubprogramName, s2: Other.SubprogramName) &&
392 !strcmp(s1: Name, s2: Other.Name) && !DeclFile.compare(str: Other.DeclFile);
393 }
394
395 bool operator<(const VarKey &Other) const {
396 int A = strcmp(s1: SubprogramName, s2: Other.SubprogramName);
397 if (A)
398 return A < 0;
399 int B = strcmp(s1: Name, s2: Other.Name);
400 if (B)
401 return B < 0;
402 int C = DeclFile.compare(str: Other.DeclFile);
403 if (C)
404 return C < 0;
405 return DeclLine < Other.DeclLine;
406 }
407};
408
409struct VarCoverage {
410 SmallVector<DWARFDie> Parents;
411 size_t Cov;
412 size_t BaselineCov;
413 size_t LTCov;
414 size_t Missing;
415 size_t Instances;
416 bool MissingBaseline;
417};
418
419typedef std::multimap<VarKey, VarCoverage, std::less<>> VarMap;
420typedef std::map<VarKey, DenseSet<SourceLocation>, std::less<>> BaselineVarMap;
421
422static std::optional<const VarKey> getVarKey(DWARFDie VariableDIE,
423 DWARFDie SubroutineDIE) {
424 const auto *const VariableName = VariableDIE.getName(Kind: DINameKind::LinkageName);
425 const auto DeclFile = VariableDIE.getDeclFile(
426 Kind: DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath);
427 const auto *const SubroutineName =
428 SubroutineDIE.getName(Kind: DINameKind::LinkageName);
429 if (!VariableName || !SubroutineName)
430 return std::nullopt;
431 return VarKey{.SubprogramName: SubroutineName, .Name: VariableName, .DeclFile: DeclFile,
432 .DeclLine: VariableDIE.getDeclLine()};
433}
434
435static void displayParents(SmallVector<DWARFDie> Parents, raw_ostream &OS) {
436 bool First = true;
437 for (const auto Parent : Parents) {
438 if (auto FormValue = Parent.find(Attr: DW_AT_call_file)) {
439 if (auto OptString = FormValue->getAsFile(
440 Kind: DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath)) {
441 if (First)
442 First = false;
443 else
444 OS << ", ";
445 OS << *OptString << ":" << toUnsigned(V: Parent.find(Attr: DW_AT_call_line), Default: 0);
446 }
447 }
448 }
449}
450
451static void displayVariableCoverage(const VarKey &Key, const VarCoverage &Var,
452 bool CombineInstances, raw_ostream &OS) {
453 WithColor(OS, HighlightColor::String) << Key.SubprogramName;
454 OS << "\t";
455 if (CombineInstances)
456 OS << Var.Instances;
457 else if (Var.Parents.size())
458 // FIXME: This may overflow the terminal if the inlining chain is large.
459 displayParents(Parents: Var.Parents, OS);
460 OS << "\t";
461 WithColor(OS, HighlightColor::String) << Key.Name;
462 OS << "\t";
463 if (!Key.DeclFile.empty())
464 OS << Key.DeclFile << ":" << Key.DeclLine;
465 OS << "\t" << format(Fmt: "%.3g", Vals: ((float)Var.Cov / Var.Instances));
466 if (Var.BaselineCov)
467 OS << "\t" << format(Fmt: "%.3g", Vals: ((float)Var.BaselineCov / Var.Instances))
468 << "\t" << format(Fmt: "%.3g", Vals: ((float)Var.Cov / Var.BaselineCov)) << "\t"
469 << format(Fmt: "%.3g", Vals: ((float)Var.LTCov / Var.Instances)) << "\t"
470 << format(Fmt: "%.3g", Vals: ((float)Var.LTCov / Var.BaselineCov));
471 OS << "\n";
472 if (Var.MissingBaseline)
473 WithColor(errs(), HighlightColor::Warning).warning()
474 << "DIE not found in baseline\n";
475 if (Var.Missing)
476 WithColor(errs(), HighlightColor::Warning).warning()
477 << Var.Missing << " lines not found in baseline\n";
478}
479
480bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
481 ObjectFile *BaselineObj,
482 DWARFContext *BaselineCtx,
483 StringRef BitcodeFile,
484 bool CombineInstances, raw_ostream &OS) {
485 BitcodeLineMap LM;
486 LLVMContext Context;
487 if (!BitcodeFile.empty()) {
488 SMDiagnostic Err;
489 std::unique_ptr<Module> Mod = parseIRFile(Filename: BitcodeFile, Err, Context);
490 if (!Err.getMessage().empty())
491 Err.print(ProgName: "llvm-dwarfdump", S&: OS);
492 else
493 LM = processModule(Mod: Mod.get());
494 }
495
496 BaselineVarMap BaselineVars;
497 StringMap<std::optional<uint16_t>> FileNameMap;
498
499 if (BaselineCtx) {
500 for (const auto &U : BaselineCtx->info_section_units()) {
501 const auto *const LT = BaselineCtx->getLineTableForUnit(U: U.get());
502 DenseMap<uint16_t, uint16_t> FileIndexMap;
503 for (const auto &Entry : U->dies()) {
504 DWARFDie VariableDIE = {U.get(), &Entry};
505 if (VariableDIE.getTag() != DW_TAG_variable &&
506 VariableDIE.getTag() != DW_TAG_formal_parameter)
507 continue;
508
509 const auto Parents = getParentSubroutines(DIE: VariableDIE);
510 if (!Parents.size())
511 continue;
512 const auto SubroutineDIE = Parents.front();
513 auto Key = getVarKey(VariableDIE, SubroutineDIE);
514 if (!Key)
515 continue;
516
517 const auto DefinedLines = LM.find(x: {Key->SubprogramName, Key->Name});
518 auto Cov = computeVariableCoverage(
519 VariableDIE, LineTable: LT, FileIndexMap, FileNameMap,
520 DefinedLines: DefinedLines != LM.end() ? &*DefinedLines : nullptr);
521 const auto SubroutineCov = computeSubroutineCoverage(
522 SubroutineDIE, LineTable: LT, FileIndexMap, FileNameMap);
523 set_intersect(S1&: Cov, S2: SubroutineCov);
524
525 auto Result = BaselineVars.insert(x: {*Key, Cov});
526 if (!Result.second)
527 Result.first->second.insert_range(R&: Cov);
528 }
529 }
530 }
531
532 VarMap Vars;
533
534 for (const auto &U : DICtx.info_section_units()) {
535 const auto *const LT = DICtx.getLineTableForUnit(U: U.get());
536 DenseMap<uint16_t, uint16_t> FileIndexMap;
537 for (const auto &Entry : U->dies()) {
538 DWARFDie VariableDIE = {U.get(), &Entry};
539 if (VariableDIE.getTag() != DW_TAG_variable &&
540 VariableDIE.getTag() != DW_TAG_formal_parameter)
541 continue;
542
543 const auto Parents = getParentSubroutines(DIE: VariableDIE);
544 if (!Parents.size())
545 continue;
546 const auto SubroutineDIE = Parents.front();
547 auto Key = getVarKey(VariableDIE, SubroutineDIE);
548 if (!Key)
549 continue;
550
551 const auto DefinedLines = LM.find(x: {Key->SubprogramName, Key->Name});
552 auto Cov = computeVariableCoverage(
553 VariableDIE, LineTable: LT, FileIndexMap, FileNameMap,
554 DefinedLines: DefinedLines != LM.end() ? &*DefinedLines : nullptr);
555 const auto SubroutineCov = computeSubroutineCoverage(
556 SubroutineDIE, LineTable: LT, FileIndexMap, FileNameMap);
557 set_intersect(S1&: Cov, S2: SubroutineCov);
558
559 VarCoverage VarCov = {.Parents: Parents, .Cov: Cov.size(), .BaselineCov: 0, .LTCov: 0, .Missing: 0, .Instances: 1, .MissingBaseline: false};
560
561 if (BaselineCtx) {
562 BaselineVarMap::iterator Var = BaselineVars.find(x: *Key);
563
564 if (Var != BaselineVars.end()) {
565 const auto BCov = Var->second;
566 VarCov.BaselineCov = BCov.size();
567
568 for (const auto &L : Cov)
569 VarCov.Missing += (1 - BCov.count(V: L));
570
571 for (const auto &L : BCov)
572 VarCov.LTCov += SubroutineCov.count(V: L);
573 } else {
574 VarCov.MissingBaseline = true;
575 }
576 }
577
578 Vars.insert(x: {*Key, VarCov});
579 }
580 }
581
582 std::pair<VarMap::iterator, VarMap::iterator> Range;
583
584 OS << "\nVariable coverage statistics:\nFunction\t"
585 << (CombineInstances ? "InstanceCount" : "InlChain")
586 << "\tVariable\tDecl\tLinesCovered";
587 if (BaselineCtx)
588 OS << "\tBaseline\tCoveredRatio\tLinesPresent\tLinesPresentRatio";
589 OS << "\n";
590
591 if (CombineInstances) {
592 for (auto FirstVar = Vars.begin(); FirstVar != Vars.end();
593 FirstVar = Range.second) {
594 Range = Vars.equal_range(x: FirstVar->first);
595 VarCoverage CombinedCov = {.Parents: {}, .Cov: 0, .BaselineCov: 0, .LTCov: 0, .Missing: 0, .Instances: 0, .MissingBaseline: false};
596 for (auto Var = Range.first; Var != Range.second; ++Var) {
597 ++CombinedCov.Instances;
598 CombinedCov.Cov += Var->second.Cov;
599 CombinedCov.BaselineCov += Var->second.BaselineCov;
600 CombinedCov.LTCov += Var->second.LTCov;
601 CombinedCov.Missing += Var->second.Missing;
602 CombinedCov.MissingBaseline |= Var->second.MissingBaseline;
603 }
604 displayVariableCoverage(Key: FirstVar->first, Var: CombinedCov, CombineInstances: true, OS);
605 }
606 } else {
607 for (auto Var : Vars)
608 displayVariableCoverage(Key: Var.first, Var: Var.second, CombineInstances: false, OS);
609 }
610
611 return true;
612}
613