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