From ed44921f04d41b7952a8e5e8ec3d15a418b2ac56 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 10 Jul 2018 13:30:11 -0400
Subject: [PATCH 11/49] FIXME: WIP on proper test coverage for JSON output

---
 gcc/testsuite/gcc.dg/plugin/remarks-1.c     |   2 +-
 gcc/testsuite/gcc.dg/plugin/test-remarks.py | 209 ++++++++++++++++++++++++++++
 2 files changed, 210 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/test-remarks.py

diff --git a/gcc/testsuite/gcc.dg/plugin/remarks-1.c b/gcc/testsuite/gcc.dg/plugin/remarks-1.c
index 9139b9d..204f90b 100644
--- a/gcc/testsuite/gcc.dg/plugin/remarks-1.c
+++ b/gcc/testsuite/gcc.dg/plugin/remarks-1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fremarks" } */
+/* { dg-options "-fremarks -fsave-optimization-record" } */
 
 extern void test_string_literal (void);
 extern void test_tree (void);
diff --git a/gcc/testsuite/gcc.dg/plugin/test-remarks.py b/gcc/testsuite/gcc.dg/plugin/test-remarks.py
new file mode 100644
index 0000000..f8786cd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/test-remarks.py
@@ -0,0 +1,209 @@
+#!/usr/bin/python3
+# TODO: license
+import argparse
+import html
+import json
+import os
+from pprint import pprint
+import sys
+
+def log(*args):
+    print(*args)
+
+
+class TranslationUnit:
+    """Top-level class for containing optimization records"""
+    @staticmethod
+    def from_filename(filename):
+        with open(filename) as f:
+            root_obj = json.load(f)
+            #pprint(root_obj)
+            return TranslationUnit(filename, root_obj)
+
+    def __init__(self, filename, json_obj):
+        self.filename = filename
+        self.pass_by_id = {}
+
+        # Expect a 3-tuple
+        metadata, passes, records = json_obj
+
+        self.format = metadata['format']
+        self.generator = metadata['generator']
+        self.passes = [Pass(obj, self) for obj in passes]
+        self.records = [Record.from_json(obj, self) for obj in records]
+
+    def __repr__(self):
+        return ('TranslationUnit(%r, %r, %r, %r)'
+                % (self.filename, self.generator, self.passes, self.records))
+
+    def get_records(self):
+        return [obj for obj in self.records if isinstance(obj, Record)]
+
+    def get_states(self):
+        return [obj for obj in self.records if isinstance(obj, State)]
+
+class Pass:
+    """An optimization pass"""
+    def __init__(self, json_obj, tu):
+        self.id_ = json_obj['id']
+        self.name = json_obj['name']
+        self.num = json_obj['num']
+        self.optgroups = set(json_obj['optgroups']) # list of strings
+        self.type = json_obj['type']
+        tu.pass_by_id[self.id_] = self
+        self.children = [Pass(child, tu)
+                         for child in json_obj.get('children', [])]
+
+    def __repr__(self):
+        return ('Pass(%r, %r, %r, %r)'
+                % (self.name, self.num, self.optgroups, self.type))
+
+def from_optional_json_field(cls, jsonobj, field):
+    if field not in jsonobj:
+        return None
+    return cls(jsonobj[field])
+
+class ImplLocation:
+    """An implementation location (within the compiler itself)"""
+    def __init__(self, json_obj):
+        self.file = json_obj['file']
+        self.line = json_obj['line']
+        self.function = json_obj['function']
+
+    def __repr__(self):
+        return ('ImplLocation(%r, %r, %r)'
+                % (self.file, self.line, self.function))
+
+class Location:
+    """A source location"""
+    def __init__(self, json_obj):
+        self.file = json_obj['file']
+        self.line = json_obj['line']
+        self.column = json_obj['column']
+
+    def __str__(self):
+        return '%s:%i:%i' % (self.file, self.line, self.column)
+
+    def __repr__(self):
+        return ('Location(%r, %r, %r)'
+                % (self.file, self.line, self.column))
+
+class Count:
+    """An execution count"""
+    def __init__(self, json_obj):
+        self.quality = json_obj['quality']
+        self.value = json_obj['value']
+
+    def __repr__(self):
+        return ('Count(%r, %r)'
+                % (self.quality, self.value))
+
+    def is_precise(self):
+        return self.quality in ('precise', 'adjusted')
+
+class BaseRecord:
+    """A optimization record: success/failure/note/state"""
+    @staticmethod
+    def from_json(json_obj, tu):
+        if json_obj['kind'] == 'state':
+            return State(json_obj, tu)
+        else:
+            return Record(json_obj, tu)
+
+    def __init__(self, json_obj, tu):
+        self.kind = json_obj['kind']
+        if 'pass' in json_obj:
+            self.pass_ = tu.pass_by_id[json_obj['pass']]
+        else:
+            self.pass_ = None
+        self.function = json_obj.get('function', None)
+
+class InliningNode:
+    """A node within an inlining chain"""
+    def __init__(self, json_obj):
+        self.fndecl = json_obj['fndecl']
+        self.site = from_optional_json_field(Location, json_obj, 'site')
+
+class Record(BaseRecord):
+    """A optimization record that's not a "state": success/failure/note"""
+    def __init__(self, json_obj, tu):
+        BaseRecord.__init__(self, json_obj, tu)
+        #print('Record.__init: ')
+        #pprint(json_obj)
+        self.impl_location = from_optional_json_field(ImplLocation, json_obj, 'impl_location')
+        self.message = [Item.from_json(obj) for obj in json_obj['message']]
+        self.count = from_optional_json_field(Count, json_obj, 'count')
+        self.location = from_optional_json_field(Location, json_obj, 'location')
+        if 'inlining_chain' in json_obj:
+            self.inlining_chain = [InliningNode(obj)
+                                   for obj in json_obj['inlining_chain']]
+        else:
+            self.inlining_chain = None
+        self.children = [BaseRecord.from_json(child, tu)
+                         for child in json_obj.get('children', [])]
+
+    def __repr__(self):
+        return ('Record(%r, %r, %r, %r, %r)'
+                % (self.kind, self.message, self.pass_, self.function, self.children))
+
+class Item:
+    """Base class for non-string items within a message"""
+    @staticmethod
+    def from_json(json_obj):
+        if isinstance(json_obj, str):
+            return json_obj
+        if 'expr' in json_obj:
+            return Expr(json_obj)
+        elif 'stmt' in json_obj:
+            return Stmt(json_obj)
+        elif 'symtab_node' in json_obj:
+            return SymtabNode(json_obj)
+        else:
+            raise ValueError('unrecognized item: %r' % json_obj)
+
+class Expr(Item):
+    """An expression within a message"""
+    def __init__(self, json_obj):
+        self.expr = json_obj['expr']
+        self.location = from_optional_json_field(Location, json_obj, 'location')
+
+    def __str__(self):
+        return self.expr
+
+    def __repr__(self):
+        return 'Expr(%r)' % self.expr
+
+class Stmt(Item):
+    """A statement within a message"""
+    def __init__(self, json_obj):
+        self.stmt = json_obj['stmt']
+        self.location = from_optional_json_field(Location, json_obj, 'location')
+
+    def __str__(self):
+        return self.stmt
+
+    def __repr__(self):
+        return 'Stmt(%r)' % self.stmt
+
+class SymtabNode(Item):
+    """A symbol table node within a message"""
+    def __init__(self, json_obj):
+        self.node = json_obj['symtab_node']
+        self.location = from_optional_json_field(Location, json_obj, 'location')
+
+    def __str__(self):
+        return self.node
+
+    def __repr__(self):
+        return 'SymtabNode(%r)' % self.node
+
+# FIXME:
+FILENAME = '/home/david/coding-3/gcc-git-static-analysis/build/gcc/testsuite/gcc/remarks-1.c.opt-record.json'
+
+tu = TranslationUnit.from_filename(FILENAME)
+
+print(tu.generator)
+print(tu.passes)
+for r in tu.records:
+    print(r)
+assert len(tu.records) == 8
-- 
1.8.5.3

