Index: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h =================================================================== --- include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -269,6 +269,9 @@ /// \sa shouldDisplayNotesAsEvents Optional DisplayNotesAsEvents; + /// \sa shouldRecordCoverage + Optional CoverageExportDir; + /// A helper function that retrieves option for a given full-qualified /// checker name. /// Options for checkers can be specified via 'analyzer-config' command-line @@ -545,6 +548,10 @@ /// to false when unset. bool shouldDisplayNotesAsEvents(); + /// Determines where the coverage info should be dumped to. The coverage + /// information is recorded on the basic block level granularity. + StringRef coverageExportDir(); + public: AnalyzerOptions() : AnalysisStoreOpt(RegionStoreModel), Index: lib/StaticAnalyzer/Core/AnalyzerOptions.cpp =================================================================== --- lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -351,3 +351,10 @@ getBooleanOption("notes-as-events", /*Default=*/false); return DisplayNotesAsEvents.getValue(); } + +StringRef AnalyzerOptions::coverageExportDir() { + if (!CoverageExportDir.hasValue()) + CoverageExportDir = getOptionAsString("record-coverage", /*Default=*/""); + return CoverageExportDir.getValue(); +} + Index: lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- lib/StaticAnalyzer/Core/ExprEngine.cpp +++ lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -28,8 +28,13 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" #include "llvm/ADT/Statistic.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/Path.h" #include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/raw_os_ostream.h" +#include #ifndef NDEBUG #include "llvm/Support/GraphWriter.h" @@ -251,6 +256,45 @@ return State; } +// Mapping from file to line indexed hit count vector. +static llvm::DenseMap> CoverageInfo; + +static void dumpCoverageInfo(llvm::SmallVectorImpl &Path, + SourceManager &SM) { + for (auto &Entry : CoverageInfo) { + SmallString<128> FilePath; + const FileEntry *FE = Entry.getFirst(); + llvm::sys::path::append(FilePath, Path, FE->getName() + ".gcov"); + SmallString<128> DirPath = FilePath; + llvm::sys::path::remove_filename(DirPath); + llvm::sys::fs::create_directories(DirPath); + bool Invalid = false; + llvm::MemoryBuffer *Buf = SM.getMemoryBufferForFile(FE, &Invalid); + if (Invalid) + continue; + std::ofstream OutFile(FilePath.c_str()); + if (!OutFile) { + llvm::errs() << FilePath << " Fuck!\n"; + continue; + } + llvm::raw_os_ostream Out(OutFile); + Out << "-:0:Source:" << FE->getName() << '\n'; + Out << "-:0:Runs:1\n"; + Out << "-:0:Programs:1\n"; + for (llvm::line_iterator LI(*Buf, false); !LI.is_at_eof(); ++LI) { + int Count = Entry.getSecond()[LI.line_number() - 1]; + if (Count > 0) { + Out << Count; + } else if (Count < 0) { + Out << "#####"; + } else { + Out << '-'; + } + Out << ':' << LI.line_number() << ':' << *LI << '\n'; + } + } +} + //===----------------------------------------------------------------------===// // Top-level transfer function logic (Dispatcher). //===----------------------------------------------------------------------===// @@ -282,6 +326,12 @@ } void ExprEngine::processEndWorklist(bool hasWorkRemaining) { + if (!AMgr.options.coverageExportDir().empty()) { + SmallString<128> Path = AMgr.options.coverageExportDir(); + SourceManager &SM = getContext().getSourceManager(); + SM.getFileManager().makeAbsolutePath(Path); + dumpCoverageInfo(Path, SM); + } getCheckerManager().runCheckersForEndAnalysis(G, BR, *this); } @@ -1409,12 +1459,82 @@ return true; } +// Add the line range of the CFGBlock to a file entry indexed map. +static void processCoverageInfo(const CFGBlock &Block, SourceManager &SM, + bool Unexecuted = false) { + llvm::SmallVector LinesInBlock; + const FileEntry *FE = nullptr; + for (unsigned I = 0; I < Block.size(); ++I) { + const Stmt *S = nullptr; + switch (Block[I].getKind()) { + case CFGElement::Statement: + S = Block[I].castAs().getStmt(); + break; + case CFGElement::Initializer: + S = Block[I].castAs().getInitializer()->getInit(); + if (!S) + continue; + break; + case CFGElement::NewAllocator: + S = Block[I].castAs().getAllocatorExpr(); + break; + default: + continue; + } + assert(S); + SourceLocation SpellingStartLoc = SM.getSpellingLoc(S->getLocStart()); + if (SM.isInSystemHeader(SpellingStartLoc)) + return; + FileID FID = SM.getFileID(SpellingStartLoc); + if (FE) { + if (FE != SM.getFileEntryForID(FID)) + continue; + } else { + FE = SM.getFileEntryForID(FID); + if (CoverageInfo.find(FE) == CoverageInfo.end()) { + unsigned Lines = SM.getSpellingLineNumber(SM.getLocForEndOfFile(FID)); + CoverageInfo.insert(std::make_pair(FE, std::vector(Lines, 0))); + } + } + bool Invalid = false; + unsigned LineBegin = SM.getSpellingLineNumber(S->getLocStart(), &Invalid); + if (Invalid) + continue; + unsigned LineEnd = SM.getSpellingLineNumber(S->getLocEnd(), &Invalid); + if (Invalid) + continue; + for (unsigned Line = LineBegin; Line <= LineEnd; ++Line) { + LinesInBlock.push_back(Line); + } + } + if (!FE) + return; + std::sort(LinesInBlock.begin(), LinesInBlock.end()); + LinesInBlock.erase(std::unique(LinesInBlock.begin(), LinesInBlock.end()), + LinesInBlock.end()); + std::vector &FileCov = CoverageInfo[FE]; + if (Unexecuted) { + for (unsigned Line : LinesInBlock) + FileCov[Line - 1] = -1; + return; + } + for (unsigned Line : LinesInBlock) { + if (FileCov[Line - 1] < 0) + FileCov[Line - 1] = 1; + else + ++FileCov[Line - 1]; + } +} + /// Block entrance. (Update counters). void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, NodeBuilderWithSinks &nodeBuilder, ExplodedNode *Pred) { PrettyStackTraceLocationContext CrashInfo(Pred->getLocationContext()); + if (!AMgr.options.coverageExportDir().empty()) + processCoverageInfo(*L.getDst(), getContext().getSourceManager()); + // If this block is terminated by a loop and it has already been visited the // maximum number of times, widen the loop. unsigned int BlockCount = nodeBuilder.getContext().blockCount(); @@ -1760,6 +1880,10 @@ ExplodedNode *Pred, ExplodedNodeSet &Dst, const BlockEdge &L) { + if (!AMgr.options.coverageExportDir().empty()) { + for (auto BlockIT : *L.getLocationContext()->getCFG()) + processCoverageInfo(*BlockIT, getContext().getSourceManager(), true); + } SaveAndRestore NodeContextRAII(currBldrCtx, &BC); getCheckerManager().runCheckersForBeginFunction(Dst, L, Pred, *this); } Index: test/Analysis/analyzer-config.c =================================================================== --- test/Analysis/analyzer-config.c +++ test/Analysis/analyzer-config.c @@ -24,8 +24,9 @@ // CHECK-NEXT: max-times-inline-large = 32 // CHECK-NEXT: min-cfg-size-treat-functions-as-large = 14 // CHECK-NEXT: mode = deep +// CHECK-NEXT: record-coverage = // CHECK-NEXT: region-store-small-struct-limit = 2 // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 15 +// CHECK-NEXT: num-entries = 16 Index: test/Analysis/analyzer-config.cpp =================================================================== --- test/Analysis/analyzer-config.cpp +++ test/Analysis/analyzer-config.cpp @@ -35,7 +35,8 @@ // CHECK-NEXT: max-times-inline-large = 32 // CHECK-NEXT: min-cfg-size-treat-functions-as-large = 14 // CHECK-NEXT: mode = deep +// CHECK-NEXT: record-coverage = // CHECK-NEXT: region-store-small-struct-limit = 2 // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 20 +// CHECK-NEXT: num-entries = 21 Index: test/Analysis/record-coverage.cpp =================================================================== --- /dev/null +++ test/Analysis/record-coverage.cpp @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=core -analyzer-config record-coverage=%T %s +// RUN: FileCheck -input-file %T/%s.gcov %s.expected + +int main() { + int i = 2; + ++i; + if (i != 0) { + ++i; + } else { + --i; + } +} Index: test/Analysis/record-coverage.cpp.expected =================================================================== --- /dev/null +++ test/Analysis/record-coverage.cpp.expected @@ -0,0 +1,9 @@ +// CHECK: -:4:int main() { +// CHECK-NEXT: 1:5: int i = 2; +// CHECK-NEXT: 1:6: ++i; +// CHECK-NEXT: 1:7: if (i != 0) { +// CHECK-NEXT: 1:8: ++i; +// CHECK-NEXT: -:9: } else { +// CHECK-NEXT: #####:10: --i; +// CHECK-NEXT: -:11: } +// CHECK-NEXT: -:12:}