brintos

brintos / llvm-project-archived public Read only

0
0
Text · 16.3 KiB · 4c07c05 Raw
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