| 1 | //===- CoverageExporterJson.cpp - Code coverage export --------------------===// |
| 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 | // This file implements export of code coverage data to JSON. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | //===----------------------------------------------------------------------===// |
| 14 | // |
| 15 | // The json code coverage export follows the following format |
| 16 | // Root: dict => Root Element containing metadata |
| 17 | // -- Data: array => Homogeneous array of one or more export objects |
| 18 | // -- Export: dict => Json representation of one CoverageMapping |
| 19 | // -- Files: array => List of objects describing coverage for files |
| 20 | // -- File: dict => Coverage for a single file |
| 21 | // -- Branches: array => List of Branches in the file |
| 22 | // -- Branch: dict => Describes a branch of the file with counters |
| 23 | // -- MCDC Records: array => List of MCDC records in the file |
| 24 | // -- MCDC Values: array => List of T/F covered condition values and |
| 25 | // list of test vectors with execution status |
| 26 | // -- Segments: array => List of Segments contained in the file |
| 27 | // -- Segment: dict => Describes a segment of the file with a counter |
| 28 | // -- Expansions: array => List of expansion records |
| 29 | // -- Expansion: dict => Object that descibes a single expansion |
| 30 | // -- CountedRegion: dict => The region to be expanded |
| 31 | // -- TargetRegions: array => List of Regions in the expansion |
| 32 | // -- CountedRegion: dict => Single Region in the expansion |
| 33 | // -- Branches: array => List of Branches in the expansion |
| 34 | // -- Branch: dict => Describes a branch in expansion and counters |
| 35 | // -- Summary: dict => Object summarizing the coverage for this file |
| 36 | // -- LineCoverage: dict => Object summarizing line coverage |
| 37 | // -- FunctionCoverage: dict => Object summarizing function coverage |
| 38 | // -- RegionCoverage: dict => Object summarizing region coverage |
| 39 | // -- BranchCoverage: dict => Object summarizing branch coverage |
| 40 | // -- MCDCCoverage: dict => Object summarizing MC/DC coverage |
| 41 | // -- Functions: array => List of objects describing coverage for functions |
| 42 | // -- Function: dict => Coverage info for a single function |
| 43 | // -- Filenames: array => List of filenames that the function relates to |
| 44 | // -- Summary: dict => Object summarizing the coverage for the entire binary |
| 45 | // -- LineCoverage: dict => Object summarizing line coverage |
| 46 | // -- FunctionCoverage: dict => Object summarizing function coverage |
| 47 | // -- InstantiationCoverage: dict => Object summarizing inst. coverage |
| 48 | // -- RegionCoverage: dict => Object summarizing region coverage |
| 49 | // -- BranchCoverage: dict => Object summarizing branch coverage |
| 50 | // -- MCDCCoverage: dict => Object summarizing MC/DC coverage |
| 51 | // |
| 52 | //===----------------------------------------------------------------------===// |
| 53 | |
| 54 | #include "CoverageExporterJson.h" |
| 55 | #include "CoverageReport.h" |
| 56 | #include "llvm/ADT/StringRef.h" |
| 57 | #include "llvm/Support/JSON.h" |
| 58 | #include "llvm/Support/ThreadPool.h" |
| 59 | #include "llvm/Support/Threading.h" |
| 60 | #include <algorithm> |
| 61 | #include <limits> |
| 62 | #include <numeric> |
| 63 | #include <utility> |
| 64 | |
| 65 | /// The semantic version combined as a string. |
| 66 | #define LLVM_COVERAGE_EXPORT_JSON_STR "3.1.0" |
| 67 | |
| 68 | /// Unique type identifier for JSON coverage export. |
| 69 | #define LLVM_COVERAGE_EXPORT_JSON_TYPE_STR "llvm.coverage.json.export" |
| 70 | |
| 71 | using namespace llvm; |
| 72 | |
| 73 | namespace { |
| 74 | |
| 75 | // The JSON library accepts int64_t, but profiling counts are stored as uint64_t. |
| 76 | // Therefore we need to explicitly convert from unsigned to signed, since a naive |
| 77 | // cast is implementation-defined behavior when the unsigned value cannot be |
| 78 | // represented as a signed value. We choose to clamp the values to preserve the |
| 79 | // invariant that counts are always >= 0. |
| 80 | int64_t clamp_uint64_to_int64(uint64_t u) { |
| 81 | return std::min(a: u, b: static_cast<uint64_t>(std::numeric_limits<int64_t>::max())); |
| 82 | } |
| 83 | |
| 84 | void renderSegment(json::OStream &JOS, |
| 85 | const coverage::CoverageSegment &Segment) { |
| 86 | JOS.array(Contents: [&] { |
| 87 | JOS.value(V: Segment.Line); |
| 88 | JOS.value(V: Segment.Col); |
| 89 | JOS.value(V: clamp_uint64_to_int64(u: Segment.Count)); |
| 90 | JOS.value(V: Segment.HasCount); |
| 91 | JOS.value(V: Segment.IsRegionEntry); |
| 92 | JOS.value(V: Segment.IsGapRegion); |
| 93 | }); |
| 94 | } |
| 95 | |
| 96 | void renderRegion(json::OStream &JOS, const coverage::CountedRegion &Region) { |
| 97 | JOS.array(Contents: [&] { |
| 98 | JOS.value(V: Region.LineStart); |
| 99 | JOS.value(V: Region.ColumnStart); |
| 100 | JOS.value(V: Region.LineEnd); |
| 101 | JOS.value(V: Region.ColumnEnd); |
| 102 | JOS.value(V: clamp_uint64_to_int64(u: Region.ExecutionCount)); |
| 103 | JOS.value(V: Region.FileID); |
| 104 | JOS.value(V: Region.ExpandedFileID); |
| 105 | JOS.value(V: int64_t(Region.Kind)); |
| 106 | }); |
| 107 | } |
| 108 | |
| 109 | void renderBranch(json::OStream &JOS, const coverage::CountedRegion &Region) { |
| 110 | JOS.array(Contents: [&] { |
| 111 | JOS.value(V: Region.LineStart); |
| 112 | JOS.value(V: Region.ColumnStart); |
| 113 | JOS.value(V: Region.LineEnd); |
| 114 | JOS.value(V: Region.ColumnEnd); |
| 115 | JOS.value(V: clamp_uint64_to_int64(u: Region.ExecutionCount)); |
| 116 | JOS.value(V: clamp_uint64_to_int64(u: Region.FalseExecutionCount)); |
| 117 | JOS.value(V: Region.FileID); |
| 118 | JOS.value(V: Region.ExpandedFileID); |
| 119 | JOS.value(V: int64_t(Region.Kind)); |
| 120 | }); |
| 121 | } |
| 122 | |
| 123 | void gatherConditions(json::OStream &JOS, const coverage::MCDCRecord &Record) { |
| 124 | JOS.array(Contents: [&] { |
| 125 | for (unsigned c = 0; c < Record.getNumConditions(); c++) |
| 126 | JOS.value(V: Record.isConditionIndependencePairCovered(Condition: c)); |
| 127 | }); |
| 128 | } |
| 129 | |
| 130 | void renderCondState(json::OStream &JOS, |
| 131 | const coverage::MCDCRecord::CondState CondState) { |
| 132 | switch (CondState) { |
| 133 | case coverage::MCDCRecord::MCDC_DontCare: |
| 134 | JOS.value(V: nullptr); |
| 135 | return; |
| 136 | case coverage::MCDCRecord::MCDC_True: |
| 137 | JOS.value(V: true); |
| 138 | return; |
| 139 | case coverage::MCDCRecord::MCDC_False: |
| 140 | JOS.value(V: false); |
| 141 | return; |
| 142 | } |
| 143 | llvm_unreachable("Unknown llvm::coverage::MCDCRecord::CondState enum" ); |
| 144 | } |
| 145 | |
| 146 | void gatherTestVectors(json::OStream &JOS, coverage::MCDCRecord &Record, |
| 147 | const CoverageViewOptions &Options) { |
| 148 | unsigned NumConditions = Record.getNumConditions(); |
| 149 | const bool ShowNonExecutedVectors = Options.ShowMCDCNonExecutedVectors; |
| 150 | |
| 151 | JOS.array(Contents: [&] { |
| 152 | for (unsigned tv = 0; tv < Record.getNumTestVectors(); tv++) { |
| 153 | JOS.object(Contents: [&] { |
| 154 | JOS.attributeArray(Key: "conditions" , Contents: [&] { |
| 155 | for (unsigned c = 0; c < NumConditions; c++) |
| 156 | renderCondState(JOS, CondState: Record.getTVCondition(TestVectorIndex: tv, Condition: c)); |
| 157 | }); |
| 158 | |
| 159 | JOS.attribute(Key: "executed" , Contents: true); |
| 160 | |
| 161 | JOS.attributeBegin(Key: "result" ); |
| 162 | renderCondState(JOS, CondState: Record.getTVResult(TestVectorIndex: tv)); |
| 163 | JOS.attributeEnd(); |
| 164 | }); |
| 165 | } |
| 166 | |
| 167 | if (ShowNonExecutedVectors) { |
| 168 | for (unsigned tv = 0; tv < Record.getNumNotExecutedTestVectors(); tv++) { |
| 169 | JOS.object(Contents: [&] { |
| 170 | JOS.attributeArray(Key: "conditions" , Contents: [&] { |
| 171 | for (unsigned c = 0; c < NumConditions; c++) |
| 172 | renderCondState(JOS, CondState: Record.getNotExecutedTVCondition(NotExecutedIndex: tv, Condition: c)); |
| 173 | }); |
| 174 | |
| 175 | JOS.attribute(Key: "executed" , Contents: false); |
| 176 | |
| 177 | JOS.attributeBegin(Key: "result" ); |
| 178 | renderCondState(JOS, CondState: Record.getNotExecutedTVResult(NotExecutedIndex: tv)); |
| 179 | JOS.attributeEnd(); |
| 180 | }); |
| 181 | } |
| 182 | } |
| 183 | }); |
| 184 | } |
| 185 | |
| 186 | void renderMCDCRecord(json::OStream &JOS, const coverage::MCDCRecord &Record, |
| 187 | const CoverageViewOptions &Options) { |
| 188 | const llvm::coverage::CounterMappingRegion &CMR = Record.getDecisionRegion(); |
| 189 | const auto [TrueDecisions, FalseDecisions] = Record.getDecisions(); |
| 190 | JOS.array(Contents: [&, TrueDecisions = TrueDecisions, |
| 191 | FalseDecisions = FalseDecisions] { |
| 192 | JOS.value(V: CMR.LineStart); |
| 193 | JOS.value(V: CMR.ColumnStart); |
| 194 | JOS.value(V: CMR.LineEnd); |
| 195 | JOS.value(V: CMR.ColumnEnd); |
| 196 | JOS.value(V: TrueDecisions); |
| 197 | JOS.value(V: FalseDecisions); |
| 198 | JOS.value(V: CMR.FileID); |
| 199 | JOS.value(V: CMR.ExpandedFileID); |
| 200 | JOS.value(V: int64_t(CMR.Kind)); |
| 201 | gatherConditions(JOS, Record); |
| 202 | gatherTestVectors(JOS, Record&: const_cast<coverage::MCDCRecord &>(Record), Options); |
| 203 | }); |
| 204 | } |
| 205 | |
| 206 | void renderRegions(json::OStream &JOS, |
| 207 | ArrayRef<coverage::CountedRegion> Regions) { |
| 208 | JOS.array(Contents: [&] { |
| 209 | for (const auto &Region : Regions) |
| 210 | renderRegion(JOS, Region); |
| 211 | }); |
| 212 | } |
| 213 | |
| 214 | void renderBranchRegions(json::OStream &JOS, |
| 215 | ArrayRef<coverage::CountedRegion> Regions) { |
| 216 | JOS.array(Contents: [&] { |
| 217 | for (const auto &Region : Regions) |
| 218 | if (!Region.TrueFolded || !Region.FalseFolded) |
| 219 | renderBranch(JOS, Region); |
| 220 | }); |
| 221 | } |
| 222 | |
| 223 | void renderMCDCRecords(json::OStream &JOS, |
| 224 | ArrayRef<coverage::MCDCRecord> Records, |
| 225 | const CoverageViewOptions &Options) { |
| 226 | JOS.array(Contents: [&] { |
| 227 | for (auto &Record : Records) |
| 228 | renderMCDCRecord(JOS, Record, Options); |
| 229 | }); |
| 230 | } |
| 231 | |
| 232 | std::vector<llvm::coverage::CountedRegion> |
| 233 | collectNestedBranches(const coverage::CoverageMapping &Coverage, |
| 234 | ArrayRef<llvm::coverage::ExpansionRecord> Expansions) { |
| 235 | std::vector<llvm::coverage::CountedRegion> Branches; |
| 236 | for (const auto &Expansion : Expansions) { |
| 237 | auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion); |
| 238 | |
| 239 | // Recursively collect branches from nested expansions. |
| 240 | auto NestedExpansions = ExpansionCoverage.getExpansions(); |
| 241 | auto NestedExBranches = collectNestedBranches(Coverage, Expansions: NestedExpansions); |
| 242 | append_range(C&: Branches, R&: NestedExBranches); |
| 243 | |
| 244 | // Add branches from this level of expansion. |
| 245 | auto ExBranches = ExpansionCoverage.getBranches(); |
| 246 | for (auto B : ExBranches) |
| 247 | if (B.FileID == Expansion.FileID) |
| 248 | Branches.push_back(x: B); |
| 249 | } |
| 250 | |
| 251 | return Branches; |
| 252 | } |
| 253 | |
| 254 | void renderExpansion(json::OStream &JOS, |
| 255 | const coverage::CoverageMapping &Coverage, |
| 256 | const coverage::ExpansionRecord &Expansion) { |
| 257 | std::vector<llvm::coverage::ExpansionRecord> Expansions = {Expansion}; |
| 258 | JOS.object(Contents: [&] { |
| 259 | JOS.attributeArray(Key: "filenames" , Contents: [&] { |
| 260 | for (const auto &Filename : Expansion.Function.Filenames) |
| 261 | JOS.value(V: Filename); |
| 262 | }); |
| 263 | // Enumerate the branch coverage information for the expansion. |
| 264 | JOS.attributeBegin(Key: "branches" ); |
| 265 | renderBranchRegions(JOS, Regions: collectNestedBranches(Coverage, Expansions)); |
| 266 | JOS.attributeEnd(); |
| 267 | // Mark the beginning and end of this expansion in the source file. |
| 268 | JOS.attributeBegin(Key: "source_region" ); |
| 269 | renderRegion(JOS, Region: Expansion.Region); |
| 270 | JOS.attributeEnd(); |
| 271 | // Enumerate the coverage information for the expansion. |
| 272 | JOS.attributeBegin(Key: "target_regions" ); |
| 273 | renderRegions(JOS, Regions: Expansion.Function.CountedRegions); |
| 274 | JOS.attributeEnd(); |
| 275 | }); |
| 276 | } |
| 277 | |
| 278 | void renderSummary(json::OStream &JOS, const FileCoverageSummary &Summary) { |
| 279 | JOS.object(Contents: [&] { |
| 280 | JOS.attributeObject(Key: "lines" , Contents: [&] { |
| 281 | JOS.attribute(Key: "count" , Contents: int64_t(Summary.LineCoverage.getNumLines())); |
| 282 | JOS.attribute(Key: "covered" , Contents: int64_t(Summary.LineCoverage.getCovered())); |
| 283 | JOS.attribute(Key: "percent" , Contents: Summary.LineCoverage.getPercentCovered()); |
| 284 | }); |
| 285 | JOS.attributeObject(Key: "functions" , Contents: [&] { |
| 286 | JOS.attribute(Key: "count" , |
| 287 | Contents: int64_t(Summary.FunctionCoverage.getNumFunctions())); |
| 288 | JOS.attribute(Key: "covered" , Contents: int64_t(Summary.FunctionCoverage.getExecuted())); |
| 289 | JOS.attribute(Key: "percent" , Contents: Summary.FunctionCoverage.getPercentCovered()); |
| 290 | }); |
| 291 | JOS.attributeObject(Key: "instantiations" , Contents: [&] { |
| 292 | JOS.attribute(Key: "count" , |
| 293 | Contents: int64_t(Summary.InstantiationCoverage.getNumFunctions())); |
| 294 | JOS.attribute(Key: "covered" , |
| 295 | Contents: int64_t(Summary.InstantiationCoverage.getExecuted())); |
| 296 | JOS.attribute(Key: "percent" , |
| 297 | Contents: Summary.InstantiationCoverage.getPercentCovered()); |
| 298 | }); |
| 299 | JOS.attributeObject(Key: "regions" , Contents: [&] { |
| 300 | JOS.attribute(Key: "count" , Contents: int64_t(Summary.RegionCoverage.getNumRegions())); |
| 301 | JOS.attribute(Key: "covered" , Contents: int64_t(Summary.RegionCoverage.getCovered())); |
| 302 | JOS.attribute(Key: "notcovered" , |
| 303 | Contents: int64_t(Summary.RegionCoverage.getNumRegions() - |
| 304 | Summary.RegionCoverage.getCovered())); |
| 305 | JOS.attribute(Key: "percent" , Contents: Summary.RegionCoverage.getPercentCovered()); |
| 306 | }); |
| 307 | JOS.attributeObject(Key: "branches" , Contents: [&] { |
| 308 | JOS.attribute(Key: "count" , Contents: int64_t(Summary.BranchCoverage.getNumBranches())); |
| 309 | JOS.attribute(Key: "covered" , Contents: int64_t(Summary.BranchCoverage.getCovered())); |
| 310 | JOS.attribute(Key: "notcovered" , |
| 311 | Contents: int64_t(Summary.BranchCoverage.getNumBranches() - |
| 312 | Summary.BranchCoverage.getCovered())); |
| 313 | JOS.attribute(Key: "percent" , Contents: Summary.BranchCoverage.getPercentCovered()); |
| 314 | }); |
| 315 | JOS.attributeObject(Key: "mcdc" , Contents: [&] { |
| 316 | JOS.attribute(Key: "count" , Contents: int64_t(Summary.MCDCCoverage.getNumPairs())); |
| 317 | JOS.attribute(Key: "covered" , Contents: int64_t(Summary.MCDCCoverage.getCoveredPairs())); |
| 318 | JOS.attribute(Key: "notcovered" , |
| 319 | Contents: int64_t(Summary.MCDCCoverage.getNumPairs() - |
| 320 | Summary.MCDCCoverage.getCoveredPairs())); |
| 321 | JOS.attribute(Key: "percent" , Contents: Summary.MCDCCoverage.getPercentCovered()); |
| 322 | }); |
| 323 | }); |
| 324 | } |
| 325 | |
| 326 | void renderFile(json::OStream &JOS, const coverage::CoverageMapping &Coverage, |
| 327 | const std::string &Filename, |
| 328 | const FileCoverageSummary &FileReport, |
| 329 | const CoverageViewOptions &Options) { |
| 330 | JOS.object(Contents: [&] { |
| 331 | JOS.attribute(Key: "filename" , Contents: Filename); |
| 332 | if (!Options.ExportSummaryOnly) { |
| 333 | // Calculate and render detailed coverage information for given file. |
| 334 | auto FileCoverage = Coverage.getCoverageForFile(Filename); |
| 335 | JOS.attributeArray(Key: "branches" , Contents: [&] { |
| 336 | for (const auto &Branch : FileCoverage.getBranches()) |
| 337 | renderBranch(JOS, Region: Branch); |
| 338 | }); |
| 339 | if (!Options.SkipExpansions) { |
| 340 | JOS.attributeArray(Key: "expansions" , Contents: [&] { |
| 341 | for (const auto &Expansion : FileCoverage.getExpansions()) |
| 342 | renderExpansion(JOS, Coverage, Expansion); |
| 343 | }); |
| 344 | } |
| 345 | JOS.attributeArray(Key: "mcdc_records" , Contents: [&] { |
| 346 | for (const auto &Record : FileCoverage.getMCDCRecords()) |
| 347 | renderMCDCRecord(JOS, Record, Options); |
| 348 | }); |
| 349 | JOS.attributeArray(Key: "segments" , Contents: [&] { |
| 350 | for (const auto &Segment : FileCoverage) |
| 351 | renderSegment(JOS, Segment); |
| 352 | }); |
| 353 | } |
| 354 | JOS.attributeBegin(Key: "summary" ); |
| 355 | renderSummary(JOS, Summary: FileReport); |
| 356 | JOS.attributeEnd(); |
| 357 | }); |
| 358 | } |
| 359 | |
| 360 | void renderFiles(json::OStream &JOS, const coverage::CoverageMapping &Coverage, |
| 361 | ArrayRef<std::string> SourceFiles, |
| 362 | ArrayRef<FileCoverageSummary> FileReports, |
| 363 | const CoverageViewOptions &Options) { |
| 364 | ThreadPoolStrategy S = hardware_concurrency(ThreadCount: Options.NumThreads); |
| 365 | if (Options.NumThreads == 0) { |
| 366 | // If NumThreads is not specified, create one thread for each input, up to |
| 367 | // the number of hardware cores. |
| 368 | S = heavyweight_hardware_concurrency(ThreadCount: SourceFiles.size()); |
| 369 | S.Limit = true; |
| 370 | } |
| 371 | |
| 372 | // Pre-render coverage for each file to separate string. |
| 373 | std::vector<std::string> RenderedFiles(SourceFiles.size()); |
| 374 | DefaultThreadPool Pool(S); |
| 375 | |
| 376 | for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) { |
| 377 | auto &SourceFile = SourceFiles[I]; |
| 378 | auto &FileReport = FileReports[I]; |
| 379 | Pool.async(F: [&, I] { |
| 380 | std::string Buffer; |
| 381 | llvm::raw_string_ostream RawSStream(Buffer); |
| 382 | json::OStream JOS(RawSStream); |
| 383 | renderFile(JOS, Coverage, Filename: SourceFile, FileReport, Options); |
| 384 | RawSStream.flush(); |
| 385 | RenderedFiles[I] = std::move(Buffer); |
| 386 | }); |
| 387 | } |
| 388 | Pool.wait(); |
| 389 | |
| 390 | // Dump rendered strings sorted by filename. |
| 391 | std::vector<unsigned> Indices(SourceFiles.size()); |
| 392 | std::iota(first: Indices.begin(), last: Indices.end(), value: 0u); |
| 393 | llvm::sort(C&: Indices, Comp: [&](unsigned A, unsigned B) { |
| 394 | return SourceFiles[A] < SourceFiles[B]; |
| 395 | }); |
| 396 | JOS.array(Contents: [&] { |
| 397 | for (unsigned I : Indices) |
| 398 | JOS.rawValue(Contents: RenderedFiles[I]); |
| 399 | }); |
| 400 | } |
| 401 | |
| 402 | void renderFunctions( |
| 403 | json::OStream &JOS, |
| 404 | const iterator_range<coverage::FunctionRecordIterator> &Functions, |
| 405 | const CoverageViewOptions &Options) { |
| 406 | JOS.array(Contents: [&] { |
| 407 | for (const auto &F : Functions) { |
| 408 | JOS.object(Contents: [&] { |
| 409 | JOS.attributeBegin(Key: "branches" ); |
| 410 | renderBranchRegions(JOS, Regions: F.CountedBranchRegions); |
| 411 | JOS.attributeEnd(); |
| 412 | |
| 413 | JOS.attribute(Key: "count" , Contents: clamp_uint64_to_int64(u: F.ExecutionCount)); |
| 414 | |
| 415 | JOS.attributeArray(Key: "filenames" , Contents: [&] { |
| 416 | for (const auto &Filename : F.Filenames) |
| 417 | JOS.value(V: Filename); |
| 418 | }); |
| 419 | |
| 420 | JOS.attributeBegin(Key: "mcdc_records" ); |
| 421 | renderMCDCRecords(JOS, Records: F.MCDCRecords, Options); |
| 422 | JOS.attributeEnd(); |
| 423 | |
| 424 | JOS.attribute(Key: "name" , Contents: F.Name); |
| 425 | |
| 426 | JOS.attributeBegin(Key: "regions" ); |
| 427 | renderRegions(JOS, Regions: F.CountedRegions); |
| 428 | JOS.attributeEnd(); |
| 429 | }); |
| 430 | } |
| 431 | }); |
| 432 | } |
| 433 | |
| 434 | } // end anonymous namespace |
| 435 | |
| 436 | void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) { |
| 437 | std::vector<std::string> SourceFiles; |
| 438 | for (StringRef SF : Coverage.getUniqueSourceFiles()) { |
| 439 | if (!IgnoreFilters.matchesFilename(Filename: SF)) |
| 440 | SourceFiles.emplace_back(args&: SF); |
| 441 | } |
| 442 | renderRoot(SourceFiles); |
| 443 | } |
| 444 | |
| 445 | void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) { |
| 446 | FileCoverageSummary Totals = FileCoverageSummary("Totals" ); |
| 447 | auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals, |
| 448 | Files: SourceFiles, Options); |
| 449 | |
| 450 | json::OStream JOS(OS); |
| 451 | JOS.object(Contents: [&] { |
| 452 | JOS.attributeArray(Key: "data" , Contents: [&] { |
| 453 | JOS.object(Contents: [&] { |
| 454 | JOS.attributeBegin(Key: "files" ); |
| 455 | renderFiles(JOS, Coverage, SourceFiles, FileReports, Options); |
| 456 | JOS.attributeEnd(); |
| 457 | |
| 458 | // Skip functions-level information if necessary. |
| 459 | if (!Options.ExportSummaryOnly && !Options.SkipFunctions) { |
| 460 | JOS.attributeBegin(Key: "functions" ); |
| 461 | renderFunctions(JOS, Functions: Coverage.getCoveredFunctions(), Options); |
| 462 | JOS.attributeEnd(); |
| 463 | } |
| 464 | |
| 465 | JOS.attributeBegin(Key: "totals" ); |
| 466 | renderSummary(JOS, Summary: Totals); |
| 467 | JOS.attributeEnd(); |
| 468 | }); |
| 469 | }); |
| 470 | JOS.attribute(Key: "type" , LLVM_COVERAGE_EXPORT_JSON_TYPE_STR); |
| 471 | JOS.attribute(Key: "version" , LLVM_COVERAGE_EXPORT_JSON_STR); |
| 472 | }); |
| 473 | } |
| 474 | |