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(raw_ostream &OS, StringRef Line, 22 int64_t LineNumber, 23 const CoverageSegment *WrappedSegment, 24 ArrayRef<const 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 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 CoverageSegments = RegionManager->getCoverageSegments(); 162 assert(CoverageSegments.size() && "View with no coverage?"); 163 auto NextSegment = CoverageSegments.begin(); 164 auto EndSegment = CoverageSegments.end(); 165 166 const CoverageSegment *WrappedSegment = nullptr; 167 SmallVector<const CoverageSegment *, 8> LineSegments; 168 for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof(); ++LI) { 169 // If we aren't rendering the whole file, we need to filter out the prologue 170 // and epilogue. 171 if (!WholeFile) { 172 if (NextSegment == EndSegment) 173 break; 174 else if (LI.line_number() < NextSegment->Line) 175 continue; 176 } 177 178 // Collect the coverage information relevant to this line. 179 if (LineSegments.size()) 180 WrappedSegment = LineSegments.back(); 181 LineSegments.clear(); 182 while (NextSegment != EndSegment && NextSegment->Line == LI.line_number()) 183 LineSegments.push_back(&*NextSegment++); 184 185 // Calculate a count to be for the line as a whole. 186 LineCoverageInfo LineCount; 187 if (WrappedSegment && WrappedSegment->HasCount) 188 LineCount.addRegionCount(WrappedSegment->Count); 189 for (const auto *S : LineSegments) 190 if (S->HasCount && S->IsRegionEntry) 191 LineCount.addRegionStartCount(S->Count); 192 193 // Render the line prefix. 194 renderIndent(OS, IndentLevel); 195 if (Options.ShowLineStats) 196 renderLineCoverageColumn(OS, LineCount); 197 if (Options.ShowLineNumbers) 198 renderLineNumberColumn(OS, LI.line_number()); 199 200 // If there are expansion subviews, we want to highlight the first one. 201 unsigned ExpansionColumn = 0; 202 if (NextESV != EndESV && NextESV->getLine() == LI.line_number() && 203 Options.Colors) 204 ExpansionColumn = NextESV->getStartCol(); 205 206 // Display the source code for the current line. 207 renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments, 208 ExpansionColumn); 209 210 // Show the region markers. 211 if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers || 212 LineCount.hasMultipleRegions()) && 213 !LineSegments.empty()) { 214 renderIndent(OS, IndentLevel); 215 OS.indent(CombinedColumnWidth); 216 renderRegionMarkers(OS, LineSegments); 217 } 218 219 // Show the expansions and instantiations for this line. 220 unsigned NestedIndent = IndentLevel + 1; 221 bool RenderedSubView = false; 222 for (; NextESV != EndESV && NextESV->getLine() == LI.line_number(); 223 ++NextESV) { 224 renderViewDivider(NestedIndent, DividerWidth, OS); 225 OS << "\n"; 226 if (RenderedSubView) { 227 // Re-render the current line and highlight the expansion range for 228 // this subview. 229 ExpansionColumn = NextESV->getStartCol(); 230 renderIndent(OS, IndentLevel); 231 OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1)); 232 renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments, 233 ExpansionColumn); 234 renderViewDivider(NestedIndent, DividerWidth, OS); 235 OS << "\n"; 236 } 237 // Render the child subview 238 NextESV->View->render(OS, false, NestedIndent); 239 RenderedSubView = true; 240 } 241 for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) { 242 renderViewDivider(NestedIndent, DividerWidth, OS); 243 OS << "\n"; 244 renderIndent(OS, NestedIndent); 245 OS << ' '; 246 Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName 247 << ":"; 248 OS << "\n"; 249 NextISV->View->render(OS, false, NestedIndent); 250 RenderedSubView = true; 251 } 252 if (RenderedSubView) { 253 renderViewDivider(NestedIndent, DividerWidth, OS); 254 OS << "\n"; 255 } 256 } 257 } 258