From deb7accbea782c9ef8eb22269c813c042d9afcc3 Mon Sep 17 00:00:00 2001 From: Artem Dergachev Date: Wed, 3 Jul 2019 01:26:32 +0000 Subject: [PATCH] [analyzer] exploded-graph-rewriter: Implement checker messages. They are displayed as raw lines and diffed via difflib on a per-checker basis. Differential Revision: https://reviews.llvm.org/D64100 llvm-svn: 364989 --- .../checker_messages.dot | 30 +++++ .../checker_messages_diff.dot | 93 ++++++++++++++ .../exploded-graph-rewriter/constraints.dot | 1 + .../constraints_diff.dot | 5 +- .../exploded-graph-rewriter/environment.dot | 1 + .../environment_diff.dot | 3 + .../exploded-graph-rewriter/store.dot | 1 + .../exploded-graph-rewriter/store_diff.dot | 2 + .../utils/analyzer/exploded-graph-rewriter.py | 113 ++++++++++++++++-- 9 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot create mode 100644 clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot new file mode 100644 index 000000000000..e7a7d7131d97 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot @@ -0,0 +1,30 @@ +// RUN: %exploded_graph_rewriter %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: Checker State: +// CHECK-SAME: alpha.core.FooChecker: +// CHECK-SAME: Foo stuff: +// CHECK-SAME: Foo: Bar +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "environment": null, + "checker_messages": [ + { "checker": "alpha.core.FooChecker", "messages": [ + "Foo stuff:", + "Foo: Bar" + ]} + ] + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot new file mode 100644 index 000000000000..57cbb5e83ad2 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot @@ -0,0 +1,93 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "state_id": 2, + "program_points": [], + "program_state": { + "environment": null, + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "FooChecker", "messages": [ + "Foo: Bar" + ]}, + { "checker": "BarChecker", "messages": [ + "Bar: Foo" + ]} + ] + } + } +\l}"]; + +Node0x1 -> Node0x4; + + +// CHECK: Node0x4 [ +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: BarChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: Bar: Foo +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: FooChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: Bar: Foo +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: DunnoWhateverSomeOtherChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: Dunno, some other message. +// CHECK-SAME: +Node0x4 [shape=record,label= + "{ + { "node_id": 4, + "pointer": "0x4", + "state_id": 5, + "program_points": [], + "program_state": { + "environment": null, + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "FooChecker", "messages": [ + "Foo: Bar", + "Bar: Foo" + ]}, + { "checker": "DunnoWhateverSomeOtherChecker", "messages": [ + "Dunno, some other message." + ]} + ] + } + } +\l}"]; + +Node0x4 -> Node0x6; + +Node0x6 [shape=record,label= + "{ + { "node_id": 6, + "pointer": "0x6", + "state_id": 7, + "program_points": [], + "program_state": null + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot index 58faafc0f260..10e72d67df7c 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -21,6 +21,7 @@ Node0x1 [shape=record,label= "environment": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 0] }" } ] diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot index 24aa9b41a7ae..cd7bc62299dd 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -14,6 +14,7 @@ Node0x1 [shape=record,label= "environment": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 10] }" } ] @@ -45,6 +46,7 @@ Node0x3 [shape=record,label= "environment": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "constraints": [ { "symbol": "reg_$0", "range": "{ [0, 5] }" } ] @@ -65,7 +67,8 @@ Node0x5 [shape=record,label= "environment": null, "constraints": null, "dynamic_types": null, - "constructing_objects": null + "constructing_objects": null, + "checker_messages": null } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 8c451671b4c0..4167a8c4cb76 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -36,6 +36,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot index 5264b40f802f..a624910330ab 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -15,6 +15,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ @@ -63,6 +64,7 @@ Node0x6 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ @@ -105,6 +107,7 @@ Node0x9 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "environment": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 1c306ce160f2..8331d099c050 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -31,6 +31,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "store": { "pointer": "0x2", "items": [ diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot index ddd26ce5dcd6..f8dfe5117835 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -14,6 +14,7 @@ Node0x1 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "store": { "pointer": "0x2", "items": [ @@ -61,6 +62,7 @@ Node0x4 [shape=record,label= "constraints": null, "dynamic_types": null, "constructing_objects": null, + "checker_messages": null, "store": { "pointer": "0x5", "items": [ diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index fffe2f53ee42..5b4fa1a501a1 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -13,6 +13,7 @@ from __future__ import print_function import argparse import collections +import difflib import json import logging import re @@ -211,6 +212,41 @@ class Store(object): return len(removed) != 0 or len(added) != 0 or len(updated) != 0 +# Deserialized messages from a single checker in a single program state. +# Basically a list of raw strings. +class CheckerLines(object): + def __init__(self, json_lines): + super(CheckerLines, self).__init__() + self.lines = json_lines + + def diff_lines(self, prev): + lines = difflib.ndiff(prev.lines, self.lines) + return [l.strip() for l in lines + if l.startswith('+') or l.startswith('-')] + + def is_different(self, prev): + return len(self.diff_lines(prev)) > 0 + + +# Deserialized messages of all checkers, separated by checker. +class CheckerMessages(object): + def __init__(self, json_m): + super(CheckerMessages, self).__init__() + self.items = collections.OrderedDict( + [(m['checker'], CheckerLines(m['messages'])) for m in json_m]) + + def diff_messages(self, prev): + removed = [k for k in prev.items if k not in self.items] + added = [k for k in self.items if k not in prev.items] + updated = [k for k in prev.items if k in self.items + and prev.items[k].is_different(self.items[k])] + return (removed, added, updated) + + def is_different(self, prev): + removed, added, updated = self.diff_messages(prev) + return len(removed) != 0 or len(added) != 0 or len(updated) != 0 + + # A deserialized program state. class ProgramState(object): def __init__(self, state_id, json_ps): @@ -241,7 +277,8 @@ class ProgramState(object): GenericEnvironment(json_ps['constructing_objects']) \ if json_ps['constructing_objects'] is not None else None - # TODO: Checker messages. + self.checker_messages = CheckerMessages(json_ps['checker_messages']) \ + if json_ps['checker_messages'] is not None else None # A deserialized exploded graph node. Has a default constructor because it @@ -595,16 +632,73 @@ class DotDumpVisitor(object): if m is None: self._dump(' Nothing!') else: - if prev_s is not None: - if prev_m is not None: - if m.is_different(prev_m): - self._dump('') - self.visit_generic_map(m, prev_m) - else: - self._dump(' No changes!') - if prev_m is None: + if prev_m is not None: + if m.is_different(prev_m): + self._dump('') + self.visit_generic_map(m, prev_m) + else: + self._dump(' No changes!') + else: self._dump('') self.visit_generic_map(m) + + self._dump('') + + def visit_checker_messages(self, m, prev_m=None): + self._dump('') + + def dump_line(l, is_added=None): + self._dump('' + '' + % (self._diff_plus_minus(is_added), l)) + + def dump_chk(chk, is_added=None): + dump_line('%s:' % chk, is_added) + + if prev_m is not None: + removed, added, updated = m.diff_messages(prev_m) + for chk in removed: + dump_chk(chk, False) + for l in prev_m.items[chk].lines: + dump_line(l, False) + for chk in updated: + dump_chk(chk) + for l in m.items[chk].diff_lines(prev_m.items[chk]): + dump_line(l[1:], l.startswith('+')) + for chk in added: + dump_chk(chk, True) + for l in m.items[chk].lines: + dump_line(l, True) + else: + for chk in m.items: + dump_chk(chk) + for l in m.items[chk].lines: + dump_line(l) + + self._dump('
%s%s
') + + def visit_checker_messages_in_state(self, s, prev_s=None): + m = s.checker_messages + prev_m = prev_s.checker_messages if prev_s is not None else None + if m is None and prev_m is None: + return + + self._dump('
') + self._dump('' + 'Checker State: ') + if m is None: + self._dump(' Nothing!') + else: + if prev_m is not None: + if m.is_different(prev_m): + self._dump('') + self.visit_checker_messages(m, prev_m) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_checker_messages(m) + self._dump('') def visit_state(self, s, prev_s): @@ -618,6 +712,7 @@ class DotDumpVisitor(object): self.visit_environment_in_state('constructing_objects', 'Objects Under Construction', s, prev_s) + self.visit_checker_messages_in_state(s, prev_s) def visit_node(self, node): self._dump('%s [shape=record,'