//===- SourceCoverageView.cpp - Code coverage view for source code --------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// /// /// \file This class implements rendering for code coverage of source code. /// //===----------------------------------------------------------------------===// #include "SourceCoverageView.h" #include "SourceCoverageViewHTML.h" #include "SourceCoverageViewText.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/Path.h" using namespace llvm; void CoveragePrinter::StreamDestructor::operator()(raw_ostream *OS) const { if (OS == &outs()) return; delete OS; } std::string CoveragePrinter::getOutputPath(StringRef Path, StringRef Extension, bool InToplevel, bool Relative) const { assert(!Extension.empty() && "The file extension may not be empty"); SmallString<256> FullPath; if (!Relative) FullPath.append(Opts.ShowOutputDirectory); if (!InToplevel) sys::path::append(FullPath, getCoverageDir()); SmallString<256> ParentPath = sys::path::parent_path(Path); sys::path::remove_dots(ParentPath, /*remove_dot_dot=*/true); sys::path::append(FullPath, sys::path::relative_path(ParentPath)); auto PathFilename = (sys::path::filename(Path) + "." + Extension).str(); sys::path::append(FullPath, PathFilename); sys::path::native(FullPath); return std::string(FullPath); } Expected CoveragePrinter::createOutputStream(StringRef Path, StringRef Extension, bool InToplevel) const { if (!Opts.hasOutputDirectory()) return OwnedStream(&outs()); std::string FullPath = getOutputPath(Path, Extension, InToplevel, false); auto ParentDir = sys::path::parent_path(FullPath); if (auto E = sys::fs::create_directories(ParentDir)) return errorCodeToError(E); std::error_code E; raw_ostream *RawStream = new raw_fd_ostream(FullPath, E, sys::fs::FA_Read | sys::fs::FA_Write); auto OS = CoveragePrinter::OwnedStream(RawStream); if (E) return errorCodeToError(E); return std::move(OS); } std::unique_ptr CoveragePrinter::create(const CoverageViewOptions &Opts) { switch (Opts.Format) { case CoverageViewOptions::OutputFormat::Text: if (Opts.ShowDirectoryCoverage) return std::make_unique(Opts); return std::make_unique(Opts); case CoverageViewOptions::OutputFormat::HTML: if (Opts.ShowDirectoryCoverage) return std::make_unique(Opts); return std::make_unique(Opts); case CoverageViewOptions::OutputFormat::Lcov: // Unreachable because CodeCoverage.cpp should terminate with an error // before we get here. llvm_unreachable("Lcov format is not supported!"); } llvm_unreachable("Unknown coverage output format!"); } unsigned SourceCoverageView::getFirstUncoveredLineNo() { const auto MinSegIt = find_if(CoverageInfo, [](const CoverageSegment &S) { return S.HasCount && S.Count == 0; }); // There is no uncovered line, return zero. if (MinSegIt == CoverageInfo.end()) return 0; return (*MinSegIt).Line; } std::string SourceCoverageView::formatCount(uint64_t N) { std::string Number = utostr(N); int Len = Number.size(); if (Len <= 3) return Number; int IntLen = Len % 3 == 0 ? 3 : Len % 3; std::string Result(Number.data(), IntLen); if (IntLen != 3) { Result.push_back('.'); Result += Number.substr(IntLen, 3 - IntLen); } Result.push_back(" kMGTPEZY"[(Len - 1) / 3]); return Result; } bool SourceCoverageView::shouldRenderRegionMarkers( const LineCoverageStats &LCS) const { if (!getOptions().ShowRegionMarkers) return false; CoverageSegmentArray Segments = LCS.getLineSegments(); if (Segments.empty()) return false; for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) { const auto *CurSeg = Segments[I]; if (!CurSeg->IsRegionEntry || CurSeg->Count == LCS.getExecutionCount()) continue; if (!CurSeg->HasCount) // don't show tooltips for SkippedRegions continue; return true; } return false; } bool SourceCoverageView::hasSubViews() const { return !ExpansionSubViews.empty() || !InstantiationSubViews.empty() || !BranchSubViews.empty() || !MCDCSubViews.empty(); } std::unique_ptr SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File, const CoverageViewOptions &Options, CoverageData &&CoverageInfo) { switch (Options.Format) { case CoverageViewOptions::OutputFormat::Text: return std::make_unique( SourceName, File, Options, std::move(CoverageInfo)); case CoverageViewOptions::OutputFormat::HTML: return std::make_unique( SourceName, File, Options, std::move(CoverageInfo)); case CoverageViewOptions::OutputFormat::Lcov: // Unreachable because CodeCoverage.cpp should terminate with an error // before we get here. llvm_unreachable("Lcov format is not supported!"); } llvm_unreachable("Unknown coverage output format!"); } std::string SourceCoverageView::getSourceName() const { SmallString<128> SourceText(SourceName); sys::path::remove_dots(SourceText, /*remove_dot_dot=*/true); sys::path::native(SourceText); return std::string(SourceText); } void SourceCoverageView::addExpansion( const CounterMappingRegion &Region, std::unique_ptr View) { ExpansionSubViews.emplace_back(Region, std::move(View)); } void SourceCoverageView::addBranch(unsigned Line, SmallVector Regions) { BranchSubViews.emplace_back(Line, std::move(Regions)); } void SourceCoverageView::addMCDCRecord(unsigned Line, SmallVector Records) { MCDCSubViews.emplace_back(Line, std::move(Records)); } void SourceCoverageView::addInstantiation( StringRef FunctionName, unsigned Line, std::unique_ptr View) { InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View)); } void SourceCoverageView::print(raw_ostream &OS, bool WholeFile, bool ShowSourceName, bool ShowTitle, unsigned ViewDepth) { if (ShowTitle) renderTitle(OS, "Coverage Report"); renderViewHeader(OS); if (ShowSourceName) renderSourceName(OS, WholeFile); renderTableHeader(OS, ViewDepth); // We need the expansions, instantiations, and branches sorted so we can go // through them while we iterate lines. llvm::stable_sort(ExpansionSubViews); llvm::stable_sort(InstantiationSubViews); llvm::stable_sort(BranchSubViews); llvm::stable_sort(MCDCSubViews); auto NextESV = ExpansionSubViews.begin(); auto EndESV = ExpansionSubViews.end(); auto NextISV = InstantiationSubViews.begin(); auto EndISV = InstantiationSubViews.end(); auto NextBRV = BranchSubViews.begin(); auto EndBRV = BranchSubViews.end(); auto NextMSV = MCDCSubViews.begin(); auto EndMSV = MCDCSubViews.end(); // Get the coverage information for the file. auto StartSegment = CoverageInfo.begin(); auto EndSegment = CoverageInfo.end(); LineCoverageIterator LCI{CoverageInfo, 1}; LineCoverageIterator LCIEnd = LCI.getEnd(); unsigned FirstLine = StartSegment != EndSegment ? StartSegment->Line : 0; for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof(); ++LI, ++LCI) { // If we aren't rendering the whole file, we need to filter out the prologue // and epilogue. if (!WholeFile) { if (LCI == LCIEnd) break; else if (LI.line_number() < FirstLine) continue; } renderLinePrefix(OS, ViewDepth); if (getOptions().ShowLineNumbers) renderLineNumberColumn(OS, LI.line_number()); if (getOptions().ShowLineStats) renderLineCoverageColumn(OS, *LCI); // If there are expansion subviews, we want to highlight the first one. unsigned ExpansionColumn = 0; if (NextESV != EndESV && NextESV->getLine() == LI.line_number() && getOptions().Colors) ExpansionColumn = NextESV->getStartCol(); // Display the source code for the current line. renderLine(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn, ViewDepth); // Show the region markers. if (shouldRenderRegionMarkers(*LCI)) renderRegionMarkers(OS, *LCI, ViewDepth); // Show the expansions, instantiations, and branches for this line. bool RenderedSubView = false; for (; NextESV != EndESV && NextESV->getLine() == LI.line_number(); ++NextESV) { renderViewDivider(OS, ViewDepth + 1); // Re-render the current line and highlight the expansion range for // this subview. if (RenderedSubView) { ExpansionColumn = NextESV->getStartCol(); renderExpansionSite(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn, ViewDepth); renderViewDivider(OS, ViewDepth + 1); } renderExpansionView(OS, *NextESV, ViewDepth + 1); RenderedSubView = true; } for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) { renderViewDivider(OS, ViewDepth + 1); renderInstantiationView(OS, *NextISV, ViewDepth + 1); RenderedSubView = true; } for (; NextBRV != EndBRV && NextBRV->Line == LI.line_number(); ++NextBRV) { renderViewDivider(OS, ViewDepth + 1); renderBranchView(OS, *NextBRV, ViewDepth + 1); RenderedSubView = true; } for (; NextMSV != EndMSV && NextMSV->Line == LI.line_number(); ++NextMSV) { renderViewDivider(OS, ViewDepth + 1); renderMCDCView(OS, *NextMSV, ViewDepth + 1); RenderedSubView = true; } if (RenderedSubView) renderViewDivider(OS, ViewDepth + 1); renderLineSuffix(OS, ViewDepth); } renderViewFooter(OS); }