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.0" 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, clamp_uint64_to_int64(Segment.Count), 76 Segment.HasCount, Segment.IsRegionEntry}); 77 } 78 79 json::Array renderRegion(const coverage::CountedRegion &Region) { 80 return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd, 81 Region.ColumnEnd, clamp_uint64_to_int64(Region.ExecutionCount), 82 Region.FileID, Region.ExpandedFileID, 83 int64_t(Region.Kind)}); 84 } 85 86 json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) { 87 json::Array RegionArray; 88 for (const auto &Region : Regions) 89 RegionArray.push_back(renderRegion(Region)); 90 return RegionArray; 91 } 92 93 json::Object renderExpansion(const coverage::ExpansionRecord &Expansion) { 94 return json::Object( 95 {{"filenames", json::Array(Expansion.Function.Filenames)}, 96 // Mark the beginning and end of this expansion in the source file. 97 {"source_region", renderRegion(Expansion.Region)}, 98 // Enumerate the coverage information for the expansion. 99 {"target_regions", renderRegions(Expansion.Function.CountedRegions)}}); 100 } 101 102 json::Object renderSummary(const FileCoverageSummary &Summary) { 103 return json::Object( 104 {{"lines", 105 json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())}, 106 {"covered", int64_t(Summary.LineCoverage.getCovered())}, 107 {"percent", Summary.LineCoverage.getPercentCovered()}})}, 108 {"functions", 109 json::Object( 110 {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())}, 111 {"covered", int64_t(Summary.FunctionCoverage.getExecuted())}, 112 {"percent", Summary.FunctionCoverage.getPercentCovered()}})}, 113 {"instantiations", 114 json::Object( 115 {{"count", 116 int64_t(Summary.InstantiationCoverage.getNumFunctions())}, 117 {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())}, 118 {"percent", Summary.InstantiationCoverage.getPercentCovered()}})}, 119 {"regions", 120 json::Object( 121 {{"count", int64_t(Summary.RegionCoverage.getNumRegions())}, 122 {"covered", int64_t(Summary.RegionCoverage.getCovered())}, 123 {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() - 124 Summary.RegionCoverage.getCovered())}, 125 {"percent", Summary.RegionCoverage.getPercentCovered()}})}}); 126 } 127 128 json::Array renderFileExpansions(const coverage::CoverageData &FileCoverage, 129 const FileCoverageSummary &FileReport) { 130 json::Array ExpansionArray; 131 for (const auto &Expansion : FileCoverage.getExpansions()) 132 ExpansionArray.push_back(renderExpansion(Expansion)); 133 return ExpansionArray; 134 } 135 136 json::Array renderFileSegments(const coverage::CoverageData &FileCoverage, 137 const FileCoverageSummary &FileReport) { 138 json::Array SegmentArray; 139 for (const auto &Segment : FileCoverage) 140 SegmentArray.push_back(renderSegment(Segment)); 141 return SegmentArray; 142 } 143 144 json::Object renderFile(const coverage::CoverageMapping &Coverage, 145 const std::string &Filename, 146 const FileCoverageSummary &FileReport, 147 const CoverageViewOptions &Options) { 148 json::Object File({{"filename", Filename}}); 149 if (!Options.ExportSummaryOnly) { 150 // Calculate and render detailed coverage information for given file. 151 auto FileCoverage = Coverage.getCoverageForFile(Filename); 152 File["segments"] = renderFileSegments(FileCoverage, FileReport); 153 if (!Options.SkipExpansions) { 154 File["expansions"] = renderFileExpansions(FileCoverage, FileReport); 155 } 156 } 157 File["summary"] = renderSummary(FileReport); 158 return File; 159 } 160 161 json::Array renderFiles(const coverage::CoverageMapping &Coverage, 162 ArrayRef<std::string> SourceFiles, 163 ArrayRef<FileCoverageSummary> FileReports, 164 const CoverageViewOptions &Options) { 165 auto NumThreads = Options.NumThreads; 166 if (NumThreads == 0) { 167 NumThreads = std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(), 168 unsigned(SourceFiles.size()))); 169 } 170 ThreadPool Pool(NumThreads); 171 json::Array FileArray; 172 std::mutex FileArrayMutex; 173 174 for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) { 175 auto &SourceFile = SourceFiles[I]; 176 auto &FileReport = FileReports[I]; 177 Pool.async([&] { 178 auto File = renderFile(Coverage, SourceFile, FileReport, Options); 179 { 180 std::lock_guard<std::mutex> Lock(FileArrayMutex); 181 FileArray.push_back(std::move(File)); 182 } 183 }); 184 } 185 Pool.wait(); 186 return FileArray; 187 } 188 189 json::Array renderFunctions( 190 const iterator_range<coverage::FunctionRecordIterator> &Functions) { 191 json::Array FunctionArray; 192 for (const auto &F : Functions) 193 FunctionArray.push_back( 194 json::Object({{"name", F.Name}, 195 {"count", clamp_uint64_to_int64(F.ExecutionCount)}, 196 {"regions", renderRegions(F.CountedRegions)}, 197 {"filenames", json::Array(F.Filenames)}})); 198 return FunctionArray; 199 } 200 201 } // end anonymous namespace 202 203 void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) { 204 std::vector<std::string> SourceFiles; 205 for (StringRef SF : Coverage.getUniqueSourceFiles()) { 206 if (!IgnoreFilters.matchesFilename(SF)) 207 SourceFiles.emplace_back(SF); 208 } 209 renderRoot(SourceFiles); 210 } 211 212 void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) { 213 FileCoverageSummary Totals = FileCoverageSummary("Totals"); 214 auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals, 215 SourceFiles, Options); 216 auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options); 217 // Sort files in order of their names. 218 std::sort(Files.begin(), Files.end(), 219 [](const json::Value &A, const json::Value &B) { 220 const json::Object *ObjA = A.getAsObject(); 221 const json::Object *ObjB = B.getAsObject(); 222 assert(ObjA != nullptr && "Value A was not an Object"); 223 assert(ObjB != nullptr && "Value B was not an Object"); 224 const StringRef FilenameA = ObjA->getString("filename").getValue(); 225 const StringRef FilenameB = ObjB->getString("filename").getValue(); 226 return FilenameA.compare(FilenameB) < 0; 227 }); 228 auto Export = json::Object( 229 {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}}); 230 // Skip functions-level information if necessary. 231 if (!Options.ExportSummaryOnly && !Options.SkipFunctions) 232 Export["functions"] = renderFunctions(Coverage.getCoveredFunctions()); 233 234 auto ExportArray = json::Array({std::move(Export)}); 235 236 OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR}, 237 {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR}, 238 {"data", std::move(ExportArray)}}); 239 } 240