From 3605f8423f59b78f67f4a77b2f0f5893825f3dd7 Mon Sep 17 00:00:00 2001 From: Devang Patel Date: Wed, 28 Sep 2011 18:50:00 +0000 Subject: [PATCH] Introduce llvm-cov. Add llvm-cov skeleton. It has initial support to read coverage info generated by GCOVProfiling.cpp. Today, you can do prompt> clang a.c -ftest-coverage -fprofile-arcs -o a prompt> ./a prompt> llvm-cov -gcno a.gcno -gcda a.gcda a.c : #include "a.h" : : int main() { : int i = 0; : if (i) { 1: int j = 0; 1: j = 1; 1: } else { : int k = 1; : k = 2; : } 1: return 0; : } : : llvm-svn: 140712 --- tools/Makefile | 2 +- tools/llvm-cov/CMakeLists.txt | 6 + tools/llvm-cov/GCOVReader.cpp | 276 ++++++++++++++++++++++++++++++++++ tools/llvm-cov/GCOVReader.h | 225 +++++++++++++++++++++++++++ tools/llvm-cov/Makefile | 18 +++ tools/llvm-cov/llvm-cov.cpp | 83 ++++++++++ 6 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 tools/llvm-cov/CMakeLists.txt create mode 100644 tools/llvm-cov/GCOVReader.cpp create mode 100644 tools/llvm-cov/GCOVReader.h create mode 100644 tools/llvm-cov/Makefile create mode 100644 tools/llvm-cov/llvm-cov.cpp diff --git a/tools/Makefile b/tools/Makefile index abb6e85dd3c..62779be9d28 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -26,7 +26,7 @@ PARALLEL_DIRS := opt llvm-as llvm-dis \ lli llvm-extract llvm-mc \ bugpoint llvm-bcanalyzer llvm-stub \ llvm-diff macho-dump llvm-objdump \ - llvm-rtdyld llvm-dwarfdump + llvm-rtdyld llvm-dwarfdump llvm-cov # Let users override the set of tools to build from the command line. ifdef ONLY_TOOLS diff --git a/tools/llvm-cov/CMakeLists.txt b/tools/llvm-cov/CMakeLists.txt new file mode 100644 index 00000000000..fe824d010cc --- /dev/null +++ b/tools/llvm-cov/CMakeLists.txt @@ -0,0 +1,6 @@ +set(LLVM_LINK_COMPONENTS instrumentation ) + +add_llvm_tool(llvm-cov + GCOVReader.cpp + llvm-cov.cpp + ) diff --git a/tools/llvm-cov/GCOVReader.cpp b/tools/llvm-cov/GCOVReader.cpp new file mode 100644 index 00000000000..a8466cf601c --- /dev/null +++ b/tools/llvm-cov/GCOVReader.cpp @@ -0,0 +1,276 @@ +//===- tools/llvm-cov/GCOVReader.cpp - LLVM coverage tool -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// GCOVReader implements the interface to read coverage files that use 'gcov' +// format. +// +// +//===----------------------------------------------------------------------===// + +#include "GCOVReader.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/Support/MemoryObject.h" +#include "llvm/Support/system_error.h" +using namespace llvm; + +//===----------------------------------------------------------------------===// +// GCOVFile implementation. + +/// ~GCOVFile - Delete GCOVFile and its content. +GCOVFile::~GCOVFile() { + DeleteContainerPointers(Functions); +} + +/// read - Read GCOV buffer. +bool GCOVFile::read(GCOVBuffer &Buffer) { + Format = Buffer.readGCOVFormat(); + if (Format == InvalidGCOV) + return false; + + unsigned i = 0; + while(1) { + GCOVFunction *GFun = NULL; + if(Format == GCDA_402 || Format == GCDA_404) { + if (i < Functions.size()) + GFun = Functions[i]; + } else + GFun = new GCOVFunction(); + + if (GFun && GFun->read(Buffer, Format)) { + if(Format == GCNO_402 || Format == GCNO_404) + Functions.push_back(GFun); + } + else { + delete GFun; + break; + } + ++i; + } + return true; +} + +/// dump - Dump GCOVFile content on standard out for debugging purposes. +void GCOVFile::dump() { + for (SmallVector::iterator I = Functions.begin(), + E = Functions.end(); I != E; ++I) + (*I)->dump(); +} + +/// collectLineCounts - Collect line counts. This must be used after +/// reading .gcno and .gcda files. +void GCOVFile::collectLineCounts(FileInfo &FI) { + for (SmallVector::iterator I = Functions.begin(), + E = Functions.end(); I != E; ++I) + (*I)->collectLineCounts(FI); + FI.print(); +} + +//===----------------------------------------------------------------------===// +// GCOVFunction implementation. + +/// ~GCOVFunction - Delete GCOVFunction and its content. +GCOVFunction::~GCOVFunction() { + DeleteContainerPointers(Blocks); +} + +/// read - Read a aunction from the buffer. Return false if buffer cursor +/// does not point to a function tag. +bool GCOVFunction::read(GCOVBuffer &Buff, GCOVFormat Format) { + if (!Buff.readFunctionTag()) + return false; + + Buff.readInt(); // Function header length + Ident = Buff.readInt(); + Buff.readInt(); // Checksum #1 + if (Format != GCNO_402) + Buff.readInt(); // Checksum #2 + + Name = Buff.readString(); + if(Format == GCNO_402 || Format == GCNO_404) + Filename = Buff.readString(); + + if(Format == GCDA_402 || Format == GCDA_404) { + Buff.readArcTag(); + uint32_t Count = Buff.readInt() / 2; + for (unsigned i = 0, e = Count; i != e; ++i) { + Blocks[i]->addCount(Buff.readInt64()); + } + return true;; + } + + LineNumber = Buff.readInt(); + + // read blocks. + assert (Buff.readBlockTag() && "Block Tag not found!"); + uint32_t BlockCount = Buff.readInt(); + for (int i = 0, e = BlockCount; i != e; ++i) { + Buff.readInt(); // Block flags; + Blocks.push_back(new GCOVBlock(i)); + } + + // read edges. + while (Buff.readEdgeTag()) { + uint32_t EdgeCount = (Buff.readInt() - 1) / 2; + uint32_t BlockNo = Buff.readInt(); + assert (BlockNo < BlockCount && "Unexpected Block number!"); + for (int i = 0, e = EdgeCount; i != e; ++i) { + Blocks[BlockNo]->addEdge(Buff.readInt()); + Buff.readInt(); // Edge flag + } + } + + // read line table. + while (Buff.readLineTag()) { + uint32_t LineTableLength = Buff.readInt(); + uint32_t Size = Buff.getCursor() + LineTableLength*4; + uint32_t BlockNo = Buff.readInt(); + assert (BlockNo < BlockCount && "Unexpected Block number!"); + GCOVBlock *Block = Blocks[BlockNo]; + Buff.readInt(); // flag + while (Buff.getCursor() != (Size - 4)) { + StringRef Filename = Buff.readString(); + if (Buff.getCursor() == (Size - 4)) break; + while (uint32_t L = Buff.readInt()) + Block->addLine(Filename, L); + } + Buff.readInt(); // flag + } + return true; +} + +/// dump - Dump GCOVFunction content on standard out for debugging purposes. +void GCOVFunction::dump() { + outs() << "===== " << Name << " @ " << Filename << ":" << LineNumber << "\n"; + for (SmallVector::iterator I = Blocks.begin(), + E = Blocks.end(); I != E; ++I) + (*I)->dump(); +} + +/// collectLineCounts - Collect line counts. This must be used after +/// reading .gcno and .gcda files. +void GCOVFunction::collectLineCounts(FileInfo &FI) { + for (SmallVector::iterator I = Blocks.begin(), + E = Blocks.end(); I != E; ++I) + (*I)->collectLineCounts(FI); +} + +//===----------------------------------------------------------------------===// +// GCOVBlock implementation. + +/// ~GCOVBlock - Delete GCOVBlock and its content. +GCOVBlock::~GCOVBlock() { + Edges.clear(); + DeleteContainerSeconds(Lines); +} + +void GCOVBlock::addLine(StringRef Filename, uint32_t LineNo) { + GCOVLines *&LinesForFile = Lines[Filename]; + if (!LinesForFile) + LinesForFile = new GCOVLines(); + LinesForFile->add(LineNo); +} + +/// collectLineCounts - Collect line counts. This must be used after +/// reading .gcno and .gcda files. +void GCOVBlock::collectLineCounts(FileInfo &FI) { + for (StringMap::iterator I = Lines.begin(), + E = Lines.end(); I != E; ++I) + I->second->collectLineCounts(FI, I->first(), Counter); +} + +/// dump - Dump GCOVBlock content on standard out for debugging purposes. +void GCOVBlock::dump() { + outs() << "Block : " << Number << " Counter : " << Counter << "\n"; + if (!Edges.empty()) { + outs() << "\tEdges : "; + for (SmallVector::iterator I = Edges.begin(), E = Edges.end(); + I != E; ++I) + outs() << (*I) << ","; + outs() << "\n"; + } + if (!Lines.empty()) { + outs() << "\tLines : "; + for (StringMap::iterator LI = Lines.begin(), + LE = Lines.end(); LI != LE; ++LI) { + outs() << LI->first() << " -> "; + LI->second->dump(); + outs() << "\n"; + } + } +} + +//===----------------------------------------------------------------------===// +// GCOVLines implementation. + +/// collectLineCounts - Collect line counts. This must be used after +/// reading .gcno and .gcda files. +void GCOVLines::collectLineCounts(FileInfo &FI, StringRef Filename, + uint32_t Count) { + for (SmallVector::iterator I = Lines.begin(), + E = Lines.end(); I != E; ++I) + FI.addLineCount(Filename, *I, Count); +} + +/// dump - Dump GCOVLines content on standard out for debugging purposes. +void GCOVLines::dump() { + for (SmallVector::iterator I = Lines.begin(), + E = Lines.end(); I != E; ++I) + outs() << (*I) << ","; +} + +//===----------------------------------------------------------------------===// +// FileInfo implementation. + +/// addLineCount - Add line count for the given line number in a file. +void FileInfo::addLineCount(StringRef Filename, uint32_t Line, uint32_t Count) { + if (LineInfo.find(Filename) == LineInfo.end()) { + OwningPtr Buff; + if (error_code ec = MemoryBuffer::getFileOrSTDIN(Filename, Buff)) { + errs() << Filename << ": " << ec.message() << "\n"; + return; + } + StringRef AllLines = Buff.take()->getBuffer(); + LineCounts L(AllLines.count('\n')+2); + L[Line-1] = Count; + LineInfo[Filename] = L; + return; + } + LineCounts &L = LineInfo[Filename]; + L[Line-1] = Count; +} + +/// print - Print source files with collected line count information. +void FileInfo::print() { + for (StringMap::iterator I = LineInfo.begin(), E = LineInfo.end(); + I != E; ++I) { + StringRef Filename = I->first(); + outs() << Filename << "\n"; + LineCounts &L = LineInfo[Filename]; + OwningPtr Buff; + if (error_code ec = MemoryBuffer::getFileOrSTDIN(Filename, Buff)) { + errs() << Filename << ": " << ec.message() << "\n"; + return; + } + StringRef AllLines = Buff.take()->getBuffer(); + for (unsigned i = 0, e = L.size(); i != e; ++i) { + if (L[i]) + outs() << L[i] << ":\t"; + else + outs() << " :\t"; + std::pair P = AllLines.split('\n'); + if (AllLines != P.first) + outs() << P.first; + outs() << "\n"; + AllLines = P.second; + } + } +} + + diff --git a/tools/llvm-cov/GCOVReader.h b/tools/llvm-cov/GCOVReader.h new file mode 100644 index 00000000000..5dfc75f152e --- /dev/null +++ b/tools/llvm-cov/GCOVReader.h @@ -0,0 +1,225 @@ +//===-- tools/cov/GCOVReader.h - LLVM coverage tool -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This header provides the interface to read coverage files that use 'gcov' +// format. +// +//===----------------------------------------------------------------------===// + +#ifndef GCOVREADER_H +#define GCOVREADER_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +class GCOVFunction; +class GCOVBlock; +class GCOVLines; +class FileInfo; + +enum GCOVFormat { + InvalidGCOV, + GCNO_402, + GCNO_404, + GCDA_402, + GCDA_404 +}; + +/// GCOVBuffer - A wrapper around MemoryBuffer to provid GCOV specific +/// read operations. +class GCOVBuffer { +public: + GCOVBuffer(MemoryBuffer *B) : Buffer(B), Cursor(0) {} + + /// readGCOVFormat - Read GCOV signature at the beginning of buffer. + enum GCOVFormat readGCOVFormat() { + StringRef Magic = Buffer->getBuffer().slice(0, 12); + Cursor = 12; + if (Magic == "oncg*404MVLL") + return GCNO_404; + else if (Magic == "oncg*204MVLL") + return GCNO_402; + else if (Magic == "adcg*404MVLL") + return GCDA_404; + else if (Magic == "adcg*204MVLL") + return GCDA_402; + + Cursor = 0; + return InvalidGCOV; + } + + /// readFunctionTag - If cursor points to a function tag then increment the + /// cursor and return true otherwise return false. + bool readFunctionTag() { + StringRef Tag = Buffer->getBuffer().slice(Cursor, Cursor+4); + if (Tag.empty() || + Tag[0] != '\0' || Tag[1] != '\0' || + Tag[2] != '\0' || Tag[3] != '\1') { + return false; + } + Cursor += 4; + return true; + } + + /// readBlockTag - If cursor points to a block tag then increment the + /// cursor and return true otherwise return false. + bool readBlockTag() { + StringRef Tag = Buffer->getBuffer().slice(Cursor, Cursor+4); + if (Tag.empty() || + Tag[0] != '\0' || Tag[1] != '\0' || + Tag[2] != '\x41' || Tag[3] != '\x01') { + return false; + } + Cursor += 4; + return true; + } + + /// readEdgeTag - If cursor points to an edge tag then increment the + /// cursor and return true otherwise return false. + bool readEdgeTag() { + StringRef Tag = Buffer->getBuffer().slice(Cursor, Cursor+4); + if (Tag.empty() || + Tag[0] != '\0' || Tag[1] != '\0' || + Tag[2] != '\x43' || Tag[3] != '\x01') { + return false; + } + Cursor += 4; + return true; + } + + /// readLineTag - If cursor points to a line tag then increment the + /// cursor and return true otherwise return false. + bool readLineTag() { + StringRef Tag = Buffer->getBuffer().slice(Cursor, Cursor+4); + if (Tag.empty() || + Tag[0] != '\0' || Tag[1] != '\0' || + Tag[2] != '\x45' || Tag[3] != '\x01') { + return false; + } + Cursor += 4; + return true; + } + + /// readArcTag - If cursor points to an gcda arc tag then increment the + /// cursor and return true otherwise return false. + bool readArcTag() { + StringRef Tag = Buffer->getBuffer().slice(Cursor, Cursor+4); + if (Tag.empty() || + Tag[0] != '\0' || Tag[1] != '\0' || + Tag[2] != '\xa1' || Tag[3] != '\1') { + return false; + } + Cursor += 4; + return true; + } + + uint32_t readInt() { + uint32_t Result; + StringRef Str = Buffer->getBuffer().slice(Cursor, Cursor+4); + assert (Str.empty() == false && "Unexpected memory buffer end!"); + Cursor += 4; + Result = *(uint32_t *)(Str.data()); + return Result; + } + + uint64_t readInt64() { + uint64_t Lo = readInt(); + uint64_t Hi = readInt(); + uint64_t Result = Lo | (Hi << 32); + return Result; + } + + StringRef readString() { + uint32_t Len = readInt() * 4; + StringRef Str = Buffer->getBuffer().slice(Cursor, Cursor+Len); + Cursor += Len; + return Str; + } + + uint64_t getCursor() const { return Cursor; } +private: + MemoryBuffer *Buffer; + uint64_t Cursor; +}; + +/// GCOVFile - Collects file information. +class GCOVFile { +public: + GCOVFile() : Format(InvalidGCOV) {} + ~GCOVFile(); + bool read(GCOVBuffer &Buffer); + void dump(); + void collectLineCounts(FileInfo &FI); +private: + enum GCOVFormat Format; + SmallVector Functions; +}; + +/// GCOVFunction - Collects function information. +class GCOVFunction { +public: + GCOVFunction() : Ident(0), LineNumber(0) {} + ~GCOVFunction(); + bool read(GCOVBuffer &Buffer, GCOVFormat Format); + void dump(); + void collectLineCounts(FileInfo &FI); +private: + uint32_t Ident; + uint32_t LineNumber; + StringRef Name; + StringRef Filename; + SmallVector Blocks; +}; + +/// GCOVBlock - Collects block information. +class GCOVBlock { +public: + GCOVBlock(uint32_t N) : Number(N), Counter(0) {} + ~GCOVBlock(); + void addEdge(uint32_t N) { Edges.push_back(N); } + void addLine(StringRef Filename, uint32_t LineNo); + void addCount(uint64_t N) { Counter = N; } + void dump(); + void collectLineCounts(FileInfo &FI); +private: + uint32_t Number; + uint64_t Counter; + SmallVector Edges; + StringMap Lines; +}; + +/// GCOVLines - A wrapper around a vector of int to keep track of line nos. +class GCOVLines { +public: + ~GCOVLines() { Lines.clear(); } + void add(uint32_t N) { Lines.push_back(N); } + void collectLineCounts(FileInfo &FI, StringRef Filename, uint32_t Count); + void dump(); + +private: + SmallVector Lines; +}; + +typedef SmallVector LineCounts; +class FileInfo { +public: + void addLineCount(StringRef Filename, uint32_t Line, uint32_t Count); + void print(); +private: + StringMap LineInfo; +}; + + +} + +#endif diff --git a/tools/llvm-cov/Makefile b/tools/llvm-cov/Makefile new file mode 100644 index 00000000000..63696905f1d --- /dev/null +++ b/tools/llvm-cov/Makefile @@ -0,0 +1,18 @@ +##===- tools/llvm-gcov/Makefile ----------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +LEVEL = ../.. + +TOOLNAME = llvm-cov +LINK_COMPONENTS := instrumentation + +# This tool has no plugins, optimize startup time. +TOOL_NO_EXPORTS = 1 + +include $(LEVEL)/Makefile.common diff --git a/tools/llvm-cov/llvm-cov.cpp b/tools/llvm-cov/llvm-cov.cpp new file mode 100644 index 00000000000..466deff7660 --- /dev/null +++ b/tools/llvm-cov/llvm-cov.cpp @@ -0,0 +1,83 @@ +//===- tools/llvm-cov/llvm-cov.cpp - LLVM coverage tool -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// +// llvm-cov is a command line tools to analyze and report coverage information. +// +// +//===----------------------------------------------------------------------===// + +#include "GCOVReader.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/MemoryObject.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/system_error.h" +using namespace llvm; + +static cl::opt +DumpGCOV("dump", cl::init(false), cl::desc("dump gcov file")); + +static cl::opt +InputGCNO("gcno", cl::desc(""), cl::init("")); + +static cl::opt +InputGCDA("gcda", cl::desc(""), cl::init("")); + + +//===----------------------------------------------------------------------===// +int main(int argc, 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::ParseCommandLineOptions(argc, argv, "llvm cov\n"); + + + GCOVFile GF; + if (InputGCNO.empty()) + errs() << " " << argv[0] << ": No gcov input file!\n"; + + OwningPtr Buff; + if (error_code ec = MemoryBuffer::getFileOrSTDIN(InputGCNO, Buff)) { + errs() << InputGCNO << ": " << ec.message() << "\n"; + return 1; + } + GCOVBuffer GB(Buff.take()); + + if (!GF.read(GB)) { + errs() << "Invalid .gcno File!\n"; + return 1; + } + + if (!InputGCDA.empty()) { + OwningPtr Buff2; + if (error_code ec = MemoryBuffer::getFileOrSTDIN(InputGCDA, Buff2)) { + errs() << InputGCDA << ": " << ec.message() << "\n"; + return 1; + } + GCOVBuffer GB2(Buff2.take()); + + if (!GF.read(GB2)) { + errs() << "Invalid .gcda File!\n"; + return 1; + } + } + + + if (DumpGCOV) + GF.dump(); + + FileInfo FI; + GF.collectLineCounts(FI); + return 0; +}