Index: include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h =================================================================== --- include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -733,6 +735,9 @@ void Profile(llvm::FoldingSetNodeID &ID) const override; }; +/// File IDs mapped to sets of line numbers. +typedef std::map> FilesToLineNumsMap; + /// PathDiagnostic - PathDiagnostic objects represent a single path-sensitive /// diagnostic. It represents an ordered-collection of PathDiagnosticPieces, /// each which represent the pieces of the path. @@ -756,12 +761,16 @@ PathDiagnosticLocation UniqueingLoc; const Decl *UniqueingDecl; + /// Lines executed in the path. + std::unique_ptr ExecutedLines; + PathDiagnostic() = delete; public: PathDiagnostic(StringRef CheckName, const Decl *DeclWithIssue, StringRef bugtype, StringRef verboseDesc, StringRef shortDesc, StringRef category, PathDiagnosticLocation LocationToUnique, - const Decl *DeclToUnique); + const Decl *DeclToUnique, + std::unique_ptr ExecutedLines); ~PathDiagnostic(); @@ -830,6 +839,12 @@ meta_iterator meta_end() const { return OtherDesc.end(); } void addMeta(StringRef s) { OtherDesc.push_back(s); } + typedef FilesToLineNumsMap::const_iterator filesmap_iterator; + filesmap_iterator executedLines_begin() const { + return ExecutedLines->begin(); + } + filesmap_iterator executedLines_end() const { return ExecutedLines->end(); } + PathDiagnosticLocation getLocation() const { assert(Loc.isValid() && "No report location set yet!"); return Loc; Index: lib/Rewrite/HTMLRewrite.cpp =================================================================== --- lib/Rewrite/HTMLRewrite.cpp +++ lib/Rewrite/HTMLRewrite.cpp @@ -210,9 +210,9 @@ SmallString<256> Str; llvm::raw_svector_ostream OS(Str); - OS << "" - << LineNo << ""; + OS << "" + << "" << LineNo + << ""; if (B == E) { // Handle empty lines. OS << " "; @@ -263,7 +263,10 @@ } // Add one big table tag that surrounds all of the code. - RB.InsertTextBefore(0, "\n"); + std::string s; + llvm::raw_string_ostream os(s); + os << "
\n"; + RB.InsertTextBefore(0, os.str()); RB.InsertTextAfter(FileEnd - FileBeg, "
"); } Index: lib/StaticAnalyzer/Core/BugReporter.cpp =================================================================== --- lib/StaticAnalyzer/Core/BugReporter.cpp +++ lib/StaticAnalyzer/Core/BugReporter.cpp @@ -3509,6 +3509,66 @@ } } +/// Insert all lines participating in the function signature \p Signature +/// into \p ExecutedLines. +static void populateExecutedLinesWithFunctionSignature( + const Decl *Signature, SourceManager &SM, + std::unique_ptr &ExecutedLines) { + + SourceRange SignatureSourceRange; + const Stmt* Body = Signature->getBody(); + if (auto FD = dyn_cast(Signature)) { + SignatureSourceRange = FD->getSourceRange(); + } else if (auto OD = dyn_cast(Signature)) { + SignatureSourceRange = OD->getSourceRange(); + } else { + return; + } + SourceLocation Start = SignatureSourceRange.getBegin(); + SourceLocation End = Body ? Body->getSourceRange().getBegin() + : SignatureSourceRange.getEnd(); + unsigned StartLine = SM.getExpansionLineNumber(Start); + unsigned EndLine = SM.getExpansionLineNumber(End); + + FileID FID = SM.getFileID(SM.getExpansionLoc(Start)); + for (unsigned Line = StartLine; Line <= EndLine; Line++) + ExecutedLines->operator[](FID.getHashValue()).insert(Line); +} + +/// \return all executed lines including function signatures on the path +/// starting from \p N. +static std::unique_ptr +findExecutedLines(SourceManager &SM, const ExplodedNode *N) { + auto ExecutedLines = llvm::make_unique(); + + while (N) { + if (N->getFirstPred() == nullptr) { + + // First node: show signature of the entrance point. + const Decl *D = N->getLocationContext()->getDecl(); + populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines); + + } else if (auto CE = N->getLocationAs()) { + + // Inlined function: show signature. + const Decl* D = CE->getCalleeContext()->getDecl(); + populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines); + + } else if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) { + + // Otherwise: show lines associated with the processed statement. + SourceLocation Loc = S->getSourceRange().getBegin(); + SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc); + FileID FID = SM.getFileID(ExpansionLoc); + unsigned LineNo = SM.getExpansionLineNumber(ExpansionLoc); + ExecutedLines->operator[](FID.getHashValue()).insert(LineNo); + } + + N = N->getFirstPred(); + } + return ExecutedLines; +} + void BugReporter::FlushReport(BugReport *exampleReport, PathDiagnosticConsumer &PD, ArrayRef bugReports) { @@ -3517,13 +3577,13 @@ // Probably doesn't make a difference in practice. BugType& BT = exampleReport->getBugType(); - std::unique_ptr D(new PathDiagnostic( + auto D = llvm::make_unique( exampleReport->getBugType().getCheckName(), exampleReport->getDeclWithIssue(), exampleReport->getBugType().getName(), exampleReport->getDescription(), exampleReport->getShortDescription(/*Fallback=*/false), BT.getCategory(), - exampleReport->getUniqueingLocation(), - exampleReport->getUniqueingDecl())); + exampleReport->getUniqueingLocation(), exampleReport->getUniqueingDecl(), + findExecutedLines(getSourceManager(), exampleReport->getErrorNode())); if (exampleReport->isPathSensitive()) { // Generate the full path diagnostic, using the generation scheme Index: lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp =================================================================== --- lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -94,6 +94,13 @@ /// \return Javascript for navigating the HTML report using j/k keys. std::string generateKeyboardNavigationJavascript(); + +private: + /// \return JavaScript for an option to only show relevant lines. + std::string showRelevantLinesJavascript(const PathDiagnostic &D); + + /// \return Executed lines from \p D in JSON format. + std::string serializeExecutedLines(const PathDiagnostic &D); }; } // end anonymous namespace @@ -343,6 +350,10 @@ R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), generateKeyboardNavigationJavascript()); + // Checkbox and javascript for filtering the output to the counterexample. + R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), + showRelevantLinesJavascript(D)); + // Add the name of the file as an

tag. { std::string s; @@ -450,6 +461,94 @@ html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); } +std::string +HTMLDiagnostics::showRelevantLinesJavascript(const PathDiagnostic &D) { + std::string s; + llvm::raw_string_ostream os(s); + os << " + +
+ + +
+)<<<"; + + return os.str(); +} + +std::string HTMLDiagnostics::serializeExecutedLines(const PathDiagnostic &D) { + std::string s; + llvm::raw_string_ostream os(s); + os << "var relevant_lines = {"; + for (auto I = D.executedLines_begin(), + E = D.executedLines_end(); I != E; ++I) { + if (I != D.executedLines_begin()) + os << ", "; + + os << "\"" << I->first << "\": {"; + for (unsigned LineNo : I->second) { + if (LineNo != *(I->second.begin())) + os << ", "; + + os << "\"" << LineNo << "\": 1"; + } + os << "}"; + } + + os << "};"; + return os.str(); +} + void HTMLDiagnostics::RewriteFile(Rewriter &R, const SourceManager& SMgr, const PathPieces& path, FileID FID) { // Process the path. Index: lib/StaticAnalyzer/Core/PathDiagnostic.cpp =================================================================== --- lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -98,20 +98,18 @@ PathDiagnostic::~PathDiagnostic() {} -PathDiagnostic::PathDiagnostic(StringRef CheckName, const Decl *declWithIssue, - StringRef bugtype, StringRef verboseDesc, - StringRef shortDesc, StringRef category, - PathDiagnosticLocation LocationToUnique, - const Decl *DeclToUnique) - : CheckName(CheckName), - DeclWithIssue(declWithIssue), - BugType(StripTrailingDots(bugtype)), - VerboseDesc(StripTrailingDots(verboseDesc)), - ShortDesc(StripTrailingDots(shortDesc)), - Category(StripTrailingDots(category)), - UniqueingLoc(LocationToUnique), - UniqueingDecl(DeclToUnique), - path(pathImpl) {} +PathDiagnostic::PathDiagnostic( + StringRef CheckName, const Decl *declWithIssue, StringRef bugtype, + StringRef verboseDesc, StringRef shortDesc, StringRef category, + PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, + std::unique_ptr ExecutedLines) + : CheckName(CheckName), DeclWithIssue(declWithIssue), + BugType(StripTrailingDots(bugtype)), + VerboseDesc(StripTrailingDots(verboseDesc)), + ShortDesc(StripTrailingDots(shortDesc)), + Category(StripTrailingDots(category)), UniqueingLoc(LocationToUnique), + UniqueingDecl(DeclToUnique), ExecutedLines(std::move(ExecutedLines)), + path(pathImpl) {} static PathDiagnosticCallPiece * getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, Index: test/Analysis/html_diagnostics/relevant_lines/header.h =================================================================== --- test/Analysis/html_diagnostics/relevant_lines/header.h +++ test/Analysis/html_diagnostics/relevant_lines/header.h @@ -0,0 +1,12 @@ +#define deref(X) (*X) + +char helper( + char *out, + int doDereference) { + if (doDereference) { + return deref(out); + } else { + return 'x'; + } + return 'c'; +} Index: test/Analysis/html_diagnostics/relevant_lines/macros_same_file.c =================================================================== --- test/Analysis/html_diagnostics/relevant_lines/macros_same_file.c +++ test/Analysis/html_diagnostics/relevant_lines/macros_same_file.c @@ -0,0 +1,15 @@ +#define deref(X) (*X) + +int f(int coin) { + if (coin) { + int *x = 0; + return deref(x); + } else { + return 0; + } +} + +// RUN: rm -rf %t.output +// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core -analyzer-output html -o %t.output %s +// RUN: cat %t.output/* | FileCheck %s --match-full-lines +// CHECK: var relevant_lines = {"1": {"3": 1, "4": 1, "5": 1, "6": 1}}; Index: test/Analysis/html_diagnostics/relevant_lines/multifile.c =================================================================== --- test/Analysis/html_diagnostics/relevant_lines/multifile.c +++ test/Analysis/html_diagnostics/relevant_lines/multifile.c @@ -0,0 +1,14 @@ +#include "header.h" + +int f(int coin) { + char *p = 0; + if (coin) { + return helper(p, coin); + } + return 0; +} + +// RUN: rm -rf %t.output +// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core -analyzer-output html -o %t.output %s +// RUN: cat %t.output/* | FileCheck %s --match-full-lines +// CHECK: var relevant_lines = {"1": {"3": 1, "4": 1, "5": 1, "6": 1}, "3": {"3": 1, "4": 1, "5": 1, "6": 1, "7": 1}}; Index: test/Analysis/html_diagnostics/relevant_lines/multiline_func_def.c =================================================================== --- test/Analysis/html_diagnostics/relevant_lines/multiline_func_def.c +++ test/Analysis/html_diagnostics/relevant_lines/multiline_func_def.c @@ -0,0 +1,16 @@ +int f( + int coin, + int paramA, + int paramB) { + if (coin) { + int *x = 0; + return *x; + } else { + return 0; + } +} + +// RUN: rm -rf %t.output +// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core -analyzer-output html -o %t.output %s +// RUN: cat %t.output/* | FileCheck %s --match-full-lines +// CHECK: var relevant_lines = {"1": {"1": 1, "2": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1}}; Index: test/Analysis/html_diagnostics/relevant_lines/objcmethods.m =================================================================== --- test/Analysis/html_diagnostics/relevant_lines/objcmethods.m +++ test/Analysis/html_diagnostics/relevant_lines/objcmethods.m @@ -0,0 +1,19 @@ +@interface I +- (int)func; +@end + +@implementation I +- (int)func:(int *)param { + return *param; +} +@end + +void foo(I *i) { + int *x = 0; + [i func:x]; +} + +// RUN: rm -rf %t.output +// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core -analyzer-output html -o %t.output -Wno-objc-root-class %s +// RUN: cat %t.output/* | FileCheck %s +// CHECK: var relevant_lines = {"1": {"6": 1, "7": 1, "11": 1, "12": 1, "13": 1}}; Index: test/Analysis/html_diagnostics/relevant_lines/simple_conditional.c =================================================================== --- test/Analysis/html_diagnostics/relevant_lines/simple_conditional.c +++ test/Analysis/html_diagnostics/relevant_lines/simple_conditional.c @@ -0,0 +1,13 @@ +int f(int coin) { + if (coin) { + int *x = 0; + return *x; + } else { + return 0; + } +} + +// RUN: rm -rf %t.output +// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core -analyzer-output html -o %t.output %s +// RUN: cat %t.output/* | FileCheck %s --match-full-lines +// CHECK: var relevant_lines = {"1": {"1": 1, "2": 1, "3": 1, "4": 1}}; Index: test/Analysis/html_diagnostics/relevant_lines/unused_header.c =================================================================== --- test/Analysis/html_diagnostics/relevant_lines/unused_header.c +++ test/Analysis/html_diagnostics/relevant_lines/unused_header.c @@ -0,0 +1,19 @@ +#include "header.h" + +int f(int coin) { + if (coin) { + int *x = 0; + return *x; + } else { + return 0; + } +} + +int v(int coin) { + return coin; +} + +// RUN: rm -rf %t.output +// RUN: %clang_analyze_cc1 -analyze -analyzer-checker=core -analyzer-output html -o %t.output %s +// RUN: cat %t.output/* | FileCheck %s --match-full-lines +// CHECK: var relevant_lines = {"1": {"3": 1, "4": 1, "5": 1, "6": 1}};