| 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 | |