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 19using namespace llvm; 20 21void 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 66void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) { 67 for (unsigned I = 0; I < Level; ++I) 68 OS << " |"; 69} 70 71void 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 80void 81SourceCoverageView::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 101void 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 112void 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 141void 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