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 SmallVector<std::pair<StringRef, std::optional<uint16_t>>> NewEntries;
163 for (const auto &NameEntry : FileNameMap) {
164 auto Name = NameEntry.first();
165 if (Name.find(Str: L.first, From: Name.size() - L.first.size()) !=
166 std::string_view::npos) {
167 NewEntries.push_back(Elt: {L.first, NameEntry.second});
168 IndexLines.insert(V: {*NameEntry.second, L.second});
169 }
170 }
171 for (const auto &E : NewEntries)
172 FileNameMap.insert(KV: E);
173 if (FileNameMap.find(Key: L.first) == FileNameMap.end()) {
174 assert(0 && "Files found in bitcode but not in DWARF");
175 FileNameMap.insert(KV: {L.first, std::nullopt});
176 }
177 }
178 }
179 if (!Lines)
180 assert(0 && "Source lines found in bitcode but not in DWARF");
181 else
182 set_intersect(S1&: ResultLines, S2: IndexLines);
183 }
184 }
185
186 return ResultLines;
187}
188
189/// Adds source locations to the line set that are within an inlined subroutine.
190static void getInlinedLines(DWARFDie SubroutineDIE,
191 DenseSet<SourceLocation> &Lines,
192 const DWARFDebugLine::LineTable *const LineTable) {
193 for (const auto &ChildDIE : SubroutineDIE.children()) {
194 if (ChildDIE.getTag() == DW_TAG_inlined_subroutine) {
195 auto Ranges = ChildDIE.getAddressRanges();
196 if (Ranges) {
197 for (const auto &R : Ranges.get())
198 addLines(LineTable, Lines, Range: R);
199 } else {
200 consumeError(Err: Ranges.takeError());
201 }
202 } else {
203 getInlinedLines(SubroutineDIE: ChildDIE, Lines, LineTable);
204 }
205 }
206}
207
208/// Returns the set of source lines present in the line table for a subroutine.
209static DenseSet<SourceLocation>
210computeSubroutineCoverage(DWARFDie SubroutineDIE,
211 const DWARFDebugLine::LineTable *const LineTable,
212 DenseMap<uint16_t, uint16_t> &FileIndexMap,
213 StringMap<std::optional<uint16_t>> &FileNameMap) {
214 auto Ranges = SubroutineDIE.getAddressRanges();
215 DenseSet<SourceLocation> Lines;
216 if (Ranges) {
217 for (const auto &R : Ranges.get())
218 addLines(LineTable, Lines, Range: R);
219 } else {
220 consumeError(Err: Ranges.takeError());
221 }
222
223 // Exclude lines from any subroutines inlined into this one.
224 DenseSet<SourceLocation> InlinedLines;
225 getInlinedLines(SubroutineDIE, Lines&: InlinedLines, LineTable);
226 set_subtract(S1&: Lines, S2: InlinedLines);
227
228 return convertFileIndices(Lines, LineTable, FileIndexMap, FileNameMap);
229}
230
231static const SmallVector<DWARFDie> getParentSubroutines(DWARFDie DIE) {
232 SmallVector<DWARFDie> Parents;
233 DWARFDie Parent = DIE;
234 do {
235 if (Parent.getTag() == DW_TAG_subprogram) {
236 Parents.push_back(Elt: Parent);
237 break;
238 }
239 if (Parent.getTag() == DW_TAG_inlined_subroutine)
240 Parents.push_back(Elt: Parent);
241 } while ((Parent = Parent.getParent()));
242 return Parents;
243}
244
245static bool isInScope(MDNode *Scope, const DebugLoc &Loc) {
246 MDNode *Parent = Loc.getScope();
247 while (Parent != Scope) {
248 auto *S = dyn_cast_if_present<DIScope>(Val: Parent);
249 if (!S)
250 return false;
251 Parent = S->getScope();
252 }
253 return true;
254}
255
256/// Determines whether an instruction stores to a location. For the purposes of
257/// this analysis, we consider any call-like instruction with the location as an
258/// argument to be a store to it.
259static bool isStoreToLocation(const DataLayout &DL, Instruction &I,
260 Value *Loc) {
261 std::optional<at::AssignmentInfo> Info;
262 if (StoreInst *SI = dyn_cast<StoreInst>(Val: &I)) {
263 if (SI->getPointerOperand() == Loc)
264 return true;
265 Info = at::getAssignmentInfo(DL, SI);
266 } else if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(Val: &I)) {
267 if (MI->getDest() == Loc)
268 return true;
269 Info = at::getAssignmentInfo(DL, I: MI);
270 } else if (CallBase *CI = dyn_cast<CallBase>(Val: &I)) {
271 return CI->hasArgument(V: Loc);
272 }
273 return Info && Info->Base == Loc;
274}
275
276typedef SmallDenseMap<BasicBlock *, Instruction *, 8> VarDefinitionMap;
277
278struct VarState {
279 DbgVariableRecord &DVR;
280 VarDefinitionMap Definitions;
281};
282
283/// Adds source locations to the line set for instructions in a basic block,
284/// starting with a specific instruction.
285static void addModuleLines(Instruction *I, VarState &Var,
286 DenseSet<std::pair<StringRef, uint32_t>> &Lines) {
287 auto *VarScope = Var.DVR.getVariable()->getScope();
288 do {
289 auto &Loc = I->getDebugLoc();
290 DIScope *Scope;
291 if (Loc && isInScope(Scope: VarScope, Loc) && Loc.getLine() &&
292 (Scope = dyn_cast_if_present<DIScope>(Val: Loc.getScope()))) {
293 Lines.insert(V: {Scope->getFilename(), Loc.getLine()});
294 }
295 } while ((I = I->getNextNode()));
296}
297
298/// Computes the defined lines of all variables in an IR module.
299static BitcodeLineMap processModule(Module *Mod, bool MaybeUndefined) {
300 BitcodeLineMap Result;
301 std::vector<VarState> Vars;
302 for (auto &F : Mod->functions()) {
303 Vars.clear();
304 for (auto &BB : F) {
305 for (auto &I : BB) {
306 for (DbgVariableRecord &DVR : filterDbgVars(R: I.getDbgRecordRange())) {
307 if (DVR.isKillLocation())
308 continue;
309 if (DVR.isDbgDeclare()) {
310 // For #dbg_declare, don't treat the variable as live until we find
311 // a store to it.
312 Vars.push_back(x: VarState{.DVR: DVR, .Definitions: VarDefinitionMap()});
313 } else if (DVR.isDbgValue()) {
314 // For #dbg_value, the variable is live immediately from this point.
315 if (DVR.getDebugLoc().getInlinedAt() != nullptr) {
316 assert(0 && "Variable in bitcode has been inlined");
317 continue;
318 }
319 auto Var = find_if(Range&: Vars, P: [&](auto &Var) {
320 return Var.DVR.getVariable() == DVR.getVariable();
321 });
322 if (Var != Vars.end())
323 // If a basic block contains multiple stores to a variable, use
324 // the earliest one by allowing the insertion to silently fail if
325 // the basic block is already in the map.
326 Var->Definitions.insert(KV: {&BB, &I});
327 else
328 Vars.push_back(x: VarState{.DVR: DVR, .Definitions: {{&BB, &I}}});
329 }
330 }
331 }
332 }
333
334 for (auto &BB : F) {
335 for (auto &I : BB) {
336 for (auto &Var : Vars) {
337 if (isStoreToLocation(DL: Mod->getDataLayout(), I, Loc: Var.DVR.getValue())) {
338 // The variable is live from the instruction after the store. As
339 // above, the earliest store in this basic block will be used.
340 Var.Definitions.insert(KV: {&BB, I.getNextNode()});
341 }
342 }
343 }
344 }
345
346 for (auto &Var : Vars) {
347 SmallPtrSet<BasicBlock *, 8> Visited;
348 DenseSet<std::pair<StringRef, uint32_t>> Lines;
349
350 if (MaybeUndefined) {
351 // Visit all basic blocks that are reachable from a definition.
352 auto B = Var.Definitions.keys();
353 SmallVector<BasicBlock *> BlocksToVisit{B.begin(), B.end()};
354
355 while (!BlocksToVisit.empty()) {
356 BasicBlock *BB = BlocksToVisit.pop_back_val();
357 if (!Visited.insert(Ptr: BB).second)
358 continue;
359
360 auto S = successors(BB);
361 BlocksToVisit.append(in_start: S.begin(), in_end: S.end());
362 // Treat all successor blocks as live throughout.
363 for (auto *BB : S)
364 Var.Definitions.insert_or_assign(Key: BB, Val: &BB->front());
365 }
366
367 // Add lines to the set for all blocks in the definition map.
368 for (auto I : Var.Definitions)
369 if (I.second != nullptr)
370 addModuleLines(I: I.second, Var, Lines);
371 } else {
372 // Visit all basic blocks that are reachable from the entry block
373 // without going through a block that stores to the variable.
374 SmallVector<BasicBlock *> BlocksToVisit{&F.getEntryBlock()};
375
376 while (!BlocksToVisit.empty()) {
377 BasicBlock *BB = BlocksToVisit.pop_back_val();
378 if (!Visited.insert(Ptr: BB).second)
379 continue;
380
381 auto I = Var.Definitions.find(Val: BB);
382 if (I != Var.Definitions.end()) {
383 // Block contains a definition: add all lines after it to the set
384 if (I->second != nullptr)
385 addModuleLines(I: I->second, Var, Lines);
386 } else {
387 // Block does not contain a definition: visit its successors
388 auto S = successors(BB);
389 BlocksToVisit.append(in_start: S.begin(), in_end: S.end());
390 }
391 }
392
393 // All unvisited basic blocks must only be reachable by going through a
394 // block that stores to the variable, so add lines to the set for all of
395 // their instructions.
396 for (auto &BB : F)
397 if (!Visited.count(Ptr: &BB))
398 addModuleLines(I: &BB.front(), Var, Lines);
399 }
400
401 BitcodeVarKey Key(F.getName(), Var.DVR.getVariable()->getName());
402 Result.emplace(args&: Key, args&: Lines);
403 }
404 }
405 return Result;
406}
407
408struct VarKey {
409 const char *const SubprogramName;
410 const char *const Name;
411 std::string DeclFile;
412 uint64_t DeclLine;
413
414 bool operator==(const VarKey &Other) const {
415 return DeclLine == Other.DeclLine &&
416 !strcmp(s1: SubprogramName, s2: Other.SubprogramName) &&
417 !strcmp(s1: Name, s2: Other.Name) && !DeclFile.compare(str: Other.DeclFile);
418 }
419
420 bool operator<(const VarKey &Other) const {
421 int A = strcmp(s1: SubprogramName, s2: Other.SubprogramName);
422 if (A)
423 return A < 0;
424 int B = strcmp(s1: Name, s2: Other.Name);
425 if (B)
426 return B < 0;
427 int C = DeclFile.compare(str: Other.DeclFile);
428 if (C)
429 return C < 0;
430 return DeclLine < Other.DeclLine;
431 }
432};
433
434struct VarCoverage {
435 SmallVector<DWARFDie> Parents;
436 size_t Cov;
437 size_t BaselineCov;
438 size_t LTCov;
439 size_t Missing;
440 size_t Instances;
441 bool MissingBaseline;
442};
443
444typedef std::multimap<VarKey, VarCoverage, std::less<>> VarMap;
445typedef std::map<VarKey, DenseSet<SourceLocation>, std::less<>> BaselineVarMap;
446
447static std::optional<const VarKey> getVarKey(DWARFDie VariableDIE,
448 DWARFDie SubroutineDIE) {
449 const auto *const VariableName = VariableDIE.getName(Kind: DINameKind::LinkageName);
450 const auto DeclFile = VariableDIE.getDeclFile(
451 Kind: DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath);
452 const auto *const SubroutineName =
453 SubroutineDIE.getName(Kind: DINameKind::LinkageName);
454 if (!VariableName || !SubroutineName)
455 return std::nullopt;
456 return VarKey{.SubprogramName: SubroutineName, .Name: VariableName, .DeclFile: DeclFile,
457 .DeclLine: VariableDIE.getDeclLine()};
458}
459
460static void displayParents(SmallVector<DWARFDie> Parents, raw_ostream &OS) {
461 bool First = true;
462 for (const auto Parent : Parents) {
463 if (auto FormValue = Parent.find(Attr: DW_AT_call_file)) {
464 if (auto OptString = FormValue->getAsFile(
465 Kind: DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath)) {
466 if (First)
467 First = false;
468 else
469 OS << ", ";
470 OS << *OptString << ":" << toUnsigned(V: Parent.find(Attr: DW_AT_call_line), Default: 0);
471 }
472 }
473 }
474}
475
476static void displayVariableCoverage(const VarKey &Key, const VarCoverage &Var,
477 bool CombineInstances, raw_ostream &OS) {
478 WithColor(OS, HighlightColor::String) << Key.SubprogramName;
479 OS << "\t";
480 if (CombineInstances)
481 OS << Var.Instances;
482 else if (Var.Parents.size())
483 // FIXME: This may overflow the terminal if the inlining chain is large.
484 displayParents(Parents: Var.Parents, OS);
485 OS << "\t";
486 WithColor(OS, HighlightColor::String) << Key.Name;
487 OS << "\t";
488 if (!Key.DeclFile.empty())
489 OS << Key.DeclFile << ":" << Key.DeclLine;
490 OS << "\t" << format(Fmt: "%.3g", Vals: ((float)Var.Cov / Var.Instances));
491 if (Var.BaselineCov)
492 OS << "\t" << format(Fmt: "%.3g", Vals: ((float)Var.BaselineCov / Var.Instances))
493 << "\t" << format(Fmt: "%.3g", Vals: ((float)Var.Cov / Var.BaselineCov)) << "\t"
494 << format(Fmt: "%.3g", Vals: ((float)Var.LTCov / Var.Instances)) << "\t"
495 << format(Fmt: "%.3g", Vals: ((float)Var.LTCov / Var.BaselineCov));
496 OS << "\n";
497 if (Var.MissingBaseline)
498 WithColor(errs(), HighlightColor::Warning).warning()
499 << "DIE not found in baseline\n";
500 if (Var.Missing)
501 WithColor(errs(), HighlightColor::Warning).warning()
502 << Var.Missing << " lines not found in baseline\n";
503}
504
505bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
506 ObjectFile *BaselineObj,
507 DWARFContext *BaselineCtx,
508 StringRef BitcodeFile, bool MaybeUndefined,
509 bool CombineInstances, raw_ostream &OS) {
510 BitcodeLineMap LM;
511 LLVMContext Context;
512 if (!BitcodeFile.empty()) {
513 SMDiagnostic Err;
514 std::unique_ptr<Module> Mod = parseIRFile(Filename: BitcodeFile, Err, Context);
515 if (!Err.getMessage().empty())
516 Err.print(ProgName: "llvm-dwarfdump", S&: OS);
517 else
518 LM = processModule(Mod: Mod.get(), MaybeUndefined);
519 }
520
521 BaselineVarMap BaselineVars;
522 StringMap<std::optional<uint16_t>> FileNameMap;
523
524 if (BaselineCtx) {
525 for (const auto &U : BaselineCtx->info_section_units()) {
526 const auto *const LT = BaselineCtx->getLineTableForUnit(U: U.get());
527 DenseMap<uint16_t, uint16_t> FileIndexMap;
528 for (const auto &Entry : U->dies()) {
529 DWARFDie VariableDIE = {U.get(), &Entry};
530 if (VariableDIE.getTag() != DW_TAG_variable &&
531 VariableDIE.getTag() != DW_TAG_formal_parameter)
532 continue;
533
534 const auto Parents = getParentSubroutines(DIE: VariableDIE);
535 if (!Parents.size())
536 continue;
537 const auto SubroutineDIE = Parents.front();
538 auto Key = getVarKey(VariableDIE, SubroutineDIE);
539 if (!Key)
540 continue;
541
542 const auto DefinedLines = LM.find(x: {Key->SubprogramName, Key->Name});
543 auto Cov = computeVariableCoverage(
544 VariableDIE, LineTable: LT, FileIndexMap, FileNameMap,
545 DefinedLines: DefinedLines != LM.end() ? &*DefinedLines : nullptr);
546 const auto SubroutineCov = computeSubroutineCoverage(
547 SubroutineDIE, LineTable: LT, FileIndexMap, FileNameMap);
548 set_intersect(S1&: Cov, S2: SubroutineCov);
549
550 auto Result = BaselineVars.insert(x: {*Key, Cov});
551 if (!Result.second)
552 Result.first->second.insert_range(R&: Cov);
553 }
554 }
555 }
556
557 VarMap Vars;
558
559 for (const auto &U : DICtx.info_section_units()) {
560 const auto *const LT = DICtx.getLineTableForUnit(U: U.get());
561 DenseMap<uint16_t, uint16_t> FileIndexMap;
562 for (const auto &Entry : U->dies()) {
563 DWARFDie VariableDIE = {U.get(), &Entry};
564 if (VariableDIE.getTag() != DW_TAG_variable &&
565 VariableDIE.getTag() != DW_TAG_formal_parameter)
566 continue;
567
568 const auto Parents = getParentSubroutines(DIE: VariableDIE);
569 if (!Parents.size())
570 continue;
571 const auto SubroutineDIE = Parents.front();
572 auto Key = getVarKey(VariableDIE, SubroutineDIE);
573 if (!Key)
574 continue;
575
576 const auto DefinedLines = LM.find(x: {Key->SubprogramName, Key->Name});
577 auto Cov = computeVariableCoverage(
578 VariableDIE, LineTable: LT, FileIndexMap, FileNameMap,
579 DefinedLines: DefinedLines != LM.end() ? &*DefinedLines : nullptr);
580 const auto SubroutineCov = computeSubroutineCoverage(
581 SubroutineDIE, LineTable: LT, FileIndexMap, FileNameMap);
582 set_intersect(S1&: Cov, S2: SubroutineCov);
583
584 VarCoverage VarCov = {.Parents: Parents, .Cov: Cov.size(), .BaselineCov: 0, .LTCov: 0, .Missing: 0, .Instances: 1, .MissingBaseline: false};
585
586 if (BaselineCtx) {
587 BaselineVarMap::iterator Var = BaselineVars.find(x: *Key);
588
589 if (Var != BaselineVars.end()) {
590 const auto BCov = Var->second;
591 VarCov.BaselineCov = BCov.size();
592
593 for (const auto &L : Cov)
594 VarCov.Missing += (1 - BCov.count(V: L));
595
596 for (const auto &L : BCov)
597 VarCov.LTCov += SubroutineCov.count(V: L);
598 } else {
599 VarCov.MissingBaseline = true;
600 }
601 }
602
603 Vars.insert(x: {*Key, VarCov});
604 }
605 }
606
607 for (auto V : BaselineVars)
608 if (!Vars.count(x: V.first))
609 Vars.insert(x: {V.first, {.Parents: {}, .Cov: 0, .BaselineCov: V.second.size(), .LTCov: 0, .Missing: 0, .Instances: 1, .MissingBaseline: false}});
610
611 std::pair<VarMap::iterator, VarMap::iterator> Range;
612
613 OS << "\nVariable coverage statistics:\nFunction\t"
614 << (CombineInstances ? "InstanceCount" : "InlChain")
615 << "\tVariable\tDecl\tLinesCovered";
616 if (BaselineCtx)
617 OS << "\tBaseline\tCoveredRatio\tLinesPresent\tLinesPresentRatio";
618 OS << "\n";
619
620 if (CombineInstances) {
621 for (auto FirstVar = Vars.begin(); FirstVar != Vars.end();
622 FirstVar = Range.second) {
623 Range = Vars.equal_range(x: FirstVar->first);
624 VarCoverage CombinedCov = {.Parents: {}, .Cov: 0, .BaselineCov: 0, .LTCov: 0, .Missing: 0, .Instances: 0, .MissingBaseline: false};
625 for (auto Var = Range.first; Var != Range.second; ++Var) {
626 ++CombinedCov.Instances;
627 CombinedCov.Cov += Var->second.Cov;
628 CombinedCov.BaselineCov += Var->second.BaselineCov;
629 CombinedCov.LTCov += Var->second.LTCov;
630 CombinedCov.Missing += Var->second.Missing;
631 CombinedCov.MissingBaseline |= Var->second.MissingBaseline;
632 }
633 displayVariableCoverage(Key: FirstVar->first, Var: CombinedCov, CombineInstances: true, OS);
634 }
635 } else {
636 for (auto Var : Vars)
637 displayVariableCoverage(Key: Var.first, Var: Var.second, CombineInstances: false, OS);
638 }
639
640 return true;
641}
642