Path: blob/main/contrib/llvm-project/llvm/tools/llvm-cov/SourceCoverageView.cpp
35231 views
//===- SourceCoverageView.cpp - Code coverage view for source code --------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file This class implements rendering for code coverage of source code.9///10//===----------------------------------------------------------------------===//1112#include "SourceCoverageView.h"13#include "SourceCoverageViewHTML.h"14#include "SourceCoverageViewText.h"15#include "llvm/ADT/SmallString.h"16#include "llvm/ADT/StringExtras.h"17#include "llvm/Support/FileSystem.h"18#include "llvm/Support/LineIterator.h"19#include "llvm/Support/Path.h"2021using namespace llvm;2223void CoveragePrinter::StreamDestructor::operator()(raw_ostream *OS) const {24if (OS == &outs())25return;26delete OS;27}2829std::string CoveragePrinter::getOutputPath(StringRef Path, StringRef Extension,30bool InToplevel,31bool Relative) const {32assert(!Extension.empty() && "The file extension may not be empty");3334SmallString<256> FullPath;3536if (!Relative)37FullPath.append(Opts.ShowOutputDirectory);3839if (!InToplevel)40sys::path::append(FullPath, getCoverageDir());4142SmallString<256> ParentPath = sys::path::parent_path(Path);43sys::path::remove_dots(ParentPath, /*remove_dot_dot=*/true);44sys::path::append(FullPath, sys::path::relative_path(ParentPath));4546auto PathFilename = (sys::path::filename(Path) + "." + Extension).str();47sys::path::append(FullPath, PathFilename);48sys::path::native(FullPath);4950return std::string(FullPath);51}5253Expected<CoveragePrinter::OwnedStream>54CoveragePrinter::createOutputStream(StringRef Path, StringRef Extension,55bool InToplevel) const {56if (!Opts.hasOutputDirectory())57return OwnedStream(&outs());5859std::string FullPath = getOutputPath(Path, Extension, InToplevel, false);6061auto ParentDir = sys::path::parent_path(FullPath);62if (auto E = sys::fs::create_directories(ParentDir))63return errorCodeToError(E);6465std::error_code E;66raw_ostream *RawStream =67new raw_fd_ostream(FullPath, E, sys::fs::FA_Read | sys::fs::FA_Write);68auto OS = CoveragePrinter::OwnedStream(RawStream);69if (E)70return errorCodeToError(E);71return std::move(OS);72}7374std::unique_ptr<CoveragePrinter>75CoveragePrinter::create(const CoverageViewOptions &Opts) {76switch (Opts.Format) {77case CoverageViewOptions::OutputFormat::Text:78if (Opts.ShowDirectoryCoverage)79return std::make_unique<CoveragePrinterTextDirectory>(Opts);80return std::make_unique<CoveragePrinterText>(Opts);81case CoverageViewOptions::OutputFormat::HTML:82if (Opts.ShowDirectoryCoverage)83return std::make_unique<CoveragePrinterHTMLDirectory>(Opts);84return std::make_unique<CoveragePrinterHTML>(Opts);85case CoverageViewOptions::OutputFormat::Lcov:86// Unreachable because CodeCoverage.cpp should terminate with an error87// before we get here.88llvm_unreachable("Lcov format is not supported!");89}90llvm_unreachable("Unknown coverage output format!");91}9293unsigned SourceCoverageView::getFirstUncoveredLineNo() {94const auto MinSegIt = find_if(CoverageInfo, [](const CoverageSegment &S) {95return S.HasCount && S.Count == 0;96});9798// There is no uncovered line, return zero.99if (MinSegIt == CoverageInfo.end())100return 0;101102return (*MinSegIt).Line;103}104105std::string SourceCoverageView::formatCount(uint64_t N) {106std::string Number = utostr(N);107int Len = Number.size();108if (Len <= 3)109return Number;110int IntLen = Len % 3 == 0 ? 3 : Len % 3;111std::string Result(Number.data(), IntLen);112if (IntLen != 3) {113Result.push_back('.');114Result += Number.substr(IntLen, 3 - IntLen);115}116Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);117return Result;118}119120bool SourceCoverageView::shouldRenderRegionMarkers(121const LineCoverageStats &LCS) const {122if (!getOptions().ShowRegionMarkers)123return false;124125CoverageSegmentArray Segments = LCS.getLineSegments();126if (Segments.empty())127return false;128for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) {129const auto *CurSeg = Segments[I];130if (!CurSeg->IsRegionEntry || CurSeg->Count == LCS.getExecutionCount())131continue;132if (!CurSeg->HasCount) // don't show tooltips for SkippedRegions133continue;134return true;135}136return false;137}138139bool SourceCoverageView::hasSubViews() const {140return !ExpansionSubViews.empty() || !InstantiationSubViews.empty() ||141!BranchSubViews.empty() || !MCDCSubViews.empty();142}143144std::unique_ptr<SourceCoverageView>145SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File,146const CoverageViewOptions &Options,147CoverageData &&CoverageInfo) {148switch (Options.Format) {149case CoverageViewOptions::OutputFormat::Text:150return std::make_unique<SourceCoverageViewText>(151SourceName, File, Options, std::move(CoverageInfo));152case CoverageViewOptions::OutputFormat::HTML:153return std::make_unique<SourceCoverageViewHTML>(154SourceName, File, Options, std::move(CoverageInfo));155case CoverageViewOptions::OutputFormat::Lcov:156// Unreachable because CodeCoverage.cpp should terminate with an error157// before we get here.158llvm_unreachable("Lcov format is not supported!");159}160llvm_unreachable("Unknown coverage output format!");161}162163std::string SourceCoverageView::getSourceName() const {164SmallString<128> SourceText(SourceName);165sys::path::remove_dots(SourceText, /*remove_dot_dot=*/true);166sys::path::native(SourceText);167return std::string(SourceText);168}169170void SourceCoverageView::addExpansion(171const CounterMappingRegion &Region,172std::unique_ptr<SourceCoverageView> View) {173ExpansionSubViews.emplace_back(Region, std::move(View));174}175176void SourceCoverageView::addBranch(unsigned Line,177SmallVector<CountedRegion, 0> Regions) {178BranchSubViews.emplace_back(Line, std::move(Regions));179}180181void SourceCoverageView::addMCDCRecord(unsigned Line,182SmallVector<MCDCRecord, 0> Records) {183MCDCSubViews.emplace_back(Line, std::move(Records));184}185186void SourceCoverageView::addInstantiation(187StringRef FunctionName, unsigned Line,188std::unique_ptr<SourceCoverageView> View) {189InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));190}191192void SourceCoverageView::print(raw_ostream &OS, bool WholeFile,193bool ShowSourceName, bool ShowTitle,194unsigned ViewDepth) {195if (ShowTitle)196renderTitle(OS, "Coverage Report");197198renderViewHeader(OS);199200if (ShowSourceName)201renderSourceName(OS, WholeFile);202203renderTableHeader(OS, ViewDepth);204205// We need the expansions, instantiations, and branches sorted so we can go206// through them while we iterate lines.207llvm::stable_sort(ExpansionSubViews);208llvm::stable_sort(InstantiationSubViews);209llvm::stable_sort(BranchSubViews);210llvm::stable_sort(MCDCSubViews);211auto NextESV = ExpansionSubViews.begin();212auto EndESV = ExpansionSubViews.end();213auto NextISV = InstantiationSubViews.begin();214auto EndISV = InstantiationSubViews.end();215auto NextBRV = BranchSubViews.begin();216auto EndBRV = BranchSubViews.end();217auto NextMSV = MCDCSubViews.begin();218auto EndMSV = MCDCSubViews.end();219220// Get the coverage information for the file.221auto StartSegment = CoverageInfo.begin();222auto EndSegment = CoverageInfo.end();223LineCoverageIterator LCI{CoverageInfo, 1};224LineCoverageIterator LCIEnd = LCI.getEnd();225226unsigned FirstLine = StartSegment != EndSegment ? StartSegment->Line : 0;227for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof();228++LI, ++LCI) {229// If we aren't rendering the whole file, we need to filter out the prologue230// and epilogue.231if (!WholeFile) {232if (LCI == LCIEnd)233break;234else if (LI.line_number() < FirstLine)235continue;236}237238renderLinePrefix(OS, ViewDepth);239if (getOptions().ShowLineNumbers)240renderLineNumberColumn(OS, LI.line_number());241242if (getOptions().ShowLineStats)243renderLineCoverageColumn(OS, *LCI);244245// If there are expansion subviews, we want to highlight the first one.246unsigned ExpansionColumn = 0;247if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&248getOptions().Colors)249ExpansionColumn = NextESV->getStartCol();250251// Display the source code for the current line.252renderLine(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn, ViewDepth);253254// Show the region markers.255if (shouldRenderRegionMarkers(*LCI))256renderRegionMarkers(OS, *LCI, ViewDepth);257258// Show the expansions, instantiations, and branches for this line.259bool RenderedSubView = false;260for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();261++NextESV) {262renderViewDivider(OS, ViewDepth + 1);263264// Re-render the current line and highlight the expansion range for265// this subview.266if (RenderedSubView) {267ExpansionColumn = NextESV->getStartCol();268renderExpansionSite(OS, {*LI, LI.line_number()}, *LCI, ExpansionColumn,269ViewDepth);270renderViewDivider(OS, ViewDepth + 1);271}272273renderExpansionView(OS, *NextESV, ViewDepth + 1);274RenderedSubView = true;275}276for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {277renderViewDivider(OS, ViewDepth + 1);278renderInstantiationView(OS, *NextISV, ViewDepth + 1);279RenderedSubView = true;280}281for (; NextBRV != EndBRV && NextBRV->Line == LI.line_number(); ++NextBRV) {282renderViewDivider(OS, ViewDepth + 1);283renderBranchView(OS, *NextBRV, ViewDepth + 1);284RenderedSubView = true;285}286for (; NextMSV != EndMSV && NextMSV->Line == LI.line_number(); ++NextMSV) {287renderViewDivider(OS, ViewDepth + 1);288renderMCDCView(OS, *NextMSV, ViewDepth + 1);289RenderedSubView = true;290}291if (RenderedSubView)292renderViewDivider(OS, ViewDepth + 1);293renderLineSuffix(OS, ViewDepth);294}295296renderViewFooter(OS);297}298299300