This revision was automatically updated to reflect the committed changes. Closed by commit rC322612: [analyzer] support a mode to only show relevant lines in HTML diagnostics (authored by george.karpenkov, committed by ). Herald added a subscriber: cfe-commits.
Repository: rC Clang https://reviews.llvm.org/D41378 Files: include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h lib/Rewrite/HTMLRewrite.cpp lib/StaticAnalyzer/Core/BugReporter.cpp lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp lib/StaticAnalyzer/Core/PathDiagnostic.cpp test/Analysis/html_diagnostics/relevant_lines/header.h test/Analysis/html_diagnostics/relevant_lines/macros_same_file.c test/Analysis/html_diagnostics/relevant_lines/multifile.c test/Analysis/html_diagnostics/relevant_lines/multiline_func_def.c test/Analysis/html_diagnostics/relevant_lines/objcmethods.m test/Analysis/html_diagnostics/relevant_lines/simple_conditional.c test/Analysis/html_diagnostics/relevant_lines/unused_header.c
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 <deque> #include <iterator> #include <list> +#include <map> +#include <set> #include <string> #include <vector> @@ -733,6 +735,9 @@ void Profile(llvm::FoldingSetNodeID &ID) const override; }; +/// File IDs mapped to sets of line numbers. +typedef std::map<unsigned, std::set<unsigned>> 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<FilesToLineNumsMap> 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<FilesToLineNumsMap> 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: 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/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/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}}; 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/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/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: 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<FilesToLineNumsMap> 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: lib/StaticAnalyzer/Core/BugReporter.cpp =================================================================== --- lib/StaticAnalyzer/Core/BugReporter.cpp +++ lib/StaticAnalyzer/Core/BugReporter.cpp @@ -3509,21 +3509,81 @@ } } +/// Insert all lines participating in the function signature \p Signature +/// into \p ExecutedLines. +static void populateExecutedLinesWithFunctionSignature( + const Decl *Signature, SourceManager &SM, + std::unique_ptr<FilesToLineNumsMap> &ExecutedLines) { + + SourceRange SignatureSourceRange; + const Stmt* Body = Signature->getBody(); + if (auto FD = dyn_cast<FunctionDecl>(Signature)) { + SignatureSourceRange = FD->getSourceRange(); + } else if (auto OD = dyn_cast<ObjCMethodDecl>(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<FilesToLineNumsMap> +findExecutedLines(SourceManager &SM, const ExplodedNode *N) { + auto ExecutedLines = llvm::make_unique<FilesToLineNumsMap>(); + + 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<CallEnter>()) { + + // 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<BugReport*> bugReports) { // FIXME: Make sure we use the 'R' for the path that was actually used. // Probably doesn't make a difference in practice. BugType& BT = exampleReport->getBugType(); - std::unique_ptr<PathDiagnostic> D(new PathDiagnostic( + auto D = llvm::make_unique<PathDiagnostic>( 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 <h1> 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 << "<script type='text/javascript'>\n"; + os << serializeExecutedLines(D); + os << R"<<<( + +var filterCounterexample = function (hide) { + var tables = document.getElementsByClassName("code"); + for (var t=0; t<tables.length; t++) { + var table = tables[t]; + var file_id = table.getAttribute("data-fileid"); + var lines_in_fid = relevant_lines[file_id]; + if (!lines_in_fid) { + lines_in_fid = {}; + } + var lines = table.getElementsByClassName("codeline"); + for (var i=0; i<lines.length; i++) { + var el = lines[i]; + var lineNo = el.getAttribute("data-linenumber"); + if (!lines_in_fid[lineNo]) { + if (hide) { + el.setAttribute("hidden", ""); + } else { + el.removeAttribute("hidden"); + } + } + } + } +} + +window.addEventListener("keydown", function (event) { + if (event.defaultPrevented) { + return; + } + if (event.key == "S") { + var checked = document.getElementsByName("showCounterexample")[0].checked; + filterCounterexample(!checked); + document.getElementsByName("showCounterexample")[0].checked = !checked; + } else { + return; + } + event.preventDefault(); +}, true); + +document.addEventListener("DOMContentLoaded", function() { + document.querySelector('input[name="showCounterexample"]').onchange= + function (event) { + filterCounterexample(this.checked); + }; +}); +</script> + +<form> + <input type="checkbox" name="showCounterexample" /> + <label for="showCounterexample"> + Show only relevant lines + </label> +</form> +)<<<"; + + 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/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 << "<tr><td class=\"num\" id=\"LN" - << LineNo << "\">" - << LineNo << "</td><td class=\"line\">"; + OS << "<tr class=\"codeline\" data-linenumber=\"" << LineNo << "\">" + << "<td class=\"num\" id=\"LN" << LineNo << "\">" << LineNo + << "</td><td class=\"line\">"; if (B == E) { // Handle empty lines. OS << " </td></tr>"; @@ -263,7 +263,10 @@ } // Add one big table tag that surrounds all of the code. - RB.InsertTextBefore(0, "<table class=\"code\">\n"); + std::string s; + llvm::raw_string_ostream os(s); + os << "<table class=\"code\" data-fileid=\"" << FID.getHashValue() << "\">\n"; + RB.InsertTextBefore(0, os.str()); RB.InsertTextAfter(FileEnd - FileBeg, "</table>"); }
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits