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. 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 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 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 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 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 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 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 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 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 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 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 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 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