Index: clang/include/clang/Basic/JsonSupport.h =================================================================== --- clang/include/clang/Basic/JsonSupport.h +++ clang/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 @@ 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 Index: clang/lib/Analysis/AnalysisDeclContext.cpp =================================================================== --- clang/lib/Analysis/AnalysisDeclContext.cpp +++ clang/lib/Analysis/AnalysisDeclContext.cpp @@ -538,11 +538,9 @@ 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 @@ 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; Index: clang/lib/Analysis/ProgramPoint.cpp =================================================================== --- clang/lib/Analysis/ProgramPoint.cpp +++ clang/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 @@ 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 @@ 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 @@ 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 @@ 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()) Index: clang/test/Analysis/dump_egraph.cpp =================================================================== --- clang/test/Analysis/dump_egraph.cpp +++ clang/test/Analysis/dump_egraph.cpp @@ -18,9 +18,9 @@ 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\}\" Index: clang/test/Analysis/exploded-graph-rewriter/environment.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ clang/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 @@ "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, Index: clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -25,7 +25,7 @@ "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 5, @@ -76,7 +76,7 @@ "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 9, @@ -121,7 +121,7 @@ "location_context": "#0 Call", "lctx_id": 3, "calling": "foo", - "call_line": 4, + "location": null, "items": [ { "stmt_id": 9, Index: clang/test/Analysis/exploded-graph-rewriter/macros.c =================================================================== --- /dev/null +++ clang/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; +} Index: clang/test/Analysis/exploded-graph-rewriter/program_points.dot =================================================================== --- clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -57,10 +57,11 @@ // 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 @@ "kind": "Statement", "stmt_kind": "DeclRefExpr", "stmt_point_kind": "PreStmt", - "stmd_id": 3, + "stmt_id": 3, "pointer": "0x3", "pretty": "x", "location": { @@ -95,7 +96,7 @@ // 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 @@ "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": { Index: clang/test/Analysis/expr-inspection.c =================================================================== --- clang/test/Analysis/expr-inspection.c +++ clang/test/Analysis/expr-inspection.c @@ -30,7 +30,7 @@ // 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: ]}, Index: clang/utils/analyzer/exploded-graph-rewriter.py =================================================================== --- clang/utils/analyzer/exploded-graph-rewriter.py +++ clang/utils/analyzer/exploded-graph-rewriter.py @@ -49,10 +49,16 @@ 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 @@ 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 @@ 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 @@ 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 @@ # 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 @@ '%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'