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