Home | History | Annotate | Download | only in llvm-cov
      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 // This class implements rendering for code coverage of source code.
     11 //
     12 //===----------------------------------------------------------------------===//
     13 
     14 #include "SourceCoverageView.h"
     15 #include "llvm/ADT/Optional.h"
     16 #include "llvm/ADT/SmallString.h"
     17 #include "llvm/Support/LineIterator.h"
     18 
     19 using namespace llvm;
     20 
     21 void SourceCoverageView::renderLine(
     22     raw_ostream &OS, StringRef Line, int64_t LineNumber,
     23     const coverage::CoverageSegment *WrappedSegment,
     24     ArrayRef<const coverage::CoverageSegment *> Segments,
     25     unsigned ExpansionCol) {
     26   Optional<raw_ostream::Colors> Highlight;
     27   SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges;
     28 
     29   // The first segment overlaps from a previous line, so we treat it specially.
     30   if (WrappedSegment && WrappedSegment->HasCount && WrappedSegment->Count == 0)
     31     Highlight = raw_ostream::RED;
     32 
     33   // Output each segment of the line, possibly highlighted.
     34   unsigned Col = 1;
     35   for (const auto *S : Segments) {
     36     unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
     37     colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
     38                     Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
     39         << Line.substr(Col - 1, End - Col);
     40     if (Options.Debug && Highlight)
     41       HighlightedRanges.push_back(std::make_pair(Col, End));
     42     Col = End;
     43     if (Col == ExpansionCol)
     44       Highlight = raw_ostream::CYAN;
     45     else if (S->HasCount && S->Count == 0)
     46       Highlight = raw_ostream::RED;
     47     else
     48       Highlight = None;
     49   }
     50 
     51   // Show the rest of the line
     52   colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
     53                   Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
     54       << Line.substr(Col - 1, Line.size() - Col + 1);
     55   OS << "\n";
     56 
     57   if (Options.Debug) {
     58     for (const auto &Range : HighlightedRanges)
     59       errs() << "Highlighted line " << LineNumber << ", " << Range.first
     60              << " -> " << Range.second << "\n";
     61     if (Highlight)
     62       errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
     63   }
     64 }
     65 
     66 void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
     67   for (unsigned I = 0; I < Level; ++I)
     68     OS << "  |";
     69 }
     70 
     71 void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
     72                                            raw_ostream &OS) {
     73   assert(Level != 0 && "Cannot render divider at top level");
     74   renderIndent(OS, Level - 1);
     75   OS.indent(2);
     76   for (unsigned I = 0; I < Length; ++I)
     77     OS << "-";
     78 }
     79 
     80 void
     81 SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
     82                                              const LineCoverageInfo &Line) {
     83   if (!Line.isMapped()) {
     84     OS.indent(LineCoverageColumnWidth) << '|';
     85     return;
     86   }
     87   SmallString<32> Buffer;
     88   raw_svector_ostream BufferOS(Buffer);
     89   BufferOS << Line.ExecutionCount;
     90   auto Str = BufferOS.str();
     91   // Trim
     92   Str = Str.substr(0, std::min(Str.size(), (size_t)LineCoverageColumnWidth));
     93   // Align to the right
     94   OS.indent(LineCoverageColumnWidth - Str.size());
     95   colored_ostream(OS, raw_ostream::MAGENTA,
     96                   Line.hasMultipleRegions() && Options.Colors)
     97       << Str;
     98   OS << '|';
     99 }
    100 
    101 void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
    102                                                 unsigned LineNo) {
    103   SmallString<32> Buffer;
    104   raw_svector_ostream BufferOS(Buffer);
    105   BufferOS << LineNo;
    106   auto Str = BufferOS.str();
    107   // Trim and align to the right
    108   Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
    109   OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
    110 }
    111 
    112 void SourceCoverageView::renderRegionMarkers(
    113     raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments) {
    114   SmallString<32> Buffer;
    115   raw_svector_ostream BufferOS(Buffer);
    116 
    117   unsigned PrevColumn = 1;
    118   for (const auto *S : Segments) {
    119     if (!S->IsRegionEntry)
    120       continue;
    121     // Skip to the new region
    122     if (S->Col > PrevColumn)
    123       OS.indent(S->Col - PrevColumn);
    124     PrevColumn = S->Col + 1;
    125     BufferOS << S->Count;
    126     StringRef Str = BufferOS.str();
    127     // Trim the execution count
    128     Str = Str.substr(0, std::min(Str.size(), (size_t)7));
    129     PrevColumn += Str.size();
    130     OS << '^' << Str;
    131     Buffer.clear();
    132   }
    133   OS << "\n";
    134 
    135   if (Options.Debug)
    136     for (const auto *S : Segments)
    137       errs() << "Marker at " << S->Line << ":" << S->Col << " = " << S->Count
    138              << (S->IsRegionEntry ? "\n" : " (pop)\n");
    139 }
    140 
    141 void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
    142                                 unsigned IndentLevel) {
    143   // The width of the leading columns
    144   unsigned CombinedColumnWidth =
    145       (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
    146       (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
    147   // The width of the line that is used to divide between the view and the
    148   // subviews.
    149   unsigned DividerWidth = CombinedColumnWidth + 4;
    150 
    151   // We need the expansions and instantiations sorted so we can go through them
    152   // while we iterate lines.
    153   std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
    154   std::sort(InstantiationSubViews.begin(), InstantiationSubViews.end());
    155   auto NextESV = ExpansionSubViews.begin();
    156   auto EndESV = ExpansionSubViews.end();
    157   auto NextISV = InstantiationSubViews.begin();
    158   auto EndISV = InstantiationSubViews.end();
    159 
    160   // Get the coverage information for the file.
    161   auto NextSegment = CoverageInfo.begin();
    162   auto EndSegment = CoverageInfo.end();
    163 
    164   unsigned FirstLine = NextSegment != EndSegment ? NextSegment->Line : 0;
    165   const coverage::CoverageSegment *WrappedSegment = nullptr;
    166   SmallVector<const coverage::CoverageSegment *, 8> LineSegments;
    167   for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof(); ++LI) {
    168     // If we aren't rendering the whole file, we need to filter out the prologue
    169     // and epilogue.
    170     if (!WholeFile) {
    171       if (NextSegment == EndSegment)
    172         break;
    173       else if (LI.line_number() < FirstLine)
    174         continue;
    175     }
    176 
    177     // Collect the coverage information relevant to this line.
    178     if (LineSegments.size())
    179       WrappedSegment = LineSegments.back();
    180     LineSegments.clear();
    181     while (NextSegment != EndSegment && NextSegment->Line == LI.line_number())
    182       LineSegments.push_back(&*NextSegment++);
    183 
    184     // Calculate a count to be for the line as a whole.
    185     LineCoverageInfo LineCount;
    186     if (WrappedSegment && WrappedSegment->HasCount)
    187       LineCount.addRegionCount(WrappedSegment->Count);
    188     for (const auto *S : LineSegments)
    189       if (S->HasCount && S->IsRegionEntry)
    190           LineCount.addRegionStartCount(S->Count);
    191 
    192     // Render the line prefix.
    193     renderIndent(OS, IndentLevel);
    194     if (Options.ShowLineStats)
    195       renderLineCoverageColumn(OS, LineCount);
    196     if (Options.ShowLineNumbers)
    197       renderLineNumberColumn(OS, LI.line_number());
    198 
    199     // If there are expansion subviews, we want to highlight the first one.
    200     unsigned ExpansionColumn = 0;
    201     if (NextESV != EndESV && NextESV->getLine() == LI.line_number() &&
    202         Options.Colors)
    203       ExpansionColumn = NextESV->getStartCol();
    204 
    205     // Display the source code for the current line.
    206     renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
    207                ExpansionColumn);
    208 
    209     // Show the region markers.
    210     if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
    211                                       LineCount.hasMultipleRegions()) &&
    212         !LineSegments.empty()) {
    213       renderIndent(OS, IndentLevel);
    214       OS.indent(CombinedColumnWidth);
    215       renderRegionMarkers(OS, LineSegments);
    216     }
    217 
    218     // Show the expansions and instantiations for this line.
    219     unsigned NestedIndent = IndentLevel + 1;
    220     bool RenderedSubView = false;
    221     for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
    222          ++NextESV) {
    223       renderViewDivider(NestedIndent, DividerWidth, OS);
    224       OS << "\n";
    225       if (RenderedSubView) {
    226         // Re-render the current line and highlight the expansion range for
    227         // this subview.
    228         ExpansionColumn = NextESV->getStartCol();
    229         renderIndent(OS, IndentLevel);
    230         OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
    231         renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
    232                    ExpansionColumn);
    233         renderViewDivider(NestedIndent, DividerWidth, OS);
    234         OS << "\n";
    235       }
    236       // Render the child subview
    237       if (Options.Debug)
    238         errs() << "Expansion at line " << NextESV->getLine() << ", "
    239                << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
    240                << "\n";
    241       NextESV->View->render(OS, false, NestedIndent);
    242       RenderedSubView = true;
    243     }
    244     for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
    245       renderViewDivider(NestedIndent, DividerWidth, OS);
    246       OS << "\n";
    247       renderIndent(OS, NestedIndent);
    248       OS << ' ';
    249       Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
    250                                                      << ":";
    251       OS << "\n";
    252       NextISV->View->render(OS, false, NestedIndent);
    253       RenderedSubView = true;
    254     }
    255     if (RenderedSubView) {
    256       renderViewDivider(NestedIndent, DividerWidth, OS);
    257       OS << "\n";
    258     }
    259   }
    260 }
    261