kripkomat/analyze.py

341 lines
10 KiB
Python

#-----------------------------------------------------------------
# pycparser: dump_ast.py
#
# Basic example of parsing a file and dumping its parsed AST.
#
# Eli Bendersky [https://eli.thegreenplace.net/]
# License: BSD
#-----------------------------------------------------------------
from __future__ import print_function
import argparse
import sys
from modelbuilder import *
# This is not required if you've installed pycparser into
# your site-packages/ with setup.py
sys.path.extend(['.', '..'])
from pycparser import parse_file, c_ast
func_table = {}
enum_table = {}
assign_table = []
state_enums = []
# A visitor with some state information (the funcname it's looking for)
class FuncCallVisitor(c_ast.NodeVisitor):
def __init__(self, funcname):
self.funcname = funcname
def visit_FuncCall(self, node):
if node.name.name == self.funcname:
print('%s called at %s' % (self.funcname, node.name.coord))
# Visit args in case they contain more func calls.
if node.args:
self.visit(node.args)
def show_func_calls(ast, funcname):
v = FuncCallVisitor(funcname)
v.visit(ast)
class FuncDefVisitor(c_ast.NodeVisitor):
def visit_FuncDef(self, node):
print('%s at %s' % (node.decl.name, node.decl.coord))
func_table[node.decl.name] = node
def show_func_defs(ast):
v = FuncDefVisitor()
v.visit(ast)
class StateAssignmentVisitor(c_ast.NodeVisitor):
def __init__(self, state):
super().__init__()
self.state = state
self.assignments = []
def visit(self, node, path = None):
""" Visit a node.
"""
if path is None:
path = []
if self._method_cache is None:
self._method_cache = {}
visitor = self._method_cache.get(node.__class__.__name__, None)
if visitor is None:
method = 'visit_' + node.__class__.__name__
visitor = getattr(self, method, self.generic_visit)
self._method_cache[node.__class__.__name__] = visitor
return visitor(node, path)
def generic_visit(self, node, path):
""" Called if no explicit visitor function exists for a
node. Implements preorder visiting of the node.
"""
path = path.copy()
path.append(node)
for c in node:
self.visit(c, path)
def visit_Assignment(self, n, path):
rval_str = n.rvalue.name
#return '%s %s %s' % (self.visit(n.lvalue), n.op, rval_str)
if rval_str == self.state:
if isinstance(n.lvalue, c_ast.StructRef):
print(f"found struct assignment '{n.lvalue.children()[0][1].name}->{n.lvalue.children()[1][1].name}'<='{rval_str}'")
else:
print(f"found assignment '{n.lvalue.name}'<='{rval_str}'")
print(" -> at path '",end="")
for p in path:
print(p.__class__.__name__,end=",")
print("'")
self.assignments.append((n,path))
#print("+++")
#n.show()
#print("---")
class EnumDefVisitor(c_ast.NodeVisitor):
def __init__(self, name):
super().__init__()
self._name = name
def visit_Enum(self, node):
print('enum %s at %s' % (node.name, node.coord))
enum_table[self._name] = node
class TypedefVisitor(c_ast.NodeVisitor):
def visit_Typedef(self, node):
print(f"typedef {node.name} at {node.coord}")
print(f"---")
node.children()[0][1].show()
print("+++")
ev = EnumDefVisitor(node.name)
ev.visit(node)
class EnumVisitor(c_ast.NodeVisitor):
def __init__(self):
super().__init__()
self.enum_names = []
def visit_Enumerator(self, node):
#print('enumerator %s at %s' % (node.name, node.coord))
self.enum_names.append(node.name)
def find_longest_path(paths):
ms = 0
mp = None
for path in paths:
s = len(path)
if s > ms:
ms = s
mp = path
return mp
def find_shortest_path(paths):
ms = 9000
mp = None
for path in paths:
s = len(path)
if s < ms:
ms = s
mp = path
return mp
def find_common_ancestor(paths):
shortest_path = find_shortest_path(paths)
last_common = None
for gen_i,_ in enumerate(shortest_path):
common = True
last_node = paths[0][gen_i]
for path in paths:
if last_node == path[gen_i]:
common = True
last_node = path[gen_i]
else:
common = False
if common:
last_common = last_node
return last_common
class SwitchCaseTermVisitor(c_ast.NodeVisitor):
def __init__(self, asm_node):
super().__init__()
self._asm_node = asm_node
self.hit = False
def visit_Assignment(self, node):
if node == self._asm_node:
self.hit = True
class SwitchCaseTranVisitor(c_ast.NodeVisitor):
def __init__(self, asm_node):
super().__init__()
self._asm_node = asm_node
self.tran_table = []
def visit_Case(self, node):
sctv = SwitchCaseTermVisitor(self._asm_node)
sctv.visit(node)
if sctv.hit:
#print(f"Transition! {node.children()[0][1].name} -> {self._asm_node.rvalue.name}");
self.tran_table.append((node.children()[0][1].name, self._asm_node.rvalue.name))
class SwitchCasePropertyVisitor(c_ast.NodeVisitor):
def __init__(self, state_asmts):
super().__init__()
self._sas = state_asmts
self.properties = []
def visit_Assignment(self, node):
if not(node in self._sas):
#print("Property:", node)
prop = None
if isinstance(node.lvalue, c_ast.StructRef):
prop = f"{node.lvalue.children()[0][1].name}->{node.lvalue.children()[1][1].name}<={node.rvalue.name}";
else:
prop = f"{node.lvalue.name}<={node.rvalue.name}"
#print(f"found property '{prop}'")
self.properties.append(prop)
class SwitchCaseCodePropertyVisitor(c_ast.NodeVisitor):
def __init__(self, case, state_asmts):
super().__init__()
self._case = case
self._sas = state_asmts
self.properties = []
def visit_Case(self, node):
label = node.children()[0][1]
block = node
if label.name == self._case:
scpv = SwitchCasePropertyVisitor(self._sas)
scpv.visit(block)
self.properties += scpv.properties
if __name__ == "__main__":
argparser = argparse.ArgumentParser('Create a Kripke Structure Model from C Code')
argparser.add_argument('filename',
default='examples/c_files/basic.c',
nargs='?',
help='name of file to parse')
argparser.add_argument('--coord', help='show coordinates in the dump',
action='store_true')
argparser.add_argument('--func', help='process function')
argparser.add_argument('--enum', help='state enum')
argparser.add_argument('--initial', help='initial state')
argparser.add_argument('--ltlfile', help='file containing LTL formulae')
argparser.add_argument('-o', '--output', dest='output', help='output NuSMV file')
args = argparser.parse_args()
ast = parse_file(args.filename, use_cpp=False)
#ast.show(showcoord=args.coord)
show_func_defs(ast)
enumvis = TypedefVisitor()
enumvis.visit(ast)
#func_table[args.func].show(showcoord=args.coord)
# for name,node in enum_table.items():
# print(f"Enum '{name}'")
# print("---")
# node.show()
# print("+++")
# ev = EnumVisitor()
# ev.visit(node)
state_asmts = []
if args.enum in enum_table:
ev = EnumVisitor()
ev.visit(enum_table[args.enum])
for ename in ev.enum_names:
sav = StateAssignmentVisitor(ename)
sav.visit(func_table[args.func])
state_asmts += sav.assignments
else:
print(f"state enum '{args.enum}' not found")
paths = []
for asm in state_asmts:
paths.append(asm[1])
common = find_common_ancestor(paths)
#print(f"common ancestor is {common}")
tran_table = []
for sa in state_asmts:
#print(sa[0])
sctv = SwitchCaseTranVisitor(sa[0])
sctv.visit(common)
tran_table += sctv.tran_table
comp_tt = {}
print("Compacting Transition Table")
for t in tran_table:
print(f"{t[0]}->{t[1]}")
if t[0] in comp_tt:
comp_tt[t[0]].append(t[1])
else:
comp_tt[t[0]] = [t[1]]
pure_sa = [x[0] for x in state_asmts]
#print(pure_sa)
states = comp_tt.keys()
print("States: ", ",".join(states))
props_by_state = {}
print("Compact Table:")
for n,ms in comp_tt.items():
print(n,end="->{")
for m in ms:
print(m,end=",")
print("}")
# find properties
sccpv = SwitchCaseCodePropertyVisitor(n, pure_sa)
sccpv.visit(common)
if len(sccpv.properties) > 0:
props_by_state[n] = sccpv.properties
properties = {}
for state,props in props_by_state.items():
#print(f"{state} properties:")
for prop in props:
#print(f" - {prop}")
if prop in properties:
properties[prop].append(state)
else:
properties[prop] = [state]
print("Properties")
for prop,pstates in properties.items():
ss = ','.join(pstates)
print(f" - '{prop}' when {ss}")
ltls = []
if args.ltlfile is not None:
with open(args.ltlfile) as f:
ltls = [line.rstrip() for line in f]
mod = ModelBuilder(states, args.initial, comp_tt, properties, ltls=ltls)
nusmv = mod.generate()
print("-------------------")
print(nusmv)
if args.output is not None:
with open(args.output, "wt") as f:
n = f.write(nusmv)