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 // -- Segments: array => List of Segments contained in the file
22 // -- Segment: dict => Describes a segment of the file with a counter
23 // -- Expansions: array => List of expansion records
24 // -- Expansion: dict => Object that descibes a single expansion
25 // -- CountedRegion: dict => The region to be expanded
26 // -- TargetRegions: array => List of Regions in the expansion
27 // -- CountedRegion: dict => Single Region in the expansion
28 // -- Summary: dict => Object summarizing the coverage for this file
29 // -- LineCoverage: dict => Object summarizing line coverage
30 // -- FunctionCoverage: dict => Object summarizing function coverage
31 // -- RegionCoverage: dict => Object summarizing region coverage
32 // -- Functions: array => List of objects describing coverage for functions
33 // -- Function: dict => Coverage info for a single function
34 // -- Filenames: array => List of filenames that the function relates to
35 // -- Summary: dict => Object summarizing the coverage for the entire binary
36 // -- LineCoverage: dict => Object summarizing line coverage
37 // -- FunctionCoverage: dict => Object summarizing function coverage
38 // -- InstantiationCoverage: dict => Object summarizing inst. coverage
39 // -- RegionCoverage: dict => Object summarizing region coverage
40 //
41 //===----------------------------------------------------------------------===//
42
43 #include "CoverageExporterJson.h"
44 #include "CoverageReport.h"
45 #include "llvm/ADT/Optional.h"
46 #include "llvm/ADT/StringRef.h"
47 #include "llvm/Support/JSON.h"
48 #include "llvm/Support/ThreadPool.h"
49 #include "llvm/Support/Threading.h"
50 #include <algorithm>
51 #include <limits>
52 #include <mutex>
53 #include <utility>
54
55 /// The semantic version combined as a string.
56 #define LLVM_COVERAGE_EXPORT_JSON_STR "2.0.1"
57
58 /// Unique type identifier for JSON coverage export.
59 #define LLVM_COVERAGE_EXPORT_JSON_TYPE_STR "llvm.coverage.json.export"
60
61 using namespace llvm;
62
63 namespace {
64
65 // The JSON library accepts int64_t, but profiling counts are stored as uint64_t.
66 // Therefore we need to explicitly convert from unsigned to signed, since a naive
67 // cast is implementation-defined behavior when the unsigned value cannot be
68 // represented as a signed value. We choose to clamp the values to preserve the
69 // invariant that counts are always >= 0.
clamp_uint64_to_int64(uint64_t u)70 int64_t clamp_uint64_to_int64(uint64_t u) {
71 return std::min(u, static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
72 }
73
renderSegment(const coverage::CoverageSegment & Segment)74 json::Array renderSegment(const coverage::CoverageSegment &Segment) {
75 return json::Array({Segment.Line, Segment.Col,
76 clamp_uint64_to_int64(Segment.Count), Segment.HasCount,
77 Segment.IsRegionEntry, Segment.IsGapRegion});
78 }
79
renderRegion(const coverage::CountedRegion & Region)80 json::Array renderRegion(const coverage::CountedRegion &Region) {
81 return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd,
82 Region.ColumnEnd, clamp_uint64_to_int64(Region.ExecutionCount),
83 Region.FileID, Region.ExpandedFileID,
84 int64_t(Region.Kind)});
85 }
86
renderRegions(ArrayRef<coverage::CountedRegion> Regions)87 json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) {
88 json::Array RegionArray;
89 for (const auto &Region : Regions)
90 RegionArray.push_back(renderRegion(Region));
91 return RegionArray;
92 }
93
renderExpansion(const coverage::ExpansionRecord & Expansion)94 json::Object renderExpansion(const coverage::ExpansionRecord &Expansion) {
95 return json::Object(
96 {{"filenames", json::Array(Expansion.Function.Filenames)},
97 // Mark the beginning and end of this expansion in the source file.
98 {"source_region", renderRegion(Expansion.Region)},
99 // Enumerate the coverage information for the expansion.
100 {"target_regions", renderRegions(Expansion.Function.CountedRegions)}});
101 }
102
renderSummary(const FileCoverageSummary & Summary)103 json::Object renderSummary(const FileCoverageSummary &Summary) {
104 return json::Object(
105 {{"lines",
106 json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())},
107 {"covered", int64_t(Summary.LineCoverage.getCovered())},
108 {"percent", Summary.LineCoverage.getPercentCovered()}})},
109 {"functions",
110 json::Object(
111 {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())},
112 {"covered", int64_t(Summary.FunctionCoverage.getExecuted())},
113 {"percent", Summary.FunctionCoverage.getPercentCovered()}})},
114 {"instantiations",
115 json::Object(
116 {{"count",
117 int64_t(Summary.InstantiationCoverage.getNumFunctions())},
118 {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())},
119 {"percent", Summary.InstantiationCoverage.getPercentCovered()}})},
120 {"regions",
121 json::Object(
122 {{"count", int64_t(Summary.RegionCoverage.getNumRegions())},
123 {"covered", int64_t(Summary.RegionCoverage.getCovered())},
124 {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() -
125 Summary.RegionCoverage.getCovered())},
126 {"percent", Summary.RegionCoverage.getPercentCovered()}})}});
127 }
128
renderFileExpansions(const coverage::CoverageData & FileCoverage,const FileCoverageSummary & FileReport)129 json::Array renderFileExpansions(const coverage::CoverageData &FileCoverage,
130 const FileCoverageSummary &FileReport) {
131 json::Array ExpansionArray;
132 for (const auto &Expansion : FileCoverage.getExpansions())
133 ExpansionArray.push_back(renderExpansion(Expansion));
134 return ExpansionArray;
135 }
136
renderFileSegments(const coverage::CoverageData & FileCoverage,const FileCoverageSummary & FileReport)137 json::Array renderFileSegments(const coverage::CoverageData &FileCoverage,
138 const FileCoverageSummary &FileReport) {
139 json::Array SegmentArray;
140 for (const auto &Segment : FileCoverage)
141 SegmentArray.push_back(renderSegment(Segment));
142 return SegmentArray;
143 }
144
renderFile(const coverage::CoverageMapping & Coverage,const std::string & Filename,const FileCoverageSummary & FileReport,const CoverageViewOptions & Options)145 json::Object renderFile(const coverage::CoverageMapping &Coverage,
146 const std::string &Filename,
147 const FileCoverageSummary &FileReport,
148 const CoverageViewOptions &Options) {
149 json::Object File({{"filename", Filename}});
150 if (!Options.ExportSummaryOnly) {
151 // Calculate and render detailed coverage information for given file.
152 auto FileCoverage = Coverage.getCoverageForFile(Filename);
153 File["segments"] = renderFileSegments(FileCoverage, FileReport);
154 if (!Options.SkipExpansions) {
155 File["expansions"] = renderFileExpansions(FileCoverage, FileReport);
156 }
157 }
158 File["summary"] = renderSummary(FileReport);
159 return File;
160 }
161
renderFiles(const coverage::CoverageMapping & Coverage,ArrayRef<std::string> SourceFiles,ArrayRef<FileCoverageSummary> FileReports,const CoverageViewOptions & Options)162 json::Array renderFiles(const coverage::CoverageMapping &Coverage,
163 ArrayRef<std::string> SourceFiles,
164 ArrayRef<FileCoverageSummary> FileReports,
165 const CoverageViewOptions &Options) {
166 ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
167 if (Options.NumThreads == 0) {
168 // If NumThreads is not specified, create one thread for each input, up to
169 // the number of hardware cores.
170 S = heavyweight_hardware_concurrency(SourceFiles.size());
171 S.Limit = true;
172 }
173 ThreadPool Pool(S);
174 json::Array FileArray;
175 std::mutex FileArrayMutex;
176
177 for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) {
178 auto &SourceFile = SourceFiles[I];
179 auto &FileReport = FileReports[I];
180 Pool.async([&] {
181 auto File = renderFile(Coverage, SourceFile, FileReport, Options);
182 {
183 std::lock_guard<std::mutex> Lock(FileArrayMutex);
184 FileArray.push_back(std::move(File));
185 }
186 });
187 }
188 Pool.wait();
189 return FileArray;
190 }
191
renderFunctions(const iterator_range<coverage::FunctionRecordIterator> & Functions)192 json::Array renderFunctions(
193 const iterator_range<coverage::FunctionRecordIterator> &Functions) {
194 json::Array FunctionArray;
195 for (const auto &F : Functions)
196 FunctionArray.push_back(
197 json::Object({{"name", F.Name},
198 {"count", clamp_uint64_to_int64(F.ExecutionCount)},
199 {"regions", renderRegions(F.CountedRegions)},
200 {"filenames", json::Array(F.Filenames)}}));
201 return FunctionArray;
202 }
203
204 } // end anonymous namespace
205
renderRoot(const CoverageFilters & IgnoreFilters)206 void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) {
207 std::vector<std::string> SourceFiles;
208 for (StringRef SF : Coverage.getUniqueSourceFiles()) {
209 if (!IgnoreFilters.matchesFilename(SF))
210 SourceFiles.emplace_back(SF);
211 }
212 renderRoot(SourceFiles);
213 }
214
renderRoot(ArrayRef<std::string> SourceFiles)215 void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) {
216 FileCoverageSummary Totals = FileCoverageSummary("Totals");
217 auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
218 SourceFiles, Options);
219 auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options);
220 // Sort files in order of their names.
221 std::sort(Files.begin(), Files.end(),
222 [](const json::Value &A, const json::Value &B) {
223 const json::Object *ObjA = A.getAsObject();
224 const json::Object *ObjB = B.getAsObject();
225 assert(ObjA != nullptr && "Value A was not an Object");
226 assert(ObjB != nullptr && "Value B was not an Object");
227 const StringRef FilenameA = ObjA->getString("filename").getValue();
228 const StringRef FilenameB = ObjB->getString("filename").getValue();
229 return FilenameA.compare(FilenameB) < 0;
230 });
231 auto Export = json::Object(
232 {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}});
233 // Skip functions-level information if necessary.
234 if (!Options.ExportSummaryOnly && !Options.SkipFunctions)
235 Export["functions"] = renderFunctions(Coverage.getCoveredFunctions());
236
237 auto ExportArray = json::Array({std::move(Export)});
238
239 OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR},
240 {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR},
241 {"data", std::move(ExportArray)}});
242 }
243