| //===- SourceCoverageView.cpp - Code coverage view for source code --------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This class implements rendering for code coverage of source code. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SourceCoverageView.h" |
| #include "llvm/ADT/SmallString.h" |
| #include "llvm/Support/LineIterator.h" |
| |
| using namespace llvm; |
| |
| void SourceCoverageView::renderLine(raw_ostream &OS, StringRef Line, |
| ArrayRef<HighlightRange> Ranges) { |
| if (Ranges.empty()) { |
| OS << Line << "\n"; |
| return; |
| } |
| if (Line.empty()) |
| Line = " "; |
| |
| unsigned PrevColumnStart = 0; |
| unsigned Start = 1; |
| for (const auto &Range : Ranges) { |
| if (PrevColumnStart == Range.ColumnStart) |
| continue; |
| |
| // Show the unhighlighted part |
| unsigned ColumnStart = PrevColumnStart = Range.ColumnStart; |
| OS << Line.substr(Start - 1, ColumnStart - Start); |
| |
| // Show the highlighted part |
| auto Color = Range.Kind == HighlightRange::NotCovered ? raw_ostream::RED |
| : raw_ostream::CYAN; |
| OS.changeColor(Color, false, true); |
| unsigned ColumnEnd = std::min(Range.ColumnEnd, (unsigned)Line.size() + 1); |
| OS << Line.substr(ColumnStart - 1, ColumnEnd - ColumnStart); |
| Start = ColumnEnd; |
| OS.resetColor(); |
| } |
| |
| // Show the rest of the line |
| OS << Line.substr(Start - 1, Line.size() - Start + 1); |
| OS << "\n"; |
| |
| if (Options.Debug) { |
| for (const auto &Range : Ranges) { |
| errs() << "Highlighted line " << Range.Line << ", " << Range.ColumnStart |
| << " -> "; |
| if (Range.ColumnEnd == std::numeric_limits<unsigned>::max()) { |
| errs() << "?\n"; |
| } else { |
| errs() << Range.ColumnEnd << "\n"; |
| } |
| } |
| } |
| } |
| |
| void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) { |
| for (unsigned I = 0; I < Level; ++I) |
| OS << " |"; |
| } |
| |
| void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length, |
| raw_ostream &OS) { |
| assert(Level != 0 && "Cannot render divider at top level"); |
| renderIndent(OS, Level - 1); |
| OS.indent(2); |
| for (unsigned I = 0; I < Length; ++I) |
| OS << "-"; |
| } |
| |
| void |
| SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS, |
| const LineCoverageInfo &Line) { |
| if (!Line.isMapped()) { |
| OS.indent(LineCoverageColumnWidth) << '|'; |
| return; |
| } |
| SmallString<32> Buffer; |
| raw_svector_ostream BufferOS(Buffer); |
| BufferOS << Line.ExecutionCount; |
| auto Str = BufferOS.str(); |
| // Trim |
| Str = Str.substr(0, std::min(Str.size(), (size_t)LineCoverageColumnWidth)); |
| // Align to the right |
| OS.indent(LineCoverageColumnWidth - Str.size()); |
| colored_ostream(OS, raw_ostream::MAGENTA, |
| Line.hasMultipleRegions() && Options.Colors) |
| << Str; |
| OS << '|'; |
| } |
| |
| void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS, |
| unsigned LineNo) { |
| SmallString<32> Buffer; |
| raw_svector_ostream BufferOS(Buffer); |
| BufferOS << LineNo; |
| auto Str = BufferOS.str(); |
| // Trim and align to the right |
| Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth)); |
| OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|'; |
| } |
| |
| void SourceCoverageView::renderRegionMarkers(raw_ostream &OS, |
| ArrayRef<RegionMarker> Regions) { |
| SmallString<32> Buffer; |
| raw_svector_ostream BufferOS(Buffer); |
| |
| unsigned PrevColumn = 1; |
| for (const auto &Region : Regions) { |
| // Skip to the new region |
| if (Region.Column > PrevColumn) |
| OS.indent(Region.Column - PrevColumn); |
| PrevColumn = Region.Column + 1; |
| BufferOS << Region.ExecutionCount; |
| StringRef Str = BufferOS.str(); |
| // Trim the execution count |
| Str = Str.substr(0, std::min(Str.size(), (size_t)7)); |
| PrevColumn += Str.size(); |
| OS << '^' << Str; |
| Buffer.clear(); |
| } |
| OS << "\n"; |
| |
| if (Options.Debug) { |
| for (const auto &Region : Regions) { |
| errs() << "Marker at " << Region.Line << ":" << Region.Column << " = " |
| << Region.ExecutionCount << "\n"; |
| } |
| } |
| } |
| |
| /// \brief Insert a new highlighting range into the line's highlighting ranges |
| /// Return line's new highlighting ranges in result. |
| static void insertHighlightRange( |
| ArrayRef<SourceCoverageView::HighlightRange> Ranges, |
| SourceCoverageView::HighlightRange RangeToInsert, |
| SmallVectorImpl<SourceCoverageView::HighlightRange> &Result) { |
| Result.clear(); |
| size_t I = 0; |
| auto E = Ranges.size(); |
| for (; I < E; ++I) { |
| if (RangeToInsert.ColumnStart < Ranges[I].ColumnEnd) { |
| const auto &Range = Ranges[I]; |
| bool NextRangeContainsInserted = false; |
| // If the next range starts before the inserted range, move the end of the |
| // next range to the start of the inserted range. |
| if (Range.ColumnStart < RangeToInsert.ColumnStart) { |
| if (RangeToInsert.ColumnStart != Range.ColumnStart) |
| Result.push_back(SourceCoverageView::HighlightRange( |
| Range.Line, Range.ColumnStart, RangeToInsert.ColumnStart, |
| Range.Kind)); |
| // If the next range also ends after the inserted range, keep this range |
| // and create a new range that starts at the inserted range and ends |
| // at the next range later. |
| if (Range.ColumnEnd > RangeToInsert.ColumnEnd) |
| NextRangeContainsInserted = true; |
| } |
| if (!NextRangeContainsInserted) { |
| ++I; |
| // Ignore ranges that are contained in inserted range |
| while (I < E && RangeToInsert.contains(Ranges[I])) |
| ++I; |
| } |
| break; |
| } |
| Result.push_back(Ranges[I]); |
| } |
| Result.push_back(RangeToInsert); |
| // If the next range starts before the inserted range end, move the start |
| // of the next range to the end of the inserted range. |
| if (I < E && Ranges[I].ColumnStart < RangeToInsert.ColumnEnd) { |
| const auto &Range = Ranges[I]; |
| if (RangeToInsert.ColumnEnd != Range.ColumnEnd) |
| Result.push_back(SourceCoverageView::HighlightRange( |
| Range.Line, RangeToInsert.ColumnEnd, Range.ColumnEnd, Range.Kind)); |
| ++I; |
| } |
| // Add the remaining ranges that are located after the inserted range |
| for (; I < E; ++I) |
| Result.push_back(Ranges[I]); |
| } |
| |
| void SourceCoverageView::sortChildren() { |
| for (auto &I : Children) |
| I->sortChildren(); |
| std::sort(Children.begin(), Children.end(), |
| [](const std::unique_ptr<SourceCoverageView> &LHS, |
| const std::unique_ptr<SourceCoverageView> &RHS) { |
| return LHS->ExpansionRegion < RHS->ExpansionRegion; |
| }); |
| } |
| |
| SourceCoverageView::HighlightRange |
| SourceCoverageView::getExpansionHighlightRange() const { |
| return HighlightRange(ExpansionRegion.LineStart, ExpansionRegion.ColumnStart, |
| ExpansionRegion.ColumnEnd, HighlightRange::Expanded); |
| } |
| |
| template <typename T> |
| ArrayRef<T> gatherLineItems(size_t &CurrentIdx, const std::vector<T> &Items, |
| unsigned LineNo) { |
| auto PrevIdx = CurrentIdx; |
| auto E = Items.size(); |
| while (CurrentIdx < E && Items[CurrentIdx].Line == LineNo) |
| ++CurrentIdx; |
| return ArrayRef<T>(Items.data() + PrevIdx, CurrentIdx - PrevIdx); |
| } |
| |
| ArrayRef<std::unique_ptr<SourceCoverageView>> |
| gatherLineSubViews(size_t &CurrentIdx, |
| ArrayRef<std::unique_ptr<SourceCoverageView>> Items, |
| unsigned LineNo) { |
| auto PrevIdx = CurrentIdx; |
| auto E = Items.size(); |
| while (CurrentIdx < E && |
| Items[CurrentIdx]->getSubViewsExpansionLine() == LineNo) |
| ++CurrentIdx; |
| return Items.slice(PrevIdx, CurrentIdx - PrevIdx); |
| } |
| |
| void SourceCoverageView::render(raw_ostream &OS, unsigned IndentLevel) { |
| // Make sure that the children are in sorted order. |
| sortChildren(); |
| |
| SmallVector<HighlightRange, 8> AdjustedLineHighlightRanges; |
| size_t CurrentChild = 0; |
| size_t CurrentHighlightRange = 0; |
| size_t CurrentRegionMarker = 0; |
| |
| line_iterator Lines(File); |
| // Advance the line iterator to the first line. |
| while (Lines.line_number() < LineOffset) |
| ++Lines; |
| |
| // The width of the leading columns |
| unsigned CombinedColumnWidth = |
| (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) + |
| (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0); |
| // The width of the line that is used to divide between the view and the |
| // subviews. |
| unsigned DividerWidth = CombinedColumnWidth + 4; |
| |
| for (size_t I = 0, E = LineStats.size(); I < E; ++I) { |
| unsigned LineNo = I + LineOffset; |
| |
| // Gather the child subviews that are visible on this line. |
| auto LineSubViews = gatherLineSubViews(CurrentChild, Children, LineNo); |
| |
| renderIndent(OS, IndentLevel); |
| if (Options.ShowLineStats) |
| renderLineCoverageColumn(OS, LineStats[I]); |
| if (Options.ShowLineNumbers) |
| renderLineNumberColumn(OS, LineNo); |
| |
| // Gather highlighting ranges. |
| auto LineHighlightRanges = |
| gatherLineItems(CurrentHighlightRange, HighlightRanges, LineNo); |
| auto LineRanges = LineHighlightRanges; |
| // Highlight the expansion range if there is an expansion subview on this |
| // line. |
| if (!LineSubViews.empty() && LineSubViews.front()->isExpansionSubView() && |
| Options.Colors) { |
| insertHighlightRange(LineHighlightRanges, |
| LineSubViews.front()->getExpansionHighlightRange(), |
| AdjustedLineHighlightRanges); |
| LineRanges = AdjustedLineHighlightRanges; |
| } |
| |
| // Display the source code for the current line. |
| StringRef Line = *Lines; |
| // Check if the line is empty, as line_iterator skips blank lines. |
| if (LineNo < Lines.line_number()) |
| Line = ""; |
| else if (!Lines.is_at_eof()) |
| ++Lines; |
| renderLine(OS, Line, LineRanges); |
| |
| // Show the region markers. |
| bool ShowMarkers = !Options.ShowLineStatsOrRegionMarkers || |
| LineStats[I].hasMultipleRegions(); |
| auto LineMarkers = gatherLineItems(CurrentRegionMarker, Markers, LineNo); |
| if (ShowMarkers && !LineMarkers.empty()) { |
| renderIndent(OS, IndentLevel); |
| OS.indent(CombinedColumnWidth); |
| renderRegionMarkers(OS, LineMarkers); |
| } |
| |
| // Show the line's expanded child subviews. |
| bool FirstChildExpansion = true; |
| if (LineSubViews.empty()) |
| continue; |
| unsigned NestedIndent = IndentLevel + 1; |
| renderViewDivider(NestedIndent, DividerWidth, OS); |
| OS << "\n"; |
| for (const auto &Child : LineSubViews) { |
| // If this subview shows a function instantiation, render the function's |
| // name. |
| if (Child->isInstantiationSubView()) { |
| renderIndent(OS, NestedIndent); |
| OS << ' '; |
| Options.colored_ostream(OS, raw_ostream::CYAN) << Child->FunctionName |
| << ":"; |
| OS << "\n"; |
| } else { |
| if (!FirstChildExpansion) { |
| // Re-render the current line and highlight the expansion range for |
| // this |
| // subview. |
| insertHighlightRange(LineHighlightRanges, |
| Child->getExpansionHighlightRange(), |
| AdjustedLineHighlightRanges); |
| renderIndent(OS, IndentLevel); |
| OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1)); |
| renderLine(OS, Line, AdjustedLineHighlightRanges); |
| renderViewDivider(NestedIndent, DividerWidth, OS); |
| OS << "\n"; |
| } else |
| FirstChildExpansion = false; |
| } |
| // Render the child subview |
| Child->render(OS, NestedIndent); |
| renderViewDivider(NestedIndent, DividerWidth, OS); |
| OS << "\n"; |
| } |
| } |
| } |
| |
| void SourceCoverageView::setUpVisibleRange(SourceCoverageDataManager &Data) { |
| auto CountedRegions = Data.getSourceRegions(); |
| if (!CountedRegions.size()) |
| return; |
| |
| unsigned Start = CountedRegions.front().LineStart, End = 0; |
| for (const auto &CR : CountedRegions) { |
| Start = std::min(Start, CR.LineStart); |
| End = std::max(End, CR.LineEnd); |
| } |
| LineOffset = Start; |
| LineStats.resize(End - Start + 1); |
| } |
| |
| void |
| SourceCoverageView::createLineCoverageInfo(SourceCoverageDataManager &Data) { |
| auto CountedRegions = Data.getSourceRegions(); |
| for (const auto &CR : CountedRegions) { |
| if (CR.Kind == coverage::CounterMappingRegion::SkippedRegion) { |
| // Reset the line stats for skipped regions. |
| for (unsigned Line = CR.LineStart; Line <= CR.LineEnd; |
| ++Line) |
| LineStats[Line - LineOffset] = LineCoverageInfo(); |
| continue; |
| } |
| LineStats[CR.LineStart - LineOffset].addRegionStartCount(CR.ExecutionCount); |
| for (unsigned Line = CR.LineStart + 1; Line <= CR.LineEnd; ++Line) |
| LineStats[Line - LineOffset].addRegionCount(CR.ExecutionCount); |
| } |
| } |
| |
| void |
| SourceCoverageView::createHighlightRanges(SourceCoverageDataManager &Data) { |
| auto CountedRegions = Data.getSourceRegions(); |
| std::vector<bool> AlreadyHighlighted; |
| AlreadyHighlighted.resize(CountedRegions.size(), false); |
| |
| for (size_t I = 0, S = CountedRegions.size(); I < S; ++I) { |
| const auto &CR = CountedRegions[I]; |
| if (CR.Kind == coverage::CounterMappingRegion::SkippedRegion || |
| CR.ExecutionCount != 0) |
| continue; |
| if (AlreadyHighlighted[I]) |
| continue; |
| for (size_t J = 0; J < S; ++J) { |
| if (CR.contains(CountedRegions[J])) { |
| AlreadyHighlighted[J] = true; |
| } |
| } |
| if (CR.LineStart == CR.LineEnd) { |
| HighlightRanges.push_back(HighlightRange( |
| CR.LineStart, CR.ColumnStart, CR.ColumnEnd)); |
| continue; |
| } |
| HighlightRanges.push_back( |
| HighlightRange(CR.LineStart, CR.ColumnStart, |
| std::numeric_limits<unsigned>::max())); |
| HighlightRanges.push_back( |
| HighlightRange(CR.LineEnd, 1, CR.ColumnEnd)); |
| for (unsigned Line = CR.LineStart + 1; Line < CR.LineEnd; |
| ++Line) { |
| HighlightRanges.push_back( |
| HighlightRange(Line, 1, std::numeric_limits<unsigned>::max())); |
| } |
| } |
| |
| std::sort(HighlightRanges.begin(), HighlightRanges.end()); |
| } |
| |
| void SourceCoverageView::createRegionMarkers(SourceCoverageDataManager &Data) { |
| for (const auto &CR : Data.getSourceRegions()) |
| if (CR.Kind != coverage::CounterMappingRegion::SkippedRegion) |
| Markers.push_back( |
| RegionMarker(CR.LineStart, CR.ColumnStart, CR.ExecutionCount)); |
| } |
| |
| void SourceCoverageView::load(SourceCoverageDataManager &Data) { |
| setUpVisibleRange(Data); |
| if (Options.ShowLineStats) |
| createLineCoverageInfo(Data); |
| if (Options.Colors) |
| createHighlightRanges(Data); |
| if (Options.ShowRegionMarkers) |
| createRegionMarkers(Data); |
| } |