xref: /freebsd/contrib/llvm-project/llvm/tools/llvm-cov/CoverageExporterJson.cpp (revision fe6060f10f634930ff71b7c50291ddc610da2475)
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/Optional.h"
520b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
530b57cec5SDimitry Andric #include "llvm/Support/JSON.h"
540b57cec5SDimitry Andric #include "llvm/Support/ThreadPool.h"
550b57cec5SDimitry Andric #include "llvm/Support/Threading.h"
560b57cec5SDimitry Andric #include <algorithm>
57480093f4SDimitry Andric #include <limits>
580b57cec5SDimitry Andric #include <mutex>
590b57cec5SDimitry Andric #include <utility>
600b57cec5SDimitry Andric 
610b57cec5SDimitry Andric /// The semantic version combined as a string.
625ffd83dbSDimitry Andric #define LLVM_COVERAGE_EXPORT_JSON_STR "2.0.1"
630b57cec5SDimitry Andric 
640b57cec5SDimitry Andric /// Unique type identifier for JSON coverage export.
650b57cec5SDimitry Andric #define LLVM_COVERAGE_EXPORT_JSON_TYPE_STR "llvm.coverage.json.export"
660b57cec5SDimitry Andric 
670b57cec5SDimitry Andric using namespace llvm;
680b57cec5SDimitry Andric 
690b57cec5SDimitry Andric namespace {
700b57cec5SDimitry Andric 
71480093f4SDimitry Andric // The JSON library accepts int64_t, but profiling counts are stored as uint64_t.
72480093f4SDimitry Andric // Therefore we need to explicitly convert from unsigned to signed, since a naive
73480093f4SDimitry Andric // cast is implementation-defined behavior when the unsigned value cannot be
74480093f4SDimitry Andric // represented as a signed value. We choose to clamp the values to preserve the
75480093f4SDimitry Andric // invariant that counts are always >= 0.
76480093f4SDimitry Andric int64_t clamp_uint64_to_int64(uint64_t u) {
77480093f4SDimitry Andric   return std::min(u, static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
78480093f4SDimitry Andric }
79480093f4SDimitry Andric 
800b57cec5SDimitry Andric json::Array renderSegment(const coverage::CoverageSegment &Segment) {
815ffd83dbSDimitry Andric   return json::Array({Segment.Line, Segment.Col,
825ffd83dbSDimitry Andric                       clamp_uint64_to_int64(Segment.Count), Segment.HasCount,
835ffd83dbSDimitry Andric                       Segment.IsRegionEntry, Segment.IsGapRegion});
840b57cec5SDimitry Andric }
850b57cec5SDimitry Andric 
860b57cec5SDimitry Andric json::Array renderRegion(const coverage::CountedRegion &Region) {
870b57cec5SDimitry Andric   return json::Array({Region.LineStart, Region.ColumnStart, Region.LineEnd,
88480093f4SDimitry Andric                       Region.ColumnEnd, clamp_uint64_to_int64(Region.ExecutionCount),
890b57cec5SDimitry Andric                       Region.FileID, Region.ExpandedFileID,
900b57cec5SDimitry Andric                       int64_t(Region.Kind)});
910b57cec5SDimitry Andric }
920b57cec5SDimitry Andric 
93e8d8bef9SDimitry Andric json::Array renderBranch(const coverage::CountedRegion &Region) {
94e8d8bef9SDimitry Andric   return json::Array(
95e8d8bef9SDimitry Andric       {Region.LineStart, Region.ColumnStart, Region.LineEnd, Region.ColumnEnd,
96e8d8bef9SDimitry Andric        clamp_uint64_to_int64(Region.ExecutionCount),
97e8d8bef9SDimitry Andric        clamp_uint64_to_int64(Region.FalseExecutionCount), Region.FileID,
98e8d8bef9SDimitry Andric        Region.ExpandedFileID, int64_t(Region.Kind)});
99e8d8bef9SDimitry Andric }
100e8d8bef9SDimitry Andric 
1010b57cec5SDimitry Andric json::Array renderRegions(ArrayRef<coverage::CountedRegion> Regions) {
1020b57cec5SDimitry Andric   json::Array RegionArray;
1030b57cec5SDimitry Andric   for (const auto &Region : Regions)
1040b57cec5SDimitry Andric     RegionArray.push_back(renderRegion(Region));
1050b57cec5SDimitry Andric   return RegionArray;
1060b57cec5SDimitry Andric }
1070b57cec5SDimitry Andric 
108e8d8bef9SDimitry Andric json::Array renderBranchRegions(ArrayRef<coverage::CountedRegion> Regions) {
109e8d8bef9SDimitry Andric   json::Array RegionArray;
110e8d8bef9SDimitry Andric   for (const auto &Region : Regions)
111e8d8bef9SDimitry Andric     if (!Region.Folded)
112e8d8bef9SDimitry Andric       RegionArray.push_back(renderBranch(Region));
113e8d8bef9SDimitry Andric   return RegionArray;
114e8d8bef9SDimitry Andric }
115e8d8bef9SDimitry Andric 
116e8d8bef9SDimitry Andric std::vector<llvm::coverage::CountedRegion>
117e8d8bef9SDimitry Andric collectNestedBranches(const coverage::CoverageMapping &Coverage,
118e8d8bef9SDimitry Andric                       ArrayRef<llvm::coverage::ExpansionRecord> Expansions) {
119e8d8bef9SDimitry Andric   std::vector<llvm::coverage::CountedRegion> Branches;
120e8d8bef9SDimitry Andric   for (const auto &Expansion : Expansions) {
121e8d8bef9SDimitry Andric     auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);
122e8d8bef9SDimitry Andric 
123e8d8bef9SDimitry Andric     // Recursively collect branches from nested expansions.
124e8d8bef9SDimitry Andric     auto NestedExpansions = ExpansionCoverage.getExpansions();
125e8d8bef9SDimitry Andric     auto NestedExBranches = collectNestedBranches(Coverage, NestedExpansions);
126*fe6060f1SDimitry Andric     append_range(Branches, NestedExBranches);
127e8d8bef9SDimitry Andric 
128e8d8bef9SDimitry Andric     // Add branches from this level of expansion.
129e8d8bef9SDimitry Andric     auto ExBranches = ExpansionCoverage.getBranches();
130e8d8bef9SDimitry Andric     for (auto B : ExBranches)
131e8d8bef9SDimitry Andric       if (B.FileID == Expansion.FileID)
132e8d8bef9SDimitry Andric         Branches.push_back(B);
133e8d8bef9SDimitry Andric   }
134e8d8bef9SDimitry Andric 
135e8d8bef9SDimitry Andric   return Branches;
136e8d8bef9SDimitry Andric }
137e8d8bef9SDimitry Andric 
138e8d8bef9SDimitry Andric json::Object renderExpansion(const coverage::CoverageMapping &Coverage,
139e8d8bef9SDimitry Andric                              const coverage::ExpansionRecord &Expansion) {
140e8d8bef9SDimitry Andric   std::vector<llvm::coverage::ExpansionRecord> Expansions = {Expansion};
1410b57cec5SDimitry Andric   return json::Object(
1420b57cec5SDimitry Andric       {{"filenames", json::Array(Expansion.Function.Filenames)},
1430b57cec5SDimitry Andric        // Mark the beginning and end of this expansion in the source file.
1440b57cec5SDimitry Andric        {"source_region", renderRegion(Expansion.Region)},
1450b57cec5SDimitry Andric        // Enumerate the coverage information for the expansion.
146e8d8bef9SDimitry Andric        {"target_regions", renderRegions(Expansion.Function.CountedRegions)},
147e8d8bef9SDimitry Andric        // Enumerate the branch coverage information for the expansion.
148e8d8bef9SDimitry Andric        {"branches",
149e8d8bef9SDimitry Andric         renderBranchRegions(collectNestedBranches(Coverage, Expansions))}});
1500b57cec5SDimitry Andric }
1510b57cec5SDimitry Andric 
1520b57cec5SDimitry Andric json::Object renderSummary(const FileCoverageSummary &Summary) {
1530b57cec5SDimitry Andric   return json::Object(
1540b57cec5SDimitry Andric       {{"lines",
1550b57cec5SDimitry Andric         json::Object({{"count", int64_t(Summary.LineCoverage.getNumLines())},
1560b57cec5SDimitry Andric                       {"covered", int64_t(Summary.LineCoverage.getCovered())},
1570b57cec5SDimitry Andric                       {"percent", Summary.LineCoverage.getPercentCovered()}})},
1580b57cec5SDimitry Andric        {"functions",
1590b57cec5SDimitry Andric         json::Object(
1600b57cec5SDimitry Andric             {{"count", int64_t(Summary.FunctionCoverage.getNumFunctions())},
1610b57cec5SDimitry Andric              {"covered", int64_t(Summary.FunctionCoverage.getExecuted())},
1620b57cec5SDimitry Andric              {"percent", Summary.FunctionCoverage.getPercentCovered()}})},
1630b57cec5SDimitry Andric        {"instantiations",
1640b57cec5SDimitry Andric         json::Object(
1650b57cec5SDimitry Andric             {{"count",
1660b57cec5SDimitry Andric               int64_t(Summary.InstantiationCoverage.getNumFunctions())},
1670b57cec5SDimitry Andric              {"covered", int64_t(Summary.InstantiationCoverage.getExecuted())},
1680b57cec5SDimitry Andric              {"percent", Summary.InstantiationCoverage.getPercentCovered()}})},
1690b57cec5SDimitry Andric        {"regions",
1700b57cec5SDimitry Andric         json::Object(
1710b57cec5SDimitry Andric             {{"count", int64_t(Summary.RegionCoverage.getNumRegions())},
1720b57cec5SDimitry Andric              {"covered", int64_t(Summary.RegionCoverage.getCovered())},
1730b57cec5SDimitry Andric              {"notcovered", int64_t(Summary.RegionCoverage.getNumRegions() -
1740b57cec5SDimitry Andric                                     Summary.RegionCoverage.getCovered())},
175e8d8bef9SDimitry Andric              {"percent", Summary.RegionCoverage.getPercentCovered()}})},
176e8d8bef9SDimitry Andric        {"branches",
177e8d8bef9SDimitry Andric         json::Object(
178e8d8bef9SDimitry Andric             {{"count", int64_t(Summary.BranchCoverage.getNumBranches())},
179e8d8bef9SDimitry Andric              {"covered", int64_t(Summary.BranchCoverage.getCovered())},
180e8d8bef9SDimitry Andric              {"notcovered", int64_t(Summary.BranchCoverage.getNumBranches() -
181e8d8bef9SDimitry Andric                                     Summary.BranchCoverage.getCovered())},
182e8d8bef9SDimitry Andric              {"percent", Summary.BranchCoverage.getPercentCovered()}})}});
1830b57cec5SDimitry Andric }
1840b57cec5SDimitry Andric 
185e8d8bef9SDimitry Andric json::Array renderFileExpansions(const coverage::CoverageMapping &Coverage,
186e8d8bef9SDimitry Andric                                  const coverage::CoverageData &FileCoverage,
1870b57cec5SDimitry Andric                                  const FileCoverageSummary &FileReport) {
1880b57cec5SDimitry Andric   json::Array ExpansionArray;
1890b57cec5SDimitry Andric   for (const auto &Expansion : FileCoverage.getExpansions())
190e8d8bef9SDimitry Andric     ExpansionArray.push_back(renderExpansion(Coverage, Expansion));
1910b57cec5SDimitry Andric   return ExpansionArray;
1920b57cec5SDimitry Andric }
1930b57cec5SDimitry Andric 
1940b57cec5SDimitry Andric json::Array renderFileSegments(const coverage::CoverageData &FileCoverage,
1950b57cec5SDimitry Andric                                const FileCoverageSummary &FileReport) {
1960b57cec5SDimitry Andric   json::Array SegmentArray;
1970b57cec5SDimitry Andric   for (const auto &Segment : FileCoverage)
1980b57cec5SDimitry Andric     SegmentArray.push_back(renderSegment(Segment));
1990b57cec5SDimitry Andric   return SegmentArray;
2000b57cec5SDimitry Andric }
2010b57cec5SDimitry Andric 
202e8d8bef9SDimitry Andric json::Array renderFileBranches(const coverage::CoverageData &FileCoverage,
203e8d8bef9SDimitry Andric                                const FileCoverageSummary &FileReport) {
204e8d8bef9SDimitry Andric   json::Array BranchArray;
205e8d8bef9SDimitry Andric   for (const auto &Branch : FileCoverage.getBranches())
206e8d8bef9SDimitry Andric     BranchArray.push_back(renderBranch(Branch));
207e8d8bef9SDimitry Andric   return BranchArray;
208e8d8bef9SDimitry Andric }
209e8d8bef9SDimitry Andric 
2100b57cec5SDimitry Andric json::Object renderFile(const coverage::CoverageMapping &Coverage,
2110b57cec5SDimitry Andric                         const std::string &Filename,
2120b57cec5SDimitry Andric                         const FileCoverageSummary &FileReport,
2130b57cec5SDimitry Andric                         const CoverageViewOptions &Options) {
2140b57cec5SDimitry Andric   json::Object File({{"filename", Filename}});
2150b57cec5SDimitry Andric   if (!Options.ExportSummaryOnly) {
2160b57cec5SDimitry Andric     // Calculate and render detailed coverage information for given file.
2170b57cec5SDimitry Andric     auto FileCoverage = Coverage.getCoverageForFile(Filename);
2180b57cec5SDimitry Andric     File["segments"] = renderFileSegments(FileCoverage, FileReport);
219e8d8bef9SDimitry Andric     File["branches"] = renderFileBranches(FileCoverage, FileReport);
2200b57cec5SDimitry Andric     if (!Options.SkipExpansions) {
221e8d8bef9SDimitry Andric       File["expansions"] =
222e8d8bef9SDimitry Andric           renderFileExpansions(Coverage, FileCoverage, FileReport);
2230b57cec5SDimitry Andric     }
2240b57cec5SDimitry Andric   }
2250b57cec5SDimitry Andric   File["summary"] = renderSummary(FileReport);
2260b57cec5SDimitry Andric   return File;
2270b57cec5SDimitry Andric }
2280b57cec5SDimitry Andric 
2290b57cec5SDimitry Andric json::Array renderFiles(const coverage::CoverageMapping &Coverage,
2300b57cec5SDimitry Andric                         ArrayRef<std::string> SourceFiles,
2310b57cec5SDimitry Andric                         ArrayRef<FileCoverageSummary> FileReports,
2320b57cec5SDimitry Andric                         const CoverageViewOptions &Options) {
2335ffd83dbSDimitry Andric   ThreadPoolStrategy S = hardware_concurrency(Options.NumThreads);
2345ffd83dbSDimitry Andric   if (Options.NumThreads == 0) {
2355ffd83dbSDimitry Andric     // If NumThreads is not specified, create one thread for each input, up to
2365ffd83dbSDimitry Andric     // the number of hardware cores.
2375ffd83dbSDimitry Andric     S = heavyweight_hardware_concurrency(SourceFiles.size());
2385ffd83dbSDimitry Andric     S.Limit = true;
2390b57cec5SDimitry Andric   }
2405ffd83dbSDimitry Andric   ThreadPool Pool(S);
2410b57cec5SDimitry Andric   json::Array FileArray;
2420b57cec5SDimitry Andric   std::mutex FileArrayMutex;
2430b57cec5SDimitry Andric 
2440b57cec5SDimitry Andric   for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I) {
2450b57cec5SDimitry Andric     auto &SourceFile = SourceFiles[I];
2460b57cec5SDimitry Andric     auto &FileReport = FileReports[I];
2470b57cec5SDimitry Andric     Pool.async([&] {
2480b57cec5SDimitry Andric       auto File = renderFile(Coverage, SourceFile, FileReport, Options);
2490b57cec5SDimitry Andric       {
2500b57cec5SDimitry Andric         std::lock_guard<std::mutex> Lock(FileArrayMutex);
2510b57cec5SDimitry Andric         FileArray.push_back(std::move(File));
2520b57cec5SDimitry Andric       }
2530b57cec5SDimitry Andric     });
2540b57cec5SDimitry Andric   }
2550b57cec5SDimitry Andric   Pool.wait();
2560b57cec5SDimitry Andric   return FileArray;
2570b57cec5SDimitry Andric }
2580b57cec5SDimitry Andric 
2590b57cec5SDimitry Andric json::Array renderFunctions(
2600b57cec5SDimitry Andric     const iterator_range<coverage::FunctionRecordIterator> &Functions) {
2610b57cec5SDimitry Andric   json::Array FunctionArray;
2620b57cec5SDimitry Andric   for (const auto &F : Functions)
2630b57cec5SDimitry Andric     FunctionArray.push_back(
2640b57cec5SDimitry Andric         json::Object({{"name", F.Name},
265480093f4SDimitry Andric                       {"count", clamp_uint64_to_int64(F.ExecutionCount)},
2660b57cec5SDimitry Andric                       {"regions", renderRegions(F.CountedRegions)},
267e8d8bef9SDimitry Andric                       {"branches", renderBranchRegions(F.CountedBranchRegions)},
2680b57cec5SDimitry Andric                       {"filenames", json::Array(F.Filenames)}}));
2690b57cec5SDimitry Andric   return FunctionArray;
2700b57cec5SDimitry Andric }
2710b57cec5SDimitry Andric 
2720b57cec5SDimitry Andric } // end anonymous namespace
2730b57cec5SDimitry Andric 
2740b57cec5SDimitry Andric void CoverageExporterJson::renderRoot(const CoverageFilters &IgnoreFilters) {
2750b57cec5SDimitry Andric   std::vector<std::string> SourceFiles;
2760b57cec5SDimitry Andric   for (StringRef SF : Coverage.getUniqueSourceFiles()) {
2770b57cec5SDimitry Andric     if (!IgnoreFilters.matchesFilename(SF))
2780b57cec5SDimitry Andric       SourceFiles.emplace_back(SF);
2790b57cec5SDimitry Andric   }
2800b57cec5SDimitry Andric   renderRoot(SourceFiles);
2810b57cec5SDimitry Andric }
2820b57cec5SDimitry Andric 
2830b57cec5SDimitry Andric void CoverageExporterJson::renderRoot(ArrayRef<std::string> SourceFiles) {
2840b57cec5SDimitry Andric   FileCoverageSummary Totals = FileCoverageSummary("Totals");
2850b57cec5SDimitry Andric   auto FileReports = CoverageReport::prepareFileReports(Coverage, Totals,
2860b57cec5SDimitry Andric                                                         SourceFiles, Options);
2870b57cec5SDimitry Andric   auto Files = renderFiles(Coverage, SourceFiles, FileReports, Options);
2880b57cec5SDimitry Andric   // Sort files in order of their names.
289e8d8bef9SDimitry Andric   llvm::sort(Files, [](const json::Value &A, const json::Value &B) {
2900b57cec5SDimitry Andric     const json::Object *ObjA = A.getAsObject();
2910b57cec5SDimitry Andric     const json::Object *ObjB = B.getAsObject();
2920b57cec5SDimitry Andric     assert(ObjA != nullptr && "Value A was not an Object");
2930b57cec5SDimitry Andric     assert(ObjB != nullptr && "Value B was not an Object");
2940b57cec5SDimitry Andric     const StringRef FilenameA = ObjA->getString("filename").getValue();
2950b57cec5SDimitry Andric     const StringRef FilenameB = ObjB->getString("filename").getValue();
2960b57cec5SDimitry Andric     return FilenameA.compare(FilenameB) < 0;
2970b57cec5SDimitry Andric   });
2980b57cec5SDimitry Andric   auto Export = json::Object(
2990b57cec5SDimitry Andric       {{"files", std::move(Files)}, {"totals", renderSummary(Totals)}});
3000b57cec5SDimitry Andric   // Skip functions-level information  if necessary.
3010b57cec5SDimitry Andric   if (!Options.ExportSummaryOnly && !Options.SkipFunctions)
3020b57cec5SDimitry Andric     Export["functions"] = renderFunctions(Coverage.getCoveredFunctions());
3030b57cec5SDimitry Andric 
3040b57cec5SDimitry Andric   auto ExportArray = json::Array({std::move(Export)});
3050b57cec5SDimitry Andric 
3060b57cec5SDimitry Andric   OS << json::Object({{"version", LLVM_COVERAGE_EXPORT_JSON_STR},
3070b57cec5SDimitry Andric                       {"type", LLVM_COVERAGE_EXPORT_JSON_TYPE_STR},
3080b57cec5SDimitry Andric                       {"data", std::move(ExportArray)}});
3090b57cec5SDimitry Andric }
310