summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h17
-rw-r--r--lib/Rewrite/HTMLRewrite.cpp11
-rw-r--r--lib/StaticAnalyzer/Core/BugReporter.cpp66
-rw-r--r--lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp99
-rw-r--r--lib/StaticAnalyzer/Core/PathDiagnostic.cpp26
-rw-r--r--test/Analysis/html_diagnostics/relevant_lines/header.h12
-rw-r--r--test/Analysis/html_diagnostics/relevant_lines/macros_same_file.c15
-rw-r--r--test/Analysis/html_diagnostics/relevant_lines/multifile.c14
-rw-r--r--test/Analysis/html_diagnostics/relevant_lines/multiline_func_def.c16
-rw-r--r--test/Analysis/html_diagnostics/relevant_lines/objcmethods.m19
-rw-r--r--test/Analysis/html_diagnostics/relevant_lines/simple_conditional.c13
-rw-r--r--test/Analysis/html_diagnostics/relevant_lines/unused_header.c19
12 files changed, 305 insertions, 22 deletions
diff --git a/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h
index f31ab2cd81..2adc09cd8d 100644
--- a/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h
+++ b/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 @@ public:
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 @@ class PathDiagnostic : public llvm::FoldingSetNode {
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 @@ public:
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;
diff --git a/lib/Rewrite/HTMLRewrite.cpp b/lib/Rewrite/HTMLRewrite.cpp
index 618c0179f1..d3247846bc 100644
--- a/lib/Rewrite/HTMLRewrite.cpp
+++ b/lib/Rewrite/HTMLRewrite.cpp
@@ -210,9 +210,9 @@ static void AddLineNumber(RewriteBuffer &RB, unsigned LineNo,
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 @@ void html::AddLineNumbers(Rewriter& R, FileID FID) {
}
// 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>");
}
diff --git a/lib/StaticAnalyzer/Core/BugReporter.cpp b/lib/StaticAnalyzer/Core/BugReporter.cpp
index de0e87bb82..1d48bbcf60 100644
--- a/lib/StaticAnalyzer/Core/BugReporter.cpp
+++ b/lib/StaticAnalyzer/Core/BugReporter.cpp
@@ -3509,6 +3509,66 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) {
}
}
+/// 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) {
@@ -3517,13 +3577,13 @@ void BugReporter::FlushReport(BugReport *exampleReport,
// 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
diff --git a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
index ebf1487d4b..635625107f 100644
--- a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -94,6 +94,13 @@ public:
/// \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 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
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 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
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.
diff --git a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
index 669748c012..82ab910d1e 100644
--- a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
+++ b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
@@ -98,20 +98,18 @@ void PathPieces::flattenTo(PathPieces &Primary, PathPieces &Current,
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,
diff --git a/test/Analysis/html_diagnostics/relevant_lines/header.h b/test/Analysis/html_diagnostics/relevant_lines/header.h
new file mode 100644
index 0000000000..579b66a1cb
--- /dev/null
+++ b/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';
+}
diff --git a/test/Analysis/html_diagnostics/relevant_lines/macros_same_file.c b/test/Analysis/html_diagnostics/relevant_lines/macros_same_file.c
new file mode 100644
index 0000000000..5b3556526a
--- /dev/null
+++ b/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}};
diff --git a/test/Analysis/html_diagnostics/relevant_lines/multifile.c b/test/Analysis/html_diagnostics/relevant_lines/multifile.c
new file mode 100644
index 0000000000..3abffd609b
--- /dev/null
+++ b/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}};
diff --git a/test/Analysis/html_diagnostics/relevant_lines/multiline_func_def.c b/test/Analysis/html_diagnostics/relevant_lines/multiline_func_def.c
new file mode 100644
index 0000000000..35158a4686
--- /dev/null
+++ b/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}};
diff --git a/test/Analysis/html_diagnostics/relevant_lines/objcmethods.m b/test/Analysis/html_diagnostics/relevant_lines/objcmethods.m
new file mode 100644
index 0000000000..41a4c1d2e0
--- /dev/null
+++ b/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}};
diff --git a/test/Analysis/html_diagnostics/relevant_lines/simple_conditional.c b/test/Analysis/html_diagnostics/relevant_lines/simple_conditional.c
new file mode 100644
index 0000000000..769859dea5
--- /dev/null
+++ b/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}};
diff --git a/test/Analysis/html_diagnostics/relevant_lines/unused_header.c b/test/Analysis/html_diagnostics/relevant_lines/unused_header.c
new file mode 100644
index 0000000000..4b77c651ed
--- /dev/null
+++ b/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}};