mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-26 04:32:44 +01:00
f85fa6973c
This is an enhancement to LLVM Source-Based Code Coverage in clang to track how many times individual branch-generating conditions are taken (evaluate to TRUE) and not taken (evaluate to FALSE). Individual conditions may comprise larger boolean expressions using boolean logical operators. This functionality is very similar to what is supported by GCOV except that it is very closely anchored to the ASTs. Differential Revision: https://reviews.llvm.org/D84467
300 lines
11 KiB
C++
300 lines
11 KiB
C++
//===- SourceCoverageViewText.cpp - A text-based code coverage view -------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file This file implements the text-based coverage renderer.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SourceCoverageViewText.h"
|
|
#include "CoverageReport.h"
|
|
#include "llvm/ADT/Optional.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Format.h"
|
|
|
|
using namespace llvm;
|
|
|
|
Expected<CoveragePrinter::OwnedStream>
|
|
CoveragePrinterText::createViewFile(StringRef Path, bool InToplevel) {
|
|
return createOutputStream(Path, "txt", InToplevel);
|
|
}
|
|
|
|
void CoveragePrinterText::closeViewFile(OwnedStream OS) {
|
|
OS->operator<<('\n');
|
|
}
|
|
|
|
Error CoveragePrinterText::createIndexFile(
|
|
ArrayRef<std::string> SourceFiles, const CoverageMapping &Coverage,
|
|
const CoverageFiltersMatchAll &Filters) {
|
|
auto OSOrErr = createOutputStream("index", "txt", /*InToplevel=*/true);
|
|
if (Error E = OSOrErr.takeError())
|
|
return E;
|
|
auto OS = std::move(OSOrErr.get());
|
|
raw_ostream &OSRef = *OS.get();
|
|
|
|
CoverageReport Report(Opts, Coverage);
|
|
Report.renderFileReports(OSRef, SourceFiles, Filters);
|
|
|
|
Opts.colored_ostream(OSRef, raw_ostream::CYAN) << "\n"
|
|
<< Opts.getLLVMVersionString();
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
namespace {
|
|
|
|
static const unsigned LineCoverageColumnWidth = 7;
|
|
static const unsigned LineNumberColumnWidth = 5;
|
|
|
|
/// Get the width of the leading columns.
|
|
unsigned getCombinedColumnWidth(const CoverageViewOptions &Opts) {
|
|
return (Opts.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
|
|
(Opts.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
|
|
}
|
|
|
|
/// The width of the line that is used to divide between the view and
|
|
/// the subviews.
|
|
unsigned getDividerWidth(const CoverageViewOptions &Opts) {
|
|
return getCombinedColumnWidth(Opts) + 4;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
void SourceCoverageViewText::renderViewHeader(raw_ostream &) {}
|
|
|
|
void SourceCoverageViewText::renderViewFooter(raw_ostream &) {}
|
|
|
|
void SourceCoverageViewText::renderSourceName(raw_ostream &OS, bool WholeFile) {
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN) << getSourceName()
|
|
<< ":\n";
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLinePrefix(raw_ostream &OS,
|
|
unsigned ViewDepth) {
|
|
for (unsigned I = 0; I < ViewDepth; ++I)
|
|
OS << " |";
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLineSuffix(raw_ostream &, unsigned) {}
|
|
|
|
void SourceCoverageViewText::renderViewDivider(raw_ostream &OS,
|
|
unsigned ViewDepth) {
|
|
assert(ViewDepth != 0 && "Cannot render divider at top level");
|
|
renderLinePrefix(OS, ViewDepth - 1);
|
|
OS.indent(2);
|
|
unsigned Length = getDividerWidth(getOptions());
|
|
for (unsigned I = 0; I < Length; ++I)
|
|
OS << '-';
|
|
OS << '\n';
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L,
|
|
const LineCoverageStats &LCS,
|
|
unsigned ExpansionCol,
|
|
unsigned ViewDepth) {
|
|
StringRef Line = L.Line;
|
|
unsigned LineNumber = L.LineNo;
|
|
auto *WrappedSegment = LCS.getWrappedSegment();
|
|
CoverageSegmentArray Segments = LCS.getLineSegments();
|
|
|
|
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->IsGapRegion &&
|
|
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,
|
|
getOptions().Colors && Highlight, /*Bold=*/false,
|
|
/*BG=*/true)
|
|
<< Line.substr(Col - 1, End - Col);
|
|
if (getOptions().Debug && Highlight)
|
|
HighlightedRanges.push_back(std::make_pair(Col, End));
|
|
Col = End;
|
|
if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) &&
|
|
S->HasCount && S->Count == 0)
|
|
Highlight = raw_ostream::RED;
|
|
else if (Col == ExpansionCol)
|
|
Highlight = raw_ostream::CYAN;
|
|
else
|
|
Highlight = None;
|
|
}
|
|
|
|
// Show the rest of the line.
|
|
colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
|
|
getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true)
|
|
<< Line.substr(Col - 1, Line.size() - Col + 1);
|
|
OS << '\n';
|
|
|
|
if (getOptions().Debug) {
|
|
for (const auto &Range : HighlightedRanges)
|
|
errs() << "Highlighted line " << LineNumber << ", " << Range.first
|
|
<< " -> " << Range.second << '\n';
|
|
if (Highlight)
|
|
errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n";
|
|
}
|
|
}
|
|
|
|
void SourceCoverageViewText::renderLineCoverageColumn(
|
|
raw_ostream &OS, const LineCoverageStats &Line) {
|
|
if (!Line.isMapped()) {
|
|
OS.indent(LineCoverageColumnWidth) << '|';
|
|
return;
|
|
}
|
|
std::string C = formatCount(Line.getExecutionCount());
|
|
OS.indent(LineCoverageColumnWidth - C.size());
|
|
colored_ostream(OS, raw_ostream::MAGENTA,
|
|
Line.hasMultipleRegions() && getOptions().Colors)
|
|
<< C;
|
|
OS << '|';
|
|
}
|
|
|
|
void SourceCoverageViewText::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 SourceCoverageViewText::renderRegionMarkers(raw_ostream &OS,
|
|
const LineCoverageStats &Line,
|
|
unsigned ViewDepth) {
|
|
renderLinePrefix(OS, ViewDepth);
|
|
OS.indent(getCombinedColumnWidth(getOptions()));
|
|
|
|
CoverageSegmentArray Segments = Line.getLineSegments();
|
|
|
|
// Just consider the segments which start *and* end on this line.
|
|
if (Segments.size() > 1)
|
|
Segments = Segments.drop_back();
|
|
|
|
unsigned PrevColumn = 1;
|
|
for (const auto *S : Segments) {
|
|
if (!S->IsRegionEntry)
|
|
continue;
|
|
if (S->Count == Line.getExecutionCount())
|
|
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;
|
|
|
|
if (getOptions().Debug)
|
|
errs() << "Marker at " << S->Line << ":" << S->Col << " = "
|
|
<< formatCount(S->Count) << "\n";
|
|
}
|
|
OS << '\n';
|
|
}
|
|
|
|
void SourceCoverageViewText::renderExpansionSite(raw_ostream &OS, LineRef L,
|
|
const LineCoverageStats &LCS,
|
|
unsigned ExpansionCol,
|
|
unsigned ViewDepth) {
|
|
renderLinePrefix(OS, ViewDepth);
|
|
OS.indent(getCombinedColumnWidth(getOptions()) + (ViewDepth == 0 ? 0 : 1));
|
|
renderLine(OS, L, LCS, ExpansionCol, ViewDepth);
|
|
}
|
|
|
|
void SourceCoverageViewText::renderExpansionView(raw_ostream &OS,
|
|
ExpansionView &ESV,
|
|
unsigned ViewDepth) {
|
|
// Render the child subview.
|
|
if (getOptions().Debug)
|
|
errs() << "Expansion at line " << ESV.getLine() << ", " << ESV.getStartCol()
|
|
<< " -> " << ESV.getEndCol() << '\n';
|
|
ESV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/false,
|
|
/*ShowTitle=*/false, ViewDepth + 1);
|
|
}
|
|
|
|
void SourceCoverageViewText::renderBranchView(raw_ostream &OS, BranchView &BRV,
|
|
unsigned ViewDepth) {
|
|
// Render the child subview.
|
|
if (getOptions().Debug)
|
|
errs() << "Branch at line " << BRV.getLine() << '\n';
|
|
|
|
for (const auto &R : BRV.Regions) {
|
|
double TruePercent = 0.0;
|
|
double FalsePercent = 0.0;
|
|
unsigned Total = R.ExecutionCount + R.FalseExecutionCount;
|
|
|
|
if (!getOptions().ShowBranchCounts && Total != 0) {
|
|
TruePercent = ((double)(R.ExecutionCount) / (double)Total) * 100.0;
|
|
FalsePercent = ((double)(R.FalseExecutionCount) / (double)Total) * 100.0;
|
|
}
|
|
|
|
renderLinePrefix(OS, ViewDepth);
|
|
OS << " Branch (" << R.LineStart << ":" << R.ColumnStart << "): [";
|
|
|
|
if (R.Folded) {
|
|
OS << "Folded - Ignored]\n";
|
|
continue;
|
|
}
|
|
|
|
colored_ostream(OS, raw_ostream::RED,
|
|
getOptions().Colors && !R.ExecutionCount,
|
|
/*Bold=*/false, /*BG=*/true)
|
|
<< "True";
|
|
|
|
if (getOptions().ShowBranchCounts)
|
|
OS << ": " << formatCount(R.ExecutionCount) << ", ";
|
|
else
|
|
OS << ": " << format("%0.2f", TruePercent) << "%, ";
|
|
|
|
colored_ostream(OS, raw_ostream::RED,
|
|
getOptions().Colors && !R.FalseExecutionCount,
|
|
/*Bold=*/false, /*BG=*/true)
|
|
<< "False";
|
|
|
|
if (getOptions().ShowBranchCounts)
|
|
OS << ": " << formatCount(R.FalseExecutionCount);
|
|
else
|
|
OS << ": " << format("%0.2f", FalsePercent) << "%";
|
|
OS << "]\n";
|
|
}
|
|
}
|
|
|
|
void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS,
|
|
InstantiationView &ISV,
|
|
unsigned ViewDepth) {
|
|
renderLinePrefix(OS, ViewDepth);
|
|
OS << ' ';
|
|
if (!ISV.View)
|
|
getOptions().colored_ostream(OS, raw_ostream::RED)
|
|
<< "Unexecuted instantiation: " << ISV.FunctionName << "\n";
|
|
else
|
|
ISV.View->print(OS, /*WholeFile=*/false, /*ShowSourceName=*/true,
|
|
/*ShowTitle=*/false, ViewDepth);
|
|
}
|
|
|
|
void SourceCoverageViewText::renderTitle(raw_ostream &OS, StringRef Title) {
|
|
if (getOptions().hasProjectTitle())
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN)
|
|
<< getOptions().ProjectTitle << "\n";
|
|
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN) << Title << "\n";
|
|
|
|
if (getOptions().hasCreatedTime())
|
|
getOptions().colored_ostream(OS, raw_ostream::CYAN)
|
|
<< getOptions().CreatedTimeStr << "\n";
|
|
}
|
|
|
|
void SourceCoverageViewText::renderTableHeader(raw_ostream &, unsigned,
|
|
unsigned) {}
|