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