xref: /freebsd/contrib/llvm-project/llvm/tools/llvm-cov/CoverageExporterJson.cpp (revision bdd1243df58e60e85101c09001d9812a789b6bc4)
10b57cec5SDimitry Andric //===- CoverageExporterJson.cpp - Code coverage export --------------------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This file implements export of code coverage data to JSON.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
120b57cec5SDimitry Andric 
130b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
140b57cec5SDimitry Andric //
150b57cec5SDimitry Andric // The json code coverage export follows the following format
160b57cec5SDimitry Andric // Root: dict => Root Element containing metadata
170b57cec5SDimitry Andric // -- Data: array => Homogeneous array of one or more export objects
180b57cec5SDimitry Andric //   -- Export: dict => Json representation of one CoverageMapping
190b57cec5SDimitry Andric //     -- Files: array => List of objects describing coverage for files
200b57cec5SDimitry Andric //       -- File: dict => Coverage for a single file
21e8d8bef9SDimitry Andric //         -- Branches: array => List of Branches in the file
22e8d8bef9SDimitry Andric //           -- Branch: dict => Describes a branch of the file with counters
230b57cec5SDimitry Andric //         -- Segments: array => List of Segments contained in the file
240b57cec5SDimitry Andric //           -- Segment: dict => Describes a segment of the file with a counter
250b57cec5SDimitry Andric //         -- Expansions: array => List of expansion records
260b57cec5SDimitry Andric //           -- Expansion: dict => Object that descibes a single expansion
270b57cec5SDimitry Andric //             -- CountedRegion: dict => The region to be expanded
280b57cec5SDimitry Andric //             -- TargetRegions: array => List of Regions in the expansion
290b57cec5SDimitry Andric //               -- CountedRegion: dict => Single Region in the expansion
30e8d8bef9SDimitry Andric //             -- Branches: array => List of Branches in the expansion
31e8d8bef9SDimitry Andric //               -- Branch: dict => Describes a branch in expansion and counters
320b57cec5SDimitry Andric //         -- Summary: dict => Object summarizing the coverage for this file
330b57cec5SDimitry Andric //           -- LineCoverage: dict => Object summarizing line coverage
340b57cec5SDimitry Andric //           -- FunctionCoverage: dict => Object summarizing function coverage
350b57cec5SDimitry Andric //           -- RegionCoverage: dict => Object summarizing region coverage
36e8d8bef9SDimitry Andric //           -- BranchCoverage: dict => Object summarizing branch coverage
370b57cec5SDimitry Andric //     -- Functions: array => List of objects describing coverage for functions
380b57cec5SDimitry Andric //       -- Function: dict => Coverage info for a single function
390b57cec5SDimitry Andric //         -- Filenames: array => List of filenames that the function relates to
400b57cec5SDimitry Andric //   -- Summary: dict => Object summarizing the coverage for the entire binary
410b57cec5SDimitry Andric //     -- LineCoverage: dict => Object summarizing line coverage
420b57cec5SDimitry Andric //     -- FunctionCoverage: dict => Object summarizing function coverage
430b57cec5SDimitry Andric //     -- InstantiationCoverage: dict => Object summarizing inst. coverage
440b57cec5SDimitry Andric //     -- RegionCoverage: dict => Object summarizing region coverage
45e8d8bef9SDimitry Andric //     -- BranchCoverage: dict => Object summarizing branch coverage
460b57cec5SDimitry Andric //
470b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
480b57cec5SDimitry Andric 
490b57cec5SDimitry Andric #include "CoverageExporterJson.h"
500b57cec5SDimitry Andric #include "CoverageReport.h"
510b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
520b57cec5SDimitry Andric #include "llvm/Support/JSON.h"
530b57cec5SDimitry Andric #include "llvm/Support/ThreadPool.h"
540b57cec5SDimitry Andric #include "llvm/Support/Threading.h"
550b57cec5SDimitry Andric #include <algorithm>
56480093f4SDimitry Andric #include <limits>
570b57cec5SDimitry Andric #include <mutex>
580b57cec5SDimitry Andric #include <utility>
590b57cec5SDimitry Andric 
600b57cec5SDimitry Andric /// The semantic version combined as a string.
615ffd83dbSDimitry Andric #define LLVM_COVERAGE_EXPORT_JSON_STR "2.0.1"
620b57cec5SDimitry Andric 
630b57cec5SDimitry Andric /// Unique type identifier for JSON coverage export.
640b57cec5SDimitry Andric #define LLVM_COVERAGE_EXPORT_JSON_TYPE_STR "llvm.coverage.json.export"
650b57cec5SDimitry Andric 
660b57cec5SDimitry Andric using namespace llvm;
670b57cec5SDimitry Andric 
680b57cec5SDimitry Andric namespace {
690b57cec5SDimitry Andric 
70480093f4SDimitry Andric // The JSON library accepts int64_t, but profiling counts are stored as uint64_t.
71480093f4SDimitry Andric // Therefore we need to explicitly convert from unsigned to signed, since a naive
72480093f4SDimitry Andric // cast is implementation-defined behavior when the unsigned value cannot be
73480093f4SDimitry Andric // represented as a signed value. We choose to clamp the values to preserve the
74480093f4SDimitry Andric // invariant that counts are always >= 0.
75480093f4SDimitry Andric int64_t clamp_uint64_to_int64(uint64_t u) {
76480093f4SDimitry Andric   return std::min(u, static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
77480093f4SDimitry Andric }
78480093f4SDimitry Andric 
790b57cec5SDimitry Andric json::Array renderSegment(const coverage::CoverageSegment &Segment) {
805ffd83dbSDimitry Andric   return json::Array({Segment.Line, Segment.Col,
815ffd83dbSDimitry Andric                       clamp_uint64_to_int64(Segment.Count), Segment.HasCount,
825ffd83dbSDimitry Andric                       Segment.IsRegionEntry, Segment.IsGapRegion});
830b57cec5SDimitry Andric }
840b57cec5SDimitry Andric 
850b57cec5SDimitry Andric json::Array renderRegion(const coverage::CountedRegion &Region) {
860b57cec5SDimitry Andric   return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd,
87480093f4SDimitry Andric                       Region.ColumnEnd, clamp_uint64_to_int64(Region.ExecutionCount),
880b57cec5SDimitry Andric                       Region.FileID, Region.ExpandedFileID,
890b57cec5SDimitry Andric                       int64_t(Region.Kind)});
900b57cec5SDimitry Andric }
910b57cec5SDimitry Andric 
92e8d8bef9SDimitry Andric json::Array renderBranch(const coverage::CountedRegion &Region) {
93e8d8bef9SDimitry Andric   return json::Array(
94e8d8bef9SDimitry Andric       {Region.LineStart, Region.ColumnStart, Region.LineEnd, Region.ColumnEnd,
95e8d8bef9SDimitry Andric        clamp_uint64_to_int64(Region.ExecutionCount),
96e8d8bef9SDimitry Andric        clamp_uint64_to_int64(Region.FalseExecutionCount), Region.FileID,
97e8d8bef9SDimitry Andric        Region.ExpandedFileID, int64_t(Region.Kind)});
98e8d8bef9SDimitry Andric }
99e8d8bef9SDimitry Andric 
1000b57cec5SDimitry Andric json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) {
1010b57cec5SDimitry Andric   json::Array RegionArray;
1020b57cec5SDimitry Andric   for (const auto &Region : Regions)
1030b57cec5SDimitry Andric     RegionArray.push_back(renderRegion(Region));
1040b57cec5SDimitry Andric   return RegionArray;
1050b57cec5SDimitry Andric }
1060b57cec5SDimitry Andric 
107e8d8bef9SDimitry Andric json::Array renderBranchRegions(ArrayRef<coverage::CountedRegion> Regions) {
108e8d8bef9SDimitry Andric   json::Array RegionArray;
109e8d8bef9SDimitry Andric   for (const auto &Region : Regions)
110e8d8bef9SDimitry Andric     if (!Region.Folded)
111e8d8bef9SDimitry Andric       RegionArray.push_back(renderBranch(Region));
112e8d8bef9SDimitry Andric   return RegionArray;
113e8d8bef9SDimitry Andric }
114e8d8bef9SDimitry Andric 
115e8d8bef9SDimitry Andric std::vector<llvm::coverage::CountedRegion>
116e8d8bef9SDimitry Andric collectNestedBranches(const coverage::CoverageMapping &Coverage,
117e8d8bef9SDimitry Andric                       ArrayRef<llvm::coverage::ExpansionRecord> Expansions) {
118e8d8bef9SDimitry Andric   std::vector<llvm::coverage::CountedRegion> Branches;
119e8d8bef9SDimitry Andric   for (const auto &Expansion : Expansions) {
120e8d8bef9SDimitry Andric     auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);
121e8d8bef9SDimitry Andric 
122e8d8bef9SDimitry Andric     // Recursively collect branches from nested expansions.
123e8d8bef9SDimitry Andric     auto NestedExpansions = ExpansionCoverage.getExpansions();
124e8d8bef9SDimitry Andric     auto NestedExBranches = collectNestedBranches(Coverage, NestedExpansions);
125fe6060f1SDimitry Andric     append_range(Branches, NestedExBranches);
126e8d8bef9SDimitry Andric 
127e8d8bef9SDimitry Andric     // Add branches from this level of expansion.
128e8d8bef9SDimitry Andric     auto ExBranches = ExpansionCoverage.getBranches();
129e8d8bef9SDimitry Andric     for (auto B : ExBranches)
130e8d8bef9SDimitry Andric       if (B.FileID == Expansion.FileID)
131e8d8bef9SDimitry Andric         Branches.push_back(B);
132e8d8bef9SDimitry Andric   }
133e8d8bef9SDimitry Andric 
134e8d8bef9SDimitry Andric   return Branches;
135e8d8bef9SDimitry Andric }
136e8d8bef9SDimitry Andric 
137e8d8bef9SDimitry Andric json::Object renderExpansion(const coverage::CoverageMapping &Coverage,
138e8d8bef9SDimitry Andric                              const coverage::ExpansionRecord &Expansion) {
139e8d8bef9SDimitry Andric   std::vector<llvm::coverage::ExpansionRecord> Expansions = {Expansion};
1400b57cec5SDimitry Andric   return json::Object(
1410b57cec5SDimitry Andric       {{"filenames", json::Array(Expansion.Function.Filenames)},
1420b57cec5SDimitry Andric        // Mark the beginning and end of this expansion in the source file.
1430b57cec5SDimitry Andric        {"source_region", renderRegion(Expansion.Region)},
1440b57cec5SDimitry Andric        // Enumerate the coverage information for the expansion.
145e8d8bef9SDimitry Andric        {"target_regions", renderRegions(Expansion.Function.CountedRegions)},
146e8d8bef9SDimitry Andric        // Enumerate the branch coverage information for the expansion.
147e8d8bef9SDimitry Andric        {"branches",
148e8d8bef9SDimitry Andric         renderBranchRegions(collectNestedBranches(Coverage, Expansions))}});
1490b57cec5SDimitry Andric }
1500b57cec5SDimitry Andric 
1510b57cec5SDimitry Andric json::Object renderSummary(const FileCoverageSummary &Summary) {
1520b57cec5SDimitry Andric   return json::Object(
1530b57cec5SDimitry Andric       {{"lines",
1540b57cec5SDimitry Andric         json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())},
1550b57cec5SDimitry Andric                       {"covered", int64_t(Summary.LineCoverage.getCovered())},
1560b57cec5SDimitry Andric                       {"percent", Summary.LineCoverage.getPercentCovered()}})},
1570b57cec5SDimitry Andric        {"functions",
1580b57cec5SDimitry Andric         json::Object(
1590b57cec5SDimitry Andric             {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())},
1600b57cec5SDimitry Andric              {"covered", int64_t(Summary.FunctionCoverage.getExecuted())},
1610b57cec5SDimitry Andric              {"percent", Summary.FunctionCoverage.getPercentCovered()}})},
1620b57cec5SDimitry Andric        {"instantiations",
1630b57cec5SDimitry Andric         json::Object(
1640b57cec5SDimitry Andric             {{"count",
1650b57cec5SDimitry Andric               int64_t(Summary.InstantiationCoverage.getNumFunctions())},
1660b57cec5SDimitry Andric              {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())},
1670b57cec5SDimitry Andric              {"percent", Summary.InstantiationCoverage.getPercentCovered()}})},
1680b57cec5SDimitry Andric        {"regions",
1690b57cec5SDimitry Andric         json::Object(
1700b57cec5SDimitry Andric             {{"count", int64_t(Summary.RegionCoverage.getNumRegions())},
1710b57cec5SDimitry Andric              {"covered", int64_t(Summary.RegionCoverage.getCovered())},
1720b57cec5SDimitry Andric              {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() -
1730b57cec5SDimitry Andric                                     Summary.RegionCoverage.getCovered())},
174e8d8bef9SDimitry Andric              {"percent", Summary.RegionCoverage.getPercentCovered()}})},
175e8d8bef9SDimitry Andric        {"branches",
176e8d8bef9SDimitry Andric         json::Object(
177e8d8bef9SDimitry Andric             {{"count", int64_t(Summary.BranchCoverage.getNumBranches())},
178e8d8bef9SDimitry Andric              {"covered", int64_t(Summary.BranchCoverage.getCovered())},
179e8d8bef9SDimitry Andric              {"notcovered", int64_t(Summary.BranchCoverage.getNumBranches() -
180e8d8bef9SDimitry Andric                                     Summary.BranchCoverage.getCovered())},
181e8d8bef9SDimitry Andric              {"percent", Summary.BranchCoverage.getPercentCovered()}})}});
1820b57cec5SDimitry Andric }
1830b57cec5SDimitry Andric 
184e8d8bef9SDimitry Andric json::Array renderFileExpansions(const coverage::CoverageMapping &Coverage,
185e8d8bef9SDimitry Andric                                  const coverage::CoverageData &FileCoverage,
1860b57cec5SDimitry Andric                                  const FileCoverageSummary &FileReport) {
1870b57cec5SDimitry Andric   json::Array ExpansionArray;
1880b57cec5SDimitry Andric   for (const auto &Expansion : FileCoverage.getExpansions())
189e8d8bef9SDimitry Andric     ExpansionArray.push_back(renderExpansion(Coverage, Expansion));
1900b57cec5SDimitry Andric   return ExpansionArray;
1910b57cec5SDimitry Andric }
1920b57cec5SDimitry Andric 
1930b57cec5SDimitry Andric json::Array renderFileSegments(const coverage::CoverageData &FileCoverage,
1940b57cec5SDimitry Andric                                const FileCoverageSummary &FileReport) {
1950b57cec5SDimitry Andric   json::Array SegmentArray;
1960b57cec5SDimitry Andric   for (const auto &Segment : FileCoverage)
1970b57cec5SDimitry Andric     SegmentArray.push_back(renderSegment(Segment));
1980b57cec5SDimitry Andric   return SegmentArray;
1990b57cec5SDimitry Andric }
2000b57cec5SDimitry Andric 
201e8d8bef9SDimitry Andric json::Array renderFileBranches(const coverage::CoverageData &FileCoverage,
202e8d8bef9SDimitry Andric                                const FileCoverageSummary &FileReport) {
203e8d8bef9SDimitry Andric   json::Array BranchArray;
204e8d8bef9SDimitry Andric   for (const auto &Branch : FileCoverage.getBranches())
205e8d8bef9SDimitry Andric     BranchArray.push_back(renderBranch(Branch));
206e8d8bef9SDimitry Andric   return BranchArray;
207e8d8bef9SDimitry Andric }
208e8d8bef9SDimitry Andric 
2090b57cec5SDimitry Andric json::Object renderFile(const coverage::CoverageMapping &Coverage,
2100b57cec5SDimitry Andric                         const std::string &Filename,
2110b57cec5SDimitry Andric                         const FileCoverageSummary &FileReport,
2120b57cec5SDimitry Andric                         const CoverageViewOptions &Options) {
2130b57cec5SDimitry Andric   json::Object File({{"filename", Filename}});
2140b57cec5SDimitry Andric   if (!Options.ExportSummaryOnly) {
2150b57cec5SDimitry Andric     // Calculate and render detailed coverage information for given file.
2160b57cec5SDimitry Andric     auto FileCoverage = Coverage.getCoverageForFile(Filename);
2170b57cec5SDimitry Andric     File["segments"] = renderFileSegments(FileCoverage, FileReport);
218e8d8bef9SDimitry Andric     File["branches"] = renderFileBranches(FileCoverage, FileReport);
2190b57cec5SDimitry Andric     if (!Options.SkipExpansions) {
220e8d8bef9SDimitry Andric       File["expansions"] =
221e8d8bef9SDimitry Andric           renderFileExpansions(Coverage, FileCoverage, FileReport);
2220b57cec5SDimitry Andric     }
2230b57cec5SDimitry Andric   }
2240b57cec5SDimitry Andric   File["summary"] = renderSummary(FileReport);
2250b57cec5SDimitry Andric   return File;
2260b57cec5SDimitry Andric }
2270b57cec5SDimitry Andric 
2280b57cec5SDimitry Andric json::Array renderFiles(const coverage::CoverageMapping &Coverage,
2290b57cec5SDimitry Andric                         ArrayRef<std::string> SourceFiles,
2300b57cec5SDimitry Andric                         ArrayRef<FileCoverageSummary> FileReports,
2310b57cec5SDimitry Andric                         const CoverageViewOptions &Options) {
2325ffd83dbSDimitry Andric   ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
2335ffd83dbSDimitry Andric   if (Options.NumThreads == 0) {
2345ffd83dbSDimitry Andric     // If NumThreads is not specified, create one thread for each input, up to
2355ffd83dbSDimitry Andric     // the number of hardware cores.
2365ffd83dbSDimitry Andric     S = heavyweight_hardware_concurrency(SourceFiles.size());
2375ffd83dbSDimitry Andric     S.Limit = true;
2380b57cec5SDimitry Andric   }
2395ffd83dbSDimitry Andric   ThreadPool Pool(S);
2400b57cec5SDimitry Andric   json::Array FileArray;
2410b57cec5SDimitry Andric   std::mutex FileArrayMutex;
2420b57cec5SDimitry Andric 
2430b57cec5SDimitry Andric   for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) {
2440b57cec5SDimitry Andric     auto &SourceFile = SourceFiles[I];
2450b57cec5SDimitry Andric     auto &FileReport = FileReports[I];
2460b57cec5SDimitry Andric     Pool.async([&] {
2470b57cec5SDimitry Andric       auto File = renderFile(Coverage, SourceFile, FileReport, Options);
2480b57cec5SDimitry Andric       {
2490b57cec5SDimitry Andric         std::lock_guard<std::mutex> Lock(FileArrayMutex);
2500b57cec5SDimitry Andric         FileArray.push_back(std::move(File));
2510b57cec5SDimitry Andric       }
2520b57cec5SDimitry Andric     });
2530b57cec5SDimitry Andric   }
2540b57cec5SDimitry Andric   Pool.wait();
2550b57cec5SDimitry Andric   return FileArray;
2560b57cec5SDimitry Andric }
2570b57cec5SDimitry Andric 
2580b57cec5SDimitry Andric json::Array renderFunctions(
2590b57cec5SDimitry Andric     const iterator_range<coverage::FunctionRecordIterator> &Functions) {
2600b57cec5SDimitry Andric   json::Array FunctionArray;
2610b57cec5SDimitry Andric   for (const auto &F : Functions)
2620b57cec5SDimitry Andric     FunctionArray.push_back(
2630b57cec5SDimitry Andric         json::Object({{"name", F.Name},
264480093f4SDimitry Andric                       {"count", clamp_uint64_to_int64(F.ExecutionCount)},
2650b57cec5SDimitry Andric                       {"regions", renderRegions(F.CountedRegions)},
266e8d8bef9SDimitry Andric                       {"branches", renderBranchRegions(F.CountedBranchRegions)},
2670b57cec5SDimitry Andric                       {"filenames", json::Array(F.Filenames)}}));
2680b57cec5SDimitry Andric   return FunctionArray;
2690b57cec5SDimitry Andric }
2700b57cec5SDimitry Andric 
2710b57cec5SDimitry Andric } // end anonymous namespace
2720b57cec5SDimitry Andric 
2730b57cec5SDimitry Andric void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) {
2740b57cec5SDimitry Andric   std::vector<std::string> SourceFiles;
2750b57cec5SDimitry Andric   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
2760b57cec5SDimitry Andric     if (!IgnoreFilters.matchesFilename(SF))
2770b57cec5SDimitry Andric       SourceFiles.emplace_back(SF);
2780b57cec5SDimitry Andric   }
2790b57cec5SDimitry Andric   renderRoot(SourceFiles);
2800b57cec5SDimitry Andric }
2810b57cec5SDimitry Andric 
2820b57cec5SDimitry Andric void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) {
2830b57cec5SDimitry Andric   FileCoverageSummary Totals = FileCoverageSummary("Totals");
2840b57cec5SDimitry Andric   auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
2850b57cec5SDimitry Andric                                                         SourceFiles, Options);
2860b57cec5SDimitry Andric   auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options);
2870b57cec5SDimitry Andric   // Sort files in order of their names.
288e8d8bef9SDimitry Andric   llvm::sort(Files, [](const json::Value &A, const json::Value &B) {
2890b57cec5SDimitry Andric     const json::Object *ObjA = A.getAsObject();
2900b57cec5SDimitry Andric     const json::Object *ObjB = B.getAsObject();
2910b57cec5SDimitry Andric     assert(ObjA != nullptr && "Value A was not an Object");
2920b57cec5SDimitry Andric     assert(ObjB != nullptr && "Value B was not an Object");
293*bdd1243dSDimitry Andric     const StringRef FilenameA = *ObjA->getString("filename");
294*bdd1243dSDimitry Andric     const StringRef FilenameB = *ObjB->getString("filename");
2950b57cec5SDimitry Andric     return FilenameA.compare(FilenameB) < 0;
2960b57cec5SDimitry Andric   });
2970b57cec5SDimitry Andric   auto Export = json::Object(
2980b57cec5SDimitry Andric       {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}});
2990b57cec5SDimitry Andric   // Skip functions-level information  if necessary.
3000b57cec5SDimitry Andric   if (!Options.ExportSummaryOnly && !Options.SkipFunctions)
3010b57cec5SDimitry Andric     Export["functions"] = renderFunctions(Coverage.getCoveredFunctions());
3020b57cec5SDimitry Andric 
3030b57cec5SDimitry Andric   auto ExportArray = json::Array({std::move(Export)});
3040b57cec5SDimitry Andric 
3050b57cec5SDimitry Andric   OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR},
3060b57cec5SDimitry Andric                       {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR},
3070b57cec5SDimitry Andric                       {"data", std::move(ExportArray)}});
3080b57cec5SDimitry Andric }
309