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/SmallString.h" 16 #include "llvm/Support/LineIterator.h" 17 18 using namespace llvm; 19 20 void SourceCoverageView::renderLine(raw_ostream &OS, StringRef Line, 21 ArrayRef<HighlightRange> Ranges) { 22 if (Ranges.empty()) { 23 OS << Line << "\n"; 24 return; 25 } 26 if (Line.empty()) 27 Line = " "; 28 29 unsigned PrevColumnStart = 0; 30 unsigned Start = 1; 31 for (const auto &Range : Ranges) { 32 if (PrevColumnStart == Range.ColumnStart) 33 continue; 34 35 // Show the unhighlighted part 36 unsigned ColumnStart = PrevColumnStart = Range.ColumnStart; 37 OS << Line.substr(Start - 1, ColumnStart - Start); 38 39 // Show the highlighted part 40 auto Color = Range.Kind == HighlightRange::NotCovered ? raw_ostream::RED 41 : raw_ostream::CYAN; 42 OS.changeColor(Color, false, true); 43 unsigned ColumnEnd = std::min(Range.ColumnEnd, (unsigned)Line.size() + 1); 44 OS << Line.substr(ColumnStart - 1, ColumnEnd - ColumnStart); 45 Start = ColumnEnd; 46 OS.resetColor(); 47 } 48 49 // Show the rest of the line 50 OS << Line.substr(Start - 1, Line.size() - Start + 1); 51 OS << "\n"; 52 53 if (Options.Debug) { 54 for (const auto &Range : Ranges) { 55 errs() << "Highlighted line " << Range.Line << ", " << Range.ColumnStart 56 << " -> "; 57 if (Range.ColumnEnd == std::numeric_limits<unsigned>::max()) { 58 errs() << "?\n"; 59 } else { 60 errs() << Range.ColumnEnd << "\n"; 61 } 62 } 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(raw_ostream &OS, 113 ArrayRef<RegionMarker> Regions) { 114 SmallString<32> Buffer; 115 raw_svector_ostream BufferOS(Buffer); 116 117 unsigned PrevColumn = 1; 118 for (const auto &Region : Regions) { 119 // Skip to the new region 120 if (Region.Column > PrevColumn) 121 OS.indent(Region.Column - PrevColumn); 122 PrevColumn = Region.Column + 1; 123 BufferOS << Region.ExecutionCount; 124 StringRef Str = BufferOS.str(); 125 // Trim the execution count 126 Str = Str.substr(0, std::min(Str.size(), (size_t)7)); 127 PrevColumn += Str.size(); 128 OS << '^' << Str; 129 Buffer.clear(); 130 } 131 OS << "\n"; 132 133 if (Options.Debug) { 134 for (const auto &Region : Regions) { 135 errs() << "Marker at " << Region.Line << ":" << Region.Column << " = " 136 << Region.ExecutionCount << "\n"; 137 } 138 } 139 } 140 141 /// \brief Insert a new highlighting range into the line's highlighting ranges 142 /// Return line's new highlighting ranges in result. 143 static void insertHighlightRange( 144 ArrayRef<SourceCoverageView::HighlightRange> Ranges, 145 SourceCoverageView::HighlightRange RangeToInsert, 146 SmallVectorImpl<SourceCoverageView::HighlightRange> &Result) { 147 Result.clear(); 148 size_t I = 0; 149 auto E = Ranges.size(); 150 for (; I < E; ++I) { 151 if (RangeToInsert.ColumnStart < Ranges[I].ColumnEnd) { 152 const auto &Range = Ranges[I]; 153 bool NextRangeContainsInserted = false; 154 // If the next range starts before the inserted range, move the end of the 155 // next range to the start of the inserted range. 156 if (Range.ColumnStart < RangeToInsert.ColumnStart) { 157 if (RangeToInsert.ColumnStart != Range.ColumnStart) 158 Result.push_back(SourceCoverageView::HighlightRange( 159 Range.Line, Range.ColumnStart, RangeToInsert.ColumnStart, 160 Range.Kind)); 161 // If the next range also ends after the inserted range, keep this range 162 // and create a new range that starts at the inserted range and ends 163 // at the next range later. 164 if (Range.ColumnEnd > RangeToInsert.ColumnEnd) 165 NextRangeContainsInserted = true; 166 } 167 if (!NextRangeContainsInserted) { 168 ++I; 169 // Ignore ranges that are contained in inserted range 170 while (I < E && RangeToInsert.contains(Ranges[I])) 171 ++I; 172 } 173 break; 174 } 175 Result.push_back(Ranges[I]); 176 } 177 Result.push_back(RangeToInsert); 178 // If the next range starts before the inserted range end, move the start 179 // of the next range to the end of the inserted range. 180 if (I < E && Ranges[I].ColumnStart < RangeToInsert.ColumnEnd) { 181 const auto &Range = Ranges[I]; 182 if (RangeToInsert.ColumnEnd != Range.ColumnEnd) 183 Result.push_back(SourceCoverageView::HighlightRange( 184 Range.Line, RangeToInsert.ColumnEnd, Range.ColumnEnd, Range.Kind)); 185 ++I; 186 } 187 // Add the remaining ranges that are located after the inserted range 188 for (; I < E; ++I) 189 Result.push_back(Ranges[I]); 190 } 191 192 void SourceCoverageView::sortChildren() { 193 for (auto &I : Children) 194 I->sortChildren(); 195 std::sort(Children.begin(), Children.end(), 196 [](const std::unique_ptr<SourceCoverageView> &LHS, 197 const std::unique_ptr<SourceCoverageView> &RHS) { 198 return LHS->ExpansionRegion < RHS->ExpansionRegion; 199 }); 200 } 201 202 SourceCoverageView::HighlightRange 203 SourceCoverageView::getExpansionHighlightRange() const { 204 return HighlightRange(ExpansionRegion.LineStart, ExpansionRegion.ColumnStart, 205 ExpansionRegion.ColumnEnd, HighlightRange::Expanded); 206 } 207 208 template <typename T> 209 ArrayRef<T> gatherLineItems(size_t &CurrentIdx, const std::vector<T> &Items, 210 unsigned LineNo) { 211 auto PrevIdx = CurrentIdx; 212 auto E = Items.size(); 213 while (CurrentIdx < E && Items[CurrentIdx].Line == LineNo) 214 ++CurrentIdx; 215 return ArrayRef<T>(Items.data() + PrevIdx, CurrentIdx - PrevIdx); 216 } 217 218 ArrayRef<std::unique_ptr<SourceCoverageView>> 219 gatherLineSubViews(size_t &CurrentIdx, 220 ArrayRef<std::unique_ptr<SourceCoverageView>> Items, 221 unsigned LineNo) { 222 auto PrevIdx = CurrentIdx; 223 auto E = Items.size(); 224 while (CurrentIdx < E && 225 Items[CurrentIdx]->getSubViewsExpansionLine() == LineNo) 226 ++CurrentIdx; 227 return Items.slice(PrevIdx, CurrentIdx - PrevIdx); 228 } 229 230 void SourceCoverageView::render(raw_ostream &OS, unsigned IndentLevel) { 231 // Make sure that the children are in sorted order. 232 sortChildren(); 233 234 SmallVector<HighlightRange, 8> AdjustedLineHighlightRanges; 235 size_t CurrentChild = 0; 236 size_t CurrentHighlightRange = 0; 237 size_t CurrentRegionMarker = 0; 238 239 line_iterator Lines(File); 240 // Advance the line iterator to the first line. 241 while (Lines.line_number() < LineOffset) 242 ++Lines; 243 244 // The width of the leading columns 245 unsigned CombinedColumnWidth = 246 (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + 247 (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); 248 // The width of the line that is used to divide between the view and the 249 // subviews. 250 unsigned DividerWidth = CombinedColumnWidth + 4; 251 252 for (size_t I = 0, E = LineStats.size(); I < E; ++I) { 253 unsigned LineNo = I + LineOffset; 254 255 // Gather the child subviews that are visible on this line. 256 auto LineSubViews = gatherLineSubViews(CurrentChild, Children, LineNo); 257 258 renderIndent(OS, IndentLevel); 259 if (Options.ShowLineStats) 260 renderLineCoverageColumn(OS, LineStats[I]); 261 if (Options.ShowLineNumbers) 262 renderLineNumberColumn(OS, LineNo); 263 264 // Gather highlighting ranges. 265 auto LineHighlightRanges = 266 gatherLineItems(CurrentHighlightRange, HighlightRanges, LineNo); 267 auto LineRanges = LineHighlightRanges; 268 // Highlight the expansion range if there is an expansion subview on this 269 // line. 270 if (!LineSubViews.empty() && LineSubViews.front()->isExpansionSubView() && 271 Options.Colors) { 272 insertHighlightRange(LineHighlightRanges, 273 LineSubViews.front()->getExpansionHighlightRange(), 274 AdjustedLineHighlightRanges); 275 LineRanges = AdjustedLineHighlightRanges; 276 } 277 278 // Display the source code for the current line. 279 StringRef Line = *Lines; 280 // Check if the line is empty, as line_iterator skips blank lines. 281 if (LineNo < Lines.line_number()) 282 Line = ""; 283 else if (!Lines.is_at_eof()) 284 ++Lines; 285 renderLine(OS, Line, LineRanges); 286 287 // Show the region markers. 288 bool ShowMarkers = !Options.ShowLineStatsOrRegionMarkers || 289 LineStats[I].hasMultipleRegions(); 290 auto LineMarkers = gatherLineItems(CurrentRegionMarker, Markers, LineNo); 291 if (ShowMarkers && !LineMarkers.empty()) { 292 renderIndent(OS, IndentLevel); 293 OS.indent(CombinedColumnWidth); 294 renderRegionMarkers(OS, LineMarkers); 295 } 296 297 // Show the line's expanded child subviews. 298 bool FirstChildExpansion = true; 299 if (LineSubViews.empty()) 300 continue; 301 unsigned NestedIndent = IndentLevel + 1; 302 renderViewDivider(NestedIndent, DividerWidth, OS); 303 OS << "\n"; 304 for (const auto &Child : LineSubViews) { 305 // If this subview shows a function instantiation, render the function's 306 // name. 307 if (Child->isInstantiationSubView()) { 308 renderIndent(OS, NestedIndent); 309 OS << ' '; 310 Options.colored_ostream(OS, raw_ostream::CYAN) << Child->FunctionName 311 << ":"; 312 OS << "\n"; 313 } else { 314 if (!FirstChildExpansion) { 315 // Re-render the current line and highlight the expansion range for 316 // this 317 // subview. 318 insertHighlightRange(LineHighlightRanges, 319 Child->getExpansionHighlightRange(), 320 AdjustedLineHighlightRanges); 321 renderIndent(OS, IndentLevel); 322 OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1)); 323 renderLine(OS, Line, AdjustedLineHighlightRanges); 324 renderViewDivider(NestedIndent, DividerWidth, OS); 325 OS << "\n"; 326 } else 327 FirstChildExpansion = false; 328 } 329 // Render the child subview 330 Child->render(OS, NestedIndent); 331 renderViewDivider(NestedIndent, DividerWidth, OS); 332 OS << "\n"; 333 } 334 } 335 } 336 337 void SourceCoverageView::setUpVisibleRange(SourceCoverageDataManager &Data) { 338 auto CountedRegions = Data.getSourceRegions(); 339 if (!CountedRegions.size()) 340 return; 341 342 unsigned Start = CountedRegions.front().LineStart, End = 0; 343 for (const auto &CR : CountedRegions) { 344 Start = std::min(Start, CR.LineStart); 345 End = std::max(End, CR.LineEnd); 346 } 347 LineOffset = Start; 348 LineStats.resize(End - Start + 1); 349 } 350 351 void 352 SourceCoverageView::createLineCoverageInfo(SourceCoverageDataManager &Data) { 353 auto CountedRegions = Data.getSourceRegions(); 354 for (const auto &CR : CountedRegions) { 355 if (CR.Kind == coverage::CounterMappingRegion::SkippedRegion) { 356 // Reset the line stats for skipped regions. 357 for (unsigned Line = CR.LineStart; Line <= CR.LineEnd; 358 ++Line) 359 LineStats[Line - LineOffset] = LineCoverageInfo(); 360 continue; 361 } 362 LineStats[CR.LineStart - LineOffset].addRegionStartCount(CR.ExecutionCount); 363 for (unsigned Line = CR.LineStart + 1; Line <= CR.LineEnd; ++Line) 364 LineStats[Line - LineOffset].addRegionCount(CR.ExecutionCount); 365 } 366 } 367 368 void 369 SourceCoverageView::createHighlightRanges(SourceCoverageDataManager &Data) { 370 auto CountedRegions = Data.getSourceRegions(); 371 std::vector<bool> AlreadyHighlighted; 372 AlreadyHighlighted.resize(CountedRegions.size(), false); 373 374 for (size_t I = 0, S = CountedRegions.size(); I < S; ++I) { 375 const auto &CR = CountedRegions[I]; 376 if (CR.Kind == coverage::CounterMappingRegion::SkippedRegion || 377 CR.ExecutionCount != 0) 378 continue; 379 if (AlreadyHighlighted[I]) 380 continue; 381 for (size_t J = 0; J < S; ++J) { 382 if (CR.contains(CountedRegions[J])) { 383 AlreadyHighlighted[J] = true; 384 } 385 } 386 if (CR.LineStart == CR.LineEnd) { 387 HighlightRanges.push_back(HighlightRange( 388 CR.LineStart, CR.ColumnStart, CR.ColumnEnd)); 389 continue; 390 } 391 HighlightRanges.push_back( 392 HighlightRange(CR.LineStart, CR.ColumnStart, 393 std::numeric_limits<unsigned>::max())); 394 HighlightRanges.push_back( 395 HighlightRange(CR.LineEnd, 1, CR.ColumnEnd)); 396 for (unsigned Line = CR.LineStart + 1; Line < CR.LineEnd; 397 ++Line) { 398 HighlightRanges.push_back( 399 HighlightRange(Line, 1, std::numeric_limits<unsigned>::max())); 400 } 401 } 402 403 std::sort(HighlightRanges.begin(), HighlightRanges.end()); 404 } 405 406 void SourceCoverageView::createRegionMarkers(SourceCoverageDataManager &Data) { 407 for (const auto &CR : Data.getSourceRegions()) 408 if (CR.Kind != coverage::CounterMappingRegion::SkippedRegion) 409 Markers.push_back( 410 RegionMarker(CR.LineStart, CR.ColumnStart, CR.ExecutionCount)); 411 } 412 413 void SourceCoverageView::load(SourceCoverageDataManager &Data) { 414 setUpVisibleRange(Data); 415 if (Options.ShowLineStats) 416 createLineCoverageInfo(Data); 417 if (Options.Colors) 418 createHighlightRanges(Data); 419 if (Options.ShowRegionMarkers) 420 createRegionMarkers(Data); 421 } 422