379 lines · cpp
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-exception6//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 format16// Root: dict => Root Element containing metadata17// -- Data: array => Homogeneous array of one or more export objects18// -- Export: dict => Json representation of one CoverageMapping19// -- Files: array => List of objects describing coverage for files20// -- File: dict => Coverage for a single file21// -- Branches: array => List of Branches in the file22// -- Branch: dict => Describes a branch of the file with counters23// -- MCDC Records: array => List of MCDC records in the file24// -- MCDC Values: array => List of T/F covered condition values and25// list of executed test vectors26// -- Segments: array => List of Segments contained in the file27// -- Segment: dict => Describes a segment of the file with a counter28// -- Expansions: array => List of expansion records29// -- Expansion: dict => Object that descibes a single expansion30// -- CountedRegion: dict => The region to be expanded31// -- TargetRegions: array => List of Regions in the expansion32// -- CountedRegion: dict => Single Region in the expansion33// -- Branches: array => List of Branches in the expansion34// -- Branch: dict => Describes a branch in expansion and counters35// -- Summary: dict => Object summarizing the coverage for this file36// -- LineCoverage: dict => Object summarizing line coverage37// -- FunctionCoverage: dict => Object summarizing function coverage38// -- RegionCoverage: dict => Object summarizing region coverage39// -- BranchCoverage: dict => Object summarizing branch coverage40// -- MCDCCoverage: dict => Object summarizing MC/DC coverage41// -- Functions: array => List of objects describing coverage for functions42// -- Function: dict => Coverage info for a single function43// -- Filenames: array => List of filenames that the function relates to44// -- Summary: dict => Object summarizing the coverage for the entire binary45// -- LineCoverage: dict => Object summarizing line coverage46// -- FunctionCoverage: dict => Object summarizing function coverage47// -- InstantiationCoverage: dict => Object summarizing inst. coverage48// -- RegionCoverage: dict => Object summarizing region coverage49// -- BranchCoverage: dict => Object summarizing branch coverage50// -- MCDCCoverage: dict => Object summarizing MC/DC coverage51//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 <mutex>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 naive77// cast is implementation-defined behavior when the unsigned value cannot be78// represented as a signed value. We choose to clamp the values to preserve the79// invariant that counts are always >= 0.80int64_t clamp_uint64_to_int64(uint64_t u) {81 return std::min(u, static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));82}83 84json::Array renderSegment(const coverage::CoverageSegment &Segment) {85 return json::Array({Segment.Line, Segment.Col,86 clamp_uint64_to_int64(Segment.Count), Segment.HasCount,87 Segment.IsRegionEntry, Segment.IsGapRegion});88}89 90json::Array renderRegion(const coverage::CountedRegion &Region) {91 return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd,92 Region.ColumnEnd, clamp_uint64_to_int64(Region.ExecutionCount),93 Region.FileID, Region.ExpandedFileID,94 int64_t(Region.Kind)});95}96 97json::Array renderBranch(const coverage::CountedRegion &Region) {98 return json::Array(99 {Region.LineStart, Region.ColumnStart, Region.LineEnd, Region.ColumnEnd,100 clamp_uint64_to_int64(Region.ExecutionCount),101 clamp_uint64_to_int64(Region.FalseExecutionCount), Region.FileID,102 Region.ExpandedFileID, int64_t(Region.Kind)});103}104 105json::Array gatherConditions(const coverage::MCDCRecord &Record) {106 json::Array Conditions;107 for (unsigned c = 0; c < Record.getNumConditions(); c++)108 Conditions.push_back(Record.isConditionIndependencePairCovered(c));109 return Conditions;110}111 112json::Value renderCondState(const coverage::MCDCRecord::CondState CondState) {113 switch (CondState) {114 case coverage::MCDCRecord::MCDC_DontCare:115 return json::Value(nullptr);116 case coverage::MCDCRecord::MCDC_True:117 return json::Value(true);118 case coverage::MCDCRecord::MCDC_False:119 return json::Value(false);120 }121 llvm_unreachable("Unknown llvm::coverage::MCDCRecord::CondState enum");122}123 124json::Array gatherTestVectors(coverage::MCDCRecord &Record) {125 json::Array TestVectors;126 unsigned NumConditions = Record.getNumConditions();127 for (unsigned tv = 0; tv < Record.getNumTestVectors(); tv++) {128 129 json::Array TVConditions;130 for (unsigned c = 0; c < NumConditions; c++)131 TVConditions.push_back(renderCondState(Record.getTVCondition(tv, c)));132 133 TestVectors.push_back(134 json::Object({{"executed", json::Value(true)},135 {"result", renderCondState(Record.getTVResult(tv))},136 {"conditions", std::move(TVConditions)}}));137 }138 return TestVectors;139}140 141json::Array renderMCDCRecord(const coverage::MCDCRecord &Record) {142 const llvm::coverage::CounterMappingRegion &CMR = Record.getDecisionRegion();143 const auto [TrueDecisions, FalseDecisions] = Record.getDecisions();144 return json::Array(145 {CMR.LineStart, CMR.ColumnStart, CMR.LineEnd, CMR.ColumnEnd,146 TrueDecisions, FalseDecisions, CMR.FileID, CMR.ExpandedFileID,147 int64_t(CMR.Kind), gatherConditions(Record),148 gatherTestVectors(const_cast<coverage::MCDCRecord &>(Record))});149}150 151json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) {152 json::Array RegionArray;153 for (const auto &Region : Regions)154 RegionArray.push_back(renderRegion(Region));155 return RegionArray;156}157 158json::Array renderBranchRegions(ArrayRef<coverage::CountedRegion> Regions) {159 json::Array RegionArray;160 for (const auto &Region : Regions)161 if (!Region.TrueFolded || !Region.FalseFolded)162 RegionArray.push_back(renderBranch(Region));163 return RegionArray;164}165 166json::Array renderMCDCRecords(ArrayRef<coverage::MCDCRecord> Records) {167 json::Array RecordArray;168 for (auto &Record : Records)169 RecordArray.push_back(renderMCDCRecord(Record));170 return RecordArray;171}172 173std::vector<llvm::coverage::CountedRegion>174collectNestedBranches(const coverage::CoverageMapping &Coverage,175 ArrayRef<llvm::coverage::ExpansionRecord> Expansions) {176 std::vector<llvm::coverage::CountedRegion> Branches;177 for (const auto &Expansion : Expansions) {178 auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);179 180 // Recursively collect branches from nested expansions.181 auto NestedExpansions = ExpansionCoverage.getExpansions();182 auto NestedExBranches = collectNestedBranches(Coverage, NestedExpansions);183 append_range(Branches, NestedExBranches);184 185 // Add branches from this level of expansion.186 auto ExBranches = ExpansionCoverage.getBranches();187 for (auto B : ExBranches)188 if (B.FileID == Expansion.FileID)189 Branches.push_back(B);190 }191 192 return Branches;193}194 195json::Object renderExpansion(const coverage::CoverageMapping &Coverage,196 const coverage::ExpansionRecord &Expansion) {197 std::vector<llvm::coverage::ExpansionRecord> Expansions = {Expansion};198 return json::Object(199 {{"filenames", json::Array(Expansion.Function.Filenames)},200 // Mark the beginning and end of this expansion in the source file.201 {"source_region", renderRegion(Expansion.Region)},202 // Enumerate the coverage information for the expansion.203 {"target_regions", renderRegions(Expansion.Function.CountedRegions)},204 // Enumerate the branch coverage information for the expansion.205 {"branches",206 renderBranchRegions(collectNestedBranches(Coverage, Expansions))}});207}208 209json::Object renderSummary(const FileCoverageSummary &Summary) {210 return json::Object(211 {{"lines",212 json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())},213 {"covered", int64_t(Summary.LineCoverage.getCovered())},214 {"percent", Summary.LineCoverage.getPercentCovered()}})},215 {"functions",216 json::Object(217 {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())},218 {"covered", int64_t(Summary.FunctionCoverage.getExecuted())},219 {"percent", Summary.FunctionCoverage.getPercentCovered()}})},220 {"instantiations",221 json::Object(222 {{"count",223 int64_t(Summary.InstantiationCoverage.getNumFunctions())},224 {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())},225 {"percent", Summary.InstantiationCoverage.getPercentCovered()}})},226 {"regions",227 json::Object(228 {{"count", int64_t(Summary.RegionCoverage.getNumRegions())},229 {"covered", int64_t(Summary.RegionCoverage.getCovered())},230 {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() -231 Summary.RegionCoverage.getCovered())},232 {"percent", Summary.RegionCoverage.getPercentCovered()}})},233 {"branches",234 json::Object(235 {{"count", int64_t(Summary.BranchCoverage.getNumBranches())},236 {"covered", int64_t(Summary.BranchCoverage.getCovered())},237 {"notcovered", int64_t(Summary.BranchCoverage.getNumBranches() -238 Summary.BranchCoverage.getCovered())},239 {"percent", Summary.BranchCoverage.getPercentCovered()}})},240 {"mcdc",241 json::Object(242 {{"count", int64_t(Summary.MCDCCoverage.getNumPairs())},243 {"covered", int64_t(Summary.MCDCCoverage.getCoveredPairs())},244 {"notcovered", int64_t(Summary.MCDCCoverage.getNumPairs() -245 Summary.MCDCCoverage.getCoveredPairs())},246 {"percent", Summary.MCDCCoverage.getPercentCovered()}})}});247}248 249json::Array renderFileExpansions(const coverage::CoverageMapping &Coverage,250 const coverage::CoverageData &FileCoverage) {251 json::Array ExpansionArray;252 for (const auto &Expansion : FileCoverage.getExpansions())253 ExpansionArray.push_back(renderExpansion(Coverage, Expansion));254 return ExpansionArray;255}256 257json::Array renderFileSegments(const coverage::CoverageData &FileCoverage) {258 json::Array SegmentArray;259 for (const auto &Segment : FileCoverage)260 SegmentArray.push_back(renderSegment(Segment));261 return SegmentArray;262}263 264json::Array renderFileBranches(const coverage::CoverageData &FileCoverage) {265 json::Array BranchArray;266 for (const auto &Branch : FileCoverage.getBranches())267 BranchArray.push_back(renderBranch(Branch));268 return BranchArray;269}270 271json::Array renderFileMCDC(const coverage::CoverageData &FileCoverage) {272 json::Array MCDCRecordArray;273 for (const auto &Record : FileCoverage.getMCDCRecords())274 MCDCRecordArray.push_back(renderMCDCRecord(Record));275 return MCDCRecordArray;276}277 278json::Object renderFile(const coverage::CoverageMapping &Coverage,279 const std::string &Filename,280 const FileCoverageSummary &FileReport,281 const CoverageViewOptions &Options) {282 json::Object File({{"filename", Filename}});283 if (!Options.ExportSummaryOnly) {284 // Calculate and render detailed coverage information for given file.285 auto FileCoverage = Coverage.getCoverageForFile(Filename);286 File["segments"] = renderFileSegments(FileCoverage);287 File["branches"] = renderFileBranches(FileCoverage);288 File["mcdc_records"] = renderFileMCDC(FileCoverage);289 if (!Options.SkipExpansions) {290 File["expansions"] = renderFileExpansions(Coverage, FileCoverage);291 }292 }293 File["summary"] = renderSummary(FileReport);294 return File;295}296 297json::Array renderFiles(const coverage::CoverageMapping &Coverage,298 ArrayRef<std::string> SourceFiles,299 ArrayRef<FileCoverageSummary> FileReports,300 const CoverageViewOptions &Options) {301 ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);302 if (Options.NumThreads == 0) {303 // If NumThreads is not specified, create one thread for each input, up to304 // the number of hardware cores.305 S = heavyweight_hardware_concurrency(SourceFiles.size());306 S.Limit = true;307 }308 DefaultThreadPool Pool(S);309 json::Array FileArray;310 std::mutex FileArrayMutex;311 312 for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) {313 auto &SourceFile = SourceFiles[I];314 auto &FileReport = FileReports[I];315 Pool.async([&] {316 auto File = renderFile(Coverage, SourceFile, FileReport, Options);317 {318 std::lock_guard<std::mutex> Lock(FileArrayMutex);319 FileArray.push_back(std::move(File));320 }321 });322 }323 Pool.wait();324 return FileArray;325}326 327json::Array renderFunctions(328 const iterator_range<coverage::FunctionRecordIterator> &Functions) {329 json::Array FunctionArray;330 for (const auto &F : Functions)331 FunctionArray.push_back(332 json::Object({{"name", F.Name},333 {"count", clamp_uint64_to_int64(F.ExecutionCount)},334 {"regions", renderRegions(F.CountedRegions)},335 {"branches", renderBranchRegions(F.CountedBranchRegions)},336 {"mcdc_records", renderMCDCRecords(F.MCDCRecords)},337 {"filenames", json::Array(F.Filenames)}}));338 return FunctionArray;339}340 341} // end anonymous namespace342 343void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) {344 std::vector<std::string> SourceFiles;345 for (StringRef SF : Coverage.getUniqueSourceFiles()) {346 if (!IgnoreFilters.matchesFilename(SF))347 SourceFiles.emplace_back(SF);348 }349 renderRoot(SourceFiles);350}351 352void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) {353 FileCoverageSummary Totals = FileCoverageSummary("Totals");354 auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,355 SourceFiles, Options);356 auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options);357 // Sort files in order of their names.358 llvm::sort(Files, [](const json::Value &A, const json::Value &B) {359 const json::Object *ObjA = A.getAsObject();360 const json::Object *ObjB = B.getAsObject();361 assert(ObjA != nullptr && "Value A was not an Object");362 assert(ObjB != nullptr && "Value B was not an Object");363 const StringRef FilenameA = *ObjA->getString("filename");364 const StringRef FilenameB = *ObjB->getString("filename");365 return FilenameA.compare(FilenameB) < 0;366 });367 auto Export = json::Object(368 {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}});369 // Skip functions-level information if necessary.370 if (!Options.ExportSummaryOnly && !Options.SkipFunctions)371 Export["functions"] = renderFunctions(Coverage.getCoveredFunctions());372 373 auto ExportArray = json::Array({std::move(Export)});374 375 OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR},376 {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR},377 {"data", std::move(ExportArray)}});378}379