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
71using namespace llvm;
72
73namespace {
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.
80int64_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
84void 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
96void 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
109void 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
123void 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
130void 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
146void 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
186void 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
206void 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
214void 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
223void 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
232std::vector<llvm::coverage::CountedRegion>
233collectNestedBranches(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
254void 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
278void 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
326void 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
360void 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
402void 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
436void 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
445void 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