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