xref: /freebsd/contrib/llvm-project/llvm/tools/llvm-cov/CoverageExporterJson.cpp (revision 5ffd83dbcc34f10e07f6d3e968ae6365869615f4)
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
210b57cec5SDimitry Andric //         -- Segments: array => List of Segments contained in the file
220b57cec5SDimitry Andric //           -- Segment: dict => Describes a segment of the file with a counter
230b57cec5SDimitry Andric //         -- Expansions: array => List of expansion records
240b57cec5SDimitry Andric //           -- Expansion: dict => Object that descibes a single expansion
250b57cec5SDimitry Andric //             -- CountedRegion: dict => The region to be expanded
260b57cec5SDimitry Andric //             -- TargetRegions: array => List of Regions in the expansion
270b57cec5SDimitry Andric //               -- CountedRegion: dict => Single Region in the expansion
280b57cec5SDimitry Andric //         -- Summary: dict => Object summarizing the coverage for this file
290b57cec5SDimitry Andric //           -- LineCoverage: dict => Object summarizing line coverage
300b57cec5SDimitry Andric //           -- FunctionCoverage: dict => Object summarizing function coverage
310b57cec5SDimitry Andric //           -- RegionCoverage: dict => Object summarizing region coverage
320b57cec5SDimitry Andric //     -- Functions: array => List of objects describing coverage for functions
330b57cec5SDimitry Andric //       -- Function: dict => Coverage info for a single function
340b57cec5SDimitry Andric //         -- Filenames: array => List of filenames that the function relates to
350b57cec5SDimitry Andric //   -- Summary: dict => Object summarizing the coverage for the entire binary
360b57cec5SDimitry Andric //     -- LineCoverage: dict => Object summarizing line coverage
370b57cec5SDimitry Andric //     -- FunctionCoverage: dict => Object summarizing function coverage
380b57cec5SDimitry Andric //     -- InstantiationCoverage: dict => Object summarizing inst. coverage
390b57cec5SDimitry Andric //     -- RegionCoverage: dict => Object summarizing region coverage
400b57cec5SDimitry Andric //
410b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
420b57cec5SDimitry Andric 
430b57cec5SDimitry Andric #include "CoverageExporterJson.h"
440b57cec5SDimitry Andric #include "CoverageReport.h"
450b57cec5SDimitry Andric #include "llvm/ADT/Optional.h"
460b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
470b57cec5SDimitry Andric #include "llvm/Support/JSON.h"
480b57cec5SDimitry Andric #include "llvm/Support/ThreadPool.h"
490b57cec5SDimitry Andric #include "llvm/Support/Threading.h"
500b57cec5SDimitry Andric #include <algorithm>
51480093f4SDimitry Andric #include <limits>
520b57cec5SDimitry Andric #include <mutex>
530b57cec5SDimitry Andric #include <utility>
540b57cec5SDimitry Andric 
550b57cec5SDimitry Andric /// The semantic version combined as a string.
56*5ffd83dbSDimitry Andric #define LLVM_COVERAGE_EXPORT_JSON_STR "2.0.1"
570b57cec5SDimitry Andric 
580b57cec5SDimitry Andric /// Unique type identifier for JSON coverage export.
590b57cec5SDimitry Andric #define LLVM_COVERAGE_EXPORT_JSON_TYPE_STR "llvm.coverage.json.export"
600b57cec5SDimitry Andric 
610b57cec5SDimitry Andric using namespace llvm;
620b57cec5SDimitry Andric 
630b57cec5SDimitry Andric namespace {
640b57cec5SDimitry Andric 
65480093f4SDimitry Andric // The JSON library accepts int64_t, but profiling counts are stored as uint64_t.
66480093f4SDimitry Andric // Therefore we need to explicitly convert from unsigned to signed, since a naive
67480093f4SDimitry Andric // cast is implementation-defined behavior when the unsigned value cannot be
68480093f4SDimitry Andric // represented as a signed value. We choose to clamp the values to preserve the
69480093f4SDimitry Andric // invariant that counts are always >= 0.
70480093f4SDimitry Andric int64_t clamp_uint64_to_int64(uint64_t u) {
71480093f4SDimitry Andric   return std::min(u, static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
72480093f4SDimitry Andric }
73480093f4SDimitry Andric 
740b57cec5SDimitry Andric json::Array renderSegment(const coverage::CoverageSegment &Segment) {
75*5ffd83dbSDimitry Andric   return json::Array({Segment.Line, Segment.Col,
76*5ffd83dbSDimitry Andric                       clamp_uint64_to_int64(Segment.Count), Segment.HasCount,
77*5ffd83dbSDimitry Andric                       Segment.IsRegionEntry, Segment.IsGapRegion});
780b57cec5SDimitry Andric }
790b57cec5SDimitry Andric 
800b57cec5SDimitry Andric json::Array renderRegion(const coverage::CountedRegion &Region) {
810b57cec5SDimitry Andric   return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd,
82480093f4SDimitry Andric                       Region.ColumnEnd, clamp_uint64_to_int64(Region.ExecutionCount),
830b57cec5SDimitry Andric                       Region.FileID, Region.ExpandedFileID,
840b57cec5SDimitry Andric                       int64_t(Region.Kind)});
850b57cec5SDimitry Andric }
860b57cec5SDimitry Andric 
870b57cec5SDimitry Andric json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) {
880b57cec5SDimitry Andric   json::Array RegionArray;
890b57cec5SDimitry Andric   for (const auto &Region : Regions)
900b57cec5SDimitry Andric     RegionArray.push_back(renderRegion(Region));
910b57cec5SDimitry Andric   return RegionArray;
920b57cec5SDimitry Andric }
930b57cec5SDimitry Andric 
940b57cec5SDimitry Andric json::Object renderExpansion(const coverage::ExpansionRecord &Expansion) {
950b57cec5SDimitry Andric   return json::Object(
960b57cec5SDimitry Andric       {{"filenames", json::Array(Expansion.Function.Filenames)},
970b57cec5SDimitry Andric        // Mark the beginning and end of this expansion in the source file.
980b57cec5SDimitry Andric        {"source_region", renderRegion(Expansion.Region)},
990b57cec5SDimitry Andric        // Enumerate the coverage information for the expansion.
1000b57cec5SDimitry Andric        {"target_regions", renderRegions(Expansion.Function.CountedRegions)}});
1010b57cec5SDimitry Andric }
1020b57cec5SDimitry Andric 
1030b57cec5SDimitry Andric json::Object renderSummary(const FileCoverageSummary &Summary) {
1040b57cec5SDimitry Andric   return json::Object(
1050b57cec5SDimitry Andric       {{"lines",
1060b57cec5SDimitry Andric         json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())},
1070b57cec5SDimitry Andric                       {"covered", int64_t(Summary.LineCoverage.getCovered())},
1080b57cec5SDimitry Andric                       {"percent", Summary.LineCoverage.getPercentCovered()}})},
1090b57cec5SDimitry Andric        {"functions",
1100b57cec5SDimitry Andric         json::Object(
1110b57cec5SDimitry Andric             {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())},
1120b57cec5SDimitry Andric              {"covered", int64_t(Summary.FunctionCoverage.getExecuted())},
1130b57cec5SDimitry Andric              {"percent", Summary.FunctionCoverage.getPercentCovered()}})},
1140b57cec5SDimitry Andric        {"instantiations",
1150b57cec5SDimitry Andric         json::Object(
1160b57cec5SDimitry Andric             {{"count",
1170b57cec5SDimitry Andric               int64_t(Summary.InstantiationCoverage.getNumFunctions())},
1180b57cec5SDimitry Andric              {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())},
1190b57cec5SDimitry Andric              {"percent", Summary.InstantiationCoverage.getPercentCovered()}})},
1200b57cec5SDimitry Andric        {"regions",
1210b57cec5SDimitry Andric         json::Object(
1220b57cec5SDimitry Andric             {{"count", int64_t(Summary.RegionCoverage.getNumRegions())},
1230b57cec5SDimitry Andric              {"covered", int64_t(Summary.RegionCoverage.getCovered())},
1240b57cec5SDimitry Andric              {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() -
1250b57cec5SDimitry Andric                                     Summary.RegionCoverage.getCovered())},
1260b57cec5SDimitry Andric              {"percent", Summary.RegionCoverage.getPercentCovered()}})}});
1270b57cec5SDimitry Andric }
1280b57cec5SDimitry Andric 
1290b57cec5SDimitry Andric json::Array renderFileExpansions(const coverage::CoverageData &FileCoverage,
1300b57cec5SDimitry Andric                                  const FileCoverageSummary &FileReport) {
1310b57cec5SDimitry Andric   json::Array ExpansionArray;
1320b57cec5SDimitry Andric   for (const auto &Expansion : FileCoverage.getExpansions())
1330b57cec5SDimitry Andric     ExpansionArray.push_back(renderExpansion(Expansion));
1340b57cec5SDimitry Andric   return ExpansionArray;
1350b57cec5SDimitry Andric }
1360b57cec5SDimitry Andric 
1370b57cec5SDimitry Andric json::Array renderFileSegments(const coverage::CoverageData &FileCoverage,
1380b57cec5SDimitry Andric                                const FileCoverageSummary &FileReport) {
1390b57cec5SDimitry Andric   json::Array SegmentArray;
1400b57cec5SDimitry Andric   for (const auto &Segment : FileCoverage)
1410b57cec5SDimitry Andric     SegmentArray.push_back(renderSegment(Segment));
1420b57cec5SDimitry Andric   return SegmentArray;
1430b57cec5SDimitry Andric }
1440b57cec5SDimitry Andric 
1450b57cec5SDimitry Andric json::Object renderFile(const coverage::CoverageMapping &Coverage,
1460b57cec5SDimitry Andric                         const std::string &Filename,
1470b57cec5SDimitry Andric                         const FileCoverageSummary &FileReport,
1480b57cec5SDimitry Andric                         const CoverageViewOptions &Options) {
1490b57cec5SDimitry Andric   json::Object File({{"filename", Filename}});
1500b57cec5SDimitry Andric   if (!Options.ExportSummaryOnly) {
1510b57cec5SDimitry Andric     // Calculate and render detailed coverage information for given file.
1520b57cec5SDimitry Andric     auto FileCoverage = Coverage.getCoverageForFile(Filename);
1530b57cec5SDimitry Andric     File["segments"] = renderFileSegments(FileCoverage, FileReport);
1540b57cec5SDimitry Andric     if (!Options.SkipExpansions) {
1550b57cec5SDimitry Andric       File["expansions"] = renderFileExpansions(FileCoverage, FileReport);
1560b57cec5SDimitry Andric     }
1570b57cec5SDimitry Andric   }
1580b57cec5SDimitry Andric   File["summary"] = renderSummary(FileReport);
1590b57cec5SDimitry Andric   return File;
1600b57cec5SDimitry Andric }
1610b57cec5SDimitry Andric 
1620b57cec5SDimitry Andric json::Array renderFiles(const coverage::CoverageMapping &Coverage,
1630b57cec5SDimitry Andric                         ArrayRef<std::string> SourceFiles,
1640b57cec5SDimitry Andric                         ArrayRef<FileCoverageSummary> FileReports,
1650b57cec5SDimitry Andric                         const CoverageViewOptions &Options) {
166*5ffd83dbSDimitry Andric   ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
167*5ffd83dbSDimitry Andric   if (Options.NumThreads == 0) {
168*5ffd83dbSDimitry Andric     // If NumThreads is not specified, create one thread for each input, up to
169*5ffd83dbSDimitry Andric     // the number of hardware cores.
170*5ffd83dbSDimitry Andric     S = heavyweight_hardware_concurrency(SourceFiles.size());
171*5ffd83dbSDimitry Andric     S.Limit = true;
1720b57cec5SDimitry Andric   }
173*5ffd83dbSDimitry Andric   ThreadPool Pool(S);
1740b57cec5SDimitry Andric   json::Array FileArray;
1750b57cec5SDimitry Andric   std::mutex FileArrayMutex;
1760b57cec5SDimitry Andric 
1770b57cec5SDimitry Andric   for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) {
1780b57cec5SDimitry Andric     auto &SourceFile = SourceFiles[I];
1790b57cec5SDimitry Andric     auto &FileReport = FileReports[I];
1800b57cec5SDimitry Andric     Pool.async([&] {
1810b57cec5SDimitry Andric       auto File = renderFile(Coverage, SourceFile, FileReport, Options);
1820b57cec5SDimitry Andric       {
1830b57cec5SDimitry Andric         std::lock_guard<std::mutex> Lock(FileArrayMutex);
1840b57cec5SDimitry Andric         FileArray.push_back(std::move(File));
1850b57cec5SDimitry Andric       }
1860b57cec5SDimitry Andric     });
1870b57cec5SDimitry Andric   }
1880b57cec5SDimitry Andric   Pool.wait();
1890b57cec5SDimitry Andric   return FileArray;
1900b57cec5SDimitry Andric }
1910b57cec5SDimitry Andric 
1920b57cec5SDimitry Andric json::Array renderFunctions(
1930b57cec5SDimitry Andric     const iterator_range<coverage::FunctionRecordIterator> &Functions) {
1940b57cec5SDimitry Andric   json::Array FunctionArray;
1950b57cec5SDimitry Andric   for (const auto &F : Functions)
1960b57cec5SDimitry Andric     FunctionArray.push_back(
1970b57cec5SDimitry Andric         json::Object({{"name", F.Name},
198480093f4SDimitry Andric                       {"count", clamp_uint64_to_int64(F.ExecutionCount)},
1990b57cec5SDimitry Andric                       {"regions", renderRegions(F.CountedRegions)},
2000b57cec5SDimitry Andric                       {"filenames", json::Array(F.Filenames)}}));
2010b57cec5SDimitry Andric   return FunctionArray;
2020b57cec5SDimitry Andric }
2030b57cec5SDimitry Andric 
2040b57cec5SDimitry Andric } // end anonymous namespace
2050b57cec5SDimitry Andric 
2060b57cec5SDimitry Andric void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) {
2070b57cec5SDimitry Andric   std::vector<std::string> SourceFiles;
2080b57cec5SDimitry Andric   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
2090b57cec5SDimitry Andric     if (!IgnoreFilters.matchesFilename(SF))
2100b57cec5SDimitry Andric       SourceFiles.emplace_back(SF);
2110b57cec5SDimitry Andric   }
2120b57cec5SDimitry Andric   renderRoot(SourceFiles);
2130b57cec5SDimitry Andric }
2140b57cec5SDimitry Andric 
2150b57cec5SDimitry Andric void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) {
2160b57cec5SDimitry Andric   FileCoverageSummary Totals = FileCoverageSummary("Totals");
2170b57cec5SDimitry Andric   auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
2180b57cec5SDimitry Andric                                                         SourceFiles, Options);
2190b57cec5SDimitry Andric   auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options);
2200b57cec5SDimitry Andric   // Sort files in order of their names.
2210b57cec5SDimitry Andric   std::sort(Files.begin(), Files.end(),
2220b57cec5SDimitry Andric     [](const json::Value &A, const json::Value &B) {
2230b57cec5SDimitry Andric       const json::Object *ObjA = A.getAsObject();
2240b57cec5SDimitry Andric       const json::Object *ObjB = B.getAsObject();
2250b57cec5SDimitry Andric       assert(ObjA != nullptr && "Value A was not an Object");
2260b57cec5SDimitry Andric       assert(ObjB != nullptr && "Value B was not an Object");
2270b57cec5SDimitry Andric       const StringRef FilenameA = ObjA->getString("filename").getValue();
2280b57cec5SDimitry Andric       const StringRef FilenameB = ObjB->getString("filename").getValue();
2290b57cec5SDimitry Andric       return FilenameA.compare(FilenameB) < 0;
2300b57cec5SDimitry Andric     });
2310b57cec5SDimitry Andric   auto Export = json::Object(
2320b57cec5SDimitry Andric       {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}});
2330b57cec5SDimitry Andric   // Skip functions-level information  if necessary.
2340b57cec5SDimitry Andric   if (!Options.ExportSummaryOnly && !Options.SkipFunctions)
2350b57cec5SDimitry Andric     Export["functions"] = renderFunctions(Coverage.getCoveredFunctions());
2360b57cec5SDimitry Andric 
2370b57cec5SDimitry Andric   auto ExportArray = json::Array({std::move(Export)});
2380b57cec5SDimitry Andric 
2390b57cec5SDimitry Andric   OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR},
2400b57cec5SDimitry Andric                       {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR},
2410b57cec5SDimitry Andric                       {"data", std::move(ExportArray)}});
2420b57cec5SDimitry Andric }
243