316 lines · cpp
1//===- SourceCoverageView.cpp - Code coverage view for source code --------===//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/// \file This class implements rendering for code coverage of source code.10///11//===----------------------------------------------------------------------===//12 13#include "SourceCoverageView.h"14#include "SourceCoverageViewHTML.h"15#include "SourceCoverageViewText.h"16#include "llvm/ADT/SmallString.h"17#include "llvm/ADT/StringExtras.h"18#include "llvm/Support/FileSystem.h"19#include "llvm/Support/LineIterator.h"20#include "llvm/Support/Path.h"21 22using namespace llvm;23 24ExpansionView::ExpansionView(const CounterMappingRegion &Region,25 std::unique_ptr<SourceCoverageView> View)26 : Region(Region), View(std::move(View)) {}27 28ExpansionView::ExpansionView(ExpansionView &&RHS)29 : Region(std::move(RHS.Region)), View(std::move(RHS.View)) {}30 31ExpansionView &ExpansionView::operator=(ExpansionView &&RHS) {32 Region = std::move(RHS.Region);33 View = std::move(RHS.View);34 return *this;35}36 37InstantiationView::InstantiationView(StringRef FunctionName, unsigned Line,38 std::unique_ptr<SourceCoverageView> View)39 : FunctionName(FunctionName), Line(Line), View(std::move(View)) {}40 41void CoveragePrinter::StreamDestructor::operator()(raw_ostream *OS) const {42 if (OS == &outs())43 return;44 delete OS;45}46 47std::string CoveragePrinter::getOutputPath(StringRef Path, StringRef Extension,48 bool InToplevel,49 bool Relative) const {50 assert(!Extension.empty() && "The file extension may not be empty");51 52 SmallString<256> FullPath;53 54 if (!Relative)55 FullPath.append(Opts.ShowOutputDirectory);56 57 if (!InToplevel)58 sys::path::append(FullPath, getCoverageDir());59 60 SmallString<256> ParentPath = sys::path::parent_path(Path);61 sys::path::remove_dots(ParentPath, /*remove_dot_dot=*/true);62 sys::path::append(FullPath, sys::path::relative_path(ParentPath));63 64 auto PathFilename = (sys::path::filename(Path) + "." + Extension).str();65 sys::path::append(FullPath, PathFilename);66 sys::path::native(FullPath);67 68 return std::string(FullPath);69}70 71Expected<CoveragePrinter::OwnedStream>72CoveragePrinter::createOutputStream(StringRef Path, StringRef Extension,73 bool InToplevel) const {74 if (!Opts.hasOutputDirectory())75 return OwnedStream(&outs());76 77 std::string FullPath = getOutputPath(Path, Extension, InToplevel, false);78 79 auto ParentDir = sys::path::parent_path(FullPath);80 if (auto E = sys::fs::create_directories(ParentDir))81 return errorCodeToError(E);82 83 std::error_code E;84 raw_ostream *RawStream =85 new raw_fd_ostream(FullPath, E, sys::fs::FA_Read | sys::fs::FA_Write);86 auto OS = CoveragePrinter::OwnedStream(RawStream);87 if (E)88 return errorCodeToError(E);89 return std::move(OS);90}91 92std::unique_ptr<CoveragePrinter>93CoveragePrinter::create(const CoverageViewOptions &Opts) {94 switch (Opts.Format) {95 case CoverageViewOptions::OutputFormat::Text:96 if (Opts.ShowDirectoryCoverage)97 return std::make_unique<CoveragePrinterTextDirectory>(Opts);98 return std::make_unique<CoveragePrinterText>(Opts);99 case CoverageViewOptions::OutputFormat::HTML:100 if (Opts.ShowDirectoryCoverage)101 return std::make_unique<CoveragePrinterHTMLDirectory>(Opts);102 return std::make_unique<CoveragePrinterHTML>(Opts);103 case CoverageViewOptions::OutputFormat::Lcov:104 // Unreachable because CodeCoverage.cpp should terminate with an error105 // before we get here.106 llvm_unreachable("Lcov format is not supported!");107 }108 llvm_unreachable("Unknown coverage output format!");109}110 111unsigned SourceCoverageView::getFirstUncoveredLineNo() {112 const auto MinSegIt = find_if(CoverageInfo, [](const CoverageSegment &S) {113 return S.HasCount && S.Count == 0;114 });115 116 // There is no uncovered line, return zero.117 if (MinSegIt == CoverageInfo.end())118 return 0;119 120 return (*MinSegIt).Line;121}122 123std::string SourceCoverageView::formatCount(uint64_t N) {124 std::string Number = utostr(N);125 int Len = Number.size();126 if (Len <= 3)127 return Number;128 int IntLen = Len % 3 == 0 ? 3 : Len % 3;129 std::string Result(Number.data(), IntLen);130 if (IntLen != 3) {131 Result.push_back('.');132 Result += Number.substr(IntLen, 3 - IntLen);133 }134 Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);135 return Result;136}137 138bool SourceCoverageView::shouldRenderRegionMarkers(139 const LineCoverageStats &LCS) const {140 if (!getOptions().ShowRegionMarkers)141 return false;142 143 CoverageSegmentArray Segments = LCS.getLineSegments();144 if (Segments.empty())145 return false;146 for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {147 const auto *CurSeg = Segments[I];148 if (!CurSeg->IsRegionEntry || CurSeg->Count == LCS.getExecutionCount())149 continue;150 if (!CurSeg->HasCount) // don't show tooltips for SkippedRegions151 continue;152 return true;153 }154 return false;155}156 157bool SourceCoverageView::hasSubViews() const {158 return !ExpansionSubViews.empty() || !InstantiationSubViews.empty() ||159 !BranchSubViews.empty() || !MCDCSubViews.empty();160}161 162std::unique_ptr<SourceCoverageView>163SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File,164 const CoverageViewOptions &Options,165 CoverageData &&CoverageInfo) {166 switch (Options.Format) {167 case CoverageViewOptions::OutputFormat::Text:168 return std::make_unique<SourceCoverageViewText>(169 SourceName, File, Options, std::move(CoverageInfo));170 case CoverageViewOptions::OutputFormat::HTML:171 return std::make_unique<SourceCoverageViewHTML>(172 SourceName, File, Options, std::move(CoverageInfo));173 case CoverageViewOptions::OutputFormat::Lcov:174 // Unreachable because CodeCoverage.cpp should terminate with an error175 // before we get here.176 llvm_unreachable("Lcov format is not supported!");177 }178 llvm_unreachable("Unknown coverage output format!");179}180 181std::string SourceCoverageView::getSourceName() const {182 SmallString<128> SourceText(SourceName);183 sys::path::remove_dots(SourceText, /*remove_dot_dot=*/true);184 sys::path::native(SourceText);185 return std::string(SourceText);186}187 188void SourceCoverageView::addExpansion(189 const CounterMappingRegion &Region,190 std::unique_ptr<SourceCoverageView> View) {191 ExpansionSubViews.emplace_back(Region, std::move(View));192}193 194void SourceCoverageView::addBranch(unsigned Line,195 SmallVector<CountedRegion, 0> Regions) {196 BranchSubViews.emplace_back(Line, std::move(Regions));197}198 199void SourceCoverageView::addMCDCRecord(unsigned Line,200 SmallVector<MCDCRecord, 0> Records) {201 MCDCSubViews.emplace_back(Line, std::move(Records));202}203 204void SourceCoverageView::addInstantiation(205 StringRef FunctionName, unsigned Line,206 std::unique_ptr<SourceCoverageView> View) {207 InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));208}209 210void SourceCoverageView::print(raw_ostream &OS, bool WholeFile,211 bool ShowSourceName, bool ShowTitle,212 unsigned ViewDepth) {213 if (ShowTitle)214 renderTitle(OS, "Coverage Report");215 216 renderViewHeader(OS);217 218 if (ShowSourceName)219 renderSourceName(OS, WholeFile);220 221 renderTableHeader(OS, ViewDepth);222 223 // We need the expansions, instantiations, and branches sorted so we can go224 // through them while we iterate lines.225 llvm::stable_sort(ExpansionSubViews);226 llvm::stable_sort(InstantiationSubViews);227 llvm::stable_sort(BranchSubViews);228 llvm::stable_sort(MCDCSubViews);229 auto NextESV = ExpansionSubViews.begin();230 auto EndESV = ExpansionSubViews.end();231 auto NextISV = InstantiationSubViews.begin();232 auto EndISV = InstantiationSubViews.end();233 auto NextBRV = BranchSubViews.begin();234 auto EndBRV = BranchSubViews.end();235 auto NextMSV = MCDCSubViews.begin();236 auto EndMSV = MCDCSubViews.end();237 238 // Get the coverage information for the file.239 auto StartSegment = CoverageInfo.begin();240 auto EndSegment = CoverageInfo.end();241 LineCoverageIterator LCI{CoverageInfo, 1};242 LineCoverageIterator LCIEnd = LCI.getEnd();243 244 unsigned FirstLine = StartSegment != EndSegment ? StartSegment->Line : 0;245 for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof();246 ++LI, ++LCI) {247 // If we aren't rendering the whole file, we need to filter out the prologue248 // and epilogue.249 if (!WholeFile) {250 if (LCI == LCIEnd)251 break;252 else if (LI.line_number() < FirstLine)253 continue;254 }255 256 renderLinePrefix(OS, ViewDepth);257 if (getOptions().ShowLineNumbers)258 renderLineNumberColumn(OS, LI.line_number());259 260 if (getOptions().ShowLineStats)261 renderLineCoverageColumn(OS, *LCI);262 263 // If there are expansion subviews, we want to highlight the first one.264 unsigned ExpansionColumn = 0;265 if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&266 getOptions().Colors)267 ExpansionColumn = NextESV->getStartCol();268 269 // Display the source code for the current line.270 renderLine(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn, ViewDepth);271 272 // Show the region markers.273 if (shouldRenderRegionMarkers(*LCI))274 renderRegionMarkers(OS, *LCI, ViewDepth);275 276 // Show the expansions, instantiations, and branches for this line.277 bool RenderedSubView = false;278 for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();279 ++NextESV) {280 renderViewDivider(OS, ViewDepth + 1);281 282 // Re-render the current line and highlight the expansion range for283 // this subview.284 if (RenderedSubView) {285 ExpansionColumn = NextESV->getStartCol();286 renderExpansionSite(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn,287 ViewDepth);288 renderViewDivider(OS, ViewDepth + 1);289 }290 291 renderExpansionView(OS, *NextESV, ViewDepth + 1);292 RenderedSubView = true;293 }294 for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {295 renderViewDivider(OS, ViewDepth + 1);296 renderInstantiationView(OS, *NextISV, ViewDepth + 1);297 RenderedSubView = true;298 }299 for (; NextBRV != EndBRV && NextBRV->Line == LI.line_number(); ++NextBRV) {300 renderViewDivider(OS, ViewDepth + 1);301 renderBranchView(OS, *NextBRV, ViewDepth + 1);302 RenderedSubView = true;303 }304 for (; NextMSV != EndMSV && NextMSV->Line == LI.line_number(); ++NextMSV) {305 renderViewDivider(OS, ViewDepth + 1);306 renderMCDCView(OS, *NextMSV, ViewDepth + 1);307 RenderedSubView = true;308 }309 if (RenderedSubView)310 renderViewDivider(OS, ViewDepth + 1);311 renderLineSuffix(OS, ViewDepth);312 }313 314 renderViewFooter(OS);315}316