1 //===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // The 'CodeCoverageTool' class implements a command line tool to analyze and 11 // report coverage information using the profiling instrumentation and code 12 // coverage mapping. 13 // 14 //===----------------------------------------------------------------------===// 15 16 #include "CoverageFilters.h" 17 #include "CoverageReport.h" 18 #include "CoverageViewOptions.h" 19 #include "RenderingSupport.h" 20 #include "SourceCoverageView.h" 21 #include "llvm/ADT/SmallString.h" 22 #include "llvm/ADT/StringRef.h" 23 #include "llvm/ADT/Triple.h" 24 #include "llvm/ProfileData/Coverage/CoverageMapping.h" 25 #include "llvm/ProfileData/InstrProfReader.h" 26 #include "llvm/Support/CommandLine.h" 27 #include "llvm/Support/FileSystem.h" 28 #include "llvm/Support/Format.h" 29 #include "llvm/Support/Path.h" 30 #include "llvm/Support/Process.h" 31 #include "llvm/Support/ThreadPool.h" 32 #include <functional> 33 #include <system_error> 34 35 using namespace llvm; 36 using namespace coverage; 37 38 namespace { 39 /// \brief The implementation of the coverage tool. 40 class CodeCoverageTool { 41 public: 42 enum Command { 43 /// \brief The show command. 44 Show, 45 /// \brief The report command. 46 Report 47 }; 48 49 /// \brief Print the error message to the error output stream. 50 void error(const Twine &Message, StringRef Whence = ""); 51 52 /// \brief Record (but do not print) an error message in a thread-safe way. 53 void deferError(const Twine &Message, StringRef Whence = ""); 54 55 /// \brief Record (but do not print) a warning message in a thread-safe way. 56 void deferWarning(const Twine &Message, StringRef Whence = ""); 57 58 /// \brief Print (and then clear) all deferred error and warning messages. 59 void consumeDeferredMessages(); 60 61 /// \brief Append a reference to a private copy of \p Path into SourceFiles. 62 void addCollectedPath(const std::string &Path); 63 64 /// \brief Return a memory buffer for the given source file. 65 ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile); 66 67 /// \brief Create source views for the expansions of the view. 68 void attachExpansionSubViews(SourceCoverageView &View, 69 ArrayRef<ExpansionRecord> Expansions, 70 const CoverageMapping &Coverage); 71 72 /// \brief Create the source view of a particular function. 73 std::unique_ptr<SourceCoverageView> 74 createFunctionView(const FunctionRecord &Function, 75 const CoverageMapping &Coverage); 76 77 /// \brief Create the main source view of a particular source file. 78 std::unique_ptr<SourceCoverageView> 79 createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage); 80 81 /// \brief Load the coverage mapping data. Return nullptr if an error occured. 82 std::unique_ptr<CoverageMapping> load(); 83 84 int run(Command Cmd, int argc, const char **argv); 85 86 typedef llvm::function_ref<int(int, const char **)> CommandLineParserType; 87 88 int show(int argc, const char **argv, 89 CommandLineParserType commandLineParser); 90 91 int report(int argc, const char **argv, 92 CommandLineParserType commandLineParser); 93 94 std::string ObjectFilename; 95 CoverageViewOptions ViewOpts; 96 std::string PGOFilename; 97 CoverageFiltersMatchAll Filters; 98 std::vector<StringRef> SourceFiles; 99 bool CompareFilenamesOnly; 100 StringMap<std::string> RemappedFilenames; 101 std::string CoverageArch; 102 103 private: 104 /// File paths (absolute, or otherwise) to input source files. 105 std::vector<std::string> CollectedPaths; 106 107 /// Errors and warnings which have not been printed. 108 std::mutex DeferredMessagesLock; 109 std::vector<std::string> DeferredMessages; 110 111 /// A container for input source file buffers. 112 std::mutex LoadedSourceFilesLock; 113 std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>> 114 LoadedSourceFiles; 115 }; 116 } 117 118 static std::string getErrorString(const Twine &Message, StringRef Whence, 119 bool Warning) { 120 std::string Str = (Warning ? "warning" : "error"); 121 Str += ": "; 122 if (!Whence.empty()) 123 Str += Whence.str() + ": "; 124 Str += Message.str() + "\n"; 125 return Str; 126 } 127 128 void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { 129 errs() << getErrorString(Message, Whence, false); 130 } 131 132 void CodeCoverageTool::deferError(const Twine &Message, StringRef Whence) { 133 std::unique_lock<std::mutex> Guard{DeferredMessagesLock}; 134 DeferredMessages.emplace_back(getErrorString(Message, Whence, false)); 135 } 136 137 void CodeCoverageTool::deferWarning(const Twine &Message, StringRef Whence) { 138 std::unique_lock<std::mutex> Guard{DeferredMessagesLock}; 139 DeferredMessages.emplace_back(getErrorString(Message, Whence, true)); 140 } 141 142 void CodeCoverageTool::consumeDeferredMessages() { 143 std::unique_lock<std::mutex> Guard{DeferredMessagesLock}; 144 for (const std::string &Message : DeferredMessages) 145 ViewOpts.colored_ostream(errs(), raw_ostream::RED) << Message; 146 DeferredMessages.clear(); 147 } 148 149 void CodeCoverageTool::addCollectedPath(const std::string &Path) { 150 CollectedPaths.push_back(Path); 151 SourceFiles.emplace_back(CollectedPaths.back()); 152 } 153 154 ErrorOr<const MemoryBuffer &> 155 CodeCoverageTool::getSourceFile(StringRef SourceFile) { 156 // If we've remapped filenames, look up the real location for this file. 157 std::unique_lock<std::mutex> Guard{LoadedSourceFilesLock}; 158 if (!RemappedFilenames.empty()) { 159 auto Loc = RemappedFilenames.find(SourceFile); 160 if (Loc != RemappedFilenames.end()) 161 SourceFile = Loc->second; 162 } 163 for (const auto &Files : LoadedSourceFiles) 164 if (sys::fs::equivalent(SourceFile, Files.first)) 165 return *Files.second; 166 auto Buffer = MemoryBuffer::getFile(SourceFile); 167 if (auto EC = Buffer.getError()) { 168 deferError(EC.message(), SourceFile); 169 return EC; 170 } 171 LoadedSourceFiles.emplace_back(SourceFile, std::move(Buffer.get())); 172 return *LoadedSourceFiles.back().second; 173 } 174 175 void CodeCoverageTool::attachExpansionSubViews( 176 SourceCoverageView &View, ArrayRef<ExpansionRecord> Expansions, 177 const CoverageMapping &Coverage) { 178 if (!ViewOpts.ShowExpandedRegions) 179 return; 180 for (const auto &Expansion : Expansions) { 181 auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion); 182 if (ExpansionCoverage.empty()) 183 continue; 184 auto SourceBuffer = getSourceFile(ExpansionCoverage.getFilename()); 185 if (!SourceBuffer) 186 continue; 187 188 auto SubViewExpansions = ExpansionCoverage.getExpansions(); 189 auto SubView = 190 SourceCoverageView::create(Expansion.Function.Name, SourceBuffer.get(), 191 ViewOpts, std::move(ExpansionCoverage)); 192 attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); 193 View.addExpansion(Expansion.Region, std::move(SubView)); 194 } 195 } 196 197 std::unique_ptr<SourceCoverageView> 198 CodeCoverageTool::createFunctionView(const FunctionRecord &Function, 199 const CoverageMapping &Coverage) { 200 auto FunctionCoverage = Coverage.getCoverageForFunction(Function); 201 if (FunctionCoverage.empty()) 202 return nullptr; 203 auto SourceBuffer = getSourceFile(FunctionCoverage.getFilename()); 204 if (!SourceBuffer) 205 return nullptr; 206 207 auto Expansions = FunctionCoverage.getExpansions(); 208 auto View = SourceCoverageView::create(Function.Name, SourceBuffer.get(), 209 ViewOpts, std::move(FunctionCoverage)); 210 attachExpansionSubViews(*View, Expansions, Coverage); 211 212 return View; 213 } 214 215 std::unique_ptr<SourceCoverageView> 216 CodeCoverageTool::createSourceFileView(StringRef SourceFile, 217 const CoverageMapping &Coverage) { 218 auto SourceBuffer = getSourceFile(SourceFile); 219 if (!SourceBuffer) 220 return nullptr; 221 auto FileCoverage = Coverage.getCoverageForFile(SourceFile); 222 if (FileCoverage.empty()) 223 return nullptr; 224 225 auto Expansions = FileCoverage.getExpansions(); 226 auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(), 227 ViewOpts, std::move(FileCoverage)); 228 attachExpansionSubViews(*View, Expansions, Coverage); 229 230 for (const auto *Function : Coverage.getInstantiations(SourceFile)) { 231 auto SubViewCoverage = Coverage.getCoverageForFunction(*Function); 232 auto SubViewExpansions = SubViewCoverage.getExpansions(); 233 auto SubView = 234 SourceCoverageView::create(Function->Name, SourceBuffer.get(), ViewOpts, 235 std::move(SubViewCoverage)); 236 attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); 237 238 if (SubView) { 239 unsigned FileID = Function->CountedRegions.front().FileID; 240 unsigned Line = 0; 241 for (const auto &CR : Function->CountedRegions) 242 if (CR.FileID == FileID) 243 Line = std::max(CR.LineEnd, Line); 244 View->addInstantiation(Function->Name, Line, std::move(SubView)); 245 } 246 } 247 return View; 248 } 249 250 static bool modifiedTimeGT(StringRef LHS, StringRef RHS) { 251 sys::fs::file_status Status; 252 if (sys::fs::status(LHS, Status)) 253 return false; 254 auto LHSTime = Status.getLastModificationTime(); 255 if (sys::fs::status(RHS, Status)) 256 return false; 257 auto RHSTime = Status.getLastModificationTime(); 258 return LHSTime > RHSTime; 259 } 260 261 std::unique_ptr<CoverageMapping> CodeCoverageTool::load() { 262 if (modifiedTimeGT(ObjectFilename, PGOFilename)) 263 errs() << "warning: profile data may be out of date - object is newer\n"; 264 auto CoverageOrErr = CoverageMapping::load(ObjectFilename, PGOFilename, 265 CoverageArch); 266 if (Error E = CoverageOrErr.takeError()) { 267 colored_ostream(errs(), raw_ostream::RED) 268 << "error: Failed to load coverage: " << toString(std::move(E)) << "\n"; 269 return nullptr; 270 } 271 auto Coverage = std::move(CoverageOrErr.get()); 272 unsigned Mismatched = Coverage->getMismatchedCount(); 273 if (Mismatched) { 274 colored_ostream(errs(), raw_ostream::RED) 275 << "warning: " << Mismatched << " functions have mismatched data. "; 276 errs() << "\n"; 277 } 278 279 if (CompareFilenamesOnly) { 280 auto CoveredFiles = Coverage.get()->getUniqueSourceFiles(); 281 for (auto &SF : SourceFiles) { 282 StringRef SFBase = sys::path::filename(SF); 283 for (const auto &CF : CoveredFiles) 284 if (SFBase == sys::path::filename(CF)) { 285 RemappedFilenames[CF] = SF; 286 SF = CF; 287 break; 288 } 289 } 290 } 291 292 return Coverage; 293 } 294 295 int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { 296 cl::opt<std::string, true> ObjectFilename( 297 cl::Positional, cl::Required, cl::location(this->ObjectFilename), 298 cl::desc("Covered executable or object file.")); 299 300 cl::list<std::string> InputSourceFiles( 301 cl::Positional, cl::desc("<Source files>"), cl::ZeroOrMore); 302 303 cl::opt<std::string, true> PGOFilename( 304 "instr-profile", cl::Required, cl::location(this->PGOFilename), 305 cl::desc( 306 "File with the profile data obtained after an instrumented run")); 307 308 cl::opt<std::string> Arch( 309 "arch", cl::desc("architecture of the coverage mapping binary")); 310 311 cl::opt<bool> DebugDump("dump", cl::Optional, 312 cl::desc("Show internal debug dump")); 313 314 cl::opt<CoverageViewOptions::OutputFormat> Format( 315 "format", cl::desc("Output format for line-based coverage reports"), 316 cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text", 317 "Text output"), 318 clEnumValN(CoverageViewOptions::OutputFormat::HTML, "html", 319 "HTML output"), 320 clEnumValEnd), 321 cl::init(CoverageViewOptions::OutputFormat::Text)); 322 323 cl::opt<bool> FilenameEquivalence( 324 "filename-equivalence", cl::Optional, 325 cl::desc("Treat source files as equivalent to paths in the coverage data " 326 "when the file names match, even if the full paths do not")); 327 328 cl::OptionCategory FilteringCategory("Function filtering options"); 329 330 cl::list<std::string> NameFilters( 331 "name", cl::Optional, 332 cl::desc("Show code coverage only for functions with the given name"), 333 cl::ZeroOrMore, cl::cat(FilteringCategory)); 334 335 cl::list<std::string> NameRegexFilters( 336 "name-regex", cl::Optional, 337 cl::desc("Show code coverage only for functions that match the given " 338 "regular expression"), 339 cl::ZeroOrMore, cl::cat(FilteringCategory)); 340 341 cl::opt<double> RegionCoverageLtFilter( 342 "region-coverage-lt", cl::Optional, 343 cl::desc("Show code coverage only for functions with region coverage " 344 "less than the given threshold"), 345 cl::cat(FilteringCategory)); 346 347 cl::opt<double> RegionCoverageGtFilter( 348 "region-coverage-gt", cl::Optional, 349 cl::desc("Show code coverage only for functions with region coverage " 350 "greater than the given threshold"), 351 cl::cat(FilteringCategory)); 352 353 cl::opt<double> LineCoverageLtFilter( 354 "line-coverage-lt", cl::Optional, 355 cl::desc("Show code coverage only for functions with line coverage less " 356 "than the given threshold"), 357 cl::cat(FilteringCategory)); 358 359 cl::opt<double> LineCoverageGtFilter( 360 "line-coverage-gt", cl::Optional, 361 cl::desc("Show code coverage only for functions with line coverage " 362 "greater than the given threshold"), 363 cl::cat(FilteringCategory)); 364 365 cl::opt<cl::boolOrDefault> UseColor( 366 "use-color", cl::desc("Emit colored output (default=autodetect)"), 367 cl::init(cl::BOU_UNSET)); 368 369 auto commandLineParser = [&, this](int argc, const char **argv) -> int { 370 cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); 371 ViewOpts.Debug = DebugDump; 372 CompareFilenamesOnly = FilenameEquivalence; 373 374 ViewOpts.Format = Format; 375 switch (ViewOpts.Format) { 376 case CoverageViewOptions::OutputFormat::Text: 377 ViewOpts.Colors = UseColor == cl::BOU_UNSET 378 ? sys::Process::StandardOutHasColors() 379 : UseColor == cl::BOU_TRUE; 380 break; 381 case CoverageViewOptions::OutputFormat::HTML: 382 if (UseColor == cl::BOU_FALSE) 383 error("Color output cannot be disabled when generating html."); 384 ViewOpts.Colors = true; 385 break; 386 } 387 388 // Create the function filters 389 if (!NameFilters.empty() || !NameRegexFilters.empty()) { 390 auto NameFilterer = new CoverageFilters; 391 for (const auto &Name : NameFilters) 392 NameFilterer->push_back(llvm::make_unique<NameCoverageFilter>(Name)); 393 for (const auto &Regex : NameRegexFilters) 394 NameFilterer->push_back( 395 llvm::make_unique<NameRegexCoverageFilter>(Regex)); 396 Filters.push_back(std::unique_ptr<CoverageFilter>(NameFilterer)); 397 } 398 if (RegionCoverageLtFilter.getNumOccurrences() || 399 RegionCoverageGtFilter.getNumOccurrences() || 400 LineCoverageLtFilter.getNumOccurrences() || 401 LineCoverageGtFilter.getNumOccurrences()) { 402 auto StatFilterer = new CoverageFilters; 403 if (RegionCoverageLtFilter.getNumOccurrences()) 404 StatFilterer->push_back(llvm::make_unique<RegionCoverageFilter>( 405 RegionCoverageFilter::LessThan, RegionCoverageLtFilter)); 406 if (RegionCoverageGtFilter.getNumOccurrences()) 407 StatFilterer->push_back(llvm::make_unique<RegionCoverageFilter>( 408 RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter)); 409 if (LineCoverageLtFilter.getNumOccurrences()) 410 StatFilterer->push_back(llvm::make_unique<LineCoverageFilter>( 411 LineCoverageFilter::LessThan, LineCoverageLtFilter)); 412 if (LineCoverageGtFilter.getNumOccurrences()) 413 StatFilterer->push_back(llvm::make_unique<LineCoverageFilter>( 414 RegionCoverageFilter::GreaterThan, LineCoverageGtFilter)); 415 Filters.push_back(std::unique_ptr<CoverageFilter>(StatFilterer)); 416 } 417 418 if (!Arch.empty() && 419 Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) { 420 errs() << "error: Unknown architecture: " << Arch << "\n"; 421 return 1; 422 } 423 CoverageArch = Arch; 424 425 for (const auto &File : InputSourceFiles) { 426 SmallString<128> Path(File); 427 if (!CompareFilenamesOnly) 428 if (std::error_code EC = sys::fs::make_absolute(Path)) { 429 errs() << "error: " << File << ": " << EC.message(); 430 return 1; 431 } 432 addCollectedPath(Path.str()); 433 } 434 return 0; 435 }; 436 437 switch (Cmd) { 438 case Show: 439 return show(argc, argv, commandLineParser); 440 case Report: 441 return report(argc, argv, commandLineParser); 442 } 443 return 0; 444 } 445 446 int CodeCoverageTool::show(int argc, const char **argv, 447 CommandLineParserType commandLineParser) { 448 449 cl::OptionCategory ViewCategory("Viewing options"); 450 451 cl::opt<bool> ShowLineExecutionCounts( 452 "show-line-counts", cl::Optional, 453 cl::desc("Show the execution counts for each line"), cl::init(true), 454 cl::cat(ViewCategory)); 455 456 cl::opt<bool> ShowRegions( 457 "show-regions", cl::Optional, 458 cl::desc("Show the execution counts for each region"), 459 cl::cat(ViewCategory)); 460 461 cl::opt<bool> ShowBestLineRegionsCounts( 462 "show-line-counts-or-regions", cl::Optional, 463 cl::desc("Show the execution counts for each line, or the execution " 464 "counts for each region on lines that have multiple regions"), 465 cl::cat(ViewCategory)); 466 467 cl::opt<bool> ShowExpansions("show-expansions", cl::Optional, 468 cl::desc("Show expanded source regions"), 469 cl::cat(ViewCategory)); 470 471 cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional, 472 cl::desc("Show function instantiations"), 473 cl::cat(ViewCategory)); 474 475 cl::opt<std::string> ShowOutputDirectory( 476 "output-dir", cl::init(""), 477 cl::desc("Directory in which coverage information is written out")); 478 cl::alias ShowOutputDirectoryA("o", cl::desc("Alias for --output-dir"), 479 cl::aliasopt(ShowOutputDirectory)); 480 481 auto Err = commandLineParser(argc, argv); 482 if (Err) 483 return Err; 484 485 ViewOpts.ShowLineNumbers = true; 486 ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 || 487 !ShowRegions || ShowBestLineRegionsCounts; 488 ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts; 489 ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts; 490 ViewOpts.ShowExpandedRegions = ShowExpansions; 491 ViewOpts.ShowFunctionInstantiations = ShowInstantiations; 492 ViewOpts.ShowOutputDirectory = ShowOutputDirectory; 493 494 if (ViewOpts.hasOutputDirectory()) { 495 if (auto E = sys::fs::create_directories(ViewOpts.ShowOutputDirectory)) { 496 error("Could not create output directory!", E.message()); 497 return 1; 498 } 499 } 500 501 auto Coverage = load(); 502 if (!Coverage) 503 return 1; 504 505 auto Printer = CoveragePrinter::create(ViewOpts); 506 507 if (!Filters.empty()) { 508 auto OSOrErr = Printer->createViewFile("functions", /*InToplevel=*/true); 509 if (Error E = OSOrErr.takeError()) { 510 error("Could not create view file!", toString(std::move(E))); 511 return 1; 512 } 513 auto OS = std::move(OSOrErr.get()); 514 515 // Show functions. 516 for (const auto &Function : Coverage->getCoveredFunctions()) { 517 if (!Filters.matches(Function)) 518 continue; 519 520 auto mainView = createFunctionView(Function, *Coverage); 521 if (!mainView) { 522 ViewOpts.colored_ostream(errs(), raw_ostream::RED) 523 << "warning: Could not read coverage for '" << Function.Name << "'." 524 << "\n"; 525 continue; 526 } 527 528 mainView->print(*OS.get(), /*WholeFile=*/false, /*ShowSourceName=*/true); 529 } 530 531 Printer->closeViewFile(std::move(OS)); 532 return 0; 533 } 534 535 // Show files 536 bool ShowFilenames = SourceFiles.size() != 1; 537 538 if (SourceFiles.empty()) 539 // Get the source files from the function coverage mapping. 540 for (StringRef Filename : Coverage->getUniqueSourceFiles()) 541 SourceFiles.push_back(Filename); 542 543 // Create an index out of the source files. 544 if (ViewOpts.hasOutputDirectory()) { 545 if (Error E = Printer->createIndexFile(SourceFiles)) { 546 error("Could not create index file!", toString(std::move(E))); 547 return 1; 548 } 549 } 550 551 // In -output-dir mode, it's safe to use multiple threads to print files. 552 unsigned ThreadCount = 1; 553 if (ViewOpts.hasOutputDirectory()) 554 ThreadCount = std::thread::hardware_concurrency(); 555 ThreadPool Pool(ThreadCount); 556 557 for (StringRef SourceFile : SourceFiles) { 558 Pool.async([this, SourceFile, &Coverage, &Printer, ShowFilenames] { 559 auto View = createSourceFileView(SourceFile, *Coverage); 560 if (!View) { 561 deferWarning("The file '" + SourceFile.str() + "' isn't covered."); 562 return; 563 } 564 565 auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false); 566 if (Error E = OSOrErr.takeError()) { 567 deferError("Could not create view file!", toString(std::move(E))); 568 return; 569 } 570 auto OS = std::move(OSOrErr.get()); 571 572 View->print(*OS.get(), /*Wholefile=*/true, 573 /*ShowSourceName=*/ShowFilenames); 574 Printer->closeViewFile(std::move(OS)); 575 }); 576 } 577 578 Pool.wait(); 579 580 consumeDeferredMessages(); 581 582 return 0; 583 } 584 585 int CodeCoverageTool::report(int argc, const char **argv, 586 CommandLineParserType commandLineParser) { 587 auto Err = commandLineParser(argc, argv); 588 if (Err) 589 return Err; 590 591 if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) 592 error("HTML output for summary reports is not yet supported."); 593 594 auto Coverage = load(); 595 if (!Coverage) 596 return 1; 597 598 CoverageReport Report(ViewOpts, std::move(Coverage)); 599 if (SourceFiles.empty()) 600 Report.renderFileReports(llvm::outs()); 601 else 602 Report.renderFunctionReports(SourceFiles, llvm::outs()); 603 return 0; 604 } 605 606 int showMain(int argc, const char *argv[]) { 607 CodeCoverageTool Tool; 608 return Tool.run(CodeCoverageTool::Show, argc, argv); 609 } 610 611 int reportMain(int argc, const char *argv[]) { 612 CodeCoverageTool Tool; 613 return Tool.run(CodeCoverageTool::Report, argc, argv); 614 } 615