1//===- SourceCoverageView.cpp - Code coverage view for source code --------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9///
10/// \file This class implements rendering for code coverage of source code.
11///
12//===----------------------------------------------------------------------===//
13
14#include "SourceCoverageView.h"
15#include "SourceCoverageViewHTML.h"
16#include "SourceCoverageViewText.h"
17#include "llvm/ADT/SmallString.h"
18#include "llvm/ADT/StringExtras.h"
19#include "llvm/Support/FileSystem.h"
20#include "llvm/Support/LineIterator.h"
21#include "llvm/Support/Path.h"
22
23using namespace llvm;
24
25void CoveragePrinter::StreamDestructor::operator()(raw_ostream *OS) const {
26  if (OS == &outs())
27    return;
28  delete OS;
29}
30
31std::string CoveragePrinter::getOutputPath(StringRef Path, StringRef Extension,
32                                           bool InToplevel, bool Relative) {
33  assert(Extension.size() && "The file extension may not be empty");
34
35  SmallString<256> FullPath;
36
37  if (!Relative)
38    FullPath.append(Opts.ShowOutputDirectory);
39
40  if (!InToplevel)
41    sys::path::append(FullPath, getCoverageDir());
42
43  SmallString<256> ParentPath = sys::path::parent_path(Path);
44  sys::path::remove_dots(ParentPath, /*remove_dot_dots=*/true);
45  sys::path::append(FullPath, sys::path::relative_path(ParentPath));
46
47  auto PathFilename = (sys::path::filename(Path) + "." + Extension).str();
48  sys::path::append(FullPath, PathFilename);
49
50  return FullPath.str();
51}
52
53Expected<CoveragePrinter::OwnedStream>
54CoveragePrinter::createOutputStream(StringRef Path, StringRef Extension,
55                                    bool InToplevel) {
56  if (!Opts.hasOutputDirectory())
57    return OwnedStream(&outs());
58
59  std::string FullPath = getOutputPath(Path, Extension, InToplevel, false);
60
61  auto ParentDir = sys::path::parent_path(FullPath);
62  if (auto E = sys::fs::create_directories(ParentDir))
63    return errorCodeToError(E);
64
65  std::error_code E;
66  raw_ostream *RawStream = new raw_fd_ostream(FullPath, E, sys::fs::F_RW);
67  auto OS = CoveragePrinter::OwnedStream(RawStream);
68  if (E)
69    return errorCodeToError(E);
70  return std::move(OS);
71}
72
73std::unique_ptr<CoveragePrinter>
74CoveragePrinter::create(const CoverageViewOptions &Opts) {
75  switch (Opts.Format) {
76  case CoverageViewOptions::OutputFormat::Text:
77    return llvm::make_unique<CoveragePrinterText>(Opts);
78  case CoverageViewOptions::OutputFormat::HTML:
79    return llvm::make_unique<CoveragePrinterHTML>(Opts);
80  }
81  llvm_unreachable("Unknown coverage output format!");
82}
83
84std::string SourceCoverageView::formatCount(uint64_t N) {
85  std::string Number = utostr(N);
86  int Len = Number.size();
87  if (Len <= 3)
88    return Number;
89  int IntLen = Len % 3 == 0 ? 3 : Len % 3;
90  std::string Result(Number.data(), IntLen);
91  if (IntLen != 3) {
92    Result.push_back('.');
93    Result += Number.substr(IntLen, 3 - IntLen);
94  }
95  Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
96  return Result;
97}
98
99bool SourceCoverageView::shouldRenderRegionMarkers(
100    bool LineHasMultipleRegions) const {
101  return getOptions().ShowRegionMarkers &&
102         (!getOptions().ShowLineStatsOrRegionMarkers || LineHasMultipleRegions);
103}
104
105bool SourceCoverageView::hasSubViews() const {
106  return !ExpansionSubViews.empty() || !InstantiationSubViews.empty();
107}
108
109std::unique_ptr<SourceCoverageView>
110SourceCoverageView::create(StringRef SourceName, const MemoryBuffer &File,
111                           const CoverageViewOptions &Options,
112                           coverage::CoverageData &&CoverageInfo) {
113  switch (Options.Format) {
114  case CoverageViewOptions::OutputFormat::Text:
115    return llvm::make_unique<SourceCoverageViewText>(SourceName, File, Options,
116                                                     std::move(CoverageInfo));
117  case CoverageViewOptions::OutputFormat::HTML:
118    return llvm::make_unique<SourceCoverageViewHTML>(SourceName, File, Options,
119                                                     std::move(CoverageInfo));
120  }
121  llvm_unreachable("Unknown coverage output format!");
122}
123
124void SourceCoverageView::addExpansion(
125    const coverage::CounterMappingRegion &Region,
126    std::unique_ptr<SourceCoverageView> View) {
127  ExpansionSubViews.emplace_back(Region, std::move(View));
128}
129
130void SourceCoverageView::addInstantiation(
131    StringRef FunctionName, unsigned Line,
132    std::unique_ptr<SourceCoverageView> View) {
133  InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
134}
135
136void SourceCoverageView::print(raw_ostream &OS, bool WholeFile,
137                               bool ShowSourceName, unsigned ViewDepth) {
138  if (ShowSourceName)
139    renderSourceName(OS);
140
141  renderViewHeader(OS);
142
143  // We need the expansions and instantiations sorted so we can go through them
144  // while we iterate lines.
145  std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
146  std::sort(InstantiationSubViews.begin(), InstantiationSubViews.end());
147  auto NextESV = ExpansionSubViews.begin();
148  auto EndESV = ExpansionSubViews.end();
149  auto NextISV = InstantiationSubViews.begin();
150  auto EndISV = InstantiationSubViews.end();
151
152  // Get the coverage information for the file.
153  auto NextSegment = CoverageInfo.begin();
154  auto EndSegment = CoverageInfo.end();
155
156  unsigned FirstLine = NextSegment != EndSegment ? NextSegment->Line : 0;
157  const coverage::CoverageSegment *WrappedSegment = nullptr;
158  SmallVector<const coverage::CoverageSegment *, 8> LineSegments;
159  for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof(); ++LI) {
160    // If we aren't rendering the whole file, we need to filter out the prologue
161    // and epilogue.
162    if (!WholeFile) {
163      if (NextSegment == EndSegment)
164        break;
165      else if (LI.line_number() < FirstLine)
166        continue;
167    }
168
169    // Collect the coverage information relevant to this line.
170    if (LineSegments.size())
171      WrappedSegment = LineSegments.back();
172    LineSegments.clear();
173    while (NextSegment != EndSegment && NextSegment->Line == LI.line_number())
174      LineSegments.push_back(&*NextSegment++);
175
176    // Calculate a count to be for the line as a whole.
177    LineCoverageStats LineCount;
178    if (WrappedSegment && WrappedSegment->HasCount)
179      LineCount.addRegionCount(WrappedSegment->Count);
180    for (const auto *S : LineSegments)
181      if (S->HasCount && S->IsRegionEntry)
182        LineCount.addRegionStartCount(S->Count);
183
184    renderLinePrefix(OS, ViewDepth);
185    if (getOptions().ShowLineStats)
186      renderLineCoverageColumn(OS, LineCount);
187    if (getOptions().ShowLineNumbers)
188      renderLineNumberColumn(OS, LI.line_number());
189
190    // If there are expansion subviews, we want to highlight the first one.
191    unsigned ExpansionColumn = 0;
192    if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
193        getOptions().Colors)
194      ExpansionColumn = NextESV->getStartCol();
195
196    // Display the source code for the current line.
197    renderLine(OS, {*LI, LI.line_number()}, WrappedSegment, LineSegments,
198               ExpansionColumn, ViewDepth);
199
200    // Show the region markers.
201    if (shouldRenderRegionMarkers(LineCount.hasMultipleRegions()))
202      renderRegionMarkers(OS, LineSegments, ViewDepth);
203
204    // Show the expansions and instantiations for this line.
205    bool RenderedSubView = false;
206    for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
207         ++NextESV) {
208      renderViewDivider(OS, ViewDepth + 1);
209
210      // Re-render the current line and highlight the expansion range for
211      // this subview.
212      if (RenderedSubView) {
213        ExpansionColumn = NextESV->getStartCol();
214        renderExpansionSite(OS, {*LI, LI.line_number()}, WrappedSegment,
215                            LineSegments, ExpansionColumn, ViewDepth);
216        renderViewDivider(OS, ViewDepth + 1);
217      }
218
219      renderExpansionView(OS, *NextESV, ViewDepth + 1);
220      RenderedSubView = true;
221    }
222    for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
223      renderViewDivider(OS, ViewDepth + 1);
224      renderInstantiationView(OS, *NextISV, ViewDepth + 1);
225      RenderedSubView = true;
226    }
227    if (RenderedSubView)
228      renderViewDivider(OS, ViewDepth + 1);
229    renderLineSuffix(OS, ViewDepth);
230  }
231
232  renderViewFooter(OS);
233}
234