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