| //===- 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/Optional.h" |
| #include "llvm/ADT/SmallString.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/Support/LineIterator.h" |
| |
| using namespace llvm; |
| |
| void SourceCoverageView::renderLine( |
| raw_ostream &OS, StringRef Line, int64_t LineNumber, |
| const coverage::CoverageSegment *WrappedSegment, |
| ArrayRef<const coverage::CoverageSegment *> Segments, |
| unsigned ExpansionCol) { |
| Optional<raw_ostream::Colors> Highlight; |
| SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; |
| |
| // The first segment overlaps from a previous line, so we treat it specially. |
| if (WrappedSegment && WrappedSegment->HasCount && WrappedSegment->Count == 0) |
| Highlight = raw_ostream::RED; |
| |
| // Output each segment of the line, possibly highlighted. |
| unsigned Col = 1; |
| for (const auto *S : Segments) { |
| unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1); |
| colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, |
| Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true) |
| << Line.substr(Col - 1, End - Col); |
| if (Options.Debug && Highlight) |
| HighlightedRanges.push_back(std::make_pair(Col, End)); |
| Col = End; |
| if (Col == ExpansionCol) |
| Highlight = raw_ostream::CYAN; |
| else if (S->HasCount && S->Count == 0) |
| Highlight = raw_ostream::RED; |
| else |
| Highlight = None; |
| } |
| |
| // Show the rest of the line |
| colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, |
| Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true) |
| << Line.substr(Col - 1, Line.size() - Col + 1); |
| OS << "\n"; |
| |
| if (Options.Debug) { |
| for (const auto &Range : HighlightedRanges) |
| errs() << "Highlighted line " << LineNumber << ", " << Range.first |
| << " -> " << Range.second << "\n"; |
| if (Highlight) |
| errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\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 << "-"; |
| } |
| |
| /// Format a count using engineering notation with 3 significant digits. |
| static std::string formatCount(uint64_t N) { |
| std::string Number = utostr(N); |
| int Len = Number.size(); |
| if (Len <= 3) |
| return Number; |
| int IntLen = Len % 3 == 0 ? 3 : Len % 3; |
| std::string Result(Number.data(), IntLen); |
| if (IntLen != 3) { |
| Result.push_back('.'); |
| Result += Number.substr(IntLen, 3 - IntLen); |
| } |
| Result.push_back(" kMGTPEZY"[(Len - 1) / 3]); |
| return Result; |
| } |
| |
| void |
| SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS, |
| const LineCoverageInfo &Line) { |
| if (!Line.isMapped()) { |
| OS.indent(LineCoverageColumnWidth) << '|'; |
| return; |
| } |
| std::string C = formatCount(Line.ExecutionCount); |
| OS.indent(LineCoverageColumnWidth - C.size()); |
| colored_ostream(OS, raw_ostream::MAGENTA, |
| Line.hasMultipleRegions() && Options.Colors) |
| << C; |
| 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<const coverage::CoverageSegment *> Segments) { |
| unsigned PrevColumn = 1; |
| for (const auto *S : Segments) { |
| if (!S->IsRegionEntry) |
| continue; |
| // Skip to the new region |
| if (S->Col > PrevColumn) |
| OS.indent(S->Col - PrevColumn); |
| PrevColumn = S->Col + 1; |
| std::string C = formatCount(S->Count); |
| PrevColumn += C.size(); |
| OS << '^' << C; |
| } |
| OS << "\n"; |
| |
| if (Options.Debug) |
| for (const auto *S : Segments) |
| errs() << "Marker at " << S->Line << ":" << S->Col << " = " |
| << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n"); |
| } |
| |
| void SourceCoverageView::render(raw_ostream &OS, bool WholeFile, |
| unsigned IndentLevel) { |
| // 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; |
| |
| // We need the expansions and instantiations sorted so we can go through them |
| // while we iterate lines. |
| std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end()); |
| std::sort(InstantiationSubViews.begin(), InstantiationSubViews.end()); |
| auto NextESV = ExpansionSubViews.begin(); |
| auto EndESV = ExpansionSubViews.end(); |
| auto NextISV = InstantiationSubViews.begin(); |
| auto EndISV = InstantiationSubViews.end(); |
| |
| // Get the coverage information for the file. |
| auto NextSegment = CoverageInfo.begin(); |
| auto EndSegment = CoverageInfo.end(); |
| |
| unsigned FirstLine = NextSegment != EndSegment ? NextSegment->Line : 0; |
| const coverage::CoverageSegment *WrappedSegment = nullptr; |
| SmallVector<const coverage::CoverageSegment *, 8> LineSegments; |
| for (line_iterator LI(File, /*SkipBlanks=*/false); !LI.is_at_eof(); ++LI) { |
| // If we aren't rendering the whole file, we need to filter out the prologue |
| // and epilogue. |
| if (!WholeFile) { |
| if (NextSegment == EndSegment) |
| break; |
| else if (LI.line_number() < FirstLine) |
| continue; |
| } |
| |
| // Collect the coverage information relevant to this line. |
| if (LineSegments.size()) |
| WrappedSegment = LineSegments.back(); |
| LineSegments.clear(); |
| while (NextSegment != EndSegment && NextSegment->Line == LI.line_number()) |
| LineSegments.push_back(&*NextSegment++); |
| |
| // Calculate a count to be for the line as a whole. |
| LineCoverageInfo LineCount; |
| if (WrappedSegment && WrappedSegment->HasCount) |
| LineCount.addRegionCount(WrappedSegment->Count); |
| for (const auto *S : LineSegments) |
| if (S->HasCount && S->IsRegionEntry) |
| LineCount.addRegionStartCount(S->Count); |
| |
| // Render the line prefix. |
| renderIndent(OS, IndentLevel); |
| if (Options.ShowLineStats) |
| renderLineCoverageColumn(OS, LineCount); |
| if (Options.ShowLineNumbers) |
| renderLineNumberColumn(OS, LI.line_number()); |
| |
| // If there are expansion subviews, we want to highlight the first one. |
| unsigned ExpansionColumn = 0; |
| if (NextESV != EndESV && NextESV->getLine() == LI.line_number() && |
| Options.Colors) |
| ExpansionColumn = NextESV->getStartCol(); |
| |
| // Display the source code for the current line. |
| renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments, |
| ExpansionColumn); |
| |
| // Show the region markers. |
| if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers || |
| LineCount.hasMultipleRegions()) && |
| !LineSegments.empty()) { |
| renderIndent(OS, IndentLevel); |
| OS.indent(CombinedColumnWidth); |
| renderRegionMarkers(OS, LineSegments); |
| } |
| |
| // Show the expansions and instantiations for this line. |
| unsigned NestedIndent = IndentLevel + 1; |
| bool RenderedSubView = false; |
| for (; NextESV != EndESV && NextESV->getLine() == LI.line_number(); |
| ++NextESV) { |
| renderViewDivider(NestedIndent, DividerWidth, OS); |
| OS << "\n"; |
| if (RenderedSubView) { |
| // Re-render the current line and highlight the expansion range for |
| // this subview. |
| ExpansionColumn = NextESV->getStartCol(); |
| renderIndent(OS, IndentLevel); |
| OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1)); |
| renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments, |
| ExpansionColumn); |
| renderViewDivider(NestedIndent, DividerWidth, OS); |
| OS << "\n"; |
| } |
| // Render the child subview |
| if (Options.Debug) |
| errs() << "Expansion at line " << NextESV->getLine() << ", " |
| << NextESV->getStartCol() << " -> " << NextESV->getEndCol() |
| << "\n"; |
| NextESV->View->render(OS, false, NestedIndent); |
| RenderedSubView = true; |
| } |
| for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) { |
| renderViewDivider(NestedIndent, DividerWidth, OS); |
| OS << "\n"; |
| renderIndent(OS, NestedIndent); |
| OS << ' '; |
| Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName |
| << ":"; |
| OS << "\n"; |
| NextISV->View->render(OS, false, NestedIndent); |
| RenderedSubView = true; |
| } |
| if (RenderedSubView) { |
| renderViewDivider(NestedIndent, DividerWidth, OS); |
| OS << "\n"; |
| } |
| } |
| } |