1 | //===- GCOV.cpp - LLVM coverage tool --------------------------------------===// |
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 | // GCOV implements the interface to read and write coverage files that use |
10 | // 'gcov' format. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "llvm/ProfileData/GCOV.h" |
15 | #include "llvm/ADT/STLExtras.h" |
16 | #include "llvm/ADT/SmallSet.h" |
17 | #include "llvm/Config/llvm-config.h" |
18 | #include "llvm/Demangle/Demangle.h" |
19 | #include "llvm/Support/Debug.h" |
20 | #include "llvm/Support/FileSystem.h" |
21 | #include "llvm/Support/Format.h" |
22 | #include "llvm/Support/MD5.h" |
23 | #include "llvm/Support/Path.h" |
24 | #include "llvm/Support/raw_ostream.h" |
25 | #include <algorithm> |
26 | #include <optional> |
27 | #include <system_error> |
28 | |
29 | using namespace llvm; |
30 | |
31 | enum : uint32_t { |
32 | GCOV_ARC_ON_TREE = 1 << 0, |
33 | GCOV_ARC_FALLTHROUGH = 1 << 2, |
34 | |
35 | GCOV_TAG_FUNCTION = 0x01000000, |
36 | GCOV_TAG_BLOCKS = 0x01410000, |
37 | GCOV_TAG_ARCS = 0x01430000, |
38 | GCOV_TAG_LINES = 0x01450000, |
39 | GCOV_TAG_COUNTER_ARCS = 0x01a10000, |
40 | // GCOV_TAG_OBJECT_SUMMARY superseded GCOV_TAG_PROGRAM_SUMMARY in GCC 9. |
41 | GCOV_TAG_OBJECT_SUMMARY = 0xa1000000, |
42 | GCOV_TAG_PROGRAM_SUMMARY = 0xa3000000, |
43 | }; |
44 | |
45 | namespace { |
46 | struct Summary { |
47 | Summary(StringRef Name) : Name(Name) {} |
48 | |
49 | StringRef Name; |
50 | uint64_t lines = 0; |
51 | uint64_t linesExec = 0; |
52 | uint64_t branches = 0; |
53 | uint64_t branchesExec = 0; |
54 | uint64_t branchesTaken = 0; |
55 | }; |
56 | |
57 | struct LineInfo { |
58 | SmallVector<const GCOVBlock *, 1> blocks; |
59 | uint64_t count = 0; |
60 | bool exists = false; |
61 | }; |
62 | |
63 | struct SourceInfo { |
64 | StringRef filename; |
65 | SmallString<0> displayName; |
66 | std::vector<std::vector<const GCOVFunction *>> startLineToFunctions; |
67 | std::vector<LineInfo> lines; |
68 | bool ignored = false; |
69 | SourceInfo(StringRef filename) : filename(filename) {} |
70 | }; |
71 | |
72 | class Context { |
73 | public: |
74 | Context(const GCOV::Options &Options) : options(Options) {} |
75 | void print(StringRef filename, StringRef gcno, StringRef gcda, |
76 | GCOVFile &file); |
77 | |
78 | private: |
79 | std::string getCoveragePath(StringRef filename, StringRef mainFilename) const; |
80 | void printFunctionDetails(const GCOVFunction &f, raw_ostream &os) const; |
81 | void printBranchInfo(const GCOVBlock &Block, uint32_t &edgeIdx, |
82 | raw_ostream &OS) const; |
83 | void printSummary(const Summary &summary, raw_ostream &os) const; |
84 | |
85 | void collectFunction(GCOVFunction &f, Summary &summary); |
86 | void collectSourceLine(SourceInfo &si, Summary *summary, LineInfo &line, |
87 | size_t lineNum) const; |
88 | void collectSource(SourceInfo &si, Summary &summary) const; |
89 | void annotateSource(SourceInfo &si, const GCOVFile &file, StringRef gcno, |
90 | StringRef gcda, raw_ostream &os) const; |
91 | void printSourceToIntermediate(const SourceInfo &si, raw_ostream &os) const; |
92 | |
93 | const GCOV::Options &options; |
94 | std::vector<SourceInfo> sources; |
95 | }; |
96 | } // namespace |
97 | |
98 | //===----------------------------------------------------------------------===// |
99 | // GCOVFile implementation. |
100 | |
101 | /// readGCNO - Read GCNO buffer. |
102 | bool GCOVFile::readGCNO(GCOVBuffer &buf) { |
103 | if (!buf.readGCNOFormat()) |
104 | return false; |
105 | if (!buf.readGCOVVersion(version)) |
106 | return false; |
107 | |
108 | checksum = buf.getWord(); |
109 | if (version >= GCOV::V900 && !buf.readString(str&: cwd)) |
110 | return false; |
111 | if (version >= GCOV::V800) |
112 | buf.getWord(); // hasUnexecutedBlocks |
113 | |
114 | uint32_t tag, length; |
115 | GCOVFunction *fn = nullptr; |
116 | while ((tag = buf.getWord())) { |
117 | if (!buf.readInt(Val&: length)) |
118 | return false; |
119 | uint32_t pos = buf.cursor.tell(); |
120 | if (tag == GCOV_TAG_FUNCTION) { |
121 | functions.push_back(Elt: std::make_unique<GCOVFunction>(args&: *this)); |
122 | fn = functions.back().get(); |
123 | fn->ident = buf.getWord(); |
124 | fn->linenoChecksum = buf.getWord(); |
125 | if (version >= GCOV::V407) |
126 | fn->cfgChecksum = buf.getWord(); |
127 | buf.readString(str&: fn->Name); |
128 | StringRef filename; |
129 | if (version < GCOV::V800) { |
130 | if (!buf.readString(str&: filename)) |
131 | return false; |
132 | fn->startLine = buf.getWord(); |
133 | } else { |
134 | fn->artificial = buf.getWord(); |
135 | if (!buf.readString(str&: filename)) |
136 | return false; |
137 | fn->startLine = buf.getWord(); |
138 | fn->startColumn = buf.getWord(); |
139 | fn->endLine = buf.getWord(); |
140 | if (version >= GCOV::V900) |
141 | fn->endColumn = buf.getWord(); |
142 | } |
143 | fn->srcIdx = addNormalizedPathToMap(filename); |
144 | identToFunction[fn->ident] = fn; |
145 | } else if (tag == GCOV_TAG_BLOCKS && fn) { |
146 | if (version < GCOV::V800) { |
147 | for (uint32_t i = 0; i != length; ++i) { |
148 | buf.getWord(); // Ignored block flags |
149 | fn->blocks.push_back(Elt: std::make_unique<GCOVBlock>(args&: i)); |
150 | } |
151 | } else { |
152 | uint32_t num = buf.getWord(); |
153 | for (uint32_t i = 0; i != num; ++i) |
154 | fn->blocks.push_back(Elt: std::make_unique<GCOVBlock>(args&: i)); |
155 | } |
156 | } else if (tag == GCOV_TAG_ARCS && fn) { |
157 | uint32_t srcNo = buf.getWord(); |
158 | if (srcNo >= fn->blocks.size()) { |
159 | errs() << "unexpected block number: " << srcNo << " (in " |
160 | << fn->blocks.size() << ")\n" ; |
161 | return false; |
162 | } |
163 | GCOVBlock *src = fn->blocks[srcNo].get(); |
164 | const uint32_t e = |
165 | version >= GCOV::V1200 ? (length / 4 - 1) / 2 : (length - 1) / 2; |
166 | for (uint32_t i = 0; i != e; ++i) { |
167 | uint32_t dstNo = buf.getWord(), flags = buf.getWord(); |
168 | GCOVBlock *dst = fn->blocks[dstNo].get(); |
169 | auto arc = std::make_unique<GCOVArc>(args&: *src, args&: *dst, args&: flags); |
170 | src->addDstEdge(Edge: arc.get()); |
171 | dst->addSrcEdge(Edge: arc.get()); |
172 | if (arc->onTree()) |
173 | fn->treeArcs.push_back(Elt: std::move(arc)); |
174 | else |
175 | fn->arcs.push_back(Elt: std::move(arc)); |
176 | } |
177 | } else if (tag == GCOV_TAG_LINES && fn) { |
178 | uint32_t srcNo = buf.getWord(); |
179 | if (srcNo >= fn->blocks.size()) { |
180 | errs() << "unexpected block number: " << srcNo << " (in " |
181 | << fn->blocks.size() << ")\n" ; |
182 | return false; |
183 | } |
184 | GCOVBlock &Block = *fn->blocks[srcNo]; |
185 | for (;;) { |
186 | uint32_t line = buf.getWord(); |
187 | if (line) |
188 | Block.addLine(N: line); |
189 | else { |
190 | StringRef filename; |
191 | buf.readString(str&: filename); |
192 | if (filename.empty()) |
193 | break; |
194 | Block.addFile(fileIdx: addNormalizedPathToMap(filename)); |
195 | } |
196 | } |
197 | } |
198 | pos += version >= GCOV::V1200 ? length : 4 * length; |
199 | if (pos < buf.cursor.tell()) |
200 | return false; |
201 | buf.de.skip(C&: buf.cursor, Length: pos - buf.cursor.tell()); |
202 | } |
203 | |
204 | GCNOInitialized = true; |
205 | return true; |
206 | } |
207 | |
208 | /// readGCDA - Read GCDA buffer. It is required that readGCDA() can only be |
209 | /// called after readGCNO(). |
210 | bool GCOVFile::readGCDA(GCOVBuffer &buf) { |
211 | assert(GCNOInitialized && "readGCDA() can only be called after readGCNO()" ); |
212 | if (!buf.readGCDAFormat()) |
213 | return false; |
214 | GCOV::GCOVVersion GCDAVersion; |
215 | if (!buf.readGCOVVersion(version&: GCDAVersion)) |
216 | return false; |
217 | if (version != GCDAVersion) { |
218 | errs() << "GCOV versions do not match.\n" ; |
219 | return false; |
220 | } |
221 | |
222 | uint32_t GCDAChecksum; |
223 | if (!buf.readInt(Val&: GCDAChecksum)) |
224 | return false; |
225 | if (checksum != GCDAChecksum) { |
226 | errs() << "file checksums do not match: " << checksum |
227 | << " != " << GCDAChecksum << "\n" ; |
228 | return false; |
229 | } |
230 | uint32_t dummy, tag, length; |
231 | uint32_t ident; |
232 | GCOVFunction *fn = nullptr; |
233 | while ((tag = buf.getWord())) { |
234 | if (!buf.readInt(Val&: length)) |
235 | return false; |
236 | uint32_t pos = buf.cursor.tell(); |
237 | if (tag == GCOV_TAG_OBJECT_SUMMARY) { |
238 | buf.readInt(Val&: runCount); |
239 | buf.readInt(Val&: dummy); |
240 | } else if (tag == GCOV_TAG_PROGRAM_SUMMARY) { |
241 | buf.readInt(Val&: dummy); |
242 | buf.readInt(Val&: dummy); |
243 | buf.readInt(Val&: runCount); |
244 | ++programCount; |
245 | } else if (tag == GCOV_TAG_FUNCTION) { |
246 | if (length == 0) // Placeholder |
247 | continue; |
248 | if (length < 2 || !buf.readInt(Val&: ident)) |
249 | return false; |
250 | auto It = identToFunction.find(x: ident); |
251 | uint32_t linenoChecksum, cfgChecksum = 0; |
252 | buf.readInt(Val&: linenoChecksum); |
253 | if (version >= GCOV::V407) |
254 | buf.readInt(Val&: cfgChecksum); |
255 | if (It != identToFunction.end()) { |
256 | fn = It->second; |
257 | if (linenoChecksum != fn->linenoChecksum || |
258 | cfgChecksum != fn->cfgChecksum) { |
259 | errs() << fn->Name |
260 | << format(Fmt: ": checksum mismatch, (%u, %u) != (%u, %u)\n" , |
261 | Vals: linenoChecksum, Vals: cfgChecksum, Vals: fn->linenoChecksum, |
262 | Vals: fn->cfgChecksum); |
263 | return false; |
264 | } |
265 | } |
266 | } else if (tag == GCOV_TAG_COUNTER_ARCS && fn) { |
267 | uint32_t expected = 2 * fn->arcs.size(); |
268 | if (version >= GCOV::V1200) |
269 | expected *= 4; |
270 | if (length != expected) { |
271 | errs() << fn->Name |
272 | << format( |
273 | Fmt: ": GCOV_TAG_COUNTER_ARCS mismatch, got %u, expected %u\n" , |
274 | Vals: length, Vals: expected); |
275 | return false; |
276 | } |
277 | for (std::unique_ptr<GCOVArc> &arc : fn->arcs) { |
278 | if (!buf.readInt64(Val&: arc->count)) |
279 | return false; |
280 | arc->src.count += arc->count; |
281 | } |
282 | |
283 | if (fn->blocks.size() >= 2) { |
284 | GCOVBlock &src = *fn->blocks[0]; |
285 | GCOVBlock &sink = |
286 | version < GCOV::V408 ? *fn->blocks.back() : *fn->blocks[1]; |
287 | auto arc = std::make_unique<GCOVArc>(args&: sink, args&: src, args: GCOV_ARC_ON_TREE); |
288 | sink.addDstEdge(Edge: arc.get()); |
289 | src.addSrcEdge(Edge: arc.get()); |
290 | fn->treeArcs.push_back(Elt: std::move(arc)); |
291 | |
292 | for (GCOVBlock &block : fn->blocksRange()) |
293 | fn->propagateCounts(v: block, pred: nullptr); |
294 | for (size_t i = fn->treeArcs.size() - 1; i; --i) |
295 | fn->treeArcs[i - 1]->src.count += fn->treeArcs[i - 1]->count; |
296 | } |
297 | } |
298 | pos += version >= GCOV::V1200 ? length : 4 * length; |
299 | if (pos < buf.cursor.tell()) |
300 | return false; |
301 | buf.de.skip(C&: buf.cursor, Length: pos - buf.cursor.tell()); |
302 | } |
303 | |
304 | return true; |
305 | } |
306 | |
307 | void GCOVFile::print(raw_ostream &OS) const { |
308 | for (const GCOVFunction &f : *this) |
309 | f.print(OS); |
310 | } |
311 | |
312 | #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) |
313 | /// dump - Dump GCOVFile content to dbgs() for debugging purposes. |
314 | LLVM_DUMP_METHOD void GCOVFile::dump() const { print(dbgs()); } |
315 | #endif |
316 | |
317 | unsigned GCOVFile::addNormalizedPathToMap(StringRef filename) { |
318 | // unify filename, as the same path can have different form |
319 | SmallString<256> P(filename); |
320 | sys::path::remove_dots(path&: P, remove_dot_dot: true); |
321 | filename = P.str(); |
322 | |
323 | auto r = filenameToIdx.try_emplace(Key: filename, Args: filenameToIdx.size()); |
324 | if (r.second) |
325 | filenames.emplace_back(args&: filename); |
326 | |
327 | return r.first->second; |
328 | } |
329 | |
330 | bool GCOVArc::onTree() const { return flags & GCOV_ARC_ON_TREE; } |
331 | |
332 | //===----------------------------------------------------------------------===// |
333 | // GCOVFunction implementation. |
334 | |
335 | StringRef GCOVFunction::getName(bool demangle) const { |
336 | if (!demangle) |
337 | return Name; |
338 | if (demangled.empty()) { |
339 | do { |
340 | if (Name.starts_with(Prefix: "_Z" )) { |
341 | // Name is guaranteed to be NUL-terminated. |
342 | if (char *res = itaniumDemangle(mangled_name: Name.data())) { |
343 | demangled = res; |
344 | free(ptr: res); |
345 | break; |
346 | } |
347 | } |
348 | demangled = Name; |
349 | } while (false); |
350 | } |
351 | return demangled; |
352 | } |
353 | StringRef GCOVFunction::getFilename() const { return file.filenames[srcIdx]; } |
354 | |
355 | /// getEntryCount - Get the number of times the function was called by |
356 | /// retrieving the entry block's count. |
357 | uint64_t GCOVFunction::getEntryCount() const { |
358 | return blocks.front()->getCount(); |
359 | } |
360 | |
361 | GCOVBlock &GCOVFunction::getExitBlock() const { |
362 | return file.getVersion() < GCOV::V408 ? *blocks.back() : *blocks[1]; |
363 | } |
364 | |
365 | // For each basic block, the sum of incoming edge counts equals the sum of |
366 | // outgoing edge counts by Kirchoff's circuit law. If the unmeasured arcs form a |
367 | // spanning tree, the count for each unmeasured arc (GCOV_ARC_ON_TREE) can be |
368 | // uniquely identified. Use an iterative algorithm to decrease stack usage for |
369 | // library users in threads. See the edge propagation algorithm in Optimally |
370 | // Profiling and Tracing Programs, ACM Transactions on Programming Languages and |
371 | // Systems, 1994. |
372 | void GCOVFunction::propagateCounts(const GCOVBlock &v, GCOVArc *pred) { |
373 | struct Elem { |
374 | const GCOVBlock &v; |
375 | GCOVArc *pred; |
376 | bool inDst; |
377 | size_t i = 0; |
378 | uint64_t excess = 0; |
379 | }; |
380 | |
381 | SmallVector<Elem, 0> stack; |
382 | stack.push_back(Elt: {.v: v, .pred: pred, .inDst: false}); |
383 | for (;;) { |
384 | Elem &u = stack.back(); |
385 | // If GCOV_ARC_ON_TREE edges do form a tree, visited is not needed; |
386 | // otherwise, this prevents infinite recursion for bad input. |
387 | if (u.i == 0 && !visited.insert(V: &u.v).second) { |
388 | stack.pop_back(); |
389 | if (stack.empty()) |
390 | break; |
391 | continue; |
392 | } |
393 | if (u.i < u.v.pred.size()) { |
394 | GCOVArc *e = u.v.pred[u.i++]; |
395 | if (e != u.pred) { |
396 | if (e->onTree()) |
397 | stack.push_back(Elt: {.v: e->src, .pred: e, /*inDst=*/false}); |
398 | else |
399 | u.excess += e->count; |
400 | } |
401 | } else if (u.i < u.v.pred.size() + u.v.succ.size()) { |
402 | GCOVArc *e = u.v.succ[u.i++ - u.v.pred.size()]; |
403 | if (e != u.pred) { |
404 | if (e->onTree()) |
405 | stack.push_back(Elt: {.v: e->dst, .pred: e, /*inDst=*/true}); |
406 | else |
407 | u.excess -= e->count; |
408 | } |
409 | } else { |
410 | uint64_t excess = u.excess; |
411 | if (static_cast<int64_t>(excess) < 0) |
412 | excess = -excess; |
413 | if (u.pred) |
414 | u.pred->count = excess; |
415 | bool inDst = u.inDst; |
416 | stack.pop_back(); |
417 | if (stack.empty()) |
418 | break; |
419 | stack.back().excess += inDst ? -excess : excess; |
420 | } |
421 | } |
422 | } |
423 | |
424 | void GCOVFunction::print(raw_ostream &OS) const { |
425 | OS << "===== " << Name << " (" << ident << ") @ " << getFilename() << ":" |
426 | << startLine << "\n" ; |
427 | for (const auto &Block : blocks) |
428 | Block->print(OS); |
429 | } |
430 | |
431 | #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) |
432 | /// dump - Dump GCOVFunction content to dbgs() for debugging purposes. |
433 | LLVM_DUMP_METHOD void GCOVFunction::dump() const { print(dbgs()); } |
434 | #endif |
435 | |
436 | /// collectLineCounts - Collect line counts. This must be used after |
437 | /// reading .gcno and .gcda files. |
438 | |
439 | //===----------------------------------------------------------------------===// |
440 | // GCOVBlock implementation. |
441 | |
442 | void GCOVBlock::print(raw_ostream &OS) const { |
443 | OS << "Block : " << number << " Counter : " << count << "\n" ; |
444 | if (!pred.empty()) { |
445 | OS << "\tSource Edges : " ; |
446 | for (const GCOVArc *Edge : pred) |
447 | OS << Edge->src.number << " (" << Edge->count << "), " ; |
448 | OS << "\n" ; |
449 | } |
450 | if (!succ.empty()) { |
451 | OS << "\tDestination Edges : " ; |
452 | for (const GCOVArc *Edge : succ) { |
453 | if (Edge->flags & GCOV_ARC_ON_TREE) |
454 | OS << '*'; |
455 | OS << Edge->dst.number << " (" << Edge->count << "), " ; |
456 | } |
457 | OS << "\n" ; |
458 | } |
459 | if (!locations.empty()) { |
460 | for (const GCOVBlockLocation &loc : locations) { |
461 | OS << "\tFile: " << loc.srcIdx << ": " ; |
462 | for (uint32_t N : loc.lines) |
463 | OS << (N) << "," ; |
464 | OS << "\n" ; |
465 | } |
466 | } |
467 | } |
468 | |
469 | #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) |
470 | /// dump - Dump GCOVBlock content to dbgs() for debugging purposes. |
471 | LLVM_DUMP_METHOD void GCOVBlock::dump() const { print(dbgs()); } |
472 | #endif |
473 | |
474 | uint64_t |
475 | GCOVBlock::augmentOneCycle(GCOVBlock *src, |
476 | std::vector<std::pair<GCOVBlock *, size_t>> &stack) { |
477 | GCOVBlock *u; |
478 | size_t i; |
479 | stack.clear(); |
480 | stack.emplace_back(args&: src, args: 0); |
481 | src->incoming = (GCOVArc *)1; // Mark u available for cycle detection |
482 | for (;;) { |
483 | std::tie(args&: u, args&: i) = stack.back(); |
484 | if (i == u->succ.size()) { |
485 | u->traversable = false; |
486 | stack.pop_back(); |
487 | if (stack.empty()) |
488 | break; |
489 | continue; |
490 | } |
491 | ++stack.back().second; |
492 | GCOVArc *succ = u->succ[i]; |
493 | // Ignore saturated arcs (cycleCount has been reduced to 0) and visited |
494 | // blocks. Ignore self arcs to guard against bad input (.gcno has no |
495 | // self arcs). |
496 | if (succ->cycleCount == 0 || !succ->dst.traversable || &succ->dst == u) |
497 | continue; |
498 | if (succ->dst.incoming == nullptr) { |
499 | succ->dst.incoming = succ; |
500 | stack.emplace_back(args: &succ->dst, args: 0); |
501 | continue; |
502 | } |
503 | uint64_t minCount = succ->cycleCount; |
504 | for (GCOVBlock *v = u;;) { |
505 | minCount = std::min(a: minCount, b: v->incoming->cycleCount); |
506 | v = &v->incoming->src; |
507 | if (v == &succ->dst) |
508 | break; |
509 | } |
510 | succ->cycleCount -= minCount; |
511 | for (GCOVBlock *v = u;;) { |
512 | v->incoming->cycleCount -= minCount; |
513 | v = &v->incoming->src; |
514 | if (v == &succ->dst) |
515 | break; |
516 | } |
517 | return minCount; |
518 | } |
519 | return 0; |
520 | } |
521 | |
522 | // Get the total execution count of loops among blocks on the same line. |
523 | // Assuming a reducible flow graph, the count is the sum of back edge counts. |
524 | // Identifying loops is complex, so we simply find cycles and perform cycle |
525 | // cancelling iteratively. |
526 | uint64_t GCOVBlock::getCyclesCount(const BlockVector &blocks) { |
527 | std::vector<std::pair<GCOVBlock *, size_t>> stack; |
528 | uint64_t count = 0, d; |
529 | for (;;) { |
530 | // Make blocks on the line traversable and try finding a cycle. |
531 | for (const auto *b : blocks) { |
532 | const_cast<GCOVBlock *>(b)->traversable = true; |
533 | const_cast<GCOVBlock *>(b)->incoming = nullptr; |
534 | } |
535 | d = 0; |
536 | for (const auto *block : blocks) { |
537 | auto *b = const_cast<GCOVBlock *>(block); |
538 | if (b->traversable && (d = augmentOneCycle(src: b, stack)) > 0) |
539 | break; |
540 | } |
541 | if (d == 0) |
542 | break; |
543 | count += d; |
544 | } |
545 | // If there is no more loop, all traversable bits should have been cleared. |
546 | // This property is needed by subsequent calls. |
547 | for (const auto *b : blocks) { |
548 | assert(!b->traversable); |
549 | (void)b; |
550 | } |
551 | return count; |
552 | } |
553 | |
554 | //===----------------------------------------------------------------------===// |
555 | // FileInfo implementation. |
556 | |
557 | // Format dividend/divisor as a percentage. Return 1 if the result is greater |
558 | // than 0% and less than 1%. |
559 | static uint32_t formatPercentage(uint64_t dividend, uint64_t divisor) { |
560 | if (!dividend || !divisor) |
561 | return 0; |
562 | dividend *= 100; |
563 | return dividend < divisor ? 1 : dividend / divisor; |
564 | } |
565 | |
566 | // This custom division function mimics gcov's branch ouputs: |
567 | // - Round to closest whole number |
568 | // - Only output 0% or 100% if it's exactly that value |
569 | static uint32_t branchDiv(uint64_t Numerator, uint64_t Divisor) { |
570 | if (!Numerator) |
571 | return 0; |
572 | if (Numerator == Divisor) |
573 | return 100; |
574 | |
575 | uint8_t Res = (Numerator * 100 + Divisor / 2) / Divisor; |
576 | if (Res == 0) |
577 | return 1; |
578 | if (Res == 100) |
579 | return 99; |
580 | return Res; |
581 | } |
582 | |
583 | namespace { |
584 | struct formatBranchInfo { |
585 | formatBranchInfo(const GCOV::Options &Options, uint64_t Count, uint64_t Total) |
586 | : Options(Options), Count(Count), Total(Total) {} |
587 | |
588 | void print(raw_ostream &OS) const { |
589 | if (!Total) |
590 | OS << "never executed" ; |
591 | else if (Options.BranchCount) |
592 | OS << "taken " << Count; |
593 | else |
594 | OS << "taken " << branchDiv(Numerator: Count, Divisor: Total) << "%" ; |
595 | } |
596 | |
597 | const GCOV::Options &Options; |
598 | uint64_t Count; |
599 | uint64_t Total; |
600 | }; |
601 | |
602 | static raw_ostream &operator<<(raw_ostream &OS, const formatBranchInfo &FBI) { |
603 | FBI.print(OS); |
604 | return OS; |
605 | } |
606 | |
607 | class LineConsumer { |
608 | std::unique_ptr<MemoryBuffer> Buffer; |
609 | StringRef Remaining; |
610 | |
611 | public: |
612 | LineConsumer() = default; |
613 | LineConsumer(StringRef Filename) { |
614 | // Open source files without requiring a NUL terminator. The concurrent |
615 | // modification may nullify the NUL terminator condition. |
616 | ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = |
617 | MemoryBuffer::getFileOrSTDIN(Filename, /*IsText=*/false, |
618 | /*RequiresNullTerminator=*/false); |
619 | if (std::error_code EC = BufferOrErr.getError()) { |
620 | errs() << Filename << ": " << EC.message() << "\n" ; |
621 | Remaining = "" ; |
622 | } else { |
623 | Buffer = std::move(BufferOrErr.get()); |
624 | Remaining = Buffer->getBuffer(); |
625 | } |
626 | } |
627 | bool empty() { return Remaining.empty(); } |
628 | void printNext(raw_ostream &OS, uint32_t LineNum) { |
629 | StringRef Line; |
630 | if (empty()) |
631 | Line = "/*EOF*/" ; |
632 | else |
633 | std::tie(args&: Line, args&: Remaining) = Remaining.split(Separator: "\n" ); |
634 | OS << format(Fmt: "%5u:" , Vals: LineNum) << Line << "\n" ; |
635 | } |
636 | }; |
637 | } // end anonymous namespace |
638 | |
639 | /// Convert a path to a gcov filename. If PreservePaths is true, this |
640 | /// translates "/" to "#", ".." to "^", and drops ".", to match gcov. |
641 | static std::string mangleCoveragePath(StringRef Filename, bool PreservePaths) { |
642 | if (!PreservePaths) |
643 | return sys::path::filename(path: Filename).str(); |
644 | |
645 | // This behaviour is defined by gcov in terms of text replacements, so it's |
646 | // not likely to do anything useful on filesystems with different textual |
647 | // conventions. |
648 | llvm::SmallString<256> Result("" ); |
649 | StringRef::iterator I, S, E; |
650 | for (I = S = Filename.begin(), E = Filename.end(); I != E; ++I) { |
651 | if (*I != '/') |
652 | continue; |
653 | |
654 | if (I - S == 1 && *S == '.') { |
655 | // ".", the current directory, is skipped. |
656 | } else if (I - S == 2 && *S == '.' && *(S + 1) == '.') { |
657 | // "..", the parent directory, is replaced with "^". |
658 | Result.append(RHS: "^#" ); |
659 | } else { |
660 | if (S < I) |
661 | // Leave other components intact, |
662 | Result.append(in_start: S, in_end: I); |
663 | // And separate with "#". |
664 | Result.push_back(Elt: '#'); |
665 | } |
666 | S = I + 1; |
667 | } |
668 | |
669 | if (S < I) |
670 | Result.append(in_start: S, in_end: I); |
671 | return std::string(Result); |
672 | } |
673 | |
674 | std::string Context::getCoveragePath(StringRef filename, |
675 | StringRef mainFilename) const { |
676 | if (options.NoOutput) |
677 | // This is probably a bug in gcov, but when -n is specified, paths aren't |
678 | // mangled at all, and the -l and -p options are ignored. Here, we do the |
679 | // same. |
680 | return std::string(filename); |
681 | |
682 | std::string CoveragePath; |
683 | if (options.LongFileNames && filename != mainFilename) |
684 | CoveragePath = |
685 | mangleCoveragePath(Filename: mainFilename, PreservePaths: options.PreservePaths) + "##" ; |
686 | CoveragePath += mangleCoveragePath(Filename: filename, PreservePaths: options.PreservePaths); |
687 | if (options.HashFilenames) { |
688 | MD5 Hasher; |
689 | MD5::MD5Result Result; |
690 | Hasher.update(Str: filename.str()); |
691 | Hasher.final(Result); |
692 | CoveragePath += "##" + std::string(Result.digest()); |
693 | } |
694 | CoveragePath += ".gcov" ; |
695 | return CoveragePath; |
696 | } |
697 | |
698 | void Context::collectFunction(GCOVFunction &f, Summary &summary) { |
699 | SourceInfo &si = sources[f.srcIdx]; |
700 | if (f.startLine >= si.startLineToFunctions.size()) |
701 | si.startLineToFunctions.resize(new_size: f.startLine + 1); |
702 | si.startLineToFunctions[f.startLine].push_back(x: &f); |
703 | SmallSet<uint32_t, 16> lines; |
704 | SmallSet<uint32_t, 16> linesExec; |
705 | for (const GCOVBlock &b : f.blocksRange()) { |
706 | if (b.locations.empty()) |
707 | continue; |
708 | for (const GCOVBlockLocation &loc : b.locations) { |
709 | SourceInfo &locSource = sources[loc.srcIdx]; |
710 | uint32_t maxLineNum = *llvm::max_element(Range: loc.lines); |
711 | if (maxLineNum >= locSource.lines.size()) |
712 | locSource.lines.resize(new_size: maxLineNum + 1); |
713 | for (uint32_t lineNum : loc.lines) { |
714 | LineInfo &line = locSource.lines[lineNum]; |
715 | line.exists = true; |
716 | line.count += b.count; |
717 | line.blocks.push_back(Elt: &b); |
718 | if (f.srcIdx == loc.srcIdx) { |
719 | if (lines.insert(V: lineNum).second) |
720 | ++summary.lines; |
721 | if (b.count && linesExec.insert(V: lineNum).second) |
722 | ++summary.linesExec; |
723 | } |
724 | } |
725 | } |
726 | } |
727 | } |
728 | |
729 | void Context::collectSourceLine(SourceInfo &si, Summary *summary, |
730 | LineInfo &line, size_t lineNum) const { |
731 | uint64_t count = 0; |
732 | for (const GCOVBlock *b : line.blocks) { |
733 | if (b->number == 0) { |
734 | // For nonstandard control flows, arcs into the exit block may be |
735 | // duplicately counted (fork) or not be counted (abnormal exit), and thus |
736 | // the (exit,entry) counter may be inaccurate. Count the entry block with |
737 | // the outgoing arcs. |
738 | for (const GCOVArc *arc : b->succ) |
739 | count += arc->count; |
740 | } else { |
741 | // Add counts from predecessors that are not on the same line. |
742 | for (const GCOVArc *arc : b->pred) |
743 | if (!llvm::is_contained(Range&: line.blocks, Element: &arc->src)) |
744 | count += arc->count; |
745 | } |
746 | for (GCOVArc *arc : b->succ) |
747 | arc->cycleCount = arc->count; |
748 | } |
749 | |
750 | count += GCOVBlock::getCyclesCount(blocks: line.blocks); |
751 | line.count = count; |
752 | if (line.exists) { |
753 | ++summary->lines; |
754 | if (line.count != 0) |
755 | ++summary->linesExec; |
756 | } |
757 | |
758 | if (options.BranchInfo) |
759 | for (const GCOVBlock *b : line.blocks) { |
760 | if (b->getLastLine() != lineNum) |
761 | continue; |
762 | int branches = 0, execBranches = 0, takenBranches = 0; |
763 | for (const GCOVArc *arc : b->succ) { |
764 | ++branches; |
765 | if (count != 0) |
766 | ++execBranches; |
767 | if (arc->count != 0) |
768 | ++takenBranches; |
769 | } |
770 | if (branches > 1) { |
771 | summary->branches += branches; |
772 | summary->branchesExec += execBranches; |
773 | summary->branchesTaken += takenBranches; |
774 | } |
775 | } |
776 | } |
777 | |
778 | void Context::collectSource(SourceInfo &si, Summary &summary) const { |
779 | size_t lineNum = 0; |
780 | for (LineInfo &line : si.lines) { |
781 | collectSourceLine(si, summary: &summary, line, lineNum); |
782 | ++lineNum; |
783 | } |
784 | } |
785 | |
786 | void Context::annotateSource(SourceInfo &si, const GCOVFile &file, |
787 | StringRef gcno, StringRef gcda, |
788 | raw_ostream &os) const { |
789 | auto source = |
790 | options.Intermediate ? LineConsumer() : LineConsumer(si.filename); |
791 | |
792 | os << " -: 0:Source:" << si.displayName << '\n'; |
793 | os << " -: 0:Graph:" << gcno << '\n'; |
794 | os << " -: 0:Data:" << gcda << '\n'; |
795 | os << " -: 0:Runs:" << file.runCount << '\n'; |
796 | if (file.version < GCOV::V900) |
797 | os << " -: 0:Programs:" << file.programCount << '\n'; |
798 | |
799 | for (size_t lineNum = 1; !source.empty(); ++lineNum) { |
800 | if (lineNum >= si.lines.size()) { |
801 | os << " -:" ; |
802 | source.printNext(OS&: os, LineNum: lineNum); |
803 | continue; |
804 | } |
805 | |
806 | const LineInfo &line = si.lines[lineNum]; |
807 | if (options.BranchInfo && lineNum < si.startLineToFunctions.size()) |
808 | for (const auto *f : si.startLineToFunctions[lineNum]) |
809 | printFunctionDetails(f: *f, os); |
810 | if (!line.exists) |
811 | os << " -:" ; |
812 | else if (line.count == 0) |
813 | os << " #####:" ; |
814 | else |
815 | os << format(Fmt: "%9" PRIu64 ":" , Vals: line.count); |
816 | source.printNext(OS&: os, LineNum: lineNum); |
817 | |
818 | uint32_t blockIdx = 0, edgeIdx = 0; |
819 | for (const GCOVBlock *b : line.blocks) { |
820 | if (b->getLastLine() != lineNum) |
821 | continue; |
822 | if (options.AllBlocks) { |
823 | if (b->getCount() == 0) |
824 | os << " $$$$$:" ; |
825 | else |
826 | os << format(Fmt: "%9" PRIu64 ":" , Vals: b->count); |
827 | os << format(Fmt: "%5u-block %2u\n" , Vals: lineNum, Vals: blockIdx++); |
828 | } |
829 | if (options.BranchInfo) { |
830 | size_t NumEdges = b->succ.size(); |
831 | if (NumEdges > 1) |
832 | printBranchInfo(Block: *b, edgeIdx, OS&: os); |
833 | else if (options.UncondBranch && NumEdges == 1) { |
834 | uint64_t count = b->succ[0]->count; |
835 | os << format(Fmt: "unconditional %2u " , Vals: edgeIdx++) |
836 | << formatBranchInfo(options, count, count) << '\n'; |
837 | } |
838 | } |
839 | } |
840 | } |
841 | } |
842 | |
843 | void Context::printSourceToIntermediate(const SourceInfo &si, |
844 | raw_ostream &os) const { |
845 | os << "file:" << si.filename << '\n'; |
846 | for (const auto &fs : si.startLineToFunctions) |
847 | for (const GCOVFunction *f : fs) |
848 | os << "function:" << f->startLine << ',' << f->getEntryCount() << ',' |
849 | << f->getName(demangle: options.Demangle) << '\n'; |
850 | for (size_t lineNum = 1, size = si.lines.size(); lineNum < size; ++lineNum) { |
851 | const LineInfo &line = si.lines[lineNum]; |
852 | if (line.blocks.empty()) |
853 | continue; |
854 | // GCC 8 (r254259) added third third field for Ada: |
855 | // lcount:<line>,<count>,<has_unexecuted_blocks> |
856 | // We don't need the third field. |
857 | os << "lcount:" << lineNum << ',' << line.count << '\n'; |
858 | |
859 | if (!options.BranchInfo) |
860 | continue; |
861 | for (const GCOVBlock *b : line.blocks) { |
862 | if (b->succ.size() < 2 || b->getLastLine() != lineNum) |
863 | continue; |
864 | for (const GCOVArc *arc : b->succ) { |
865 | const char *type = |
866 | b->getCount() ? arc->count ? "taken" : "nottaken" : "notexec" ; |
867 | os << "branch:" << lineNum << ',' << type << '\n'; |
868 | } |
869 | } |
870 | } |
871 | } |
872 | |
873 | void Context::print(StringRef filename, StringRef gcno, StringRef gcda, |
874 | GCOVFile &file) { |
875 | for (StringRef filename : file.filenames) { |
876 | sources.emplace_back(args&: filename); |
877 | SourceInfo &si = sources.back(); |
878 | si.displayName = si.filename; |
879 | if (!options.SourcePrefix.empty() && |
880 | sys::path::replace_path_prefix(Path&: si.displayName, OldPrefix: options.SourcePrefix, |
881 | NewPrefix: "" ) && |
882 | !si.displayName.empty()) { |
883 | // TODO replace_path_prefix may strip the prefix even if the remaining |
884 | // part does not start with a separator. |
885 | if (sys::path::is_separator(value: si.displayName[0])) |
886 | si.displayName.erase(CI: si.displayName.begin()); |
887 | else |
888 | si.displayName = si.filename; |
889 | } |
890 | if (options.RelativeOnly && sys::path::is_absolute(path: si.displayName)) |
891 | si.ignored = true; |
892 | } |
893 | |
894 | raw_ostream &os = llvm::outs(); |
895 | for (GCOVFunction &f : make_pointee_range(Range&: file.functions)) { |
896 | Summary summary(f.getName(demangle: options.Demangle)); |
897 | collectFunction(f, summary); |
898 | if (options.FuncCoverage && !options.UseStdout) { |
899 | os << "Function '" << summary.Name << "'\n" ; |
900 | printSummary(summary, os); |
901 | os << '\n'; |
902 | } |
903 | } |
904 | |
905 | for (SourceInfo &si : sources) { |
906 | if (si.ignored) |
907 | continue; |
908 | Summary summary(si.displayName); |
909 | collectSource(si, summary); |
910 | |
911 | // Print file summary unless -t is specified. |
912 | std::string gcovName = getCoveragePath(filename: si.filename, mainFilename: filename); |
913 | if (!options.UseStdout) { |
914 | os << "File '" << summary.Name << "'\n" ; |
915 | printSummary(summary, os); |
916 | if (!options.NoOutput && !options.Intermediate) |
917 | os << "Creating '" << gcovName << "'\n" ; |
918 | os << '\n'; |
919 | } |
920 | |
921 | if (options.NoOutput || options.Intermediate) |
922 | continue; |
923 | std::optional<raw_fd_ostream> os; |
924 | if (!options.UseStdout) { |
925 | std::error_code ec; |
926 | os.emplace(args&: gcovName, args&: ec, args: sys::fs::OF_TextWithCRLF); |
927 | if (ec) { |
928 | errs() << ec.message() << '\n'; |
929 | continue; |
930 | } |
931 | } |
932 | annotateSource(si, file, gcno, gcda, |
933 | os&: options.UseStdout ? llvm::outs() : *os); |
934 | } |
935 | |
936 | if (options.Intermediate && !options.NoOutput) { |
937 | // gcov 7.* unexpectedly create multiple .gcov files, which was fixed in 8.0 |
938 | // (PR GCC/82702). We create just one file. |
939 | std::string outputPath(sys::path::filename(path: filename)); |
940 | std::error_code ec; |
941 | raw_fd_ostream os(outputPath + ".gcov" , ec, sys::fs::OF_TextWithCRLF); |
942 | if (ec) { |
943 | errs() << ec.message() << '\n'; |
944 | return; |
945 | } |
946 | |
947 | for (const SourceInfo &si : sources) |
948 | printSourceToIntermediate(si, os); |
949 | } |
950 | } |
951 | |
952 | void Context::printFunctionDetails(const GCOVFunction &f, |
953 | raw_ostream &os) const { |
954 | const uint64_t entryCount = f.getEntryCount(); |
955 | uint32_t blocksExec = 0; |
956 | const GCOVBlock &exitBlock = f.getExitBlock(); |
957 | uint64_t exitCount = 0; |
958 | for (const GCOVArc *arc : exitBlock.pred) |
959 | exitCount += arc->count; |
960 | for (const GCOVBlock &b : f.blocksRange()) |
961 | if (b.number != 0 && &b != &exitBlock && b.getCount()) |
962 | ++blocksExec; |
963 | |
964 | os << "function " << f.getName(demangle: options.Demangle) << " called " << entryCount |
965 | << " returned " << formatPercentage(dividend: exitCount, divisor: entryCount) |
966 | << "% blocks executed " |
967 | << formatPercentage(dividend: blocksExec, divisor: f.blocks.size() - 2) << "%\n" ; |
968 | } |
969 | |
970 | /// printBranchInfo - Print conditional branch probabilities. |
971 | void Context::printBranchInfo(const GCOVBlock &Block, uint32_t &edgeIdx, |
972 | raw_ostream &os) const { |
973 | uint64_t total = 0; |
974 | for (const GCOVArc *arc : Block.dsts()) |
975 | total += arc->count; |
976 | for (const GCOVArc *arc : Block.dsts()) |
977 | os << format(Fmt: "branch %2u " , Vals: edgeIdx++) |
978 | << formatBranchInfo(options, arc->count, total) << '\n'; |
979 | } |
980 | |
981 | void Context::printSummary(const Summary &summary, raw_ostream &os) const { |
982 | os << format(Fmt: "Lines executed:%.2f%% of %" PRIu64 "\n" , |
983 | Vals: double(summary.linesExec) * 100 / summary.lines, Vals: summary.lines); |
984 | if (options.BranchInfo) { |
985 | if (summary.branches == 0) { |
986 | os << "No branches\n" ; |
987 | } else { |
988 | os << format(Fmt: "Branches executed:%.2f%% of %" PRIu64 "\n" , |
989 | Vals: double(summary.branchesExec) * 100 / summary.branches, |
990 | Vals: summary.branches); |
991 | os << format(Fmt: "Taken at least once:%.2f%% of %" PRIu64 "\n" , |
992 | Vals: double(summary.branchesTaken) * 100 / summary.branches, |
993 | Vals: summary.branches); |
994 | } |
995 | os << "No calls\n" ; |
996 | } |
997 | } |
998 | |
999 | void llvm::gcovOneInput(const GCOV::Options &options, StringRef filename, |
1000 | StringRef gcno, StringRef gcda, GCOVFile &file) { |
1001 | Context fi(options); |
1002 | fi.print(filename, gcno, gcda, file); |
1003 | } |
1004 | |