Mercurial > hg > CbC > CbC_llvm
comparison tools/llvm-cov/CodeCoverage.cpp @ 120:1172e4bd9c6f
update 4.0.0
author | mir3636 |
---|---|
date | Fri, 25 Nov 2016 19:14:25 +0900 |
parents | afa8332a0e37 |
children | 803732b1fca8 |
comparison
equal
deleted
inserted
replaced
101:34baf5011add | 120:1172e4bd9c6f |
---|---|
11 // report coverage information using the profiling instrumentation and code | 11 // report coverage information using the profiling instrumentation and code |
12 // coverage mapping. | 12 // coverage mapping. |
13 // | 13 // |
14 //===----------------------------------------------------------------------===// | 14 //===----------------------------------------------------------------------===// |
15 | 15 |
16 #include "RenderingSupport.h" | |
17 #include "CoverageFilters.h" | 16 #include "CoverageFilters.h" |
18 #include "CoverageReport.h" | 17 #include "CoverageReport.h" |
19 #include "CoverageViewOptions.h" | 18 #include "CoverageViewOptions.h" |
19 #include "RenderingSupport.h" | |
20 #include "SourceCoverageView.h" | 20 #include "SourceCoverageView.h" |
21 #include "llvm/ADT/SmallString.h" | 21 #include "llvm/ADT/SmallString.h" |
22 #include "llvm/ADT/StringRef.h" | 22 #include "llvm/ADT/StringRef.h" |
23 #include "llvm/ADT/Triple.h" | 23 #include "llvm/ADT/Triple.h" |
24 #include "llvm/ProfileData/CoverageMapping.h" | 24 #include "llvm/ProfileData/Coverage/CoverageMapping.h" |
25 #include "llvm/ProfileData/InstrProfReader.h" | 25 #include "llvm/ProfileData/InstrProfReader.h" |
26 #include "llvm/Support/CommandLine.h" | 26 #include "llvm/Support/CommandLine.h" |
27 #include "llvm/Support/FileSystem.h" | 27 #include "llvm/Support/FileSystem.h" |
28 #include "llvm/Support/Format.h" | 28 #include "llvm/Support/Format.h" |
29 #include "llvm/Support/ManagedStatic.h" | 29 #include "llvm/Support/MemoryBuffer.h" |
30 #include "llvm/Support/Path.h" | 30 #include "llvm/Support/Path.h" |
31 #include "llvm/Support/PrettyStackTrace.h" | |
32 #include "llvm/Support/Process.h" | 31 #include "llvm/Support/Process.h" |
33 #include "llvm/Support/Signals.h" | 32 #include "llvm/Support/Program.h" |
33 #include "llvm/Support/ScopedPrinter.h" | |
34 #include "llvm/Support/ThreadPool.h" | |
35 #include "llvm/Support/ToolOutputFile.h" | |
34 #include <functional> | 36 #include <functional> |
35 #include <system_error> | 37 #include <system_error> |
36 | 38 |
37 using namespace llvm; | 39 using namespace llvm; |
38 using namespace coverage; | 40 using namespace coverage; |
41 | |
42 void exportCoverageDataToJson(const coverage::CoverageMapping &CoverageMapping, | |
43 raw_ostream &OS); | |
39 | 44 |
40 namespace { | 45 namespace { |
41 /// \brief The implementation of the coverage tool. | 46 /// \brief The implementation of the coverage tool. |
42 class CodeCoverageTool { | 47 class CodeCoverageTool { |
43 public: | 48 public: |
44 enum Command { | 49 enum Command { |
45 /// \brief The show command. | 50 /// \brief The show command. |
46 Show, | 51 Show, |
47 /// \brief The report command. | 52 /// \brief The report command. |
48 Report | 53 Report, |
54 /// \brief The export command. | |
55 Export | |
49 }; | 56 }; |
50 | 57 |
58 int run(Command Cmd, int argc, const char **argv); | |
59 | |
60 private: | |
51 /// \brief Print the error message to the error output stream. | 61 /// \brief Print the error message to the error output stream. |
52 void error(const Twine &Message, StringRef Whence = ""); | 62 void error(const Twine &Message, StringRef Whence = ""); |
63 | |
64 /// \brief Print the warning message to the error output stream. | |
65 void warning(const Twine &Message, StringRef Whence = ""); | |
66 | |
67 /// \brief Convert \p Path into an absolute path and append it to the list | |
68 /// of collected paths. | |
69 void addCollectedPath(const std::string &Path); | |
70 | |
71 /// \brief If \p Path is a regular file, collect the path. If it's a | |
72 /// directory, recursively collect all of the paths within the directory. | |
73 void collectPaths(const std::string &Path); | |
53 | 74 |
54 /// \brief Return a memory buffer for the given source file. | 75 /// \brief Return a memory buffer for the given source file. |
55 ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile); | 76 ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile); |
56 | 77 |
57 /// \brief Create source views for the expansions of the view. | 78 /// \brief Create source views for the expansions of the view. |
58 void attachExpansionSubViews(SourceCoverageView &View, | 79 void attachExpansionSubViews(SourceCoverageView &View, |
59 ArrayRef<ExpansionRecord> Expansions, | 80 ArrayRef<ExpansionRecord> Expansions, |
60 CoverageMapping &Coverage); | 81 const CoverageMapping &Coverage); |
61 | 82 |
62 /// \brief Create the source view of a particular function. | 83 /// \brief Create the source view of a particular function. |
63 std::unique_ptr<SourceCoverageView> | 84 std::unique_ptr<SourceCoverageView> |
64 createFunctionView(const FunctionRecord &Function, CoverageMapping &Coverage); | 85 createFunctionView(const FunctionRecord &Function, |
86 const CoverageMapping &Coverage); | |
65 | 87 |
66 /// \brief Create the main source view of a particular source file. | 88 /// \brief Create the main source view of a particular source file. |
67 std::unique_ptr<SourceCoverageView> | 89 std::unique_ptr<SourceCoverageView> |
68 createSourceFileView(StringRef SourceFile, CoverageMapping &Coverage); | 90 createSourceFileView(StringRef SourceFile, const CoverageMapping &Coverage); |
69 | 91 |
70 /// \brief Load the coverage mapping data. Return true if an error occured. | 92 /// \brief Load the coverage mapping data. Return nullptr if an error occurred. |
71 std::unique_ptr<CoverageMapping> load(); | 93 std::unique_ptr<CoverageMapping> load(); |
72 | 94 |
73 int run(Command Cmd, int argc, const char **argv); | 95 /// \brief Remove input source files which aren't mapped by \p Coverage. |
74 | 96 void removeUnmappedInputs(const CoverageMapping &Coverage); |
75 typedef std::function<int(int, const char **)> CommandLineParserType; | 97 |
98 /// \brief If a demangler is available, demangle all symbol names. | |
99 void demangleSymbols(const CoverageMapping &Coverage); | |
100 | |
101 /// \brief Demangle \p Sym if possible. Otherwise, just return \p Sym. | |
102 StringRef getSymbolForHumans(StringRef Sym) const; | |
103 | |
104 /// \brief Write out a source file view to the filesystem. | |
105 void writeSourceFileView(StringRef SourceFile, CoverageMapping *Coverage, | |
106 CoveragePrinter *Printer, bool ShowFilenames); | |
107 | |
108 typedef llvm::function_ref<int(int, const char **)> CommandLineParserType; | |
76 | 109 |
77 int show(int argc, const char **argv, | 110 int show(int argc, const char **argv, |
78 CommandLineParserType commandLineParser); | 111 CommandLineParserType commandLineParser); |
79 | 112 |
80 int report(int argc, const char **argv, | 113 int report(int argc, const char **argv, |
81 CommandLineParserType commandLineParser); | 114 CommandLineParserType commandLineParser); |
82 | 115 |
83 std::string ObjectFilename; | 116 int export_(int argc, const char **argv, |
117 CommandLineParserType commandLineParser); | |
118 | |
119 std::vector<StringRef> ObjectFilenames; | |
84 CoverageViewOptions ViewOpts; | 120 CoverageViewOptions ViewOpts; |
121 CoverageFiltersMatchAll Filters; | |
122 | |
123 /// The path to the indexed profile. | |
85 std::string PGOFilename; | 124 std::string PGOFilename; |
86 CoverageFiltersMatchAll Filters; | 125 |
126 /// A list of input source files. | |
87 std::vector<std::string> SourceFiles; | 127 std::vector<std::string> SourceFiles; |
128 | |
129 /// Whether or not we're in -filename-equivalence mode. | |
130 bool CompareFilenamesOnly; | |
131 | |
132 /// In -filename-equivalence mode, this maps absolute paths from the | |
133 /// coverage mapping data to input source files. | |
134 StringMap<std::string> RemappedFilenames; | |
135 | |
136 /// The architecture the coverage mapping data targets. | |
137 std::string CoverageArch; | |
138 | |
139 /// A cache for demangled symbol names. | |
140 StringMap<std::string> DemangledNames; | |
141 | |
142 /// Errors and warnings which have not been printed. | |
143 std::mutex ErrsLock; | |
144 | |
145 /// A container for input source file buffers. | |
146 std::mutex LoadedSourceFilesLock; | |
88 std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>> | 147 std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>> |
89 LoadedSourceFiles; | 148 LoadedSourceFiles; |
90 bool CompareFilenamesOnly; | |
91 StringMap<std::string> RemappedFilenames; | |
92 std::string CoverageArch; | |
93 }; | 149 }; |
94 } | 150 } |
95 | 151 |
152 static std::string getErrorString(const Twine &Message, StringRef Whence, | |
153 bool Warning) { | |
154 std::string Str = (Warning ? "warning" : "error"); | |
155 Str += ": "; | |
156 if (!Whence.empty()) | |
157 Str += Whence.str() + ": "; | |
158 Str += Message.str() + "\n"; | |
159 return Str; | |
160 } | |
161 | |
96 void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { | 162 void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { |
97 errs() << "error: "; | 163 std::unique_lock<std::mutex> Guard{ErrsLock}; |
98 if (!Whence.empty()) | 164 ViewOpts.colored_ostream(errs(), raw_ostream::RED) |
99 errs() << Whence << ": "; | 165 << getErrorString(Message, Whence, false); |
100 errs() << Message << "\n"; | 166 } |
167 | |
168 void CodeCoverageTool::warning(const Twine &Message, StringRef Whence) { | |
169 std::unique_lock<std::mutex> Guard{ErrsLock}; | |
170 ViewOpts.colored_ostream(errs(), raw_ostream::RED) | |
171 << getErrorString(Message, Whence, true); | |
172 } | |
173 | |
174 void CodeCoverageTool::addCollectedPath(const std::string &Path) { | |
175 if (CompareFilenamesOnly) { | |
176 SourceFiles.emplace_back(Path); | |
177 } else { | |
178 SmallString<128> EffectivePath(Path); | |
179 if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) { | |
180 error(EC.message(), Path); | |
181 return; | |
182 } | |
183 sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true); | |
184 SourceFiles.emplace_back(EffectivePath.str()); | |
185 } | |
186 } | |
187 | |
188 void CodeCoverageTool::collectPaths(const std::string &Path) { | |
189 llvm::sys::fs::file_status Status; | |
190 llvm::sys::fs::status(Path, Status); | |
191 if (!llvm::sys::fs::exists(Status)) { | |
192 if (CompareFilenamesOnly) | |
193 addCollectedPath(Path); | |
194 else | |
195 error("Missing source file", Path); | |
196 return; | |
197 } | |
198 | |
199 if (llvm::sys::fs::is_regular_file(Status)) { | |
200 addCollectedPath(Path); | |
201 return; | |
202 } | |
203 | |
204 if (llvm::sys::fs::is_directory(Status)) { | |
205 std::error_code EC; | |
206 for (llvm::sys::fs::recursive_directory_iterator F(Path, EC), E; | |
207 F != E && !EC; F.increment(EC)) { | |
208 if (llvm::sys::fs::is_regular_file(F->path())) | |
209 addCollectedPath(F->path()); | |
210 } | |
211 if (EC) | |
212 warning(EC.message(), Path); | |
213 } | |
101 } | 214 } |
102 | 215 |
103 ErrorOr<const MemoryBuffer &> | 216 ErrorOr<const MemoryBuffer &> |
104 CodeCoverageTool::getSourceFile(StringRef SourceFile) { | 217 CodeCoverageTool::getSourceFile(StringRef SourceFile) { |
105 // If we've remapped filenames, look up the real location for this file. | 218 // If we've remapped filenames, look up the real location for this file. |
219 std::unique_lock<std::mutex> Guard{LoadedSourceFilesLock}; | |
106 if (!RemappedFilenames.empty()) { | 220 if (!RemappedFilenames.empty()) { |
107 auto Loc = RemappedFilenames.find(SourceFile); | 221 auto Loc = RemappedFilenames.find(SourceFile); |
108 if (Loc != RemappedFilenames.end()) | 222 if (Loc != RemappedFilenames.end()) |
109 SourceFile = Loc->second; | 223 SourceFile = Loc->second; |
110 } | 224 } |
118 } | 232 } |
119 LoadedSourceFiles.emplace_back(SourceFile, std::move(Buffer.get())); | 233 LoadedSourceFiles.emplace_back(SourceFile, std::move(Buffer.get())); |
120 return *LoadedSourceFiles.back().second; | 234 return *LoadedSourceFiles.back().second; |
121 } | 235 } |
122 | 236 |
123 void | 237 void CodeCoverageTool::attachExpansionSubViews( |
124 CodeCoverageTool::attachExpansionSubViews(SourceCoverageView &View, | 238 SourceCoverageView &View, ArrayRef<ExpansionRecord> Expansions, |
125 ArrayRef<ExpansionRecord> Expansions, | 239 const CoverageMapping &Coverage) { |
126 CoverageMapping &Coverage) { | |
127 if (!ViewOpts.ShowExpandedRegions) | 240 if (!ViewOpts.ShowExpandedRegions) |
128 return; | 241 return; |
129 for (const auto &Expansion : Expansions) { | 242 for (const auto &Expansion : Expansions) { |
130 auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion); | 243 auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion); |
131 if (ExpansionCoverage.empty()) | 244 if (ExpansionCoverage.empty()) |
133 auto SourceBuffer = getSourceFile(ExpansionCoverage.getFilename()); | 246 auto SourceBuffer = getSourceFile(ExpansionCoverage.getFilename()); |
134 if (!SourceBuffer) | 247 if (!SourceBuffer) |
135 continue; | 248 continue; |
136 | 249 |
137 auto SubViewExpansions = ExpansionCoverage.getExpansions(); | 250 auto SubViewExpansions = ExpansionCoverage.getExpansions(); |
138 auto SubView = llvm::make_unique<SourceCoverageView>( | 251 auto SubView = |
139 SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage)); | 252 SourceCoverageView::create(Expansion.Function.Name, SourceBuffer.get(), |
253 ViewOpts, std::move(ExpansionCoverage)); | |
140 attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); | 254 attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); |
141 View.addExpansion(Expansion.Region, std::move(SubView)); | 255 View.addExpansion(Expansion.Region, std::move(SubView)); |
142 } | 256 } |
143 } | 257 } |
144 | 258 |
145 std::unique_ptr<SourceCoverageView> | 259 std::unique_ptr<SourceCoverageView> |
146 CodeCoverageTool::createFunctionView(const FunctionRecord &Function, | 260 CodeCoverageTool::createFunctionView(const FunctionRecord &Function, |
147 CoverageMapping &Coverage) { | 261 const CoverageMapping &Coverage) { |
148 auto FunctionCoverage = Coverage.getCoverageForFunction(Function); | 262 auto FunctionCoverage = Coverage.getCoverageForFunction(Function); |
149 if (FunctionCoverage.empty()) | 263 if (FunctionCoverage.empty()) |
150 return nullptr; | 264 return nullptr; |
151 auto SourceBuffer = getSourceFile(FunctionCoverage.getFilename()); | 265 auto SourceBuffer = getSourceFile(FunctionCoverage.getFilename()); |
152 if (!SourceBuffer) | 266 if (!SourceBuffer) |
153 return nullptr; | 267 return nullptr; |
154 | 268 |
155 auto Expansions = FunctionCoverage.getExpansions(); | 269 auto Expansions = FunctionCoverage.getExpansions(); |
156 auto View = llvm::make_unique<SourceCoverageView>( | 270 auto View = SourceCoverageView::create(getSymbolForHumans(Function.Name), |
157 SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage)); | 271 SourceBuffer.get(), ViewOpts, |
272 std::move(FunctionCoverage)); | |
158 attachExpansionSubViews(*View, Expansions, Coverage); | 273 attachExpansionSubViews(*View, Expansions, Coverage); |
159 | 274 |
160 return View; | 275 return View; |
161 } | 276 } |
162 | 277 |
163 std::unique_ptr<SourceCoverageView> | 278 std::unique_ptr<SourceCoverageView> |
164 CodeCoverageTool::createSourceFileView(StringRef SourceFile, | 279 CodeCoverageTool::createSourceFileView(StringRef SourceFile, |
165 CoverageMapping &Coverage) { | 280 const CoverageMapping &Coverage) { |
166 auto SourceBuffer = getSourceFile(SourceFile); | 281 auto SourceBuffer = getSourceFile(SourceFile); |
167 if (!SourceBuffer) | 282 if (!SourceBuffer) |
168 return nullptr; | 283 return nullptr; |
169 auto FileCoverage = Coverage.getCoverageForFile(SourceFile); | 284 auto FileCoverage = Coverage.getCoverageForFile(SourceFile); |
170 if (FileCoverage.empty()) | 285 if (FileCoverage.empty()) |
171 return nullptr; | 286 return nullptr; |
172 | 287 |
173 auto Expansions = FileCoverage.getExpansions(); | 288 auto Expansions = FileCoverage.getExpansions(); |
174 auto View = llvm::make_unique<SourceCoverageView>( | 289 auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(), |
175 SourceBuffer.get(), ViewOpts, std::move(FileCoverage)); | 290 ViewOpts, std::move(FileCoverage)); |
176 attachExpansionSubViews(*View, Expansions, Coverage); | 291 attachExpansionSubViews(*View, Expansions, Coverage); |
177 | 292 |
178 for (auto Function : Coverage.getInstantiations(SourceFile)) { | 293 for (const auto *Function : Coverage.getInstantiations(SourceFile)) { |
179 auto SubViewCoverage = Coverage.getCoverageForFunction(*Function); | 294 std::unique_ptr<SourceCoverageView> SubView{nullptr}; |
180 auto SubViewExpansions = SubViewCoverage.getExpansions(); | 295 |
181 auto SubView = llvm::make_unique<SourceCoverageView>( | 296 StringRef Funcname = getSymbolForHumans(Function->Name); |
182 SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage)); | 297 |
183 attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); | 298 if (Function->ExecutionCount > 0) { |
184 | 299 auto SubViewCoverage = Coverage.getCoverageForFunction(*Function); |
185 if (SubView) { | 300 auto SubViewExpansions = SubViewCoverage.getExpansions(); |
186 unsigned FileID = Function->CountedRegions.front().FileID; | 301 SubView = SourceCoverageView::create( |
187 unsigned Line = 0; | 302 Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage)); |
188 for (const auto &CR : Function->CountedRegions) | 303 attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); |
189 if (CR.FileID == FileID) | 304 } |
190 Line = std::max(CR.LineEnd, Line); | 305 |
191 View->addInstantiation(Function->Name, Line, std::move(SubView)); | 306 unsigned FileID = Function->CountedRegions.front().FileID; |
192 } | 307 unsigned Line = 0; |
308 for (const auto &CR : Function->CountedRegions) | |
309 if (CR.FileID == FileID) | |
310 Line = std::max(CR.LineEnd, Line); | |
311 View->addInstantiation(Funcname, Line, std::move(SubView)); | |
193 } | 312 } |
194 return View; | 313 return View; |
195 } | 314 } |
196 | 315 |
197 static bool modifiedTimeGT(StringRef LHS, StringRef RHS) { | 316 static bool modifiedTimeGT(StringRef LHS, StringRef RHS) { |
204 auto RHSTime = Status.getLastModificationTime(); | 323 auto RHSTime = Status.getLastModificationTime(); |
205 return LHSTime > RHSTime; | 324 return LHSTime > RHSTime; |
206 } | 325 } |
207 | 326 |
208 std::unique_ptr<CoverageMapping> CodeCoverageTool::load() { | 327 std::unique_ptr<CoverageMapping> CodeCoverageTool::load() { |
209 if (modifiedTimeGT(ObjectFilename, PGOFilename)) | 328 for (StringRef ObjectFilename : ObjectFilenames) |
210 errs() << "warning: profile data may be out of date - object is newer\n"; | 329 if (modifiedTimeGT(ObjectFilename, PGOFilename)) |
211 auto CoverageOrErr = CoverageMapping::load(ObjectFilename, PGOFilename, | 330 warning("profile data may be out of date - object is newer", |
212 CoverageArch); | 331 ObjectFilename); |
213 if (std::error_code EC = CoverageOrErr.getError()) { | 332 auto CoverageOrErr = |
214 colored_ostream(errs(), raw_ostream::RED) | 333 CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArch); |
215 << "error: Failed to load coverage: " << EC.message(); | 334 if (Error E = CoverageOrErr.takeError()) { |
216 errs() << "\n"; | 335 error("Failed to load coverage: " + toString(std::move(E)), |
336 join(ObjectFilenames.begin(), ObjectFilenames.end(), ", ")); | |
217 return nullptr; | 337 return nullptr; |
218 } | 338 } |
219 auto Coverage = std::move(CoverageOrErr.get()); | 339 auto Coverage = std::move(CoverageOrErr.get()); |
220 unsigned Mismatched = Coverage->getMismatchedCount(); | 340 unsigned Mismatched = Coverage->getMismatchedCount(); |
221 if (Mismatched) { | 341 if (Mismatched) |
222 colored_ostream(errs(), raw_ostream::RED) | 342 warning(utostr(Mismatched) + " functions have mismatched data"); |
223 << "warning: " << Mismatched << " functions have mismatched data. "; | 343 |
224 errs() << "\n"; | 344 if (!SourceFiles.empty()) |
225 } | 345 removeUnmappedInputs(*Coverage); |
226 | 346 |
227 if (CompareFilenamesOnly) { | 347 demangleSymbols(*Coverage); |
228 auto CoveredFiles = Coverage.get()->getUniqueSourceFiles(); | 348 |
349 return Coverage; | |
350 } | |
351 | |
352 void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping &Coverage) { | |
353 std::vector<StringRef> CoveredFiles = Coverage.getUniqueSourceFiles(); | |
354 | |
355 auto UncoveredFilesIt = SourceFiles.end(); | |
356 if (!CompareFilenamesOnly) { | |
357 // The user may have specified source files which aren't in the coverage | |
358 // mapping. Filter these files away. | |
359 UncoveredFilesIt = std::remove_if( | |
360 SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) { | |
361 return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(), | |
362 SF); | |
363 }); | |
364 } else { | |
229 for (auto &SF : SourceFiles) { | 365 for (auto &SF : SourceFiles) { |
230 StringRef SFBase = sys::path::filename(SF); | 366 StringRef SFBase = sys::path::filename(SF); |
231 for (const auto &CF : CoveredFiles) | 367 for (const auto &CF : CoveredFiles) { |
232 if (SFBase == sys::path::filename(CF)) { | 368 if (SFBase == sys::path::filename(CF)) { |
233 RemappedFilenames[CF] = SF; | 369 RemappedFilenames[CF] = SF; |
234 SF = CF; | 370 SF = CF; |
235 break; | 371 break; |
236 } | 372 } |
237 } | 373 } |
238 } | 374 } |
239 | 375 UncoveredFilesIt = std::remove_if( |
240 return Coverage; | 376 SourceFiles.begin(), SourceFiles.end(), |
377 [&](const std::string &SF) { return !RemappedFilenames.count(SF); }); | |
378 } | |
379 | |
380 SourceFiles.erase(UncoveredFilesIt, SourceFiles.end()); | |
381 } | |
382 | |
383 void CodeCoverageTool::demangleSymbols(const CoverageMapping &Coverage) { | |
384 if (!ViewOpts.hasDemangler()) | |
385 return; | |
386 | |
387 // Pass function names to the demangler in a temporary file. | |
388 int InputFD; | |
389 SmallString<256> InputPath; | |
390 std::error_code EC = | |
391 sys::fs::createTemporaryFile("demangle-in", "list", InputFD, InputPath); | |
392 if (EC) { | |
393 error(InputPath, EC.message()); | |
394 return; | |
395 } | |
396 tool_output_file InputTOF{InputPath, InputFD}; | |
397 | |
398 unsigned NumSymbols = 0; | |
399 for (const auto &Function : Coverage.getCoveredFunctions()) { | |
400 InputTOF.os() << Function.Name << '\n'; | |
401 ++NumSymbols; | |
402 } | |
403 InputTOF.os().close(); | |
404 | |
405 // Use another temporary file to store the demangler's output. | |
406 int OutputFD; | |
407 SmallString<256> OutputPath; | |
408 EC = sys::fs::createTemporaryFile("demangle-out", "list", OutputFD, | |
409 OutputPath); | |
410 if (EC) { | |
411 error(OutputPath, EC.message()); | |
412 return; | |
413 } | |
414 tool_output_file OutputTOF{OutputPath, OutputFD}; | |
415 OutputTOF.os().close(); | |
416 | |
417 // Invoke the demangler. | |
418 std::vector<const char *> ArgsV; | |
419 for (const std::string &Arg : ViewOpts.DemanglerOpts) | |
420 ArgsV.push_back(Arg.c_str()); | |
421 ArgsV.push_back(nullptr); | |
422 StringRef InputPathRef = InputPath.str(); | |
423 StringRef OutputPathRef = OutputPath.str(); | |
424 StringRef StderrRef; | |
425 const StringRef *Redirects[] = {&InputPathRef, &OutputPathRef, &StderrRef}; | |
426 std::string ErrMsg; | |
427 int RC = sys::ExecuteAndWait(ViewOpts.DemanglerOpts[0], ArgsV.data(), | |
428 /*env=*/nullptr, Redirects, /*secondsToWait=*/0, | |
429 /*memoryLimit=*/0, &ErrMsg); | |
430 if (RC) { | |
431 error(ErrMsg, ViewOpts.DemanglerOpts[0]); | |
432 return; | |
433 } | |
434 | |
435 // Parse the demangler's output. | |
436 auto BufOrError = MemoryBuffer::getFile(OutputPath); | |
437 if (!BufOrError) { | |
438 error(OutputPath, BufOrError.getError().message()); | |
439 return; | |
440 } | |
441 | |
442 std::unique_ptr<MemoryBuffer> DemanglerBuf = std::move(*BufOrError); | |
443 | |
444 SmallVector<StringRef, 8> Symbols; | |
445 StringRef DemanglerData = DemanglerBuf->getBuffer(); | |
446 DemanglerData.split(Symbols, '\n', /*MaxSplit=*/NumSymbols, | |
447 /*KeepEmpty=*/false); | |
448 if (Symbols.size() != NumSymbols) { | |
449 error("Demangler did not provide expected number of symbols"); | |
450 return; | |
451 } | |
452 | |
453 // Cache the demangled names. | |
454 unsigned I = 0; | |
455 for (const auto &Function : Coverage.getCoveredFunctions()) | |
456 DemangledNames[Function.Name] = Symbols[I++]; | |
457 } | |
458 | |
459 StringRef CodeCoverageTool::getSymbolForHumans(StringRef Sym) const { | |
460 const auto DemangledName = DemangledNames.find(Sym); | |
461 if (DemangledName == DemangledNames.end()) | |
462 return Sym; | |
463 return DemangledName->getValue(); | |
464 } | |
465 | |
466 void CodeCoverageTool::writeSourceFileView(StringRef SourceFile, | |
467 CoverageMapping *Coverage, | |
468 CoveragePrinter *Printer, | |
469 bool ShowFilenames) { | |
470 auto View = createSourceFileView(SourceFile, *Coverage); | |
471 if (!View) { | |
472 warning("The file '" + SourceFile + "' isn't covered."); | |
473 return; | |
474 } | |
475 | |
476 auto OSOrErr = Printer->createViewFile(SourceFile, /*InToplevel=*/false); | |
477 if (Error E = OSOrErr.takeError()) { | |
478 error("Could not create view file!", toString(std::move(E))); | |
479 return; | |
480 } | |
481 auto OS = std::move(OSOrErr.get()); | |
482 | |
483 View->print(*OS.get(), /*Wholefile=*/true, | |
484 /*ShowSourceName=*/ShowFilenames); | |
485 Printer->closeViewFile(std::move(OS)); | |
241 } | 486 } |
242 | 487 |
243 int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { | 488 int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { |
244 // Print a stack trace if we signal out. | 489 cl::opt<std::string> CovFilename( |
245 sys::PrintStackTraceOnErrorSignal(); | 490 cl::Positional, cl::desc("Covered executable or object file.")); |
246 PrettyStackTraceProgram X(argc, argv); | 491 |
247 llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. | 492 cl::list<std::string> CovFilenames( |
248 | 493 "object", cl::desc("Coverage executable or object file"), cl::ZeroOrMore, |
249 cl::opt<std::string, true> ObjectFilename( | 494 cl::CommaSeparated); |
250 cl::Positional, cl::Required, cl::location(this->ObjectFilename), | |
251 cl::desc("Covered executable or object file.")); | |
252 | 495 |
253 cl::list<std::string> InputSourceFiles( | 496 cl::list<std::string> InputSourceFiles( |
254 cl::Positional, cl::desc("<Source files>"), cl::ZeroOrMore); | 497 cl::Positional, cl::desc("<Source files>"), cl::ZeroOrMore); |
498 | |
499 cl::opt<bool> DebugDumpCollectedPaths( | |
500 "dump-collected-paths", cl::Optional, cl::Hidden, | |
501 cl::desc("Show the collected paths to source files")); | |
255 | 502 |
256 cl::opt<std::string, true> PGOFilename( | 503 cl::opt<std::string, true> PGOFilename( |
257 "instr-profile", cl::Required, cl::location(this->PGOFilename), | 504 "instr-profile", cl::Required, cl::location(this->PGOFilename), |
258 cl::desc( | 505 cl::desc( |
259 "File with the profile data obtained after an instrumented run")); | 506 "File with the profile data obtained after an instrumented run")); |
261 cl::opt<std::string> Arch( | 508 cl::opt<std::string> Arch( |
262 "arch", cl::desc("architecture of the coverage mapping binary")); | 509 "arch", cl::desc("architecture of the coverage mapping binary")); |
263 | 510 |
264 cl::opt<bool> DebugDump("dump", cl::Optional, | 511 cl::opt<bool> DebugDump("dump", cl::Optional, |
265 cl::desc("Show internal debug dump")); | 512 cl::desc("Show internal debug dump")); |
513 | |
514 cl::opt<CoverageViewOptions::OutputFormat> Format( | |
515 "format", cl::desc("Output format for line-based coverage reports"), | |
516 cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text", | |
517 "Text output"), | |
518 clEnumValN(CoverageViewOptions::OutputFormat::HTML, "html", | |
519 "HTML output")), | |
520 cl::init(CoverageViewOptions::OutputFormat::Text)); | |
266 | 521 |
267 cl::opt<bool> FilenameEquivalence( | 522 cl::opt<bool> FilenameEquivalence( |
268 "filename-equivalence", cl::Optional, | 523 "filename-equivalence", cl::Optional, |
269 cl::desc("Treat source files as equivalent to paths in the coverage data " | 524 cl::desc("Treat source files as equivalent to paths in the coverage data " |
270 "when the file names match, even if the full paths do not")); | 525 "when the file names match, even if the full paths do not")); |
308 | 563 |
309 cl::opt<cl::boolOrDefault> UseColor( | 564 cl::opt<cl::boolOrDefault> UseColor( |
310 "use-color", cl::desc("Emit colored output (default=autodetect)"), | 565 "use-color", cl::desc("Emit colored output (default=autodetect)"), |
311 cl::init(cl::BOU_UNSET)); | 566 cl::init(cl::BOU_UNSET)); |
312 | 567 |
568 cl::list<std::string> DemanglerOpts( | |
569 "Xdemangler", cl::desc("<demangler-path>|<demangler-option>")); | |
570 | |
313 auto commandLineParser = [&, this](int argc, const char **argv) -> int { | 571 auto commandLineParser = [&, this](int argc, const char **argv) -> int { |
314 cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); | 572 cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); |
315 ViewOpts.Debug = DebugDump; | 573 ViewOpts.Debug = DebugDump; |
316 CompareFilenamesOnly = FilenameEquivalence; | 574 CompareFilenamesOnly = FilenameEquivalence; |
317 | 575 |
318 ViewOpts.Colors = UseColor == cl::BOU_UNSET | 576 if (!CovFilename.empty()) |
319 ? sys::Process::StandardOutHasColors() | 577 ObjectFilenames.emplace_back(CovFilename); |
320 : UseColor == cl::BOU_TRUE; | 578 for (const std::string &Filename : CovFilenames) |
579 ObjectFilenames.emplace_back(Filename); | |
580 if (ObjectFilenames.empty()) { | |
581 errs() << "No filenames specified!\n"; | |
582 ::exit(1); | |
583 } | |
584 | |
585 ViewOpts.Format = Format; | |
586 switch (ViewOpts.Format) { | |
587 case CoverageViewOptions::OutputFormat::Text: | |
588 ViewOpts.Colors = UseColor == cl::BOU_UNSET | |
589 ? sys::Process::StandardOutHasColors() | |
590 : UseColor == cl::BOU_TRUE; | |
591 break; | |
592 case CoverageViewOptions::OutputFormat::HTML: | |
593 if (UseColor == cl::BOU_FALSE) | |
594 errs() << "Color output cannot be disabled when generating html.\n"; | |
595 ViewOpts.Colors = true; | |
596 break; | |
597 } | |
598 | |
599 // If a demangler is supplied, check if it exists and register it. | |
600 if (DemanglerOpts.size()) { | |
601 auto DemanglerPathOrErr = sys::findProgramByName(DemanglerOpts[0]); | |
602 if (!DemanglerPathOrErr) { | |
603 error("Could not find the demangler!", | |
604 DemanglerPathOrErr.getError().message()); | |
605 return 1; | |
606 } | |
607 DemanglerOpts[0] = *DemanglerPathOrErr; | |
608 ViewOpts.DemanglerOpts.swap(DemanglerOpts); | |
609 } | |
321 | 610 |
322 // Create the function filters | 611 // Create the function filters |
323 if (!NameFilters.empty() || !NameRegexFilters.empty()) { | 612 if (!NameFilters.empty() || !NameRegexFilters.empty()) { |
324 auto NameFilterer = new CoverageFilters; | 613 auto NameFilterer = new CoverageFilters; |
325 for (const auto &Name : NameFilters) | 614 for (const auto &Name : NameFilters) |
349 Filters.push_back(std::unique_ptr<CoverageFilter>(StatFilterer)); | 638 Filters.push_back(std::unique_ptr<CoverageFilter>(StatFilterer)); |
350 } | 639 } |
351 | 640 |
352 if (!Arch.empty() && | 641 if (!Arch.empty() && |
353 Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) { | 642 Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) { |
354 errs() << "error: Unknown architecture: " << Arch << "\n"; | 643 error("Unknown architecture: " + Arch); |
355 return 1; | 644 return 1; |
356 } | 645 } |
357 CoverageArch = Arch; | 646 CoverageArch = Arch; |
358 | 647 |
359 for (const auto &File : InputSourceFiles) { | 648 for (const std::string &File : InputSourceFiles) |
360 SmallString<128> Path(File); | 649 collectPaths(File); |
361 if (!CompareFilenamesOnly) | 650 |
362 if (std::error_code EC = sys::fs::make_absolute(Path)) { | 651 if (DebugDumpCollectedPaths) { |
363 errs() << "error: " << File << ": " << EC.message(); | 652 for (const std::string &SF : SourceFiles) |
364 return 1; | 653 outs() << SF << '\n'; |
365 } | 654 ::exit(0); |
366 SourceFiles.push_back(Path.str()); | 655 } |
367 } | 656 |
368 return 0; | 657 return 0; |
369 }; | 658 }; |
370 | 659 |
371 switch (Cmd) { | 660 switch (Cmd) { |
372 case Show: | 661 case Show: |
373 return show(argc, argv, commandLineParser); | 662 return show(argc, argv, commandLineParser); |
374 case Report: | 663 case Report: |
375 return report(argc, argv, commandLineParser); | 664 return report(argc, argv, commandLineParser); |
665 case Export: | |
666 return export_(argc, argv, commandLineParser); | |
376 } | 667 } |
377 return 0; | 668 return 0; |
378 } | 669 } |
379 | 670 |
380 int CodeCoverageTool::show(int argc, const char **argv, | 671 int CodeCoverageTool::show(int argc, const char **argv, |
403 cl::cat(ViewCategory)); | 694 cl::cat(ViewCategory)); |
404 | 695 |
405 cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional, | 696 cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional, |
406 cl::desc("Show function instantiations"), | 697 cl::desc("Show function instantiations"), |
407 cl::cat(ViewCategory)); | 698 cl::cat(ViewCategory)); |
699 | |
700 cl::opt<std::string> ShowOutputDirectory( | |
701 "output-dir", cl::init(""), | |
702 cl::desc("Directory in which coverage information is written out")); | |
703 cl::alias ShowOutputDirectoryA("o", cl::desc("Alias for --output-dir"), | |
704 cl::aliasopt(ShowOutputDirectory)); | |
705 | |
706 cl::opt<uint32_t> TabSize( | |
707 "tab-size", cl::init(2), | |
708 cl::desc( | |
709 "Set tab expansion size for html coverage reports (default = 2)")); | |
710 | |
711 cl::opt<std::string> ProjectTitle( | |
712 "project-title", cl::Optional, | |
713 cl::desc("Set project title for the coverage report")); | |
408 | 714 |
409 auto Err = commandLineParser(argc, argv); | 715 auto Err = commandLineParser(argc, argv); |
410 if (Err) | 716 if (Err) |
411 return Err; | 717 return Err; |
412 | 718 |
415 !ShowRegions || ShowBestLineRegionsCounts; | 721 !ShowRegions || ShowBestLineRegionsCounts; |
416 ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts; | 722 ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts; |
417 ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts; | 723 ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts; |
418 ViewOpts.ShowExpandedRegions = ShowExpansions; | 724 ViewOpts.ShowExpandedRegions = ShowExpansions; |
419 ViewOpts.ShowFunctionInstantiations = ShowInstantiations; | 725 ViewOpts.ShowFunctionInstantiations = ShowInstantiations; |
726 ViewOpts.ShowOutputDirectory = ShowOutputDirectory; | |
727 ViewOpts.TabSize = TabSize; | |
728 ViewOpts.ProjectTitle = ProjectTitle; | |
729 | |
730 if (ViewOpts.hasOutputDirectory()) { | |
731 if (auto E = sys::fs::create_directories(ViewOpts.ShowOutputDirectory)) { | |
732 error("Could not create output directory!", E.message()); | |
733 return 1; | |
734 } | |
735 } | |
736 | |
737 sys::fs::file_status Status; | |
738 if (sys::fs::status(PGOFilename, Status)) { | |
739 error("profdata file error: can not get the file status. \n"); | |
740 return 1; | |
741 } | |
742 | |
743 auto ModifiedTime = Status.getLastModificationTime(); | |
744 std::string ModifiedTimeStr = to_string(ModifiedTime); | |
745 size_t found = ModifiedTimeStr.rfind(":"); | |
746 ViewOpts.CreatedTimeStr = (found != std::string::npos) | |
747 ? "Created: " + ModifiedTimeStr.substr(0, found) | |
748 : "Created: " + ModifiedTimeStr; | |
420 | 749 |
421 auto Coverage = load(); | 750 auto Coverage = load(); |
422 if (!Coverage) | 751 if (!Coverage) |
423 return 1; | 752 return 1; |
424 | 753 |
754 auto Printer = CoveragePrinter::create(ViewOpts); | |
755 | |
425 if (!Filters.empty()) { | 756 if (!Filters.empty()) { |
426 // Show functions | 757 auto OSOrErr = Printer->createViewFile("functions", /*InToplevel=*/true); |
758 if (Error E = OSOrErr.takeError()) { | |
759 error("Could not create view file!", toString(std::move(E))); | |
760 return 1; | |
761 } | |
762 auto OS = std::move(OSOrErr.get()); | |
763 | |
764 // Show functions. | |
427 for (const auto &Function : Coverage->getCoveredFunctions()) { | 765 for (const auto &Function : Coverage->getCoveredFunctions()) { |
428 if (!Filters.matches(Function)) | 766 if (!Filters.matches(Function)) |
429 continue; | 767 continue; |
430 | 768 |
431 auto mainView = createFunctionView(Function, *Coverage); | 769 auto mainView = createFunctionView(Function, *Coverage); |
432 if (!mainView) { | 770 if (!mainView) { |
433 ViewOpts.colored_ostream(outs(), raw_ostream::RED) | 771 warning("Could not read coverage for '" + Function.Name + "'."); |
434 << "warning: Could not read coverage for '" << Function.Name; | |
435 outs() << "\n"; | |
436 continue; | 772 continue; |
437 } | 773 } |
438 ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name | 774 |
439 << ":"; | 775 mainView->print(*OS.get(), /*WholeFile=*/false, /*ShowSourceName=*/true); |
440 outs() << "\n"; | 776 } |
441 mainView->render(outs(), /*WholeFile=*/false); | 777 |
442 outs() << "\n"; | 778 Printer->closeViewFile(std::move(OS)); |
443 } | |
444 return 0; | 779 return 0; |
445 } | 780 } |
446 | 781 |
447 // Show files | 782 // Show files |
448 bool ShowFilenames = SourceFiles.size() != 1; | 783 bool ShowFilenames = |
784 (SourceFiles.size() != 1) || ViewOpts.hasOutputDirectory() || | |
785 (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML); | |
449 | 786 |
450 if (SourceFiles.empty()) | 787 if (SourceFiles.empty()) |
451 // Get the source files from the function coverage mapping | 788 // Get the source files from the function coverage mapping. |
452 for (StringRef Filename : Coverage->getUniqueSourceFiles()) | 789 for (StringRef Filename : Coverage->getUniqueSourceFiles()) |
453 SourceFiles.push_back(Filename); | 790 SourceFiles.push_back(Filename); |
454 | 791 |
455 for (const auto &SourceFile : SourceFiles) { | 792 // Create an index out of the source files. |
456 auto mainView = createSourceFileView(SourceFile, *Coverage); | 793 if (ViewOpts.hasOutputDirectory()) { |
457 if (!mainView) { | 794 if (Error E = Printer->createIndexFile(SourceFiles, *Coverage)) { |
458 ViewOpts.colored_ostream(outs(), raw_ostream::RED) | 795 error("Could not create index file!", toString(std::move(E))); |
459 << "warning: The file '" << SourceFile << "' isn't covered."; | 796 return 1; |
460 outs() << "\n"; | 797 } |
461 continue; | 798 } |
462 } | 799 |
463 | 800 // FIXME: Sink the hardware_concurrency() == 1 check into ThreadPool. |
464 if (ShowFilenames) { | 801 if (!ViewOpts.hasOutputDirectory() || |
465 ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":"; | 802 std::thread::hardware_concurrency() == 1) { |
466 outs() << "\n"; | 803 for (const std::string &SourceFile : SourceFiles) |
467 } | 804 writeSourceFileView(SourceFile, Coverage.get(), Printer.get(), |
468 mainView->render(outs(), /*Wholefile=*/true); | 805 ShowFilenames); |
469 if (SourceFiles.size() > 1) | 806 } else { |
470 outs() << "\n"; | 807 // In -output-dir mode, it's safe to use multiple threads to print files. |
808 ThreadPool Pool; | |
809 for (const std::string &SourceFile : SourceFiles) | |
810 Pool.async(&CodeCoverageTool::writeSourceFileView, this, SourceFile, | |
811 Coverage.get(), Printer.get(), ShowFilenames); | |
812 Pool.wait(); | |
471 } | 813 } |
472 | 814 |
473 return 0; | 815 return 0; |
474 } | 816 } |
475 | 817 |
477 CommandLineParserType commandLineParser) { | 819 CommandLineParserType commandLineParser) { |
478 auto Err = commandLineParser(argc, argv); | 820 auto Err = commandLineParser(argc, argv); |
479 if (Err) | 821 if (Err) |
480 return Err; | 822 return Err; |
481 | 823 |
824 if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) | |
825 error("HTML output for summary reports is not yet supported."); | |
826 | |
482 auto Coverage = load(); | 827 auto Coverage = load(); |
483 if (!Coverage) | 828 if (!Coverage) |
484 return 1; | 829 return 1; |
485 | 830 |
486 CoverageReport Report(ViewOpts, std::move(Coverage)); | 831 CoverageReport Report(ViewOpts, *Coverage.get()); |
487 if (SourceFiles.empty()) | 832 if (SourceFiles.empty()) |
488 Report.renderFileReports(llvm::outs()); | 833 Report.renderFileReports(llvm::outs()); |
489 else | 834 else |
490 Report.renderFunctionReports(SourceFiles, llvm::outs()); | 835 Report.renderFunctionReports(SourceFiles, llvm::outs()); |
491 return 0; | 836 return 0; |
492 } | 837 } |
493 | 838 |
839 int CodeCoverageTool::export_(int argc, const char **argv, | |
840 CommandLineParserType commandLineParser) { | |
841 | |
842 auto Err = commandLineParser(argc, argv); | |
843 if (Err) | |
844 return Err; | |
845 | |
846 auto Coverage = load(); | |
847 if (!Coverage) { | |
848 error("Could not load coverage information"); | |
849 return 1; | |
850 } | |
851 | |
852 exportCoverageDataToJson(*Coverage.get(), outs()); | |
853 | |
854 return 0; | |
855 } | |
856 | |
494 int showMain(int argc, const char *argv[]) { | 857 int showMain(int argc, const char *argv[]) { |
495 CodeCoverageTool Tool; | 858 CodeCoverageTool Tool; |
496 return Tool.run(CodeCoverageTool::Show, argc, argv); | 859 return Tool.run(CodeCoverageTool::Show, argc, argv); |
497 } | 860 } |
498 | 861 |
499 int reportMain(int argc, const char *argv[]) { | 862 int reportMain(int argc, const char *argv[]) { |
500 CodeCoverageTool Tool; | 863 CodeCoverageTool Tool; |
501 return Tool.run(CodeCoverageTool::Report, argc, argv); | 864 return Tool.run(CodeCoverageTool::Report, argc, argv); |
502 } | 865 } |
866 | |
867 int exportMain(int argc, const char *argv[]) { | |
868 CodeCoverageTool Tool; | |
869 return Tool.run(CodeCoverageTool::Export, argc, argv); | |
870 } |