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