1004 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// GCOV implements the interface to read and write coverage files that use10// '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 29using namespace llvm;30 31enum : 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 45namespace {46struct 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 57struct LineInfo {58 SmallVector<const GCOVBlock *, 1> blocks;59 uint64_t count = 0;60 bool exists = false;61};62 63struct 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 72class Context {73public:74 Context(const GCOV::Options &Options) : options(Options) {}75 void print(StringRef filename, StringRef gcno, StringRef gcda,76 GCOVFile &file);77 78private: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} // namespace97 98//===----------------------------------------------------------------------===//99// GCOVFile implementation.100 101/// readGCNO - Read GCNO buffer.102bool 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(cwd))110 return false;111 if (version >= GCOV::V800)112 buf.getWord(); // hasUnexecutedBlocks113 114 uint32_t tag, length;115 GCOVFunction *fn = nullptr;116 while ((tag = buf.getWord())) {117 if (!buf.readInt(length))118 return false;119 uint32_t pos = buf.cursor.tell();120 if (tag == GCOV_TAG_FUNCTION) {121 functions.push_back(std::make_unique<GCOVFunction>(*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(fn->Name);128 StringRef filename;129 if (version < GCOV::V800) {130 if (!buf.readString(filename))131 return false;132 fn->startLine = buf.getWord();133 } else {134 fn->artificial = buf.getWord();135 if (!buf.readString(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 flags149 fn->blocks.push_back(std::make_unique<GCOVBlock>(i));150 }151 } else {152 uint32_t num = buf.getWord();153 for (uint32_t i = 0; i != num; ++i)154 fn->blocks.push_back(std::make_unique<GCOVBlock>(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>(*src, *dst, flags);170 src->addDstEdge(arc.get());171 dst->addSrcEdge(arc.get());172 if (arc->onTree())173 fn->treeArcs.push_back(std::move(arc));174 else175 fn->arcs.push_back(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(line);189 else {190 StringRef filename;191 buf.readString(filename);192 if (filename.empty())193 break;194 Block.addFile(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(buf.cursor, 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 be209/// called after readGCNO().210bool 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(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(GCDAChecksum))224 return false;225 if (checksum != GCDAChecksum) {226 errs() << "file checksums do not match: " << checksum227 << " != " << 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(length))235 return false;236 uint32_t pos = buf.cursor.tell();237 if (tag == GCOV_TAG_OBJECT_SUMMARY) {238 buf.readInt(runCount);239 buf.readInt(dummy);240 } else if (tag == GCOV_TAG_PROGRAM_SUMMARY) {241 buf.readInt(dummy);242 buf.readInt(dummy);243 buf.readInt(runCount);244 ++programCount;245 } else if (tag == GCOV_TAG_FUNCTION) {246 if (length == 0) // Placeholder247 continue;248 if (length < 2 || !buf.readInt(ident))249 return false;250 auto It = identToFunction.find(ident);251 uint32_t linenoChecksum, cfgChecksum = 0;252 buf.readInt(linenoChecksum);253 if (version >= GCOV::V407)254 buf.readInt(cfgChecksum);255 if (It != identToFunction.end()) {256 fn = It->second;257 if (linenoChecksum != fn->linenoChecksum ||258 cfgChecksum != fn->cfgChecksum) {259 errs() << fn->Name260 << format(": checksum mismatch, (%u, %u) != (%u, %u)\n",261 linenoChecksum, cfgChecksum, fn->linenoChecksum,262 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->Name272 << format(273 ": GCOV_TAG_COUNTER_ARCS mismatch, got %u, expected %u\n",274 length, expected);275 return false;276 }277 for (std::unique_ptr<GCOVArc> &arc : fn->arcs) {278 if (!buf.readInt64(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>(sink, src, GCOV_ARC_ON_TREE);288 sink.addDstEdge(arc.get());289 src.addSrcEdge(arc.get());290 fn->treeArcs.push_back(std::move(arc));291 292 for (GCOVBlock &block : fn->blocksRange())293 fn->propagateCounts(block, 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(buf.cursor, pos - buf.cursor.tell());302 }303 304 return true;305}306 307void 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.314LLVM_DUMP_METHOD void GCOVFile::dump() const { print(dbgs()); }315#endif316 317unsigned GCOVFile::addNormalizedPathToMap(StringRef filename) {318 // unify filename, as the same path can have different form319 SmallString<256> P(filename);320 sys::path::remove_dots(P, true);321 filename = P.str();322 323 auto r = filenameToIdx.try_emplace(filename, filenameToIdx.size());324 if (r.second)325 filenames.emplace_back(filename);326 327 return r.first->second;328}329 330bool GCOVArc::onTree() const { return flags & GCOV_ARC_ON_TREE; }331 332//===----------------------------------------------------------------------===//333// GCOVFunction implementation.334 335StringRef GCOVFunction::getName(bool demangle) const {336 if (!demangle)337 return Name;338 if (demangled.empty()) {339 do {340 if (Name.starts_with("_Z")) {341 // Name is guaranteed to be NUL-terminated.342 if (char *res = itaniumDemangle(Name.data())) {343 demangled = res;344 free(res);345 break;346 }347 }348 demangled = Name;349 } while (false);350 }351 return demangled;352}353StringRef GCOVFunction::getFilename() const { return file.filenames[srcIdx]; }354 355/// getEntryCount - Get the number of times the function was called by356/// retrieving the entry block's count.357uint64_t GCOVFunction::getEntryCount() const {358 return blocks.front()->getCount();359}360 361GCOVBlock &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 of366// outgoing edge counts by Kirchoff's circuit law. If the unmeasured arcs form a367// spanning tree, the count for each unmeasured arc (GCOV_ARC_ON_TREE) can be368// uniquely identified. Use an iterative algorithm to decrease stack usage for369// library users in threads. See the edge propagation algorithm in Optimally370// Profiling and Tracing Programs, ACM Transactions on Programming Languages and371// Systems, 1994.372void 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({v, pred, 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(&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({e->src, e, /*inDst=*/false});398 else399 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({e->dst, e, /*inDst=*/true});406 else407 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 424void 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.433LLVM_DUMP_METHOD void GCOVFunction::dump() const { print(dbgs()); }434#endif435 436/// collectLineCounts - Collect line counts. This must be used after437/// reading .gcno and .gcda files.438 439//===----------------------------------------------------------------------===//440// GCOVBlock implementation.441 442void 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.471LLVM_DUMP_METHOD void GCOVBlock::dump() const { print(dbgs()); }472#endif473 474uint64_t475GCOVBlock::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(src, 0);481 src->incoming = (GCOVArc *)1; // Mark u available for cycle detection482 for (;;) {483 std::tie(u, 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 visited494 // blocks. Ignore self arcs to guard against bad input (.gcno has no495 // 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(&succ->dst, 0);501 continue;502 }503 uint64_t minCount = succ->cycleCount;504 for (GCOVBlock *v = u;;) {505 minCount = std::min(minCount, 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 cycle525// cancelling iteratively.526uint64_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(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 greater558// than 0% and less than 1%.559static 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 number568// - Only output 0% or 100% if it's exactly that value569static 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 583namespace {584struct 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 else594 OS << "taken " << branchDiv(Count, Total) << "%";595 }596 597 const GCOV::Options &Options;598 uint64_t Count;599 uint64_t Total;600};601 602static raw_ostream &operator<<(raw_ostream &OS, const formatBranchInfo &FBI) {603 FBI.print(OS);604 return OS;605}606 607class LineConsumer {608 std::unique_ptr<MemoryBuffer> Buffer;609 StringRef Remaining;610 611public:612 LineConsumer() = default;613 LineConsumer(StringRef Filename) {614 // Open source files without requiring a NUL terminator. The concurrent615 // 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 else633 std::tie(Line, Remaining) = Remaining.split("\n");634 OS << format("%5u:", LineNum) << Line << "\n";635 }636};637} // end anonymous namespace638 639/// Convert a path to a gcov filename. If PreservePaths is true, this640/// translates "/" to "#", ".." to "^", and drops ".", to match gcov.641static std::string mangleCoveragePath(StringRef Filename, bool PreservePaths) {642 if (!PreservePaths)643 return sys::path::filename(Filename).str();644 645 // This behaviour is defined by gcov in terms of text replacements, so it's646 // not likely to do anything useful on filesystems with different textual647 // 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("^#");659 } else {660 if (S < I)661 // Leave other components intact,662 Result.append(S, I);663 // And separate with "#".664 Result.push_back('#');665 }666 S = I + 1;667 }668 669 if (S < I)670 Result.append(S, I);671 return std::string(Result);672}673 674std::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't678 // mangled at all, and the -l and -p options are ignored. Here, we do the679 // same.680 return std::string(filename);681 682 std::string CoveragePath;683 if (options.LongFileNames && filename != mainFilename)684 CoveragePath =685 mangleCoveragePath(mainFilename, options.PreservePaths) + "##";686 CoveragePath += mangleCoveragePath(filename, options.PreservePaths);687 if (options.HashFilenames) {688 MD5 Hasher;689 MD5::MD5Result Result;690 Hasher.update(filename.str());691 Hasher.final(Result);692 CoveragePath += "##" + std::string(Result.digest());693 }694 CoveragePath += ".gcov";695 return CoveragePath;696}697 698void Context::collectFunction(GCOVFunction &f, Summary &summary) {699 SourceInfo &si = sources[f.srcIdx];700 if (f.startLine >= si.startLineToFunctions.size())701 si.startLineToFunctions.resize(f.startLine + 1);702 si.startLineToFunctions[f.startLine].push_back(&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(loc.lines);711 if (maxLineNum >= locSource.lines.size())712 locSource.lines.resize(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(&b);718 if (f.srcIdx == loc.srcIdx) {719 if (lines.insert(lineNum).second)720 ++summary.lines;721 if (b.count && linesExec.insert(lineNum).second)722 ++summary.linesExec;723 }724 }725 }726 }727}728 729void 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 be735 // duplicately counted (fork) or not be counted (abnormal exit), and thus736 // the (exit,entry) counter may be inaccurate. Count the entry block with737 // 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(line.blocks, &arc->src))744 count += arc->count;745 }746 for (GCOVArc *arc : b->succ)747 arc->cycleCount = arc->count;748 }749 750 count += GCOVBlock::getCyclesCount(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 778void Context::collectSource(SourceInfo &si, Summary &summary) const {779 size_t lineNum = 0;780 for (LineInfo &line : si.lines) {781 collectSourceLine(si, &summary, line, lineNum);782 ++lineNum;783 }784}785 786void 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, 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, os);810 if (!line.exists)811 os << " -:";812 else if (line.count == 0)813 os << " #####:";814 else815 os << format("%9" PRIu64 ":", line.count);816 source.printNext(os, 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 else826 os << format("%9" PRIu64 ":", b->count);827 os << format("%5u-block %2u\n", lineNum, blockIdx++);828 }829 if (options.BranchInfo) {830 size_t NumEdges = b->succ.size();831 if (NumEdges > 1)832 printBranchInfo(*b, edgeIdx, os);833 else if (options.UncondBranch && NumEdges == 1) {834 uint64_t count = b->succ[0]->count;835 os << format("unconditional %2u ", edgeIdx++)836 << formatBranchInfo(options, count, count) << '\n';837 }838 }839 }840 }841}842 843void 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(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 873void Context::print(StringRef filename, StringRef gcno, StringRef gcda,874 GCOVFile &file) {875 for (StringRef filename : file.filenames) {876 sources.emplace_back(filename);877 SourceInfo &si = sources.back();878 si.displayName = si.filename;879 if (!options.SourcePrefix.empty() &&880 sys::path::replace_path_prefix(si.displayName, options.SourcePrefix,881 "") &&882 !si.displayName.empty()) {883 // TODO replace_path_prefix may strip the prefix even if the remaining884 // part does not start with a separator.885 if (sys::path::is_separator(si.displayName[0]))886 si.displayName.erase(si.displayName.begin());887 else888 si.displayName = si.filename;889 }890 if (options.RelativeOnly && sys::path::is_absolute(si.displayName))891 si.ignored = true;892 }893 894 raw_ostream &os = llvm::outs();895 for (GCOVFunction &f : make_pointee_range(file.functions)) {896 Summary summary(f.getName(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(si.filename, 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(gcovName, ec, sys::fs::OF_TextWithCRLF);927 if (ec) {928 errs() << ec.message() << '\n';929 continue;930 }931 }932 annotateSource(si, file, gcno, gcda,933 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.0938 // (PR GCC/82702). We create just one file.939 std::string outputPath(sys::path::filename(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 952void 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(options.Demangle) << " called " << entryCount965 << " returned " << formatPercentage(exitCount, entryCount)966 << "% blocks executed "967 << formatPercentage(blocksExec, f.blocks.size() - 2) << "%\n";968}969 970/// printBranchInfo - Print conditional branch probabilities.971void 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("branch %2u ", edgeIdx++)978 << formatBranchInfo(options, arc->count, total) << '\n';979}980 981void Context::printSummary(const Summary &summary, raw_ostream &os) const {982 os << format("Lines executed:%.2f%% of %" PRIu64 "\n",983 double(summary.linesExec) * 100 / summary.lines, summary.lines);984 if (options.BranchInfo) {985 if (summary.branches == 0) {986 os << "No branches\n";987 } else {988 os << format("Branches executed:%.2f%% of %" PRIu64 "\n",989 double(summary.branchesExec) * 100 / summary.branches,990 summary.branches);991 os << format("Taken at least once:%.2f%% of %" PRIu64 "\n",992 double(summary.branchesTaken) * 100 / summary.branches,993 summary.branches);994 }995 os << "No calls\n";996 }997}998 999void 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