275 lines
8.7 KiB
Python
275 lines
8.7 KiB
Python
from __future__ import print_function
|
|
import argparse
|
|
import os,sys,io
|
|
from pycparser import parse_file, c_ast, CParser
|
|
from pcpp import Preprocessor
|
|
import graphviz as gv
|
|
import html
|
|
|
|
from modelbuilder import *
|
|
from utils import *
|
|
from astvisitors import *
|
|
|
|
if __name__ == "__main__":
|
|
argparser = argparse.ArgumentParser('Create a Kripke Structure Model from C Code')
|
|
argparser.add_argument('filename',
|
|
nargs='?',
|
|
help='name of file to parse')
|
|
|
|
argparser.add_argument('-I', '--includedirs', nargs='+', default=[])
|
|
argparser.add_argument('-D', '--defines', nargs='+', default=[])
|
|
argparser.add_argument('-p', '--preprocess', help='output preprocessor file')
|
|
argparser.add_argument('-c', '--conditional', help='only count state assignment if this conditional applies on the path')
|
|
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')
|
|
argparser.add_argument('--dot', help='output dot file')
|
|
args = argparser.parse_args()
|
|
|
|
source = ""
|
|
with open(args.filename, "r") as f:
|
|
#read whole file to a string
|
|
source = f.read()
|
|
|
|
p = Preprocessor()
|
|
p.add_path('/usr/lib/gcc/x86_64-linux-gnu/12/include/')
|
|
for inc in args.includedirs:
|
|
print(f"Include-Dir: {inc}")
|
|
p.add_path(inc)
|
|
|
|
for define in args.defines:
|
|
name,value = define.split('=')
|
|
print(f"Define: {name}={value}")
|
|
p.define(f"{name} {value}")
|
|
|
|
p.parse(source)
|
|
oh = io.StringIO()
|
|
p.write(oh)
|
|
prep_source = oh.getvalue()#unicode(oh.getvalue(), errors='ignore')
|
|
|
|
if args.preprocess is not None:
|
|
with open(args.preprocess, "wt") as f:
|
|
n = f.write(prep_source)
|
|
|
|
parser = CParser()
|
|
ast = parser.parse(prep_source)
|
|
#ast = parse_file(args.filename, use_cpp=False)
|
|
#ast.show()
|
|
|
|
|
|
initial_state = args.initial
|
|
assign_table = []
|
|
state_enums = []
|
|
func_table = {}
|
|
enum_table = {}
|
|
state_asmts = []
|
|
fsm_funcs = []
|
|
|
|
fdv = FuncDefVisitor()
|
|
fdv.visit(ast)
|
|
func_table = fdv.func_table
|
|
proc_func = None
|
|
if not(args.func in func_table):
|
|
raise Exception(f"Function name '{args.func}' not found!")
|
|
else:
|
|
proc_func = func_table[args.func]
|
|
|
|
etv = EnumTypedefVisitor()
|
|
etv.visit(ast)
|
|
enum_table = etv.enums
|
|
states = []
|
|
|
|
def discover_cals(func, visited=None):
|
|
if visited is None:
|
|
visited = []
|
|
funcs = []
|
|
child_funcs = []
|
|
fcv = FuncCallVisitor()
|
|
fcv.visit(func)
|
|
visited.append(proc_func)
|
|
for fc in fcv.func_calls:
|
|
if fc in func_table:
|
|
funcs.append(func_table[fc])
|
|
child_funcs += discover_cals(func_table[fc], visited)
|
|
return funcs + child_funcs
|
|
|
|
# discover FSM functions
|
|
fsm_funcs.append(proc_func)
|
|
fsm_funcs += discover_cals(proc_func)
|
|
#fsm_funcs += [ func_table[x] for x in fcv.func_calls ]
|
|
|
|
print("Function Table")
|
|
for f in fsm_funcs:
|
|
print(f" - {f.decl.name} {'<<< entry' if (f.decl.name == args.func) else ''}")
|
|
print("")
|
|
|
|
config_cond = None
|
|
cond_blacklist = []
|
|
cond_whitelist = []
|
|
if args.conditional is not None:
|
|
config_cond = args.conditional
|
|
print(f"Configuration Conditional {config_cond}")
|
|
config_type = None
|
|
config_enums = None
|
|
for name,enum_type in enum_table.items():
|
|
ev = EnumVisitor()
|
|
ev.visit(enum_type)
|
|
if config_cond in ev.enum_names:
|
|
config_type = name
|
|
config_enums = ev.enum_names
|
|
break
|
|
if config_type is not None:
|
|
print(f" - Type is {config_type}")
|
|
cond_whitelist.append(config_cond)
|
|
print( " - Enum Blacklist is")
|
|
for enum in config_enums:
|
|
if enum != config_cond:
|
|
print(f" * {enum}")
|
|
cond_blacklist.append(enum)
|
|
else:
|
|
print("No type found for conditional, ignoring")
|
|
print("")
|
|
|
|
print("Enum Table")
|
|
if args.enum in enum_table:
|
|
ev = EnumVisitor()
|
|
ev.visit(enum_table[args.enum])
|
|
for ename in ev.enum_names:
|
|
print(" - ",ename)
|
|
states.append(ename)
|
|
#for f in fsm_funcs:
|
|
sav = StateAssignmentVisitor(ast, ename, cond_blacklist, cond_whitelist)
|
|
#sav.visit(f)
|
|
sav.visit(proc_func)
|
|
state_asmts += sav.assignments
|
|
else:
|
|
print(f"Initial State Enum '{args.enum}' not found")
|
|
|
|
paths = []
|
|
for asm in state_asmts:
|
|
paths.append(asm[1])
|
|
|
|
#common = find_common_ancestor(paths)
|
|
|
|
tran_table = []
|
|
|
|
for sa in state_asmts:
|
|
for f in fsm_funcs:
|
|
sctv = SwitchCaseTranVisitor(sa[0], sa[1], states, sa[2], sa[3], sa[4])
|
|
sctv.visit(f)
|
|
tran_table += sctv.tran_table
|
|
|
|
comp_tt = {}
|
|
print("Transitions")
|
|
for t in tran_table:
|
|
print(f"{t[0]}->{t[1]}")
|
|
if t[0] in comp_tt:
|
|
if t[1] not in comp_tt[t[0]]:
|
|
comp_tt[t[0]].append(t[1])
|
|
else:
|
|
comp_tt[t[0]] = [t[1]]
|
|
print("")
|
|
|
|
pure_sa = [x[0] for x in state_asmts]
|
|
#todo recomment once fixed
|
|
#states = comp_tt.keys()
|
|
print("States: ", ",".join(states))
|
|
print("")
|
|
|
|
prop_func_blacklist = [f.decl.name for f in fsm_funcs]
|
|
reachable_states = [initial_state]
|
|
props_by_state = {}
|
|
print("Compact Transition Table:")
|
|
for n,ms in comp_tt.items():
|
|
sstr = ','.join(ms)
|
|
print(f"{n}->{{{sstr}}}")
|
|
for s in ms:
|
|
if not(s in reachable_states):
|
|
reachable_states.append(s)
|
|
# find properties
|
|
for f in fsm_funcs:
|
|
sccpv = SwitchCaseCodePropertyVisitor(n, states, prop_func_blacklist)
|
|
sccpv.visit(f)
|
|
if len(sccpv.properties) > 0:
|
|
props_by_state[n] = sccpv.properties
|
|
print("")
|
|
|
|
states_by_property = {}
|
|
for state,props in props_by_state.items():
|
|
for prop in props:
|
|
if prop in states_by_property:
|
|
states_by_property[prop].append(state)
|
|
else:
|
|
states_by_property[prop] = [state]
|
|
|
|
properties = {}
|
|
property_alias = {}
|
|
for i,(prop,pstates) in enumerate(states_by_property.items()):
|
|
alias = base_10_to_alphabet(i + 1)
|
|
property_alias[prop] = alias
|
|
properties[alias] = (pstates, prop)
|
|
|
|
print("Properties")
|
|
for prop,(pstates,full_prop) in properties.items():
|
|
ss = ','.join(pstates)
|
|
print(f" - '{full_prop}' when {ss}")
|
|
print("")
|
|
|
|
print("States shortform:")
|
|
states_prefix = os.path.commonprefix(states)
|
|
state_to_short = {}
|
|
for s in states:
|
|
state_to_short[s] = remove_prefix(s, states_prefix)
|
|
ltls = []
|
|
if args.ltlfile is not None:
|
|
with open(args.ltlfile) as f:
|
|
ltls = [line.rstrip() for line in f]
|
|
|
|
|
|
mod = ModelBuilder(reachable_states, initial_state, comp_tt, properties, ltls=ltls)
|
|
nusmv = mod.generate()
|
|
|
|
if args.output is not None:
|
|
with open(args.output, "wt") as f:
|
|
n = f.write(nusmv)
|
|
else:
|
|
print("-------------------")
|
|
print(nusmv)
|
|
|
|
if args.dot is not None:
|
|
g = gv.Digraph('G')
|
|
# add states
|
|
for state in reachable_states:
|
|
state_short = state_to_short[state]
|
|
shape = 'oval'
|
|
if state == initial_state:
|
|
shape = 'doubleoctagon'
|
|
|
|
if state in props_by_state:
|
|
pstr = ",".join([property_alias[x] for x in props_by_state[state]])
|
|
g.node(state_short, label=f"{state_short}\n\n{{{pstr}}}", shape=shape)
|
|
else:
|
|
g.node(state_short, label=state_short, shape=shape)
|
|
|
|
# add transitions
|
|
for n,ms in comp_tt.items():
|
|
for m in ms:
|
|
g.edge(state_to_short[n], state_to_short[m])
|
|
|
|
# add property table
|
|
"""
|
|
tabattr = 'border="0px"'
|
|
table_rows = []
|
|
for prop,alias in property_alias.items():
|
|
alias = html.escape(alias)
|
|
prop = html.escape(prop)
|
|
table_rows.append(f"<tr><td>{alias}</td><td>{prop}</td></tr>")
|
|
table_rows = ''.join(table_rows)
|
|
html = f"<table {tabattr}>{table_rows}</table>"
|
|
print(html)
|
|
g.node("Properties", label=f"<{html}>",shape="none", rank="sink")
|
|
"""
|
|
g.render(filename=args.dot)
|