| //===- GCOV.cpp - LLVM coverage tool --------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // GCOV implements the interface to read and write coverage files that use |
| // 'gcov' format. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ProfileData/GCOV.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/Config/llvm-config.h" |
| #include "llvm/Demangle/Demangle.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Format.h" |
| #include "llvm/Support/MD5.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <algorithm> |
| #include <system_error> |
| #include <unordered_map> |
| |
| using namespace llvm; |
| |
| enum : uint32_t { |
| GCOV_ARC_ON_TREE = 1 << 0, |
| GCOV_ARC_FALLTHROUGH = 1 << 2, |
| |
| GCOV_TAG_FUNCTION = 0x01000000, |
| GCOV_TAG_BLOCKS = 0x01410000, |
| GCOV_TAG_ARCS = 0x01430000, |
| GCOV_TAG_LINES = 0x01450000, |
| GCOV_TAG_COUNTER_ARCS = 0x01a10000, |
| // GCOV_TAG_OBJECT_SUMMARY superseded GCOV_TAG_PROGRAM_SUMMARY in GCC 9. |
| GCOV_TAG_OBJECT_SUMMARY = 0xa1000000, |
| GCOV_TAG_PROGRAM_SUMMARY = 0xa3000000, |
| }; |
| |
| namespace { |
| struct Summary { |
| Summary(StringRef Name) : Name(Name) {} |
| |
| StringRef Name; |
| uint64_t lines = 0; |
| uint64_t linesExec = 0; |
| uint64_t branches = 0; |
| uint64_t branchesExec = 0; |
| uint64_t branchesTaken = 0; |
| }; |
| |
| struct LineInfo { |
| SmallVector<const GCOVBlock *, 1> blocks; |
| uint64_t count = 0; |
| bool exists = false; |
| }; |
| |
| struct SourceInfo { |
| StringRef filename; |
| SmallString<0> displayName; |
| std::vector<std::vector<const GCOVFunction *>> startLineToFunctions; |
| std::vector<LineInfo> lines; |
| bool ignored = false; |
| SourceInfo(StringRef filename) : filename(filename) {} |
| }; |
| |
| class Context { |
| public: |
| Context(const GCOV::Options &Options) : options(Options) {} |
| void print(StringRef filename, StringRef gcno, StringRef gcda, |
| GCOVFile &file); |
| |
| private: |
| std::string getCoveragePath(StringRef filename, StringRef mainFilename) const; |
| void printFunctionDetails(const GCOVFunction &f, raw_ostream &os) const; |
| void printBranchInfo(const GCOVBlock &Block, uint32_t &edgeIdx, |
| raw_ostream &OS) const; |
| void printSummary(const Summary &summary, raw_ostream &os) const; |
| |
| void collectFunction(GCOVFunction &f, Summary &summary); |
| void collectSourceLine(SourceInfo &si, Summary *summary, LineInfo &line, |
| size_t lineNum) const; |
| void collectSource(SourceInfo &si, Summary &summary) const; |
| void annotateSource(SourceInfo &si, const GCOVFile &file, StringRef gcno, |
| StringRef gcda, raw_ostream &os) const; |
| void printSourceToIntermediate(const SourceInfo &si, raw_ostream &os) const; |
| |
| const GCOV::Options &options; |
| std::vector<SourceInfo> sources; |
| }; |
| } // namespace |
| |
| //===----------------------------------------------------------------------===// |
| // GCOVFile implementation. |
| |
| /// readGCNO - Read GCNO buffer. |
| bool GCOVFile::readGCNO(GCOVBuffer &buf) { |
| if (!buf.readGCNOFormat()) |
| return false; |
| if (!buf.readGCOVVersion(version)) |
| return false; |
| |
| checksum = buf.getWord(); |
| if (version >= GCOV::V900 && !buf.readString(cwd)) |
| return false; |
| if (version >= GCOV::V800) |
| buf.getWord(); // hasUnexecutedBlocks |
| |
| uint32_t tag, length; |
| GCOVFunction *fn = nullptr; |
| while ((tag = buf.getWord())) { |
| if (!buf.readInt(length)) |
| return false; |
| uint32_t pos = buf.cursor.tell(); |
| if (tag == GCOV_TAG_FUNCTION) { |
| functions.push_back(std::make_unique<GCOVFunction>(*this)); |
| fn = functions.back().get(); |
| fn->ident = buf.getWord(); |
| fn->linenoChecksum = buf.getWord(); |
| if (version >= GCOV::V407) |
| fn->cfgChecksum = buf.getWord(); |
| buf.readString(fn->Name); |
| StringRef filename; |
| if (version < GCOV::V800) { |
| if (!buf.readString(filename)) |
| return false; |
| fn->startLine = buf.getWord(); |
| } else { |
| fn->artificial = buf.getWord(); |
| if (!buf.readString(filename)) |
| return false; |
| fn->startLine = buf.getWord(); |
| fn->startColumn = buf.getWord(); |
| fn->endLine = buf.getWord(); |
| if (version >= GCOV::V900) |
| fn->endColumn = buf.getWord(); |
| } |
| auto r = filenameToIdx.try_emplace(filename, filenameToIdx.size()); |
| if (r.second) |
| filenames.emplace_back(filename); |
| fn->srcIdx = r.first->second; |
| identToFunction[fn->ident] = fn; |
| } else if (tag == GCOV_TAG_BLOCKS && fn) { |
| if (version < GCOV::V800) { |
| for (uint32_t i = 0; i != length; ++i) { |
| buf.getWord(); // Ignored block flags |
| fn->blocks.push_back(std::make_unique<GCOVBlock>(i)); |
| } |
| } else { |
| uint32_t num = buf.getWord(); |
| for (uint32_t i = 0; i != num; ++i) |
| fn->blocks.push_back(std::make_unique<GCOVBlock>(i)); |
| } |
| } else if (tag == GCOV_TAG_ARCS && fn) { |
| uint32_t srcNo = buf.getWord(); |
| if (srcNo >= fn->blocks.size()) { |
| errs() << "unexpected block number: " << srcNo << " (in " |
| << fn->blocks.size() << ")\n"; |
| return false; |
| } |
| GCOVBlock *src = fn->blocks[srcNo].get(); |
| const uint32_t e = |
| version >= GCOV::V1200 ? (length / 4 - 1) / 2 : (length - 1) / 2; |
| for (uint32_t i = 0; i != e; ++i) { |
| uint32_t dstNo = buf.getWord(), flags = buf.getWord(); |
| GCOVBlock *dst = fn->blocks[dstNo].get(); |
| auto arc = std::make_unique<GCOVArc>(*src, *dst, flags); |
| src->addDstEdge(arc.get()); |
| dst->addSrcEdge(arc.get()); |
| if (arc->onTree()) |
| fn->treeArcs.push_back(std::move(arc)); |
| else |
| fn->arcs.push_back(std::move(arc)); |
| } |
| } else if (tag == GCOV_TAG_LINES && fn) { |
| uint32_t srcNo = buf.getWord(); |
| if (srcNo >= fn->blocks.size()) { |
| errs() << "unexpected block number: " << srcNo << " (in " |
| << fn->blocks.size() << ")\n"; |
| return false; |
| } |
| GCOVBlock &Block = *fn->blocks[srcNo]; |
| for (;;) { |
| uint32_t line = buf.getWord(); |
| if (line) |
| Block.addLine(line); |
| else { |
| StringRef filename; |
| buf.readString(filename); |
| if (filename.empty()) |
| break; |
| // TODO Unhandled |
| } |
| } |
| } |
| pos += version >= GCOV::V1200 ? length : 4 * length; |
| if (pos < buf.cursor.tell()) |
| return false; |
| buf.de.skip(buf.cursor, pos - buf.cursor.tell()); |
| } |
| |
| GCNOInitialized = true; |
| return true; |
| } |
| |
| /// readGCDA - Read GCDA buffer. It is required that readGCDA() can only be |
| /// called after readGCNO(). |
| bool GCOVFile::readGCDA(GCOVBuffer &buf) { |
| assert(GCNOInitialized && "readGCDA() can only be called after readGCNO()"); |
| if (!buf.readGCDAFormat()) |
| return false; |
| GCOV::GCOVVersion GCDAVersion; |
| if (!buf.readGCOVVersion(GCDAVersion)) |
| return false; |
| if (version != GCDAVersion) { |
| errs() << "GCOV versions do not match.\n"; |
| return false; |
| } |
| |
| uint32_t GCDAChecksum; |
| if (!buf.readInt(GCDAChecksum)) |
| return false; |
| if (checksum != GCDAChecksum) { |
| errs() << "file checksums do not match: " << checksum |
| << " != " << GCDAChecksum << "\n"; |
| return false; |
| } |
| uint32_t dummy, tag, length; |
| uint32_t ident; |
| GCOVFunction *fn = nullptr; |
| while ((tag = buf.getWord())) { |
| if (!buf.readInt(length)) |
| return false; |
| uint32_t pos = buf.cursor.tell(); |
| if (tag == GCOV_TAG_OBJECT_SUMMARY) { |
| buf.readInt(runCount); |
| buf.readInt(dummy); |
| // clang<11 uses a fake 4.2 format which sets length to 9. |
| if (length == 9) |
| buf.readInt(runCount); |
| } else if (tag == GCOV_TAG_PROGRAM_SUMMARY) { |
| // clang<11 uses a fake 4.2 format which sets length to 0. |
| if (length > 0) { |
| buf.readInt(dummy); |
| buf.readInt(dummy); |
| buf.readInt(runCount); |
| } |
| ++programCount; |
| } else if (tag == GCOV_TAG_FUNCTION) { |
| if (length == 0) // Placeholder |
| continue; |
| // As of GCC 10, GCOV_TAG_FUNCTION_LENGTH has never been larger than 3. |
| // However, clang<11 uses a fake 4.2 format which may set length larger |
| // than 3. |
| if (length < 2 || !buf.readInt(ident)) |
| return false; |
| auto It = identToFunction.find(ident); |
| uint32_t linenoChecksum, cfgChecksum = 0; |
| buf.readInt(linenoChecksum); |
| if (version >= GCOV::V407) |
| buf.readInt(cfgChecksum); |
| if (It != identToFunction.end()) { |
| fn = It->second; |
| if (linenoChecksum != fn->linenoChecksum || |
| cfgChecksum != fn->cfgChecksum) { |
| errs() << fn->Name |
| << format(": checksum mismatch, (%u, %u) != (%u, %u)\n", |
| linenoChecksum, cfgChecksum, fn->linenoChecksum, |
| fn->cfgChecksum); |
| return false; |
| } |
| } |
| } else if (tag == GCOV_TAG_COUNTER_ARCS && fn) { |
| uint32_t expected = 2 * fn->arcs.size(); |
| if (version >= GCOV::V1200) |
| expected *= 4; |
| if (length != expected) { |
| errs() << fn->Name |
| << format( |
| ": GCOV_TAG_COUNTER_ARCS mismatch, got %u, expected %u\n", |
| length, expected); |
| return false; |
| } |
| for (std::unique_ptr<GCOVArc> &arc : fn->arcs) { |
| if (!buf.readInt64(arc->count)) |
| return false; |
| arc->src.count += arc->count; |
| } |
| |
| if (fn->blocks.size() >= 2) { |
| GCOVBlock &src = *fn->blocks[0]; |
| GCOVBlock &sink = |
| version < GCOV::V408 ? *fn->blocks.back() : *fn->blocks[1]; |
| auto arc = std::make_unique<GCOVArc>(sink, src, GCOV_ARC_ON_TREE); |
| sink.addDstEdge(arc.get()); |
| src.addSrcEdge(arc.get()); |
| fn->treeArcs.push_back(std::move(arc)); |
| |
| for (GCOVBlock &block : fn->blocksRange()) |
| fn->propagateCounts(block, nullptr); |
| for (size_t i = fn->treeArcs.size() - 1; i; --i) |
| fn->treeArcs[i - 1]->src.count += fn->treeArcs[i - 1]->count; |
| } |
| } |
| pos += version >= GCOV::V1200 ? length : 4 * length; |
| if (pos < buf.cursor.tell()) |
| return false; |
| buf.de.skip(buf.cursor, pos - buf.cursor.tell()); |
| } |
| |
| return true; |
| } |
| |
| void GCOVFile::print(raw_ostream &OS) const { |
| for (const GCOVFunction &f : *this) |
| f.print(OS); |
| } |
| |
| #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) |
| /// dump - Dump GCOVFile content to dbgs() for debugging purposes. |
| LLVM_DUMP_METHOD void GCOVFile::dump() const { print(dbgs()); } |
| #endif |
| |
| bool GCOVArc::onTree() const { return flags & GCOV_ARC_ON_TREE; } |
| |
| //===----------------------------------------------------------------------===// |
| // GCOVFunction implementation. |
| |
| StringRef GCOVFunction::getName(bool demangle) const { |
| if (!demangle) |
| return Name; |
| if (demangled.empty()) { |
| do { |
| if (Name.startswith("_Z")) { |
| int status = 0; |
| // Name is guaranteed to be NUL-terminated. |
| char *res = itaniumDemangle(Name.data(), nullptr, nullptr, &status); |
| if (status == 0) { |
| demangled = res; |
| free(res); |
| break; |
| } |
| } |
| demangled = Name; |
| } while (0); |
| } |
| return demangled; |
| } |
| StringRef GCOVFunction::getFilename() const { return file.filenames[srcIdx]; } |
| |
| /// getEntryCount - Get the number of times the function was called by |
| /// retrieving the entry block's count. |
| uint64_t GCOVFunction::getEntryCount() const { |
| return blocks.front()->getCount(); |
| } |
| |
| GCOVBlock &GCOVFunction::getExitBlock() const { |
| return file.getVersion() < GCOV::V408 ? *blocks.back() : *blocks[1]; |
| } |
| |
| // For each basic block, the sum of incoming edge counts equals the sum of |
| // outgoing edge counts by Kirchoff's circuit law. If the unmeasured arcs form a |
| // spanning tree, the count for each unmeasured arc (GCOV_ARC_ON_TREE) can be |
| // uniquely identified. |
| uint64_t GCOVFunction::propagateCounts(const GCOVBlock &v, GCOVArc *pred) { |
| // If GCOV_ARC_ON_TREE edges do form a tree, visited is not needed; otherwise |
| // this prevents infinite recursion. |
| if (!visited.insert(&v).second) |
| return 0; |
| |
| uint64_t excess = 0; |
| for (GCOVArc *e : v.srcs()) |
| if (e != pred) |
| excess += e->onTree() ? propagateCounts(e->src, e) : e->count; |
| for (GCOVArc *e : v.dsts()) |
| if (e != pred) |
| excess -= e->onTree() ? propagateCounts(e->dst, e) : e->count; |
| if (int64_t(excess) < 0) |
| excess = -excess; |
| if (pred) |
| pred->count = excess; |
| return excess; |
| } |
| |
| void GCOVFunction::print(raw_ostream &OS) const { |
| OS << "===== " << Name << " (" << ident << ") @ " << getFilename() << ":" |
| << startLine << "\n"; |
| for (const auto &Block : blocks) |
| Block->print(OS); |
| } |
| |
| #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) |
| /// dump - Dump GCOVFunction content to dbgs() for debugging purposes. |
| LLVM_DUMP_METHOD void GCOVFunction::dump() const { print(dbgs()); } |
| #endif |
| |
| /// collectLineCounts - Collect line counts. This must be used after |
| /// reading .gcno and .gcda files. |
| |
| //===----------------------------------------------------------------------===// |
| // GCOVBlock implementation. |
| |
| void GCOVBlock::print(raw_ostream &OS) const { |
| OS << "Block : " << number << " Counter : " << count << "\n"; |
| if (!pred.empty()) { |
| OS << "\tSource Edges : "; |
| for (const GCOVArc *Edge : pred) |
| OS << Edge->src.number << " (" << Edge->count << "), "; |
| OS << "\n"; |
| } |
| if (!succ.empty()) { |
| OS << "\tDestination Edges : "; |
| for (const GCOVArc *Edge : succ) { |
| if (Edge->flags & GCOV_ARC_ON_TREE) |
| OS << '*'; |
| OS << Edge->dst.number << " (" << Edge->count << "), "; |
| } |
| OS << "\n"; |
| } |
| if (!lines.empty()) { |
| OS << "\tLines : "; |
| for (uint32_t N : lines) |
| OS << (N) << ","; |
| OS << "\n"; |
| } |
| } |
| |
| #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) |
| /// dump - Dump GCOVBlock content to dbgs() for debugging purposes. |
| LLVM_DUMP_METHOD void GCOVBlock::dump() const { print(dbgs()); } |
| #endif |
| |
| uint64_t |
| GCOVBlock::augmentOneCycle(GCOVBlock *src, |
| std::vector<std::pair<GCOVBlock *, size_t>> &stack) { |
| GCOVBlock *u; |
| size_t i; |
| stack.clear(); |
| stack.emplace_back(src, 0); |
| src->incoming = (GCOVArc *)1; // Mark u available for cycle detection |
| for (;;) { |
| std::tie(u, i) = stack.back(); |
| if (i == u->succ.size()) { |
| u->traversable = false; |
| stack.pop_back(); |
| if (stack.empty()) |
| break; |
| continue; |
| } |
| ++stack.back().second; |
| GCOVArc *succ = u->succ[i]; |
| // Ignore saturated arcs (cycleCount has been reduced to 0) and visited |
| // blocks. Ignore self arcs to guard against bad input (.gcno has no |
| // self arcs). |
| if (succ->cycleCount == 0 || !succ->dst.traversable || &succ->dst == u) |
| continue; |
| if (succ->dst.incoming == nullptr) { |
| succ->dst.incoming = succ; |
| stack.emplace_back(&succ->dst, 0); |
| continue; |
| } |
| uint64_t minCount = succ->cycleCount; |
| for (GCOVBlock *v = u;;) { |
| minCount = std::min(minCount, v->incoming->cycleCount); |
| v = &v->incoming->src; |
| if (v == &succ->dst) |
| break; |
| } |
| succ->cycleCount -= minCount; |
| for (GCOVBlock *v = u;;) { |
| v->incoming->cycleCount -= minCount; |
| v = &v->incoming->src; |
| if (v == &succ->dst) |
| break; |
| } |
| return minCount; |
| } |
| return 0; |
| } |
| |
| // Get the total execution count of loops among blocks on the same line. |
| // Assuming a reducible flow graph, the count is the sum of back edge counts. |
| // Identifying loops is complex, so we simply find cycles and perform cycle |
| // cancelling iteratively. |
| uint64_t GCOVBlock::getCyclesCount(const BlockVector &blocks) { |
| std::vector<std::pair<GCOVBlock *, size_t>> stack; |
| uint64_t count = 0, d; |
| for (;;) { |
| // Make blocks on the line traversable and try finding a cycle. |
| for (auto b : blocks) { |
| const_cast<GCOVBlock *>(b)->traversable = true; |
| const_cast<GCOVBlock *>(b)->incoming = nullptr; |
| } |
| d = 0; |
| for (auto block : blocks) { |
| auto *b = const_cast<GCOVBlock *>(block); |
| if (b->traversable && (d = augmentOneCycle(b, stack)) > 0) |
| break; |
| } |
| if (d == 0) |
| break; |
| count += d; |
| } |
| // If there is no more loop, all traversable bits should have been cleared. |
| // This property is needed by subsequent calls. |
| for (auto b : blocks) { |
| assert(!b->traversable); |
| (void)b; |
| } |
| return count; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // FileInfo implementation. |
| |
| // Format dividend/divisor as a percentage. Return 1 if the result is greater |
| // than 0% and less than 1%. |
| static uint32_t formatPercentage(uint64_t dividend, uint64_t divisor) { |
| if (!dividend || !divisor) |
| return 0; |
| dividend *= 100; |
| return dividend < divisor ? 1 : dividend / divisor; |
| } |
| |
| // This custom division function mimics gcov's branch ouputs: |
| // - Round to closest whole number |
| // - Only output 0% or 100% if it's exactly that value |
| static uint32_t branchDiv(uint64_t Numerator, uint64_t Divisor) { |
| if (!Numerator) |
| return 0; |
| if (Numerator == Divisor) |
| return 100; |
| |
| uint8_t Res = (Numerator * 100 + Divisor / 2) / Divisor; |
| if (Res == 0) |
| return 1; |
| if (Res == 100) |
| return 99; |
| return Res; |
| } |
| |
| namespace { |
| struct formatBranchInfo { |
| formatBranchInfo(const GCOV::Options &Options, uint64_t Count, uint64_t Total) |
| : Options(Options), Count(Count), Total(Total) {} |
| |
| void print(raw_ostream &OS) const { |
| if (!Total) |
| OS << "never executed"; |
| else if (Options.BranchCount) |
| OS << "taken " << Count; |
| else |
| OS << "taken " << branchDiv(Count, Total) << "%"; |
| } |
| |
| const GCOV::Options &Options; |
| uint64_t Count; |
| uint64_t Total; |
| }; |
| |
| static raw_ostream &operator<<(raw_ostream &OS, const formatBranchInfo &FBI) { |
| FBI.print(OS); |
| return OS; |
| } |
| |
| class LineConsumer { |
| std::unique_ptr<MemoryBuffer> Buffer; |
| StringRef Remaining; |
| |
| public: |
| LineConsumer() = default; |
| LineConsumer(StringRef Filename) { |
| // Open source files without requiring a NUL terminator. The concurrent |
| // modification may nullify the NUL terminator condition. |
| ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = |
| MemoryBuffer::getFileOrSTDIN(Filename, /*IsText=*/false, |
| /*RequiresNullTerminator=*/false); |
| if (std::error_code EC = BufferOrErr.getError()) { |
| errs() << Filename << ": " << EC.message() << "\n"; |
| Remaining = ""; |
| } else { |
| Buffer = std::move(BufferOrErr.get()); |
| Remaining = Buffer->getBuffer(); |
| } |
| } |
| bool empty() { return Remaining.empty(); } |
| void printNext(raw_ostream &OS, uint32_t LineNum) { |
| StringRef Line; |
| if (empty()) |
| Line = "/*EOF*/"; |
| else |
| std::tie(Line, Remaining) = Remaining.split("\n"); |
| OS << format("%5u:", LineNum) << Line << "\n"; |
| } |
| }; |
| } // end anonymous namespace |
| |
| /// Convert a path to a gcov filename. If PreservePaths is true, this |
| /// translates "/" to "#", ".." to "^", and drops ".", to match gcov. |
| static std::string mangleCoveragePath(StringRef Filename, bool PreservePaths) { |
| if (!PreservePaths) |
| return sys::path::filename(Filename).str(); |
| |
| // This behaviour is defined by gcov in terms of text replacements, so it's |
| // not likely to do anything useful on filesystems with different textual |
| // conventions. |
| llvm::SmallString<256> Result(""); |
| StringRef::iterator I, S, E; |
| for (I = S = Filename.begin(), E = Filename.end(); I != E; ++I) { |
| if (*I != '/') |
| continue; |
| |
| if (I - S == 1 && *S == '.') { |
| // ".", the current directory, is skipped. |
| } else if (I - S == 2 && *S == '.' && *(S + 1) == '.') { |
| // "..", the parent directory, is replaced with "^". |
| Result.append("^#"); |
| } else { |
| if (S < I) |
| // Leave other components intact, |
| Result.append(S, I); |
| // And separate with "#". |
| Result.push_back('#'); |
| } |
| S = I + 1; |
| } |
| |
| if (S < I) |
| Result.append(S, I); |
| return std::string(Result.str()); |
| } |
| |
| std::string Context::getCoveragePath(StringRef filename, |
| StringRef mainFilename) const { |
| if (options.NoOutput) |
| // This is probably a bug in gcov, but when -n is specified, paths aren't |
| // mangled at all, and the -l and -p options are ignored. Here, we do the |
| // same. |
| return std::string(filename); |
| |
| std::string CoveragePath; |
| if (options.LongFileNames && !filename.equals(mainFilename)) |
| CoveragePath = |
| mangleCoveragePath(mainFilename, options.PreservePaths) + "##"; |
| CoveragePath += mangleCoveragePath(filename, options.PreservePaths); |
| if (options.HashFilenames) { |
| MD5 Hasher; |
| MD5::MD5Result Result; |
| Hasher.update(filename.str()); |
| Hasher.final(Result); |
| CoveragePath += "##" + std::string(Result.digest()); |
| } |
| CoveragePath += ".gcov"; |
| return CoveragePath; |
| } |
| |
| void Context::collectFunction(GCOVFunction &f, Summary &summary) { |
| SourceInfo &si = sources[f.srcIdx]; |
| if (f.startLine >= si.startLineToFunctions.size()) |
| si.startLineToFunctions.resize(f.startLine + 1); |
| si.startLineToFunctions[f.startLine].push_back(&f); |
| for (const GCOVBlock &b : f.blocksRange()) { |
| if (b.lines.empty()) |
| continue; |
| uint32_t maxLineNum = *std::max_element(b.lines.begin(), b.lines.end()); |
| if (maxLineNum >= si.lines.size()) |
| si.lines.resize(maxLineNum + 1); |
| for (uint32_t lineNum : b.lines) { |
| LineInfo &line = si.lines[lineNum]; |
| if (!line.exists) |
| ++summary.lines; |
| if (line.count == 0 && b.count) |
| ++summary.linesExec; |
| line.exists = true; |
| line.count += b.count; |
| line.blocks.push_back(&b); |
| } |
| } |
| } |
| |
| void Context::collectSourceLine(SourceInfo &si, Summary *summary, |
| LineInfo &line, size_t lineNum) const { |
| uint64_t count = 0; |
| for (const GCOVBlock *b : line.blocks) { |
| if (b->number == 0) { |
| // For nonstandard control flows, arcs into the exit block may be |
| // duplicately counted (fork) or not be counted (abnormal exit), and thus |
| // the (exit,entry) counter may be inaccurate. Count the entry block with |
| // the outgoing arcs. |
| for (const GCOVArc *arc : b->succ) |
| count += arc->count; |
| } else { |
| // Add counts from predecessors that are not on the same line. |
| for (const GCOVArc *arc : b->pred) |
| if (!llvm::is_contained(line.blocks, &arc->src)) |
| count += arc->count; |
| } |
| for (GCOVArc *arc : b->succ) |
| arc->cycleCount = arc->count; |
| } |
| |
| count += GCOVBlock::getCyclesCount(line.blocks); |
| line.count = count; |
| if (line.exists) { |
| ++summary->lines; |
| if (line.count != 0) |
| ++summary->linesExec; |
| } |
| |
| if (options.BranchInfo) |
| for (const GCOVBlock *b : line.blocks) { |
| if (b->getLastLine() != lineNum) |
| continue; |
| int branches = 0, execBranches = 0, takenBranches = 0; |
| for (const GCOVArc *arc : b->succ) { |
| ++branches; |
| if (count != 0) |
| ++execBranches; |
| if (arc->count != 0) |
| ++takenBranches; |
| } |
| if (branches > 1) { |
| summary->branches += branches; |
| summary->branchesExec += execBranches; |
| summary->branchesTaken += takenBranches; |
| } |
| } |
| } |
| |
| void Context::collectSource(SourceInfo &si, Summary &summary) const { |
| size_t lineNum = 0; |
| for (LineInfo &line : si.lines) { |
| collectSourceLine(si, &summary, line, lineNum); |
| ++lineNum; |
| } |
| } |
| |
| void Context::annotateSource(SourceInfo &si, const GCOVFile &file, |
| StringRef gcno, StringRef gcda, |
| raw_ostream &os) const { |
| auto source = |
| options.Intermediate ? LineConsumer() : LineConsumer(si.filename); |
| |
| os << " -: 0:Source:" << si.displayName << '\n'; |
| os << " -: 0:Graph:" << gcno << '\n'; |
| os << " -: 0:Data:" << gcda << '\n'; |
| os << " -: 0:Runs:" << file.runCount << '\n'; |
| if (file.version < GCOV::V900) |
| os << " -: 0:Programs:" << file.programCount << '\n'; |
| |
| for (size_t lineNum = 1; !source.empty(); ++lineNum) { |
| if (lineNum >= si.lines.size()) { |
| os << " -:"; |
| source.printNext(os, lineNum); |
| continue; |
| } |
| |
| const LineInfo &line = si.lines[lineNum]; |
| if (options.BranchInfo && lineNum < si.startLineToFunctions.size()) |
| for (const auto *f : si.startLineToFunctions[lineNum]) |
| printFunctionDetails(*f, os); |
| if (!line.exists) |
| os << " -:"; |
| else if (line.count == 0) |
| os << " #####:"; |
| else |
| os << format("%9" PRIu64 ":", line.count); |
| source.printNext(os, lineNum); |
| |
| uint32_t blockIdx = 0, edgeIdx = 0; |
| for (const GCOVBlock *b : line.blocks) { |
| if (b->getLastLine() != lineNum) |
| continue; |
| if (options.AllBlocks) { |
| if (b->getCount() == 0) |
| os << " $$$$$:"; |
| else |
| os << format("%9" PRIu64 ":", b->count); |
| os << format("%5u-block %2u\n", lineNum, blockIdx++); |
| } |
| if (options.BranchInfo) { |
| size_t NumEdges = b->succ.size(); |
| if (NumEdges > 1) |
| printBranchInfo(*b, edgeIdx, os); |
| else if (options.UncondBranch && NumEdges == 1) { |
| uint64_t count = b->succ[0]->count; |
| os << format("unconditional %2u ", edgeIdx++) |
| << formatBranchInfo(options, count, count) << '\n'; |
| } |
| } |
| } |
| } |
| } |
| |
| void Context::printSourceToIntermediate(const SourceInfo &si, |
| raw_ostream &os) const { |
| os << "file:" << si.filename << '\n'; |
| for (const auto &fs : si.startLineToFunctions) |
| for (const GCOVFunction *f : fs) |
| os << "function:" << f->startLine << ',' << f->getEntryCount() << ',' |
| << f->getName(options.Demangle) << '\n'; |
| for (size_t lineNum = 1, size = si.lines.size(); lineNum < size; ++lineNum) { |
| const LineInfo &line = si.lines[lineNum]; |
| if (line.blocks.empty()) |
| continue; |
| // GCC 8 (r254259) added third third field for Ada: |
| // lcount:<line>,<count>,<has_unexecuted_blocks> |
| // We don't need the third field. |
| os << "lcount:" << lineNum << ',' << line.count << '\n'; |
| |
| if (!options.BranchInfo) |
| continue; |
| for (const GCOVBlock *b : line.blocks) { |
| if (b->succ.size() < 2 || b->getLastLine() != lineNum) |
| continue; |
| for (const GCOVArc *arc : b->succ) { |
| const char *type = |
| b->getCount() ? arc->count ? "taken" : "nottaken" : "notexec"; |
| os << "branch:" << lineNum << ',' << type << '\n'; |
| } |
| } |
| } |
| } |
| |
| void Context::print(StringRef filename, StringRef gcno, StringRef gcda, |
| GCOVFile &file) { |
| for (StringRef filename : file.filenames) { |
| sources.emplace_back(filename); |
| SourceInfo &si = sources.back(); |
| si.displayName = si.filename; |
| if (!options.SourcePrefix.empty() && |
| sys::path::replace_path_prefix(si.displayName, options.SourcePrefix, |
| "") && |
| !si.displayName.empty()) { |
| // TODO replace_path_prefix may strip the prefix even if the remaining |
| // part does not start with a separator. |
| if (sys::path::is_separator(si.displayName[0])) |
| si.displayName.erase(si.displayName.begin()); |
| else |
| si.displayName = si.filename; |
| } |
| if (options.RelativeOnly && sys::path::is_absolute(si.displayName)) |
| si.ignored = true; |
| } |
| |
| raw_ostream &os = llvm::outs(); |
| for (GCOVFunction &f : make_pointee_range(file.functions)) { |
| Summary summary(f.getName(options.Demangle)); |
| collectFunction(f, summary); |
| if (options.FuncCoverage && !options.UseStdout) { |
| os << "Function '" << summary.Name << "'\n"; |
| printSummary(summary, os); |
| os << '\n'; |
| } |
| } |
| |
| for (SourceInfo &si : sources) { |
| if (si.ignored) |
| continue; |
| Summary summary(si.displayName); |
| collectSource(si, summary); |
| |
| // Print file summary unless -t is specified. |
| std::string gcovName = getCoveragePath(si.filename, filename); |
| if (!options.UseStdout) { |
| os << "File '" << summary.Name << "'\n"; |
| printSummary(summary, os); |
| if (!options.NoOutput && !options.Intermediate) |
| os << "Creating '" << gcovName << "'\n"; |
| os << '\n'; |
| } |
| |
| if (options.NoOutput || options.Intermediate) |
| continue; |
| Optional<raw_fd_ostream> os; |
| if (!options.UseStdout) { |
| std::error_code ec; |
| os.emplace(gcovName, ec, sys::fs::OF_TextWithCRLF); |
| if (ec) { |
| errs() << ec.message() << '\n'; |
| continue; |
| } |
| } |
| annotateSource(si, file, gcno, gcda, |
| options.UseStdout ? llvm::outs() : *os); |
| } |
| |
| if (options.Intermediate && !options.NoOutput) { |
| // gcov 7.* unexpectedly create multiple .gcov files, which was fixed in 8.0 |
| // (PR GCC/82702). We create just one file. |
| std::string outputPath(sys::path::filename(filename)); |
| std::error_code ec; |
| raw_fd_ostream os(outputPath + ".gcov", ec, sys::fs::OF_TextWithCRLF); |
| if (ec) { |
| errs() << ec.message() << '\n'; |
| return; |
| } |
| |
| for (const SourceInfo &si : sources) |
| printSourceToIntermediate(si, os); |
| } |
| } |
| |
| void Context::printFunctionDetails(const GCOVFunction &f, |
| raw_ostream &os) const { |
| const uint64_t entryCount = f.getEntryCount(); |
| uint32_t blocksExec = 0; |
| const GCOVBlock &exitBlock = f.getExitBlock(); |
| uint64_t exitCount = 0; |
| for (const GCOVArc *arc : exitBlock.pred) |
| exitCount += arc->count; |
| for (const GCOVBlock &b : f.blocksRange()) |
| if (b.number != 0 && &b != &exitBlock && b.getCount()) |
| ++blocksExec; |
| |
| os << "function " << f.getName(options.Demangle) << " called " << entryCount |
| << " returned " << formatPercentage(exitCount, entryCount) |
| << "% blocks executed " |
| << formatPercentage(blocksExec, f.blocks.size() - 2) << "%\n"; |
| } |
| |
| /// printBranchInfo - Print conditional branch probabilities. |
| void Context::printBranchInfo(const GCOVBlock &Block, uint32_t &edgeIdx, |
| raw_ostream &os) const { |
| uint64_t total = 0; |
| for (const GCOVArc *arc : Block.dsts()) |
| total += arc->count; |
| for (const GCOVArc *arc : Block.dsts()) |
| os << format("branch %2u ", edgeIdx++) |
| << formatBranchInfo(options, arc->count, total) << '\n'; |
| } |
| |
| void Context::printSummary(const Summary &summary, raw_ostream &os) const { |
| os << format("Lines executed:%.2f%% of %" PRIu64 "\n", |
| double(summary.linesExec) * 100 / summary.lines, summary.lines); |
| if (options.BranchInfo) { |
| if (summary.branches == 0) { |
| os << "No branches\n"; |
| } else { |
| os << format("Branches executed:%.2f%% of %" PRIu64 "\n", |
| double(summary.branchesExec) * 100 / summary.branches, |
| summary.branches); |
| os << format("Taken at least once:%.2f%% of %" PRIu64 "\n", |
| double(summary.branchesTaken) * 100 / summary.branches, |
| summary.branches); |
| } |
| os << "No calls\n"; |
| } |
| } |
| |
| void llvm::gcovOneInput(const GCOV::Options &options, StringRef filename, |
| StringRef gcno, StringRef gcda, GCOVFile &file) { |
| Context fi(options); |
| fi.print(filename, gcno, gcda, file); |
| } |