From: Artem Dergachev Date: Fri, 12 Jul 2019 02:10:33 +0000 (+0000) Subject: [analyzer] exploded-graph-rewriter: Improve source location dumps. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=daf71df56f8ca0a5b002b44cf9b370167a026993;p=clang [analyzer] exploded-graph-rewriter: Improve source location dumps. - Correctly display macro expansion and spelling locations. - Use the same procedure to display location context call site locations. - Display statement IDs for program points. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@365861 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/JsonSupport.h b/include/clang/Basic/JsonSupport.h index a3d12cbec6..bbcc747e68 100644 --- a/include/clang/Basic/JsonSupport.h +++ b/include/clang/Basic/JsonSupport.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_BASIC_JSONSUPPORT_H #include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" @@ -77,6 +78,42 @@ inline std::string JsonFormat(StringRef RawSR, bool AddQuotes) { return '\"' + Str + '\"'; } +inline void printSourceLocationAsJson(raw_ostream &Out, SourceLocation Loc, + const SourceManager &SM, + bool AddBraces = true) { + // Mostly copy-pasted from SourceLocation::print. + if (!Loc.isValid()) { + Out << "null"; + return; + } + + if (Loc.isFileID()) { + PresumedLoc PLoc = SM.getPresumedLoc(Loc); + + if (PLoc.isInvalid()) { + Out << "null"; + return; + } + // The macro expansion and spelling pos is identical for file locs. + if (AddBraces) + Out << "{ "; + Out << "\"line\": " << PLoc.getLine() + << ", \"column\": " << PLoc.getColumn() + << ", \"file\": \"" << PLoc.getFilename() << "\""; + if (AddBraces) + Out << " }"; + return; + } + + // We want 'location: { ..., spelling: { ... }}' but not + // 'location: { ... }, spelling: { ... }', hence the dance + // with braces. + Out << "{ "; + printSourceLocationAsJson(Out, SM.getExpansionLoc(Loc), SM, false); + Out << ", \"spelling\": "; + printSourceLocationAsJson(Out, SM.getSpellingLoc(Loc), SM, true); + Out << " }"; +} } // namespace clang #endif // LLVM_CLANG_BASIC_JSONSUPPORT_H diff --git a/lib/Analysis/AnalysisDeclContext.cpp b/lib/Analysis/AnalysisDeclContext.cpp index 28d308132f..b6a429ff49 100644 --- a/lib/Analysis/AnalysisDeclContext.cpp +++ b/lib/Analysis/AnalysisDeclContext.cpp @@ -538,11 +538,9 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, else Out << "anonymous code"; - Out << "\", \"call_line\": "; + Out << "\", \"location\": "; if (const Stmt *S = cast(LCtx)->getCallSite()) { - Out << '\"'; - printLocation(Out, SM, S->getBeginLoc()); - Out << '\"'; + printSourceLocationAsJson(Out, S->getBeginLoc(), SM); } else { Out << "null"; } @@ -555,8 +553,8 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, case Block: Out << "Invoking block\" "; if (const Decl *D = cast(LCtx)->getDecl()) { - Out << ", \"decl_line\": "; - printLocation(Out, SM, D->getBeginLoc()); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, D->getBeginLoc(), SM); Out << ' '; } break; diff --git a/lib/Analysis/ProgramPoint.cpp b/lib/Analysis/ProgramPoint.cpp index 0398251b5e..97e90965d0 100644 --- a/lib/Analysis/ProgramPoint.cpp +++ b/lib/Analysis/ProgramPoint.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/JsonSupport.h" using namespace clang; @@ -46,19 +47,6 @@ LLVM_DUMP_METHOD void ProgramPoint::dump() const { return printJson(llvm::errs()); } -static void printLocJson(raw_ostream &Out, SourceLocation Loc, - const SourceManager &SM) { - Out << "\"location\": "; - if (!Loc.isFileID()) { - Out << "null"; - return; - } - - Out << "{ \"line\": " << SM.getExpansionLineNumber(Loc) - << ", \"column\": " << SM.getExpansionColumnNumber(Loc) - << ", \"file\": \"" << SM.getFilename(Loc) << "\" }"; -} - void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { const ASTContext &Context = getLocationContext()->getAnalysisDeclContext()->getASTContext(); @@ -112,16 +100,18 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { case ProgramPoint::PreImplicitCallKind: { ImplicitCallPoint PC = castAs(); Out << "PreCall\", \"decl\": \"" - << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() << "\", "; - printLocJson(Out, PC.getLocation(), SM); + << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() + << "\", \"location\": "; + printSourceLocationAsJson(Out, PC.getLocation(), SM); break; } case ProgramPoint::PostImplicitCallKind: { ImplicitCallPoint PC = castAs(); Out << "PostCall\", \"decl\": \"" - << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() << "\", "; - printLocJson(Out, PC.getLocation(), SM); + << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() + << "\", \"location\": "; + printSourceLocationAsJson(Out, PC.getLocation(), SM); break; } @@ -153,8 +143,8 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { E.getSrc()->printTerminatorJson(Out, Context.getLangOpts(), /*AddQuotes=*/true); - Out << ", "; - printLocJson(Out, T->getBeginLoc(), SM); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, T->getBeginLoc(), SM); Out << ", \"term_kind\": \""; if (isa(T)) { @@ -202,8 +192,8 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { S->printJson(Out, nullptr, PP, AddQuotes); - Out << ", "; - printLocJson(Out, S->getBeginLoc(), SM); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, S->getBeginLoc(), SM); Out << ", \"stmt_point_kind\": \""; if (getAs()) diff --git a/test/Analysis/dump_egraph.cpp b/test/Analysis/dump_egraph.cpp index f9ad71b7ab..f5db3d142e 100644 --- a/test/Analysis/dump_egraph.cpp +++ b/test/Analysis/dump_egraph.cpp @@ -18,9 +18,9 @@ void foo() { new S; } -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"call_line\": null, \"items\": [\l        \{ \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"location\": null, \"items\": [\l        \{ \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"call_line\": \"16\", \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"location\": \{ \"line\": 16, \"column\": 5, \"file\": \"{{.*}}dump_egraph.cpp\" \}, \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" // CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" diff --git a/test/Analysis/exploded-graph-rewriter/environment.dot b/test/Analysis/exploded-graph-rewriter/environment.dot index 2b8a402cd8..399484a7ee 100644 --- a/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/test/Analysis/exploded-graph-rewriter/environment.dot @@ -10,7 +10,11 @@ // CHECK-SAME: #0 Call // CHECK-SAME: // CHECK-SAME: -// CHECK-SAME: foo (line 4) +// CHECK-SAME: foo +// CHECK-SAME: (environment.cpp:4:6 +// CHECK-SAME: +// CHECK-SAME: (spelling at environment.h:7:8) +// CHECK-SAME: ) // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: @@ -46,7 +50,16 @@ Node0x1 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": { + "file": "environment.cpp", + "line": 4, + "column": 6, + "spelling": { + "file": "environment.h", + "line": 7, + "column": 8 + } + }, "items": [ { "stmt_id": 5, diff --git a/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/test/Analysis/exploded-graph-rewriter/environment_diff.dot index c3ba88622f..475247bb98 100644 --- a/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -25,7 +25,7 @@ Node0x1 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 5, @@ -76,7 +76,7 @@ Node0x6 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 9, @@ -121,7 +121,7 @@ Node0x9 [shape=record,label= "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 9, diff --git a/test/Analysis/exploded-graph-rewriter/macros.c b/test/Analysis/exploded-graph-rewriter/macros.c new file mode 100644 index 0000000000..4dcbda95d2 --- /dev/null +++ b/test/Analysis/exploded-graph-rewriter/macros.c @@ -0,0 +1,18 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s +// REQUIRES: asserts + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: macros.c:17:10 +// CHECK-SAME: +// CHECK-SAME: (spelling at macros.c:15:14) +// CHECK-SAME: +#define NULL 0 +void *foo() { + return NULL; +} diff --git a/test/Analysis/exploded-graph-rewriter/program_points.dot b/test/Analysis/exploded-graph-rewriter/program_points.dot index 772fe4de80..2f49d7f75e 100644 --- a/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -57,10 +57,11 @@ Node0x1 [shape=record,label= // CHECK-SAME: // CHECK-SAME: DeclRefExpr // CHECK-SAME: +// CHECK-SAME: S3 // CHECK-SAME: // CHECK-SAME: PreStmt // CHECK-SAME: -// CHECK-SAME: x +// CHECK-SAME: x // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: @@ -79,7 +80,7 @@ Node0x2 [shape=record,label= "kind": "Statement", "stmt_kind": "DeclRefExpr", "stmt_point_kind": "PreStmt", - "stmd_id": 3, + "stmt_id": 3, "pointer": "0x3", "pretty": "x", "location": { @@ -95,7 +96,7 @@ Node0x2 [shape=record,label= // Test collapsing large pretty prints with braces. // CHECK-NEXT: Program point: -// CHECK-SAME: \{ ... \} +// CHECK-SAME: \{ ... \} Node0x3 [shape=record,label= "{ { "node_id": 3, "pointer": "0x3", "has_report": false, "is_sink": false, @@ -104,7 +105,7 @@ Node0x3 [shape=record,label= "kind": "Statement", "stmt_kind": "CompoundStmt", "stmt_point_kind": "PostStmt", - "stmd_id": 6, + "stmt_id": 6, "pointer": "0x6", "pretty": "{ very very very very very very long pretty print }", "location": { diff --git a/test/Analysis/expr-inspection.c b/test/Analysis/expr-inspection.c index 5d07b70965..c2bcafe068 100644 --- a/test/Analysis/expr-inspection.c +++ b/test/Analysis/expr-inspection.c @@ -30,7 +30,7 @@ void foo(int x) { // CHECK-NEXT: ]} // CHECK-NEXT: ]}, // CHECK-NEXT: "environment": { "pointer": "{{0x[0-9a-f]+}}", "items": [ -// CHECK-NEXT: { "lctx_id": 1, "location_context": "#0 Call", "calling": "foo", "call_line": null, "items": [ +// CHECK-NEXT: { "lctx_id": 1, "location_context": "#0 Call", "calling": "foo", "location": null, "items": [ // CHECK-NEXT: { "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } // CHECK-NEXT: ]} // CHECK-NEXT: ]}, diff --git a/utils/analyzer/exploded-graph-rewriter.py b/utils/analyzer/exploded-graph-rewriter.py index baf1be8de0..5ce56d61c0 100755 --- a/utils/analyzer/exploded-graph-rewriter.py +++ b/utils/analyzer/exploded-graph-rewriter.py @@ -49,10 +49,16 @@ class GenericMap(object): class SourceLocation(object): def __init__(self, json_loc): super(SourceLocation, self).__init__() + logging.debug('json: %s' % json_loc) self.line = json_loc['line'] self.col = json_loc['column'] self.filename = os.path.basename(json_loc['file']) \ if 'file' in json_loc else '(main file)' + self.spelling = SourceLocation(json_loc['spelling']) \ + if 'spelling' in json_loc else None + + def is_macro(self): + return self.spelling is not None # A deserialized program point. @@ -65,8 +71,10 @@ class ProgramPoint(object): self.src_id = json_pp['src_id'] self.dst_id = json_pp['dst_id'] elif self.kind == 'Statement': + logging.debug(json_pp) self.stmt_kind = json_pp['stmt_kind'] self.stmt_point_kind = json_pp['stmt_point_kind'] + self.stmt_id = json_pp['stmt_id'] self.pointer = json_pp['pointer'] self.pretty = json_pp['pretty'] self.loc = SourceLocation(json_pp['location']) \ @@ -102,7 +110,8 @@ class LocationContext(object): self.lctx_id = json_frame['lctx_id'] self.caption = json_frame['location_context'] self.decl = json_frame['calling'] - self.line = json_frame['call_line'] + self.loc = SourceLocation(json_frame['location']) \ + if json_frame['location'] is not None else None def _key(self): return self.lctx_id @@ -432,6 +441,22 @@ class DotDumpVisitor(object): return s return candidate + @staticmethod + def _make_sloc(loc): + if loc is None: + return 'Invalid Source Location' + + def make_plain_loc(loc): + return '%s:%s:%s' \ + % (loc.filename, loc.line, loc.col) + + if loc.is_macro(): + return '%s ' \ + '(spelling at %s)' \ + % (make_plain_loc(loc), make_plain_loc(loc.spelling)) + + return make_plain_loc(loc) + def visit_begin_graph(self, graph): self._graph = graph self._dump_raw('digraph "ExplodedGraph" {\n') @@ -457,29 +482,16 @@ class DotDumpVisitor(object): # Such statements show up only at [Pre|Post]StmtPurgeDeadSymbols skip_pretty = 'PurgeDeadSymbols' in p.stmt_point_kind stmt_color = 'cyan3' - if p.loc is not None: - self._dump('' - '%s:%s:%s:' - '' - '%s' - '%s' - '%s' - % (p.loc.filename, p.loc.line, - p.loc.col, color, p.stmt_kind, - stmt_color, p.stmt_point_kind, - self._short_pretty(p.pretty) - if not skip_pretty else '')) - else: - self._dump('' - 'Invalid Source Location:' - '' - '%s' - '%s' - '%s' - % (color, p.stmt_kind, - stmt_color, p.stmt_point_kind, - self._short_pretty(p.pretty) - if not skip_pretty else '')) + self._dump('%s:' + '' + '%s ' + 'S%s' + '%s' + '%s' + % (self._make_sloc(p.loc), color, p.stmt_kind, + p.stmt_id, stmt_color, p.stmt_point_kind, + self._short_pretty(p.pretty) + if not skip_pretty else '')) elif p.kind == 'Edge': self._dump('' '' @@ -516,8 +528,8 @@ class DotDumpVisitor(object): '%s' % (self._diff_plus_minus(is_added), lc.caption, lc.decl, - ('(line %s)' % lc.line) if lc.line is not None - else '')) + ('(%s)' % self._make_sloc(lc.loc)) + if lc.loc is not None else '')) def dump_binding(f, b, is_added=None): self._dump('%s'