From: Alex Lorenz Date: Fri, 22 Aug 2014 22:56:03 +0000 (+0000) Subject: llvm-cov: add code coverage tool that's based on coverage mapping format and clang... X-Git-Url: http://plrg.eecs.uci.edu/git/?p=oota-llvm.git;a=commitdiff_plain;h=6c7a6a1ba2ba93a7d7e96d88c39c3cd48ff082e5 llvm-cov: add code coverage tool that's based on coverage mapping format and clang's pgo. This commit expands llvm-cov's functionality by adding support for a new code coverage tool that uses LLVM's coverage mapping format and clang's instrumentation based profiling. The gcov compatible tool can be invoked by supplying the 'gcov' command as the first argument, or by modifying the tool's name to end with 'gcov'. Differential Revision: http://reviews.llvm.org/D4445 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@216300 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/ProfileData/CoverageMappingReader.cpp b/lib/ProfileData/CoverageMappingReader.cpp index f10ae05c0f9..8d1508b0370 100644 --- a/lib/ProfileData/CoverageMappingReader.cpp +++ b/lib/ProfileData/CoverageMappingReader.cpp @@ -289,18 +289,6 @@ ObjectFileCoverageMappingReader::ObjectFileCoverageMappingReader( Object = std::move(File.get()); } -ObjectFileCoverageMappingReader::ObjectFileCoverageMappingReader( - std::unique_ptr &ObjectBuffer, sys::fs::file_magic Type) - : CurrentRecord(0) { - auto File = object::ObjectFile::createObjectFile( - ObjectBuffer->getMemBufferRef(), Type); - if (!File) - error(File.getError()); - else - Object = OwningBinary(std::move(File.get()), - std::move(ObjectBuffer)); -} - namespace { /// \brief The coverage mapping data for a single function. /// It points to the function's name. @@ -347,19 +335,11 @@ struct SectionData { template std::error_code readCoverageMappingData( - SectionRef &ProfileNames, SectionRef &CoverageMapping, + SectionData &ProfileNames, StringRef Data, std::vector &Records, std::vector &Filenames) { llvm::DenseSet UniqueFunctionMappingData; - // Get the contents of the given sections. - StringRef Data; - if (auto Err = CoverageMapping.getContents(Data)) - return Err; - SectionData ProfileNamesData; - if (auto Err = ProfileNamesData.load(ProfileNames)) - return Err; - // Read the records in the coverage data section. while (!Data.empty()) { if (Data.size() < sizeof(CoverageMappingTURecord)) @@ -418,9 +398,9 @@ std::error_code readCoverageMappingData( continue; UniqueFunctionMappingData.insert(MappingRecord.FunctionNamePtr); StringRef FunctionName; - if (auto Err = ProfileNamesData.get(MappingRecord.FunctionNamePtr, - MappingRecord.FunctionNameSize, - FunctionName)) + if (auto Err = + ProfileNames.get(MappingRecord.FunctionNamePtr, + MappingRecord.FunctionNameSize, FunctionName)) return Err; Records.push_back(ObjectFileCoverageMappingReader::ProfileMappingRecord( Version, FunctionName, MappingRecord.FunctionHash, Mapping, @@ -431,6 +411,63 @@ std::error_code readCoverageMappingData( return instrprof_error::success; } +static const char *TestingFormatMagic = "llvmcovmtestdata"; + +static std::error_code decodeTestingFormat(StringRef Data, + SectionData &ProfileNames, + StringRef &CoverageMapping) { + Data = Data.substr(StringRef(TestingFormatMagic).size()); + if (Data.size() < 1) + return instrprof_error::truncated; + unsigned N = 0; + auto ProfileNamesSize = + decodeULEB128(reinterpret_cast(Data.data()), &N); + if (N > Data.size()) + return instrprof_error::malformed; + Data = Data.substr(N); + if (Data.size() < 1) + return instrprof_error::truncated; + N = 0; + ProfileNames.Address = + decodeULEB128(reinterpret_cast(Data.data()), &N); + if (N > Data.size()) + return instrprof_error::malformed; + Data = Data.substr(N); + if (Data.size() < ProfileNamesSize) + return instrprof_error::malformed; + ProfileNames.Data = Data.substr(0, ProfileNamesSize); + CoverageMapping = Data.substr(ProfileNamesSize); + return instrprof_error::success; +} + +ObjectFileCoverageMappingReader::ObjectFileCoverageMappingReader( + std::unique_ptr &ObjectBuffer, sys::fs::file_magic Type) + : CurrentRecord(0) { + if (ObjectBuffer->getBuffer().startswith(TestingFormatMagic)) { + // This is a special format used for testing. + SectionData ProfileNames; + StringRef CoverageMapping; + if (auto Err = decodeTestingFormat(ObjectBuffer->getBuffer(), ProfileNames, + CoverageMapping)) { + error(Err); + return; + } + error(readCoverageMappingData(ProfileNames, CoverageMapping, + MappingRecords, Filenames)); + Object = OwningBinary(std::unique_ptr(), + std::move(ObjectBuffer)); + return; + } + + auto File = object::ObjectFile::createObjectFile( + ObjectBuffer->getMemBufferRef(), Type); + if (!File) + error(File.getError()); + else + Object = OwningBinary(std::move(File.get()), + std::move(ObjectBuffer)); +} + std::error_code ObjectFileCoverageMappingReader::readHeader() { ObjectFile *OF = Object.getBinary().get(); if (!OF) @@ -457,13 +494,21 @@ std::error_code ObjectFileCoverageMappingReader::readHeader() { if (FoundSectionCount != 2) return error(instrprof_error::bad_header); + // Get the contents of the given sections. + StringRef Data; + if (auto Err = CoverageMapping.getContents(Data)) + return Err; + SectionData ProfileNamesData; + if (auto Err = ProfileNamesData.load(ProfileNames)) + return Err; + // Load the data from the found sections. std::error_code Err; if (BytesInAddress == 4) - Err = readCoverageMappingData(ProfileNames, CoverageMapping, + Err = readCoverageMappingData(ProfileNamesData, Data, MappingRecords, Filenames); else - Err = readCoverageMappingData(ProfileNames, CoverageMapping, + Err = readCoverageMappingData(ProfileNamesData, Data, MappingRecords, Filenames); if (Err) return error(Err); diff --git a/test/tools/llvm-cov/Inputs/README b/test/tools/llvm-cov/Inputs/README index 2cfb1917965..3773ba3f5e6 100644 --- a/test/tools/llvm-cov/Inputs/README +++ b/test/tools/llvm-cov/Inputs/README @@ -1,7 +1,21 @@ These inputs were pre-generated to allow for easier testing of llvm-cov. -test.gcno and test.gcda were create by running clang: - clang++ -g -ftest-coverage -fprofile-arcs test.cpp +The files used to test the gcov compatible code coverage tool were generated +using the following method: -test.cpp.gcov was created by running gcov 4.2.1: - gcov test.cpp + test.gcno and test.gcda were create by running clang: + clang++ -g -ftest-coverage -fprofile-arcs test.cpp + + test.cpp.gcov was created by running gcov 4.2.1: + gcov test.cpp + +The 'covmapping' files that are used to test llvm-cov contain raw sections +with the coverage mapping data generated by the compiler and linker. They are +created by running clang and llvm-cov: + clang++ -fprofile-instr-generate -fcoverage-mapping -o test test.cpp + llvm-cov convert-for-testing -o test.covmapping test + +The 'profdata' files were generated by running an instrumented version of the +program and merging the raw profile data using llvm-profdata. + ./test + llvm-profdata merge -o test.profdata default.profraw diff --git a/test/tools/llvm-cov/Inputs/highlightedRanges.covmapping b/test/tools/llvm-cov/Inputs/highlightedRanges.covmapping new file mode 100644 index 00000000000..20eb0d7e8df Binary files /dev/null and b/test/tools/llvm-cov/Inputs/highlightedRanges.covmapping differ diff --git a/test/tools/llvm-cov/Inputs/highlightedRanges.profdata b/test/tools/llvm-cov/Inputs/highlightedRanges.profdata new file mode 100644 index 00000000000..b465b000b03 Binary files /dev/null and b/test/tools/llvm-cov/Inputs/highlightedRanges.profdata differ diff --git a/test/tools/llvm-cov/Inputs/lineExecutionCounts.covmapping b/test/tools/llvm-cov/Inputs/lineExecutionCounts.covmapping new file mode 100644 index 00000000000..5cfa1694da2 Binary files /dev/null and b/test/tools/llvm-cov/Inputs/lineExecutionCounts.covmapping differ diff --git a/test/tools/llvm-cov/Inputs/lineExecutionCounts.profdata b/test/tools/llvm-cov/Inputs/lineExecutionCounts.profdata new file mode 100644 index 00000000000..87122272396 Binary files /dev/null and b/test/tools/llvm-cov/Inputs/lineExecutionCounts.profdata differ diff --git a/test/tools/llvm-cov/Inputs/regionMarkers.covmapping b/test/tools/llvm-cov/Inputs/regionMarkers.covmapping new file mode 100644 index 00000000000..3ebcb0777af Binary files /dev/null and b/test/tools/llvm-cov/Inputs/regionMarkers.covmapping differ diff --git a/test/tools/llvm-cov/Inputs/regionMarkers.profdata b/test/tools/llvm-cov/Inputs/regionMarkers.profdata new file mode 100644 index 00000000000..87122272396 Binary files /dev/null and b/test/tools/llvm-cov/Inputs/regionMarkers.profdata differ diff --git a/test/tools/llvm-cov/showHighlightedRanges.cpp b/test/tools/llvm-cov/showHighlightedRanges.cpp new file mode 100644 index 00000000000..7c38fff676b --- /dev/null +++ b/test/tools/llvm-cov/showHighlightedRanges.cpp @@ -0,0 +1,45 @@ +// RUN: llvm-cov show %S/Inputs/highlightedRanges.covmapping -instr-profile %S/Inputs/highlightedRanges.profdata -dump -filename-equivalence %s | FileCheck %s + +void func() { + return; + int i = 0; // CHECK: Highlighted line [[@LINE]], 3 -> 12 +} + +void func2(int x) { + if(x > 5) { + while(x >= 9) { + return; + --x; // CHECK: Highlighted line [[@LINE]], 7 -> 10 + } + int i = 0; // CHECK: Highlighted line [[@LINE]], 5 -> 14 + } +} + +void test() { + int x = 0; + + if (x) { // CHECK: Highlighted line [[@LINE]], 10 -> ? + x = 0; // CHECK: Highlighted line [[@LINE]], 1 -> ? + } else { // CHECK: Highlighted line [[@LINE]], 1 -> 4 + x = 1; + } + + // CHECK: Highlighted line [[@LINE+1]], 26 -> 29 + for (int i = 0; i < 0; ++i) { // CHECK: Highlighted line [[@LINE]], 31 -> ? + x = 1; // CHECK: Highlighted line [[@LINE]], 1 -> ? + } // CHECK: Highlighted line [[@LINE]], 1 -> 4 + + x = x < 10 ? x + + 1 + : x - 1; // CHECK: Highlighted line [[@LINE]], 16 -> 21 + x = x > 10 ? x + // CHECK: Highlighted line [[@LINE]], 16 -> ? + 1 // CHECK: Highlighted line [[@LINE]], 1 -> 17 + : x - 1; +} + +int main() { + test(); + func(); + func2(9); + return 0; +} diff --git a/test/tools/llvm-cov/showLineExecutionCounts.cpp b/test/tools/llvm-cov/showLineExecutionCounts.cpp new file mode 100644 index 00000000000..abcf5a33cd7 --- /dev/null +++ b/test/tools/llvm-cov/showLineExecutionCounts.cpp @@ -0,0 +1,22 @@ +// RUN: llvm-cov show %S/Inputs/lineExecutionCounts.covmapping -instr-profile %S/Inputs/lineExecutionCounts.profdata -no-colors -filename-equivalence %s | FileCheck %s + +int main() { // CHECK: 1| [[@LINE]]|int main( + int x = 0; // CHECK: 1| [[@LINE]]| int x + // CHECK: 1| [[@LINE]]| + if (x) { // CHECK: 0| [[@LINE]]| if (x) + x = 0; // CHECK: 0| [[@LINE]]| x = 0 + } else { // CHECK: 1| [[@LINE]]| } else + x = 1; // CHECK: 1| [[@LINE]]| x = 1 + } // CHECK: 1| [[@LINE]]| } + // CHECK: 1| [[@LINE]]| + for (int i = 0; i < 100; ++i) { // CHECK: 100| [[@LINE]]| for ( + x = 1; // CHECK: 100| [[@LINE]]| x = 1 + } // CHECK: 100| [[@LINE]]| } + // CHECK: 1| [[@LINE]]| + x = x < 10 ? x + 1 : x - 1; // CHECK: 0| [[@LINE]]| x = + x = x > 10 ? // CHECK: 1| [[@LINE]]| x = + x - 1: // CHECK: 0| [[@LINE]]| x + x + 1; // CHECK: 1| [[@LINE]]| x + // CHECK: 1| [[@LINE]]| + return 0; // CHECK: 1| [[@LINE]]| return +} // CHECK: 1| [[@LINE]]|} diff --git a/test/tools/llvm-cov/showRegionMarkers.cpp b/test/tools/llvm-cov/showRegionMarkers.cpp new file mode 100644 index 00000000000..9866144e361 --- /dev/null +++ b/test/tools/llvm-cov/showRegionMarkers.cpp @@ -0,0 +1,23 @@ +// RUN: llvm-cov show %S/Inputs/regionMarkers.covmapping -instr-profile %S/Inputs/regionMarkers.profdata -show-regions -dump -filename-equivalence %s | FileCheck %s + +int main() { // CHECK: Marker at [[@LINE]]:12 = 1 + int x = 0; + + if (x) { // CHECK: Marker at [[@LINE]]:10 = 0 + x = 0; + } else { // CHECK: Marker at [[@LINE]]:10 = 1 + x = 1; + } + // CHECK: Marker at [[@LINE+2]]:19 = 101 + // CHECK: Marker at [[@LINE+1]]:28 = 100 + for (int i = 0; i < 100; ++i) { // CHECK: Marker at [[@LINE]]:33 = 100 + x = 1; + } + // CHECK: Marker at [[@LINE+1]]:16 = 1 + x = x < 10 ? x + 1 : x - 1; // CHECK: Marker at [[@LINE]]:24 = 0 + x = x > 10 ? + x - 1: // CHECK: Marker at [[@LINE]]:9 = 0 + x + 1; // CHECK: Marker at [[@LINE]]:9 = 1 + + return 0; +} diff --git a/tools/llvm-cov/CMakeLists.txt b/tools/llvm-cov/CMakeLists.txt index e4c10ea76dc..54d9ece3988 100644 --- a/tools/llvm-cov/CMakeLists.txt +++ b/tools/llvm-cov/CMakeLists.txt @@ -1,6 +1,14 @@ -set(LLVM_LINK_COMPONENTS core support ) +set(LLVM_LINK_COMPONENTS core support object profiledata) add_llvm_tool(llvm-cov llvm-cov.cpp gcov.cpp + CodeCoverage.cpp + CoverageFilters.cpp + CoverageReport.cpp + CoverageSummary.cpp + CoverageSummaryInfo.cpp + SourceCoverageDataManager.cpp + SourceCoverageView.cpp + TestingSupport.cpp ) diff --git a/tools/llvm-cov/CodeCoverage.cpp b/tools/llvm-cov/CodeCoverage.cpp new file mode 100644 index 00000000000..a5e039c83e2 --- /dev/null +++ b/tools/llvm-cov/CodeCoverage.cpp @@ -0,0 +1,709 @@ +//===- CodeCoverage.cpp - Coverage tool based on profiling instrumentation-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// The 'CodeCoverageTool' class implements a command line tool to analyze and +// report coverage information using the profiling instrumentation and code +// coverage mapping. +// +//===----------------------------------------------------------------------===// + +#include "FunctionCoverageMapping.h" +#include "RenderingSupport.h" +#include "CoverageViewOptions.h" +#include "CoverageFilters.h" +#include "SourceCoverageDataManager.h" +#include "SourceCoverageView.h" +#include "CoverageSummary.h" +#include "CoverageReport.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ProfileData/InstrProfReader.h" +#include "llvm/ProfileData/CoverageMapping.h" +#include "llvm/ProfileData/CoverageMappingReader.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/MemoryObject.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/PrettyStackTrace.h" +#include +#include + +using namespace llvm; +using namespace coverage; + +namespace { +/// \brief Distribute the functions into instantiation sets. +/// An instantiation set is a collection of functions +/// that have the same source code, e.g. +/// template functions specializations. +class FunctionInstantiationSetCollector { + ArrayRef FunctionMappings; + typedef uint64_t KeyType; + typedef std::vector SetType; + std::unordered_map InstantiatedFunctions; + + static KeyType getKey(const MappingRegion &R) { + return uint64_t(R.LineStart) | uint64_t(R.ColumnStart) << 32; + } + +public: + void insert(const FunctionCoverageMapping &Function, unsigned FileID) { + KeyType Key = 0; + for (const auto &R : Function.MappingRegions) { + if (R.FileID == FileID) { + Key = getKey(R); + break; + } + } + auto I = InstantiatedFunctions.find(Key); + if (I == InstantiatedFunctions.end()) { + SetType Set; + Set.push_back(&Function); + InstantiatedFunctions.insert(std::make_pair(Key, Set)); + } else + I->second.push_back(&Function); + } + + std::unordered_map::iterator begin() { + return InstantiatedFunctions.begin(); + } + + std::unordered_map::iterator end() { + return InstantiatedFunctions.end(); + } +}; + +/// \brief The implementation of the coverage tool. +class CodeCoverageTool { +public: + enum Command { + /// \brief The show command. + Show, + /// \brief The report command. + Report + }; + + /// \brief Print the error message to the error output stream. + void error(const Twine &Message, StringRef Whence = ""); + + /// \brief Return a memory buffer for the given source file. + ErrorOr getSourceFile(StringRef SourceFile); + + /// \brief Return true if two filepaths refer to the same file. + bool equivalentFiles(StringRef A, StringRef B); + + /// \brief Collect a set of function's file ids which correspond to the + /// given source file. Return false if the set is empty. + bool gatherInterestingFileIDs(StringRef SourceFile, + const FunctionCoverageMapping &Function, + SmallSet &InterestingFileIDs); + + /// \brief Find the file id which is not an expanded file id. + bool findMainViewFileID(StringRef SourceFile, + const FunctionCoverageMapping &Function, + unsigned &MainViewFileID); + + bool findMainViewFileID(const FunctionCoverageMapping &Function, + unsigned &MainViewFileID); + + /// \brief Create a source view which shows coverage for an expansion + /// of a file. + void createExpansionSubView(const MappingRegion &ExpandedRegion, + const FunctionCoverageMapping &Function, + SourceCoverageView &Parent); + + void createExpansionSubViews(SourceCoverageView &View, unsigned ViewFileID, + const FunctionCoverageMapping &Function); + + /// \brief Create a source view which shows coverage for an instantiation + /// of a funciton. + void createInstantiationSubView(StringRef SourceFile, + const FunctionCoverageMapping &Function, + SourceCoverageView &View); + + /// \brief Create the main source view of a particular source file. + /// Return true if this particular source file is not covered. + bool + createSourceFileView(StringRef SourceFile, SourceCoverageView &View, + ArrayRef FunctionMappingRecords, + bool UseOnlyRegionsInMainFile = false); + + /// \brief Load the coverage mapping data. Return true if an error occured. + bool load(); + + int run(Command Cmd, int argc, const char **argv); + + typedef std::function CommandLineParserType; + + int show(int argc, const char **argv, + CommandLineParserType commandLineParser); + + int report(int argc, const char **argv, + CommandLineParserType commandLineParser); + + StringRef ObjectFilename; + CoverageViewOptions ViewOpts; + std::unique_ptr PGOReader; + CoverageFiltersMatchAll Filters; + std::vector SourceFiles; + std::vector>> + LoadedSourceFiles; + std::vector FunctionMappingRecords; + bool CompareFilenamesOnly; +}; +} + +void CodeCoverageTool::error(const Twine &Message, StringRef Whence) { + errs() << "error: "; + if (!Whence.empty()) + errs() << Whence << ": "; + errs() << Message << "\n"; +} + +ErrorOr +CodeCoverageTool::getSourceFile(StringRef SourceFile) { + SmallString<256> Path(SourceFile); + sys::fs::make_absolute(Path); + for (const auto &Files : LoadedSourceFiles) { + if (sys::fs::equivalent(Path.str(), Files.first)) { + return *Files.second; + } + } + auto Buffer = MemoryBuffer::getFile(SourceFile); + if (auto EC = Buffer.getError()) { + error(EC.message(), SourceFile); + return EC; + } + LoadedSourceFiles.push_back(std::make_pair( + std::string(Path.begin(), Path.end()), std::move(Buffer.get()))); + return *LoadedSourceFiles.back().second; +} + +/// \brief Return a line start - line end range which contains +/// all the mapping regions of a given function with a particular file id. +std::pair +findExpandedFileInterestingLineRange(unsigned FileID, + const FunctionCoverageMapping &Function) { + unsigned LineStart = std::numeric_limits::max(); + unsigned LineEnd = 0; + for (const auto &Region : Function.MappingRegions) { + if (Region.FileID != FileID) + continue; + LineStart = std::min(Region.LineStart, LineStart); + LineEnd = std::max(Region.LineEnd, LineEnd); + } + return std::make_pair(LineStart, LineEnd); +} + +bool CodeCoverageTool::equivalentFiles(StringRef A, StringRef B) { + if (CompareFilenamesOnly) + return sys::path::filename(A).equals_lower(sys::path::filename(B)); + return sys::fs::equivalent(A, B); +} + +bool CodeCoverageTool::gatherInterestingFileIDs( + StringRef SourceFile, const FunctionCoverageMapping &Function, + SmallSet &InterestingFileIDs) { + bool Interesting = false; + for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { + if (equivalentFiles(SourceFile, Function.Filenames[I])) { + InterestingFileIDs.insert(I); + Interesting = true; + } + } + return Interesting; +} + +bool +CodeCoverageTool::findMainViewFileID(StringRef SourceFile, + const FunctionCoverageMapping &Function, + unsigned &MainViewFileID) { + llvm::SmallVector IsExpandedFile(Function.Filenames.size(), false); + llvm::SmallVector FilenameEquivalence(Function.Filenames.size(), + false); + for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { + if (equivalentFiles(SourceFile, Function.Filenames[I])) + FilenameEquivalence[I] = true; + } + for (const auto &Region : Function.MappingRegions) { + if (Region.Kind == MappingRegion::ExpansionRegion && + FilenameEquivalence[Region.FileID]) + IsExpandedFile[Region.ExpandedFileID] = true; + } + for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { + if (!FilenameEquivalence[I] || IsExpandedFile[I]) + continue; + MainViewFileID = I; + return false; + } + return true; +} + +bool +CodeCoverageTool::findMainViewFileID(const FunctionCoverageMapping &Function, + unsigned &MainViewFileID) { + llvm::SmallVector IsExpandedFile(Function.Filenames.size(), false); + for (const auto &Region : Function.MappingRegions) { + if (Region.Kind == MappingRegion::ExpansionRegion) + IsExpandedFile[Region.ExpandedFileID] = true; + } + for (unsigned I = 0, E = Function.Filenames.size(); I < E; ++I) { + if (IsExpandedFile[I]) + continue; + MainViewFileID = I; + return false; + } + return true; +} + +void CodeCoverageTool::createExpansionSubView( + const MappingRegion &ExpandedRegion, + const FunctionCoverageMapping &Function, SourceCoverageView &Parent) { + auto ExpandedLines = findExpandedFileInterestingLineRange( + ExpandedRegion.ExpandedFileID, Function); + if (ViewOpts.Debug) + llvm::outs() << "Expansion of " << ExpandedRegion.ExpandedFileID << ":" + << ExpandedLines.first << " -> " << ExpandedLines.second + << " @ " << ExpandedRegion.FileID << ", " + << ExpandedRegion.LineStart << ":" + << ExpandedRegion.ColumnStart << "\n"; + auto SourceBuffer = + getSourceFile(Function.Filenames[ExpandedRegion.ExpandedFileID]); + if (!SourceBuffer) + return; + auto SubView = llvm::make_unique( + SourceBuffer.get(), Parent.getOptions(), ExpandedLines.first, + ExpandedLines.second, ExpandedRegion); + SourceCoverageDataManager RegionManager; + for (const auto &Region : Function.MappingRegions) { + if (Region.FileID == ExpandedRegion.ExpandedFileID) + RegionManager.insert(Region); + } + SubView->load(RegionManager); + createExpansionSubViews(*SubView, ExpandedRegion.ExpandedFileID, Function); + Parent.addChild(std::move(SubView)); +} + +void CodeCoverageTool::createExpansionSubViews( + SourceCoverageView &View, unsigned ViewFileID, + const FunctionCoverageMapping &Function) { + if (!ViewOpts.ShowExpandedRegions) + return; + for (const auto &Region : Function.MappingRegions) { + if (Region.Kind != CounterMappingRegion::ExpansionRegion) + continue; + if (Region.FileID != ViewFileID) + continue; + createExpansionSubView(Region, Function, View); + } +} + +void CodeCoverageTool::createInstantiationSubView( + StringRef SourceFile, const FunctionCoverageMapping &Function, + SourceCoverageView &View) { + SourceCoverageDataManager RegionManager; + SmallSet InterestingFileIDs; + if (!gatherInterestingFileIDs(SourceFile, Function, InterestingFileIDs)) + return; + // Get the interesting regions + for (const auto &Region : Function.MappingRegions) { + if (InterestingFileIDs.count(Region.FileID)) + RegionManager.insert(Region); + } + View.load(RegionManager); + unsigned MainFileID; + if (findMainViewFileID(SourceFile, Function, MainFileID)) + return; + createExpansionSubViews(View, MainFileID, Function); +} + +bool CodeCoverageTool::createSourceFileView( + StringRef SourceFile, SourceCoverageView &View, + ArrayRef FunctionMappingRecords, + bool UseOnlyRegionsInMainFile) { + SourceCoverageDataManager RegionManager; + FunctionInstantiationSetCollector InstantiationSetCollector; + + for (const auto &Function : FunctionMappingRecords) { + unsigned MainFileID; + if (findMainViewFileID(SourceFile, Function, MainFileID)) + continue; + SmallSet InterestingFileIDs; + if (UseOnlyRegionsInMainFile) { + InterestingFileIDs.insert(MainFileID); + } else if (!gatherInterestingFileIDs(SourceFile, Function, + InterestingFileIDs)) + continue; + // Get the interesting regions + for (const auto &Region : Function.MappingRegions) { + if (InterestingFileIDs.count(Region.FileID)) + RegionManager.insert(Region); + } + InstantiationSetCollector.insert(Function, MainFileID); + createExpansionSubViews(View, MainFileID, Function); + } + if (RegionManager.getSourceRegions().empty()) + return true; + View.load(RegionManager); + // Show instantiations + if (!ViewOpts.ShowFunctionInstantiations) + return false; + for (const auto &InstantiationSet : InstantiationSetCollector) { + if (InstantiationSet.second.size() < 2) + continue; + auto InterestingRange = findExpandedFileInterestingLineRange( + InstantiationSet.second.front()->MappingRegions.front().FileID, + *InstantiationSet.second.front()); + for (auto Function : InstantiationSet.second) { + auto SubView = llvm::make_unique( + View, InterestingRange.first, InterestingRange.second, + Function->PrettyName); + createInstantiationSubView(SourceFile, *Function, *SubView); + View.addChild(std::move(SubView)); + } + } + return false; +} + +bool CodeCoverageTool::load() { + auto CounterMappingBuff = MemoryBuffer::getFileOrSTDIN(ObjectFilename); + if (auto EC = CounterMappingBuff.getError()) { + error(EC.message(), ObjectFilename); + return true; + } + ObjectFileCoverageMappingReader MappingReader(CounterMappingBuff.get()); + if (auto EC = MappingReader.readHeader()) { + error(EC.message(), ObjectFilename); + return true; + } + + std::vector Counts; + for (const auto &I : MappingReader) { + FunctionCoverageMapping Function(I.FunctionName, I.Filenames); + + // Create the mapping regions with evaluated execution counts + Counts.clear(); + PGOReader->getFunctionCounts(Function.Name, I.FunctionHash, Counts); + + // Get the biggest referenced counters + bool RegionError = false; + CounterMappingContext Ctx(I.Expressions, Counts); + for (const auto &R : I.MappingRegions) { + // Compute the values of mapped regions + if (ViewOpts.Debug) { + outs() << "File " << R.FileID << "| " << R.LineStart << ":" + << R.ColumnStart << " -> " << R.LineEnd << ":" << R.ColumnEnd + << " = "; + Ctx.dump(R.Count); + if (R.Kind == CounterMappingRegion::ExpansionRegion) { + outs() << " (Expanded file id = " << R.ExpandedFileID << ") "; + } + outs() << "\n"; + } + std::error_code Error; + Function.MappingRegions.push_back( + MappingRegion(R, Ctx.evaluate(R.Count, Error))); + if (Error && !RegionError) { + colored_ostream(errs(), raw_ostream::RED) + << "error: Regions and counters don't match in a function '" + << Function.PrettyName << "' (re-run the instrumented binary)."; + errs() << "\n"; + RegionError = true; + } + } + + if (RegionError || !Filters.matches(Function)) + continue; + + FunctionMappingRecords.push_back(Function); + } + return false; +} + +int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { + // Print a stack trace if we signal out. + sys::PrintStackTraceOnErrorSignal(); + PrettyStackTraceProgram X(argc, argv); + llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. + + cl::list InputSourceFiles( + cl::Positional, cl::desc(""), cl::ZeroOrMore); + + cl::opt PGOFilename( + "instr-profile", cl::Required, + cl::desc( + "File with the profile data obtained after an instrumented run")); + + cl::opt DebugDump("dump", cl::Optional, + cl::desc("Show internal debug dump")); + + cl::opt FilenameEquivalence( + "filename-equivalence", cl::Optional, + cl::desc("Compare the filenames instead of full filepaths")); + + cl::OptionCategory FilteringCategory("Function filtering options"); + + cl::list NameFilters( + "name", cl::Optional, + cl::desc("Show code coverage only for functions with the given name"), + cl::ZeroOrMore, cl::cat(FilteringCategory)); + + cl::list NameRegexFilters( + "name-regex", cl::Optional, + cl::desc("Show code coverage only for functions that match the given " + "regular expression"), + cl::ZeroOrMore, cl::cat(FilteringCategory)); + + cl::opt RegionCoverageLtFilter( + "region-coverage-lt", cl::Optional, + cl::desc("Show code coverage only for functions with region coverage " + "less than the given threshold"), + cl::cat(FilteringCategory)); + + cl::opt RegionCoverageGtFilter( + "region-coverage-gt", cl::Optional, + cl::desc("Show code coverage only for functions with region coverage " + "greater than the given threshold"), + cl::cat(FilteringCategory)); + + cl::opt LineCoverageLtFilter( + "line-coverage-lt", cl::Optional, + cl::desc("Show code coverage only for functions with line coverage less " + "than the given threshold"), + cl::cat(FilteringCategory)); + + cl::opt LineCoverageGtFilter( + "line-coverage-gt", cl::Optional, + cl::desc("Show code coverage only for functions with line coverage " + "greater than the given threshold"), + cl::cat(FilteringCategory)); + + auto commandLineParser = [&, this](int argc, const char **argv) -> int { + cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); + ViewOpts.Debug = DebugDump; + CompareFilenamesOnly = FilenameEquivalence; + + if (auto EC = IndexedInstrProfReader::create(PGOFilename, PGOReader)) { + error(EC.message(), PGOFilename); + return 1; + } + + // Create the function filters + if (!NameFilters.empty() || !NameRegexFilters.empty()) { + auto NameFilterer = new CoverageFilters; + for (const auto &Name : NameFilters) + NameFilterer->push_back(llvm::make_unique(Name)); + for (const auto &Regex : NameRegexFilters) + NameFilterer->push_back( + llvm::make_unique(Regex)); + Filters.push_back(std::unique_ptr(NameFilterer)); + } + if (RegionCoverageLtFilter.getNumOccurrences() || + RegionCoverageGtFilter.getNumOccurrences() || + LineCoverageLtFilter.getNumOccurrences() || + LineCoverageGtFilter.getNumOccurrences()) { + auto StatFilterer = new CoverageFilters; + if (RegionCoverageLtFilter.getNumOccurrences()) + StatFilterer->push_back(llvm::make_unique( + RegionCoverageFilter::LessThan, RegionCoverageLtFilter)); + if (RegionCoverageGtFilter.getNumOccurrences()) + StatFilterer->push_back(llvm::make_unique( + RegionCoverageFilter::GreaterThan, RegionCoverageGtFilter)); + if (LineCoverageLtFilter.getNumOccurrences()) + StatFilterer->push_back(llvm::make_unique( + LineCoverageFilter::LessThan, LineCoverageLtFilter)); + if (LineCoverageGtFilter.getNumOccurrences()) + StatFilterer->push_back(llvm::make_unique( + RegionCoverageFilter::GreaterThan, LineCoverageGtFilter)); + Filters.push_back(std::unique_ptr(StatFilterer)); + } + + SourceFiles = InputSourceFiles; + return 0; + }; + + // Parse the object filename + if (argc > 1) { + StringRef Arg(argv[1]); + if (Arg.equals_lower("-help") || Arg.equals_lower("-version")) { + cl::ParseCommandLineOptions(2, argv, "LLVM code coverage tool\n"); + return 0; + } + ObjectFilename = Arg; + + argv[1] = argv[0]; + --argc; + ++argv; + } else { + errs() << sys::path::filename(argv[0]) << ": No executable file given!\n"; + return 1; + } + + switch (Cmd) { + case Show: + return show(argc, argv, commandLineParser); + case Report: + return report(argc, argv, commandLineParser); + } + return 0; +} + +int CodeCoverageTool::show(int argc, const char **argv, + CommandLineParserType commandLineParser) { + + cl::OptionCategory ViewCategory("Viewing options"); + + cl::opt ShowLineExecutionCounts( + "show-line-counts", cl::Optional, + cl::desc("Show the execution counts for each line"), cl::init(true), + cl::cat(ViewCategory)); + + cl::opt ShowRegions( + "show-regions", cl::Optional, + cl::desc("Show the execution counts for each region"), + cl::cat(ViewCategory)); + + cl::opt ShowBestLineRegionsCounts( + "show-line-counts-or-regions", cl::Optional, + cl::desc("Show the execution counts for each line, or the execution " + "counts for each region on lines that have multiple regions"), + cl::cat(ViewCategory)); + + cl::opt ShowExpansions("show-expansions", cl::Optional, + cl::desc("Show expanded source regions"), + cl::cat(ViewCategory)); + + cl::opt ShowInstantiations("show-instantiations", cl::Optional, + cl::desc("Show function instantiations"), + cl::cat(ViewCategory)); + + cl::opt NoColors("no-colors", cl::Optional, + cl::desc("Don't show text colors"), cl::init(false), + cl::cat(ViewCategory)); + + auto Err = commandLineParser(argc, argv); + if (Err) + return Err; + + ViewOpts.Colors = !NoColors; + ViewOpts.ShowLineNumbers = true; + ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 || + !ShowRegions || ShowBestLineRegionsCounts; + ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts; + ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts; + ViewOpts.ShowExpandedRegions = ShowExpansions; + ViewOpts.ShowFunctionInstantiations = ShowInstantiations; + + if (load()) + return 1; + + if (!Filters.empty()) { + // Show functions + for (const auto &Function : FunctionMappingRecords) { + unsigned MainFileID; + if (findMainViewFileID(Function, MainFileID)) + continue; + StringRef SourceFile = Function.Filenames[MainFileID]; + std::unique_ptr mainView; + auto SourceBuffer = getSourceFile(SourceFile); + if (!SourceBuffer) + return 1; + auto Range = findExpandedFileInterestingLineRange(MainFileID, Function); + mainView.reset(new SourceCoverageView(SourceBuffer.get(), ViewOpts, + Range.first, Range.second)); + createSourceFileView(SourceFile, *mainView, Function, true); + ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) + << Function.PrettyName << " from " << SourceFile << ":"; + outs() << "\n"; + mainView->render(outs()); + if (FunctionMappingRecords.size() > 1) + outs() << "\n"; + } + return 0; + } + + // Show files + bool ShowFilenames = SourceFiles.size() != 1; + + if (SourceFiles.empty()) { + // Get the source files from the function coverage mapping + std::set UniqueFilenames; + for (const auto &Function : FunctionMappingRecords) { + for (const auto &Filename : Function.Filenames) + UniqueFilenames.insert(Filename); + } + for (const auto &Filename : UniqueFilenames) + SourceFiles.push_back(Filename); + } + + for (const auto &SourceFile : SourceFiles) { + std::unique_ptr mainView; + auto SourceBuffer = getSourceFile(SourceFile); + if (!SourceBuffer) + return 1; + mainView.reset(new SourceCoverageView(SourceBuffer.get(), ViewOpts)); + if (createSourceFileView(SourceFile, *mainView, FunctionMappingRecords)) { + ViewOpts.colored_ostream(outs(), raw_ostream::RED) + << "warning: The file '" << SourceFile << "' isn't covered."; + outs() << "\n"; + continue; + } + + if (ShowFilenames) { + ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":"; + outs() << "\n"; + } + mainView->render(outs()); + if (SourceFiles.size() > 1) + outs() << "\n"; + } + + return 0; +} + +int CodeCoverageTool::report(int argc, const char **argv, + CommandLineParserType commandLineParser) { + cl::opt NoColors("no-colors", cl::Optional, + cl::desc("Don't show text colors"), cl::init(false)); + + auto Err = commandLineParser(argc, argv); + if (Err) + return Err; + + ViewOpts.Colors = !NoColors; + + if (load()) + return 1; + + CoverageSummary Summarizer; + Summarizer.createSummaries(FunctionMappingRecords); + CoverageReport Report(ViewOpts, Summarizer); + if (SourceFiles.empty() && Filters.empty()) { + Report.renderFileReports(llvm::outs()); + return 0; + } + + Report.renderFunctionReports(llvm::outs()); + return 0; +} + +int show_main(int argc, const char **argv) { + CodeCoverageTool Tool; + return Tool.run(CodeCoverageTool::Show, argc, argv); +} + +int report_main(int argc, const char **argv) { + CodeCoverageTool Tool; + return Tool.run(CodeCoverageTool::Report, argc, argv); +} diff --git a/tools/llvm-cov/CoverageFilters.cpp b/tools/llvm-cov/CoverageFilters.cpp new file mode 100644 index 00000000000..3732d729b6c --- /dev/null +++ b/tools/llvm-cov/CoverageFilters.cpp @@ -0,0 +1,57 @@ +//===- CoverageFilters.cpp - Function coverage mapping filters ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These classes provide filtering for function coverage mapping records. +// +//===----------------------------------------------------------------------===// + +#include "CoverageFilters.h" +#include "CoverageSummaryInfo.h" +#include "llvm/Support/Regex.h" + +using namespace llvm; + +bool NameCoverageFilter::matches(const FunctionCoverageMapping &Function) { + StringRef FuncName = Function.PrettyName; + return FuncName.find(Name) != StringRef::npos; +} + +bool NameRegexCoverageFilter::matches(const FunctionCoverageMapping &Function) { + return llvm::Regex(Regex).match(Function.PrettyName); +} + +bool RegionCoverageFilter::matches(const FunctionCoverageMapping &Function) { + return PassesThreshold(FunctionCoverageSummary::get(Function) + .RegionCoverage.getPercentCovered()); +} + +bool LineCoverageFilter::matches(const FunctionCoverageMapping &Function) { + return PassesThreshold( + FunctionCoverageSummary::get(Function).LineCoverage.getPercentCovered()); +} + +void CoverageFilters::push_back(std::unique_ptr Filter) { + Filters.push_back(std::move(Filter)); +} + +bool CoverageFilters::matches(const FunctionCoverageMapping &Function) { + for (const auto &Filter : Filters) { + if (Filter->matches(Function)) + return true; + } + return false; +} + +bool CoverageFiltersMatchAll::matches(const FunctionCoverageMapping &Function) { + for (const auto &Filter : Filters) { + if (!Filter->matches(Function)) + return false; + } + return true; +} diff --git a/tools/llvm-cov/CoverageFilters.h b/tools/llvm-cov/CoverageFilters.h new file mode 100644 index 00000000000..b356377e10f --- /dev/null +++ b/tools/llvm-cov/CoverageFilters.h @@ -0,0 +1,125 @@ +//===- CoverageFilters.h - Function coverage mapping filters --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These classes provide filtering for function coverage mapping records. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGEFILTERS_H +#define LLVM_COV_COVERAGEFILTERS_H + +#include "FunctionCoverageMapping.h" +#include +#include + +namespace llvm { + +/// \brief Matches specific functions that pass the requirement of this filter. +class CoverageFilter { +public: + virtual ~CoverageFilter() {} + + /// \brief Return true if the function passes the requirements of this filter. + virtual bool matches(const FunctionCoverageMapping &Function) { return true; } +}; + +/// \brief Matches functions that contain a specific string in their name. +class NameCoverageFilter : public CoverageFilter { + StringRef Name; + +public: + NameCoverageFilter(StringRef Name) : Name(Name) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief Matches functions whose name matches a certain regular expression. +class NameRegexCoverageFilter : public CoverageFilter { + StringRef Regex; + +public: + NameRegexCoverageFilter(StringRef Regex) : Regex(Regex) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief Matches numbers that pass a certain threshold. +template class StatisticThresholdFilter { +public: + enum Operation { LessThan, GreaterThan }; + +protected: + Operation Op; + T Threshold; + + StatisticThresholdFilter(Operation Op, T Threshold) + : Op(Op), Threshold(Threshold) {} + + /// \brief Return true if the given number is less than + /// or greater than the certain threshold. + bool PassesThreshold(T Value) const { + switch (Op) { + case LessThan: + return Value < Threshold; + case GreaterThan: + return Value > Threshold; + } + return false; + } +}; + +/// \brief Matches functions whose region coverage percentage +/// is above/below a certain percentage. +class RegionCoverageFilter : public CoverageFilter, + public StatisticThresholdFilter { +public: + RegionCoverageFilter(Operation Op, double Threshold) + : StatisticThresholdFilter(Op, Threshold) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief Matches functions whose line coverage percentage +/// is above/below a certain percentage. +class LineCoverageFilter : public CoverageFilter, + public StatisticThresholdFilter { +public: + LineCoverageFilter(Operation Op, double Threshold) + : StatisticThresholdFilter(Op, Threshold) {} + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief A collection of filters. +/// Matches functions that match any filters contained +/// in an instance of this class. +class CoverageFilters : public CoverageFilter { +protected: + std::vector> Filters; + +public: + /// \brief Append a filter to this collection. + void push_back(std::unique_ptr Filter); + + bool empty() const { return Filters.empty(); } + + bool matches(const FunctionCoverageMapping &Function) override; +}; + +/// \brief A collection of filters. +/// Matches functions that match all of the filters contained +/// in an instance of this class. +class CoverageFiltersMatchAll : public CoverageFilters { +public: + bool matches(const FunctionCoverageMapping &Function) override; +}; + +} // namespace llvm + +#endif // LLVM_COV_COVERAGEFILTERS_H diff --git a/tools/llvm-cov/CoverageReport.cpp b/tools/llvm-cov/CoverageReport.cpp new file mode 100644 index 00000000000..2efa8e00798 --- /dev/null +++ b/tools/llvm-cov/CoverageReport.cpp @@ -0,0 +1,201 @@ +//===- CoverageReport.cpp - Code coverage report -------------------------===// +// +// 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 of a code coverage report. +// +//===----------------------------------------------------------------------===// + +#include "CoverageReport.h" +#include "CoverageSummary.h" +#include "RenderingSupport.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FileSystem.h" + +using namespace llvm; +namespace { +/// \brief Helper struct which prints trimmed and aligned columns. +struct Column { + enum TrimKind { NoTrim, LeftTrim, RightTrim }; + + enum AlignmentKind { LeftAlignment, RightAlignment }; + + StringRef Str; + unsigned Width; + TrimKind Trim; + AlignmentKind Alignment; + + Column(StringRef Str, unsigned Width) + : Str(Str), Width(Width), Trim(NoTrim), Alignment(LeftAlignment) {} + + Column &set(TrimKind Value) { + Trim = Value; + return *this; + } + + Column &set(AlignmentKind Value) { + Alignment = Value; + return *this; + } + + void render(raw_ostream &OS) const; +}; +raw_ostream &operator<<(raw_ostream &OS, const Column &Value) { + Value.render(OS); + return OS; +} +} + +void Column::render(raw_ostream &OS) const { + if (Str.size() <= Width) { + if (Alignment == RightAlignment) { + OS.indent(Width - Str.size()); + OS << Str; + return; + } + OS << Str; + OS.indent(Width - Str.size()); + return; + } + + switch (Trim) { + case NoTrim: + OS << Str.substr(0, Width); + break; + case LeftTrim: + OS << "..." << Str.substr(Str.size() - Width + 3); + break; + case RightTrim: + OS << Str.substr(0, Width - 3) << "..."; + break; + } +} + +static Column column(StringRef Str, unsigned Width) { + return Column(Str, Width); +} + +template +static Column column(StringRef Str, unsigned Width, const T &Value) { + return Column(Str, Width).set(Value); +} + +static const unsigned FileReportColumns[] = {25, 10, 8, 8, 10, 8}; +static const unsigned FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8}; + +/// \brief Prints a horizontal divider which spans across the given columns. +template +static void renderDivider(T (&Columns)[N], raw_ostream &OS) { + unsigned Length = 0; + for (unsigned I = 0; I < N; ++I) + Length += Columns[I]; + for (unsigned I = 0; I < Length; ++I) + OS << '-'; +} + +/// \brief Return the color which correponds to the coverage +/// percentage of a certain metric. +template +static raw_ostream::Colors determineCoveragePercentageColor(const T &Info) { + if (Info.isFullyCovered()) + return raw_ostream::GREEN; + return Info.getPercentCovered() >= 80.0 ? raw_ostream::YELLOW + : raw_ostream::RED; +} + +void CoverageReport::render(const FileCoverageSummary &File, raw_ostream &OS) { + OS << column(File.Name, FileReportColumns[0], Column::LeftTrim) + << format("%*zd", FileReportColumns[1], File.RegionCoverage.NumRegions); + Options.colored_ostream(OS, File.RegionCoverage.isFullyCovered() + ? raw_ostream::GREEN + : raw_ostream::RED) + << format("%*zd", FileReportColumns[2], File.RegionCoverage.NotCovered); + Options.colored_ostream(OS, + determineCoveragePercentageColor(File.RegionCoverage)) + << format("%*.2f", FileReportColumns[3] - 1, + File.RegionCoverage.getPercentCovered()) << '%'; + OS << format("%*zd", FileReportColumns[4], + File.FunctionCoverage.NumFunctions); + Options.colored_ostream( + OS, determineCoveragePercentageColor(File.FunctionCoverage)) + << format("%*.2f", FileReportColumns[5] - 1, + File.FunctionCoverage.getPercentCovered()) << '%'; + OS << "\n"; +} + +void CoverageReport::render(const FunctionCoverageSummary &Function, + raw_ostream &OS) { + OS << column(Function.Name, FunctionReportColumns[0], Column::RightTrim) + << format("%*zd", FunctionReportColumns[1], + Function.RegionCoverage.NumRegions); + Options.colored_ostream(OS, Function.RegionCoverage.isFullyCovered() + ? raw_ostream::GREEN + : raw_ostream::RED) + << format("%*zd", FunctionReportColumns[2], + Function.RegionCoverage.NotCovered); + Options.colored_ostream( + OS, determineCoveragePercentageColor(Function.RegionCoverage)) + << format("%*.2f", FunctionReportColumns[3] - 1, + Function.RegionCoverage.getPercentCovered()) << '%'; + OS << format("%*zd", FunctionReportColumns[4], + Function.LineCoverage.NumLines); + Options.colored_ostream(OS, Function.LineCoverage.isFullyCovered() + ? raw_ostream::GREEN + : raw_ostream::RED) + << format("%*zd", FunctionReportColumns[5], + Function.LineCoverage.NotCovered); + Options.colored_ostream( + OS, determineCoveragePercentageColor(Function.LineCoverage)) + << format("%*.2f", FunctionReportColumns[6] - 1, + Function.LineCoverage.getPercentCovered()) << '%'; + OS << "\n"; +} + +void CoverageReport::renderFunctionReports(raw_ostream &OS) { + bool isFirst = true; + for (const auto &File : Summary.getFileSummaries()) { + if (isFirst) + isFirst = false; + else + OS << "\n"; + OS << "File '" << File.Name << "':\n"; + OS << column("Name", FunctionReportColumns[0]) + << column("Regions", FunctionReportColumns[1], Column::RightAlignment) + << column("Miss", FunctionReportColumns[2], Column::RightAlignment) + << column("Cover", FunctionReportColumns[3], Column::RightAlignment) + << column("Lines", FunctionReportColumns[4], Column::RightAlignment) + << column("Miss", FunctionReportColumns[5], Column::RightAlignment) + << column("Cover", FunctionReportColumns[6], Column::RightAlignment); + OS << "\n"; + renderDivider(FunctionReportColumns, OS); + OS << "\n"; + for (const auto &Function : File.FunctionSummaries) + render(Function, OS); + renderDivider(FunctionReportColumns, OS); + OS << "\n"; + render(FunctionCoverageSummary("TOTAL", File.RegionCoverage, + File.LineCoverage), + OS); + } +} + +void CoverageReport::renderFileReports(raw_ostream &OS) { + OS << column("Filename", FileReportColumns[0]) + << column("Regions", FileReportColumns[1], Column::RightAlignment) + << column("Miss", FileReportColumns[2], Column::RightAlignment) + << column("Cover", FileReportColumns[3], Column::RightAlignment) + << column("Functions", FileReportColumns[4], Column::RightAlignment) + << column("Cover", FileReportColumns[5], Column::RightAlignment) << "\n"; + renderDivider(FileReportColumns, OS); + OS << "\n"; + for (const auto &File : Summary.getFileSummaries()) + render(File, OS); + renderDivider(FileReportColumns, OS); + OS << "\n"; + render(Summary.getCombinedFileSummaries(), OS); +} diff --git a/tools/llvm-cov/CoverageReport.h b/tools/llvm-cov/CoverageReport.h new file mode 100644 index 00000000000..e8d34f2a9cf --- /dev/null +++ b/tools/llvm-cov/CoverageReport.h @@ -0,0 +1,40 @@ +//===- CoverageReport.h - Code coverage report ---------------------------===// +// +// 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 of a code coverage report. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGEREPORT_H +#define LLVM_COV_COVERAGEREPORT_H + +#include "CoverageViewOptions.h" +#include "CoverageSummary.h" + +namespace llvm { + +/// \brief Displays the code coverage report. +class CoverageReport { + const CoverageViewOptions &Options; + CoverageSummary &Summary; + + void render(const FileCoverageSummary &File, raw_ostream &OS); + void render(const FunctionCoverageSummary &Function, raw_ostream &OS); + +public: + CoverageReport(const CoverageViewOptions &Options, CoverageSummary &Summary) + : Options(Options), Summary(Summary) {} + + void renderFunctionReports(raw_ostream &OS); + + void renderFileReports(raw_ostream &OS); +}; +} + +#endif // LLVM_COV_COVERAGEREPORT_H diff --git a/tools/llvm-cov/CoverageSummary.cpp b/tools/llvm-cov/CoverageSummary.cpp new file mode 100644 index 00000000000..e32f3412cad --- /dev/null +++ b/tools/llvm-cov/CoverageSummary.cpp @@ -0,0 +1,92 @@ +//===- CoverageSummary.cpp - Code coverage summary ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements data management and rendering for the code coverage +// summaries of all files and functions. +// +//===----------------------------------------------------------------------===// + +#include "CoverageSummary.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Format.h" + +using namespace llvm; + +unsigned CoverageSummary::getFileID(StringRef Filename) { + for (unsigned I = 0, E = Filenames.size(); I < E; ++I) { + if (sys::fs::equivalent(Filenames[I], Filename)) + return I; + } + Filenames.push_back(Filename); + return Filenames.size() - 1; +} + +void +CoverageSummary::createSummaries(ArrayRef Functions) { + std::vector> FunctionFileIDs; + + FunctionFileIDs.resize(Functions.size()); + for (size_t I = 0, E = Functions.size(); I < E; ++I) { + StringRef Filename = Functions[I].Filenames[0]; + FunctionFileIDs[I] = std::make_pair(getFileID(Filename), I); + } + + // Sort the function records by file ids + std::sort(FunctionFileIDs.begin(), FunctionFileIDs.end(), + [](const std::pair &lhs, + const std::pair &rhs) { + return lhs.first < rhs.first; + }); + + // Create function summaries in a sorted order (by file ids) + FunctionSummaries.reserve(Functions.size()); + for (size_t I = 0, E = Functions.size(); I < E; ++I) + FunctionSummaries.push_back( + FunctionCoverageSummary::get(Functions[FunctionFileIDs[I].second])); + + // Create file summaries + size_t CurrentSummary = 0; + for (unsigned FileID = 0; FileID < Filenames.size(); ++FileID) { + // Gather the relevant functions summaries + auto PrevSummary = CurrentSummary; + while (CurrentSummary < FunctionSummaries.size() && + FunctionFileIDs[CurrentSummary].first == FileID) + ++CurrentSummary; + ArrayRef LocalSummaries( + FunctionSummaries.data() + PrevSummary, + FunctionSummaries.data() + CurrentSummary); + if (LocalSummaries.empty()) + continue; + + FileSummaries.push_back( + FileCoverageSummary::get(Filenames[FileID], LocalSummaries)); + } +} + +FileCoverageSummary CoverageSummary::getCombinedFileSummaries() { + size_t NumRegions = 0, CoveredRegions = 0; + size_t NumLines = 0, NonCodeLines = 0, CoveredLines = 0; + size_t NumFunctionsCovered = 0, NumFunctions = 0; + for (const auto &File : FileSummaries) { + NumRegions += File.RegionCoverage.NumRegions; + CoveredRegions += File.RegionCoverage.Covered; + + NumLines += File.LineCoverage.NumLines; + NonCodeLines += File.LineCoverage.NonCodeLines; + CoveredLines += File.LineCoverage.Covered; + + NumFunctionsCovered += File.FunctionCoverage.FullyCovered; + NumFunctions += File.FunctionCoverage.NumFunctions; + } + return FileCoverageSummary( + "TOTAL", RegionCoverageInfo(CoveredRegions, NumRegions), + LineCoverageInfo(CoveredLines, NonCodeLines, NumLines), + FunctionCoverageInfo(NumFunctionsCovered, NumFunctions), + ArrayRef()); +} diff --git a/tools/llvm-cov/CoverageSummary.h b/tools/llvm-cov/CoverageSummary.h new file mode 100644 index 00000000000..bf4be08d923 --- /dev/null +++ b/tools/llvm-cov/CoverageSummary.h @@ -0,0 +1,45 @@ +//===- CoverageSummary.h - Code coverage summary --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class implements data management and rendering for the code coverage +// summaries of all files and functions. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGESUMMARY_H +#define LLVM_COV_COVERAGESUMMARY_H + +#include "CoverageSummaryInfo.h" +#include + +namespace llvm { + +/// \brief Manager for the function and file code coverage summaries. +class CoverageSummary { + std::vector Filenames; + std::vector FunctionSummaries; + std::vector> FunctionSummariesFileIDs; + std::vector FileSummaries; + + unsigned getFileID(StringRef Filename); + +public: + void createSummaries(ArrayRef Functions); + + ArrayRef getFileSummaries() { return FileSummaries; } + + FileCoverageSummary getCombinedFileSummaries(); + + void render(const FunctionCoverageSummary &Summary, raw_ostream &OS); + + void render(raw_ostream &OS); +}; +} + +#endif // LLVM_COV_COVERAGESUMMARY_H diff --git a/tools/llvm-cov/CoverageSummaryInfo.cpp b/tools/llvm-cov/CoverageSummaryInfo.cpp new file mode 100644 index 00000000000..765bb3ef84f --- /dev/null +++ b/tools/llvm-cov/CoverageSummaryInfo.cpp @@ -0,0 +1,95 @@ +//===- CoverageSummaryInfo.cpp - Coverage summary for function/file -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These structures are used to represent code coverage metrics +// for functions/files. +// +//===----------------------------------------------------------------------===// + +#include "CoverageSummaryInfo.h" + +using namespace llvm; +using namespace coverage; + +FunctionCoverageSummary +FunctionCoverageSummary::get(const FunctionCoverageMapping &Function) { + // Compute the region coverage + size_t NumCodeRegions = 0, CoveredRegions = 0; + for (auto &Region : Function.MappingRegions) { + if (Region.Kind != CounterMappingRegion::CodeRegion) + continue; + ++NumCodeRegions; + if (Region.ExecutionCount != 0) + ++CoveredRegions; + } + + // Compute the line coverage + size_t NumLines = 0, CoveredLines = 0; + for (unsigned FileID = 0, E = Function.Filenames.size(); FileID < E; + ++FileID) { + // Find the line start and end of the function's source code + // in that particular file + unsigned LineStart = std::numeric_limits::max(); + unsigned LineEnd = 0; + for (auto &Region : Function.MappingRegions) { + if (Region.FileID != FileID) + continue; + LineStart = std::min(LineStart, Region.LineStart); + LineEnd = std::max(LineEnd, Region.LineEnd); + } + unsigned LineCount = LineEnd - LineStart + 1; + + // Get counters + llvm::SmallVector ExecutionCounts; + ExecutionCounts.resize(LineCount, 0); + for (auto &Region : Function.MappingRegions) { + if (Region.FileID != FileID) + continue; + // Ignore the lines that were skipped by the preprocessor. + auto ExecutionCount = Region.ExecutionCount; + if (Region.Kind == MappingRegion::SkippedRegion) { + LineCount -= Region.LineEnd - Region.LineStart + 1; + ExecutionCount = 1; + } + for (unsigned I = Region.LineStart; I <= Region.LineEnd; ++I) + ExecutionCounts[I - LineStart] = ExecutionCount; + } + CoveredLines += LineCount - std::count(ExecutionCounts.begin(), + ExecutionCounts.end(), 0); + NumLines += LineCount; + } + return FunctionCoverageSummary( + Function.PrettyName, RegionCoverageInfo(CoveredRegions, NumCodeRegions), + LineCoverageInfo(CoveredLines, 0, NumLines)); +} + +FileCoverageSummary +FileCoverageSummary::get(StringRef Name, + ArrayRef FunctionSummaries) { + size_t NumRegions = 0, CoveredRegions = 0; + size_t NumLines = 0, NonCodeLines = 0, CoveredLines = 0; + size_t NumFunctionsCovered = 0; + for (const auto &Func : FunctionSummaries) { + CoveredRegions += Func.RegionCoverage.Covered; + NumRegions += Func.RegionCoverage.NumRegions; + + CoveredLines += Func.LineCoverage.Covered; + NonCodeLines += Func.LineCoverage.NonCodeLines; + NumLines += Func.LineCoverage.NumLines; + + if (Func.RegionCoverage.isFullyCovered()) + ++NumFunctionsCovered; + } + + return FileCoverageSummary( + Name, RegionCoverageInfo(CoveredRegions, NumRegions), + LineCoverageInfo(CoveredLines, NonCodeLines, NumLines), + FunctionCoverageInfo(NumFunctionsCovered, FunctionSummaries.size()), + FunctionSummaries); +} diff --git a/tools/llvm-cov/CoverageSummaryInfo.h b/tools/llvm-cov/CoverageSummaryInfo.h new file mode 100644 index 00000000000..710108d1a5d --- /dev/null +++ b/tools/llvm-cov/CoverageSummaryInfo.h @@ -0,0 +1,131 @@ +//===- CoverageSummaryInfo.h - Coverage summary for function/file ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// These structures are used to represent code coverage metrics +// for functions/files. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGESUMMARYINFO_H +#define LLVM_COV_COVERAGESUMMARYINFO_H + +#include "FunctionCoverageMapping.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +/// \brief Provides information about region coverage for a function/file. +struct RegionCoverageInfo { + /// \brief The number of regions that were executed at least once. + size_t Covered; + + /// \brief The number of regions that weren't executed. + size_t NotCovered; + + /// \brief The total number of regions in a function/file. + size_t NumRegions; + + RegionCoverageInfo(size_t Covered, size_t NumRegions) + : Covered(Covered), NotCovered(NumRegions - Covered), + NumRegions(NumRegions) {} + + bool isFullyCovered() const { return Covered == NumRegions; } + + double getPercentCovered() const { + return double(Covered) / double(NumRegions) * 100.0; + } +}; + +/// \brief Provides information about line coverage for a function/file. +struct LineCoverageInfo { + /// \brief The number of lines that were executed at least once. + size_t Covered; + + /// \brief The number of lines that weren't executed. + size_t NotCovered; + + /// \brief The number of lines that aren't code. + size_t NonCodeLines; + + /// \brief The total number of lines in a function/file. + size_t NumLines; + + LineCoverageInfo(size_t Covered, size_t NumNonCodeLines, size_t NumLines) + : Covered(Covered), NotCovered(NumLines - NumNonCodeLines - Covered), + NonCodeLines(NumNonCodeLines), NumLines(NumLines) {} + + bool isFullyCovered() const { return Covered == (NumLines - NonCodeLines); } + + double getPercentCovered() const { + return double(Covered) / double(NumLines - NonCodeLines) * 100.0; + } +}; + +/// \brief Provides information about function coverage for a file. +struct FunctionCoverageInfo { + /// \brief The number of functions that have full + /// region coverage. + size_t FullyCovered; + + /// \brief The total number of functions in this file. + size_t NumFunctions; + + FunctionCoverageInfo(size_t FullyCovered, size_t NumFunctions) + : FullyCovered(FullyCovered), NumFunctions(NumFunctions) {} + + bool isFullyCovered() const { return FullyCovered == NumFunctions; } + + double getPercentCovered() const { + return double(FullyCovered) / double(NumFunctions) * 100.0; + } +}; + +/// \brief A summary of function's code coverage. +struct FunctionCoverageSummary { + StringRef Name; + RegionCoverageInfo RegionCoverage; + LineCoverageInfo LineCoverage; + + FunctionCoverageSummary(StringRef Name, + const RegionCoverageInfo &RegionCoverage, + const LineCoverageInfo &LineCoverage) + : Name(Name), RegionCoverage(RegionCoverage), LineCoverage(LineCoverage) { + } + + /// \brief Compute the code coverage summary for the given function coverage + /// mapping record. + static FunctionCoverageSummary get(const FunctionCoverageMapping &Function); +}; + +/// \brief A summary of file's code coverage. +struct FileCoverageSummary { + StringRef Name; + RegionCoverageInfo RegionCoverage; + LineCoverageInfo LineCoverage; + FunctionCoverageInfo FunctionCoverage; + /// \brief The summary of every function + /// in this file. + ArrayRef FunctionSummaries; + + FileCoverageSummary(StringRef Name, const RegionCoverageInfo &RegionCoverage, + const LineCoverageInfo &LineCoverage, + const FunctionCoverageInfo &FunctionCoverage, + ArrayRef FunctionSummaries) + : Name(Name), RegionCoverage(RegionCoverage), LineCoverage(LineCoverage), + FunctionCoverage(FunctionCoverage), + FunctionSummaries(FunctionSummaries) {} + + /// \brief Compute the code coverage summary for a file. + static FileCoverageSummary + get(StringRef Name, ArrayRef FunctionSummaries); +}; + +} // namespace llvm + +#endif // LLVM_COV_COVERAGESUMMARYINFO_H diff --git a/tools/llvm-cov/CoverageViewOptions.h b/tools/llvm-cov/CoverageViewOptions.h new file mode 100644 index 00000000000..94b55fe793f --- /dev/null +++ b/tools/llvm-cov/CoverageViewOptions.h @@ -0,0 +1,36 @@ +//===- CoverageViewOptions.h - Code coverage display options -------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_COVERAGEVIEWOPTIONS_H +#define LLVM_COV_COVERAGEVIEWOPTIONS_H + +#include "RenderingSupport.h" + +namespace llvm { + +/// \brief The options for displaying the code coverage information. +struct CoverageViewOptions { + bool Debug; + bool Colors; + bool ShowLineNumbers; + bool ShowLineStats; + bool ShowRegionMarkers; + bool ShowLineStatsOrRegionMarkers; + bool ShowExpandedRegions; + bool ShowFunctionInstantiations; + + /// \brief Change the output's stream color if the colors are enabled. + ColoredRawOstream colored_ostream(raw_ostream &OS, + raw_ostream::Colors Color) const { + return llvm::colored_ostream(OS, Color, Colors); + } +}; +} + +#endif // LLVM_COV_COVERAGEVIEWOPTIONS_H diff --git a/tools/llvm-cov/FunctionCoverageMapping.h b/tools/llvm-cov/FunctionCoverageMapping.h new file mode 100644 index 00000000000..d171483cee9 --- /dev/null +++ b/tools/llvm-cov/FunctionCoverageMapping.h @@ -0,0 +1,50 @@ +//===- FunctionCoverageMapping.h - Function coverage mapping record -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// A structure that stores the coverage mapping record for a single function. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_FUNCTIONCOVERAGEMAPPING_H +#define LLVM_COV_FUNCTIONCOVERAGEMAPPING_H + +#include +#include +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ProfileData/CoverageMapping.h" + +namespace llvm { + +/// \brief Associates a source range with an execution count. +struct MappingRegion : public coverage::CounterMappingRegion { + uint64_t ExecutionCount; + + MappingRegion(const CounterMappingRegion &R, uint64_t ExecutionCount) + : CounterMappingRegion(R), ExecutionCount(ExecutionCount) {} +}; + +/// \brief Stores all the required information +/// about code coverage for a single function. +struct FunctionCoverageMapping { + /// \brief Raw function name. + std::string Name; + /// \brief Demangled function name. + std::string PrettyName; + std::vector Filenames; + std::vector MappingRegions; + + FunctionCoverageMapping(StringRef Name, ArrayRef Filenames) + : Name(Name), PrettyName(Name), + Filenames(Filenames.begin(), Filenames.end()) {} +}; + +} // namespace llvm + +#endif // LLVM_COV_FUNCTIONCOVERAGEMAPPING_H diff --git a/tools/llvm-cov/LLVMBuild.txt b/tools/llvm-cov/LLVMBuild.txt index 87e00d170f9..d6eb74de0d4 100644 --- a/tools/llvm-cov/LLVMBuild.txt +++ b/tools/llvm-cov/LLVMBuild.txt @@ -19,4 +19,4 @@ type = Tool name = llvm-cov parent = Tools -required_libraries = Instrumentation +required_libraries = ProfileData Support Instrumentation diff --git a/tools/llvm-cov/Makefile b/tools/llvm-cov/Makefile index efed6cc7942..6e32b4d233d 100644 --- a/tools/llvm-cov/Makefile +++ b/tools/llvm-cov/Makefile @@ -9,7 +9,7 @@ LEVEL := ../.. TOOLNAME := llvm-cov -LINK_COMPONENTS := core support +LINK_COMPONENTS := core support profiledata object # This tool has no plugins, optimize startup time. TOOL_NO_EXPORTS := 1 diff --git a/tools/llvm-cov/RenderingSupport.h b/tools/llvm-cov/RenderingSupport.h new file mode 100644 index 00000000000..619a8a68a56 --- /dev/null +++ b/tools/llvm-cov/RenderingSupport.h @@ -0,0 +1,59 @@ +//===- RenderingSupport.h - output stream rendering support functions ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_RENDERINGSUPPORT_H +#define LLVM_COV_RENDERINGSUPPORT_H + +#include "llvm/Support/raw_ostream.h" +#include + +namespace llvm { + +/// \brief A helper class that resets the output stream's color if needed +/// when destroyed. +class ColoredRawOstream { + ColoredRawOstream(const ColoredRawOstream &OS) LLVM_DELETED_FUNCTION; + +public: + raw_ostream &OS; + bool IsColorUsed; + + ColoredRawOstream(raw_ostream &OS, bool IsColorUsed) + : OS(OS), IsColorUsed(IsColorUsed) {} + + ColoredRawOstream(ColoredRawOstream &&Other) + : OS(Other.OS), IsColorUsed(Other.IsColorUsed) { + // Reset the other IsColorUsed so that the other object won't reset the + // color when destroyed. + Other.IsColorUsed = false; + } + + ~ColoredRawOstream() { + if (IsColorUsed) + OS.resetColor(); + } +}; + +template +inline raw_ostream &operator<<(const ColoredRawOstream &OS, T &&Value) { + return OS.OS << std::forward(Value); +} + +/// \brief Change the color of the output stream if the `IsColorUsed` flag +/// is true. Returns an object that resets the color when destroyed. +inline ColoredRawOstream colored_ostream(raw_ostream &OS, + raw_ostream::Colors Color, + bool IsColorUsed = false) { + if (IsColorUsed) + OS.changeColor(Color); + return ColoredRawOstream(OS, IsColorUsed); +} +} + +#endif // LLVM_COV_RENDERINGSUPPORT_H diff --git a/tools/llvm-cov/SourceCoverageDataManager.cpp b/tools/llvm-cov/SourceCoverageDataManager.cpp new file mode 100644 index 00000000000..03382b266e7 --- /dev/null +++ b/tools/llvm-cov/SourceCoverageDataManager.cpp @@ -0,0 +1,57 @@ +//===- SourceCoverageDataManager.cpp - Manager for source file coverage +// data-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class separates and merges mapping regions for a specific source file. +// +//===----------------------------------------------------------------------===// + +#include "SourceCoverageDataManager.h" + +using namespace llvm; +using namespace coverage; + +void SourceCoverageDataManager::insert(const MappingRegion &Region) { + SourceRange Range(Region.LineStart, Region.ColumnStart, Region.LineEnd, + Region.ColumnEnd); + if (Region.Kind == CounterMappingRegion::SkippedRegion) { + SkippedRegions.push_back(Range); + return; + } + Regions.push_back(std::make_pair(Range, Region.ExecutionCount)); +} + +ArrayRef> +SourceCoverageDataManager::getSourceRegions() { + if (Uniqued || Regions.size() <= 1) + return Regions; + + // Sort. + std::sort(Regions.begin(), Regions.end(), + [](const std::pair &LHS, + const std::pair &RHS) { + return LHS.first < RHS.first; + }); + + // Merge duplicate source ranges and sum their execution counts. + auto Prev = Regions.begin(); + for (auto I = Prev + 1, E = Regions.end(); I != E; ++I) { + if (I->first == Prev->first) { + Prev->second += I->second; + continue; + } + ++Prev; + *Prev = *I; + } + ++Prev; + Regions.erase(Prev, Regions.end()); + + Uniqued = true; + return Regions; +} diff --git a/tools/llvm-cov/SourceCoverageDataManager.h b/tools/llvm-cov/SourceCoverageDataManager.h new file mode 100644 index 00000000000..f389cadc7c0 --- /dev/null +++ b/tools/llvm-cov/SourceCoverageDataManager.h @@ -0,0 +1,79 @@ +//===- SourceCoverageDataManager.h - Manager for source file coverage data-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class separates and merges mapping regions for a specific source file. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_SOURCECOVERAGEDATAMANAGER_H +#define LLVM_COV_SOURCECOVERAGEDATAMANAGER_H + +#include "FunctionCoverageMapping.h" +#include "llvm/ProfileData/CoverageMapping.h" +#include "llvm/ADT/Hashing.h" +#include +#include + +namespace llvm { + +/// \brief Partions mapping regions by their kind and sums +/// the execution counts of the regions that start at the same location. +class SourceCoverageDataManager { +public: + struct SourceRange { + unsigned LineStart, ColumnStart, LineEnd, ColumnEnd; + + SourceRange(unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, + unsigned ColumnEnd) + : LineStart(LineStart), ColumnStart(ColumnStart), LineEnd(LineEnd), + ColumnEnd(ColumnEnd) {} + + bool operator==(const SourceRange &Other) const { + return LineStart == Other.LineStart && ColumnStart == Other.ColumnStart && + LineEnd == Other.LineEnd && ColumnEnd == Other.ColumnEnd; + } + + bool operator<(const SourceRange &Other) const { + if (LineStart == Other.LineStart) + return ColumnStart < Other.ColumnStart; + return LineStart < Other.LineStart; + } + + bool contains(const SourceRange &Other) { + if (LineStart > Other.LineStart || + (LineStart == Other.LineStart && ColumnStart > Other.ColumnStart)) + return false; + if (LineEnd < Other.LineEnd || + (LineEnd == Other.LineEnd && ColumnEnd < Other.ColumnEnd)) + return false; + return true; + } + }; + +protected: + std::vector> Regions; + std::vector SkippedRegions; + bool Uniqued; + +public: + SourceCoverageDataManager() : Uniqued(false) {} + + void insert(const MappingRegion &Region); + + /// \brief Return the source ranges and execution counts + /// obtained from the non-skipped mapping regions. + ArrayRef> getSourceRegions(); + + /// \brief Return the source ranges obtained from the skipped mapping regions. + ArrayRef getSkippedRegions() const { return SkippedRegions; } +}; + +} // namespace llvm + +#endif // LLVM_COV_SOURCECOVERAGEDATAMANAGER_H diff --git a/tools/llvm-cov/SourceCoverageView.cpp b/tools/llvm-cov/SourceCoverageView.cpp new file mode 100644 index 00000000000..7f33b774ebd --- /dev/null +++ b/tools/llvm-cov/SourceCoverageView.cpp @@ -0,0 +1,411 @@ +//===- 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 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"; +} + +void SourceCoverageView::renderOffset(raw_ostream &OS, unsigned I) { + for (unsigned J = 0; J < I; ++J) + OS << " |"; +} + +void SourceCoverageView::renderViewDivider(unsigned Offset, unsigned Length, + raw_ostream &OS) { + for (unsigned J = 1; J < Offset; ++J) + OS << " |"; + if (Offset != 0) + 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 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"; +} + +/// \brief Insert a new highlighting range into the line's highlighting ranges +/// Return line's new highlighting ranges in result. +static void insertHighlightRange( + ArrayRef Ranges, + SourceCoverageView::HighlightRange RangeToInsert, + SmallVectorImpl &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 &LHS, + const std::unique_ptr &RHS) { + return LHS->ExpansionRegion < RHS->ExpansionRegion; + }); +} + +SourceCoverageView::HighlightRange +SourceCoverageView::getExpansionHighlightRange() const { + return HighlightRange(ExpansionRegion.LineStart, ExpansionRegion.ColumnStart, + ExpansionRegion.ColumnEnd, HighlightRange::Expanded); +} + +template +ArrayRef gatherLineItems(size_t &CurrentIdx, const std::vector &Items, + unsigned LineNo) { + auto PrevIdx = CurrentIdx; + auto E = Items.size(); + while (CurrentIdx < E && Items[CurrentIdx].Line == LineNo) + ++CurrentIdx; + return ArrayRef(Items.data() + PrevIdx, CurrentIdx - PrevIdx); +} + +ArrayRef> +gatherLineSubViews(size_t &CurrentIdx, + ArrayRef> Items, + unsigned LineNo) { + auto PrevIdx = CurrentIdx; + auto E = Items.size(); + while (CurrentIdx < E && + Items[CurrentIdx]->getSubViewsExpansionLine() == LineNo) + ++CurrentIdx; + return ArrayRef>(Items.data() + PrevIdx, + CurrentIdx - PrevIdx); +} + +void SourceCoverageView::render(raw_ostream &OS, unsigned Offset) { + // Make sure that the children are in sorted order. + sortChildren(); + + SmallVector 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() < LineStart) + ++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; I < LineCount; ++I) { + unsigned LineNo = I + LineStart; + + // Gather the child subviews that are visible on this line. + auto LineSubViews = gatherLineSubViews(CurrentChild, Children, LineNo); + + renderOffset(OS, Offset); + 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()) { + renderOffset(OS, Offset); + OS.indent(CombinedColumnWidth); + renderRegionMarkers(OS, LineMarkers); + } + + // Show the line's expanded child subviews. + bool FirstChildExpansion = true; + if (LineSubViews.empty()) + continue; + unsigned NewOffset = Offset + 1; + renderViewDivider(NewOffset, DividerWidth, OS); + OS << "\n"; + for (const auto &Child : LineSubViews) { + // If this subview shows a function instantiation, render the function's + // name. + if (Child->isInstantiationSubView()) { + renderOffset(OS, NewOffset); + 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); + renderOffset(OS, Offset); + OS.indent(CombinedColumnWidth + (Offset == 0 ? 0 : 1)); + renderLine(OS, Line, AdjustedLineHighlightRanges); + renderViewDivider(NewOffset, DividerWidth, OS); + OS << "\n"; + } else + FirstChildExpansion = false; + } + // Render the child subview + Child->render(OS, NewOffset); + renderViewDivider(NewOffset, DividerWidth, OS); + OS << "\n"; + } + } +} + +void +SourceCoverageView::createLineCoverageInfo(SourceCoverageDataManager &Data) { + LineStats.resize(LineCount); + for (const auto &Region : Data.getSourceRegions()) { + auto Value = Region.second; + LineStats[Region.first.LineStart - LineStart].addRegionStartCount(Value); + for (unsigned Line = Region.first.LineStart + 1; + Line <= Region.first.LineEnd; ++Line) + LineStats[Line - LineStart].addRegionCount(Value); + } + + // Reset the line stats for skipped regions. + for (const auto &Region : Data.getSkippedRegions()) { + for (unsigned Line = Region.LineStart; Line <= Region.LineEnd; ++Line) + LineStats[Line - LineStart] = LineCoverageInfo(); + } +} + +void +SourceCoverageView::createHighlightRanges(SourceCoverageDataManager &Data) { + auto Regions = Data.getSourceRegions(); + std::vector AlreadyHighlighted; + AlreadyHighlighted.resize(Regions.size(), false); + + for (size_t I = 0, S = Regions.size(); I < S; ++I) { + const auto &Region = Regions[I]; + auto Value = Region.second; + auto SrcRange = Region.first; + if (Value != 0) + continue; + if (AlreadyHighlighted[I]) + continue; + for (size_t J = 0; J < S; ++J) { + if (SrcRange.contains(Regions[J].first)) { + AlreadyHighlighted[J] = true; + } + } + if (SrcRange.LineStart == SrcRange.LineEnd) { + HighlightRanges.push_back(HighlightRange( + SrcRange.LineStart, SrcRange.ColumnStart, SrcRange.ColumnEnd)); + continue; + } + HighlightRanges.push_back( + HighlightRange(SrcRange.LineStart, SrcRange.ColumnStart, + std::numeric_limits::max())); + HighlightRanges.push_back( + HighlightRange(SrcRange.LineEnd, 1, SrcRange.ColumnEnd)); + for (unsigned Line = SrcRange.LineStart + 1; Line < SrcRange.LineEnd; + ++Line) { + HighlightRanges.push_back( + HighlightRange(Line, 1, std::numeric_limits::max())); + } + } + + std::sort(HighlightRanges.begin(), HighlightRanges.end()); + + if (Options.Debug) { + for (const auto &Range : HighlightRanges) { + outs() << "Highlighted line " << Range.Line << ", " << Range.ColumnStart + << " -> "; + if (Range.ColumnEnd == std::numeric_limits::max()) { + outs() << "?\n"; + } else { + outs() << Range.ColumnEnd << "\n"; + } + } + } +} + +void SourceCoverageView::createRegionMarkers(SourceCoverageDataManager &Data) { + for (const auto &Region : Data.getSourceRegions()) { + if (Region.first.LineStart >= LineStart) + Markers.push_back(RegionMarker(Region.first.LineStart, + Region.first.ColumnStart, Region.second)); + } + + if (Options.Debug) { + for (const auto &Marker : Markers) { + outs() << "Marker at " << Marker.Line << ":" << Marker.Column << " = " + << Marker.ExecutionCount << "\n"; + } + } +} + +void SourceCoverageView::load(SourceCoverageDataManager &Data) { + if (Options.ShowLineStats) + createLineCoverageInfo(Data); + if (Options.Colors) + createHighlightRanges(Data); + if (Options.ShowRegionMarkers) + createRegionMarkers(Data); +} diff --git a/tools/llvm-cov/SourceCoverageView.h b/tools/llvm-cov/SourceCoverageView.h new file mode 100644 index 00000000000..ddfc690c482 --- /dev/null +++ b/tools/llvm-cov/SourceCoverageView.h @@ -0,0 +1,213 @@ +//===- SourceCoverageView.h - 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. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_COV_SOURCECOVERAGEVIEW_H +#define LLVM_COV_SOURCECOVERAGEVIEW_H + +#include "CoverageViewOptions.h" +#include "SourceCoverageDataManager.h" +#include "llvm/ProfileData/CoverageMapping.h" +#include "llvm/Support/MemoryBuffer.h" +#include + +namespace llvm { + +/// \brief A code coverage view of a specific source file. +/// It can have embedded coverage views. +class SourceCoverageView { +public: + enum SubViewKind { View, ExpansionView, InstantiationView }; + + /// \brief Coverage information for a single line. + struct LineCoverageInfo { + uint64_t ExecutionCount; + unsigned RegionCount; + bool Mapped; + + LineCoverageInfo() : ExecutionCount(0), RegionCount(0), Mapped(false) {} + + bool isMapped() const { return Mapped; } + + bool hasMultipleRegions() const { return RegionCount > 1; } + + void addRegionStartCount(uint64_t Count) { + Mapped = true; + ExecutionCount = Count; + ++RegionCount; + } + + void addRegionCount(uint64_t Count) { + Mapped = true; + ExecutionCount = Count; + } + }; + + /// \brief A marker that points at the start + /// of a specific mapping region. + struct RegionMarker { + unsigned Line, Column; + uint64_t ExecutionCount; + + RegionMarker(unsigned Line, unsigned Column, uint64_t Value) + : Line(Line), Column(Column), ExecutionCount(Value) {} + }; + + /// \brief A single line source range used to + /// render highlighted text. + struct HighlightRange { + enum HighlightKind { + /// The code that wasn't executed. + NotCovered, + + /// The region of code that was expanded. + Expanded + }; + HighlightKind Kind; + unsigned Line; + unsigned ColumnStart; + unsigned ColumnEnd; + + HighlightRange(unsigned Line, unsigned ColumnStart, unsigned ColumnEnd, + HighlightKind Kind = NotCovered) + : Kind(Kind), Line(Line), ColumnStart(ColumnStart), + ColumnEnd(ColumnEnd) {} + + bool operator<(const HighlightRange &Other) const { + if (Line == Other.Line) + return ColumnStart < Other.ColumnStart; + return Line < Other.Line; + } + + bool columnStartOverlaps(const HighlightRange &Other) const { + return ColumnStart <= Other.ColumnStart && ColumnEnd > Other.ColumnStart; + } + bool columnEndOverlaps(const HighlightRange &Other) const { + return ColumnEnd >= Other.ColumnEnd && ColumnStart < Other.ColumnEnd; + } + bool contains(const HighlightRange &Other) const { + if (Line != Other.Line) + return false; + return ColumnStart <= Other.ColumnStart && ColumnEnd >= Other.ColumnEnd; + } + + bool overlaps(const HighlightRange &Other) const { + if (Line != Other.Line) + return false; + return columnStartOverlaps(Other) || columnEndOverlaps(Other); + } + }; + +private: + const MemoryBuffer &File; + const CoverageViewOptions &Options; + unsigned LineStart, LineCount; + SubViewKind Kind; + coverage::CounterMappingRegion ExpansionRegion; + std::vector> Children; + std::vector LineStats; + std::vector HighlightRanges; + std::vector Markers; + StringRef FunctionName; + + /// \brief Create the line coverage information using the coverage data. + void createLineCoverageInfo(SourceCoverageDataManager &Data); + + /// \brief Create the line highlighting ranges using the coverage data. + void createHighlightRanges(SourceCoverageDataManager &Data); + + /// \brief Create the region markers using the coverage data. + void createRegionMarkers(SourceCoverageDataManager &Data); + + /// \brief Sort children by the starting location. + void sortChildren(); + + /// \brief Return a highlight range for the expansion region of this view. + HighlightRange getExpansionHighlightRange() const; + + /// \brief Render a source line with highlighting. + void renderLine(raw_ostream &OS, StringRef Line, + ArrayRef Ranges); + + void renderOffset(raw_ostream &OS, unsigned I); + + void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS); + + /// \brief Render the line's execution count column. + void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line); + + /// \brief Render the line number column. + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo); + + /// \brief Render all the region's execution counts on a line. + void renderRegionMarkers(raw_ostream &OS, ArrayRef Regions); + + static const unsigned LineCoverageColumnWidth = 7; + static const unsigned LineNumberColumnWidth = 5; + +public: + SourceCoverageView(const MemoryBuffer &File, + const CoverageViewOptions &Options) + : File(File), Options(Options), LineStart(1), Kind(View), + ExpansionRegion(coverage::Counter(), 0, 0, 0, 0, 0) { + LineCount = File.getBuffer().count('\n') + 1; + } + + SourceCoverageView(const MemoryBuffer &File, + const CoverageViewOptions &Options, unsigned LineStart, + unsigned LineEnd) + : File(File), Options(Options), LineStart(LineStart), + LineCount(LineEnd - LineStart + 1), Kind(View), + ExpansionRegion(coverage::Counter(), 0, 0, 0, 0, 0) {} + + SourceCoverageView(SourceCoverageView &Parent, unsigned LineStart, + unsigned LineEnd, StringRef FunctionName) + : File(Parent.File), Options(Parent.Options), LineStart(LineStart), + LineCount(LineEnd - LineStart + 1), Kind(InstantiationView), + ExpansionRegion(coverage::Counter(), 0, LineEnd, 0, LineEnd, 0), + FunctionName(FunctionName) {} + + SourceCoverageView(const MemoryBuffer &File, + const CoverageViewOptions &Options, unsigned LineStart, + unsigned LineEnd, + const coverage::CounterMappingRegion &ExpansionRegion) + : File(File), Options(Options), LineStart(LineStart), + LineCount(LineEnd - LineStart + 1), Kind(ExpansionView), + ExpansionRegion(ExpansionRegion) {} + + const CoverageViewOptions &getOptions() const { return Options; } + + bool isExpansionSubView() const { return Kind == ExpansionView; } + + bool isInstantiationSubView() const { return Kind == InstantiationView; } + + /// \brief Return the line number after which the subview expansion is shown. + unsigned getSubViewsExpansionLine() const { + return ExpansionRegion.LineStart; + } + + void addChild(std::unique_ptr View) { + Children.push_back(std::move(View)); + } + + /// \brief Print the code coverage information for a specific + /// portion of a source file to the output stream. + void render(raw_ostream &OS, unsigned Offset = 0); + + /// \brief Load the coverage information required for rendering + /// from the mapping regions in the data manager. + void load(SourceCoverageDataManager &Data); +}; + +} // namespace llvm + +#endif // LLVM_COV_SOURCECOVERAGEVIEW_H diff --git a/tools/llvm-cov/TestingSupport.cpp b/tools/llvm-cov/TestingSupport.cpp new file mode 100644 index 00000000000..8f580cf64f8 --- /dev/null +++ b/tools/llvm-cov/TestingSupport.cpp @@ -0,0 +1,92 @@ +//===- TestingSupport.cpp - Convert objects files into test files --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/LEB128.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/MemoryObject.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/PrettyStackTrace.h" +#include +#include + +using namespace llvm; +using namespace object; + +int convert_for_testing_main(int argc, const char **argv) { + sys::PrintStackTraceOnErrorSignal(); + PrettyStackTraceProgram X(argc, argv); + llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. + + cl::opt InputSourceFile(cl::Positional, cl::Required, + cl::desc("")); + + cl::opt OutputFilename( + "o", cl::Required, + cl::desc( + "File with the profile data obtained after an instrumented run")); + + cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); + + auto ObjErr = llvm::object::ObjectFile::createObjectFile(InputSourceFile); + if (auto Err = ObjErr.getError()) { + errs() << "error: " << Err.message() << "\n"; + return 1; + } + ObjectFile *OF = ObjErr.get().getBinary().get(); + auto BytesInAddress = OF->getBytesInAddress(); + if (BytesInAddress != 8) { + errs() << "error: 64 bit binary expected\n"; + return 1; + } + + // Look for the sections that we are interested in. + int FoundSectionCount = 0; + SectionRef ProfileNames, CoverageMapping; + for (const auto &Section : OF->sections()) { + StringRef Name; + if (Section.getName(Name)) + return 1; + if (Name == "__llvm_prf_names") { + ProfileNames = Section; + } else if (Name == "__llvm_covmap") { + CoverageMapping = Section; + } else + continue; + ++FoundSectionCount; + } + if (FoundSectionCount != 2) + return 1; + + // Get the contents of the given sections. + StringRef CoverageMappingData; + uint64_t ProfileNamesAddress; + StringRef ProfileNamesData; + if (CoverageMapping.getContents(CoverageMappingData) || + ProfileNames.getAddress(ProfileNamesAddress) || + ProfileNames.getContents(ProfileNamesData)) + return 1; + + int FD; + if (auto Err = + sys::fs::openFileForWrite(OutputFilename, FD, sys::fs::F_None)) { + errs() << "error: " << Err.message() << "\n"; + return 1; + } + + raw_fd_ostream OS(FD, true); + OS << "llvmcovmtestdata"; + encodeULEB128(ProfileNamesData.size(), OS); + encodeULEB128(ProfileNamesAddress, OS); + OS << ProfileNamesData << CoverageMappingData; + + return 0; +} diff --git a/tools/llvm-cov/llvm-cov.cpp b/tools/llvm-cov/llvm-cov.cpp index 75fa392d6f7..9c9cdef1659 100644 --- a/tools/llvm-cov/llvm-cov.cpp +++ b/tools/llvm-cov/llvm-cov.cpp @@ -11,9 +11,63 @@ // //===----------------------------------------------------------------------===// -/// \brief The main function for the gcov compatible coverage tool +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/Path.h" +#include + +using namespace llvm; + +/// \brief The main entry point for the 'show' subcommand. +int show_main(int argc, const char **argv); + +/// \brief The main entry point for the 'report' subcommand. +int report_main(int argc, const char **argv); + +/// \brief The main entry point for the 'convert-for-testing' subcommand. +int convert_for_testing_main(int argc, const char **argv); + +/// \brief The main entry point for the gcov compatible coverage tool. int gcov_main(int argc, const char **argv); int main(int argc, const char **argv) { + // If argv[0] is or ends with 'gcov', always be gcov compatible + if (sys::path::stem(argv[0]).endswith_lower("gcov")) + return gcov_main(argc, argv); + + // Check if we are invoking a specific tool command. + if (argc > 1) { + int (*func)(int, const char **) = nullptr; + + StringRef command = argv[1]; + if (command.equals_lower("show")) + func = show_main; + else if (command.equals_lower("report")) + func = report_main; + else if (command.equals_lower("convert-for-testing")) + func = convert_for_testing_main; + else if (command.equals_lower("gcov")) + func = gcov_main; + + if (func) { + std::string Invocation(std::string(argv[0]) + " " + argv[1]); + argv[1] = Invocation.c_str(); + return func(argc - 1, argv + 1); + } + } + + // Give a warning and fall back to gcov + errs().changeColor(raw_ostream::RED); + errs() << "warning:"; + // Assume that argv[1] wasn't a command when it stats with a '-' or is a + // filename (i.e. contains a '.') + if (argc > 1 && !StringRef(argv[1]).startswith("-") && + StringRef(argv[1]).find(".") == StringRef::npos) + errs() << " Unrecognized command '" << argv[1] << "'."; + errs() << " Using the gcov compatible mode " + "(this behaviour may be dropped in the future)."; + errs().resetColor(); + errs() << "\n"; + return gcov_main(argc, argv); }