kripkomat/analyze.py

277 lines
9.6 KiB
Python

from __future__ import print_function
import argparse
import os,sys,io
from pycparser import parse_file, c_ast, CParser, c_generator
from pcpp import Preprocessor
import graphviz as gv
import html
from dataclasses import dataclass
from modelbuilder import *
from utils import *
from astvisitors import *
def parse_statevar(statevar):
r_struct = r'(\w+)(\.|->)(\w+)'
match = re.match(r_struct, statevar)
if match:
name,typ,field = match.groups()
return c_ast.StructRef(c_ast.ID(name), typ, c_ast.ID(field))
return c_ast.ID(statevar)
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('--statevar', help='state variable')
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()
if args.preprocess is not None:
with open(args.preprocess, "wt") as f:
n = f.write(prep_source)
cg = CGenerator()
parser = CParser()
ast = parser.parse(prep_source)
ass = parse_statevar(args.statevar)
tg = TypedefGatherer()
tg.visit(ast)
typedefs = tg.typedefs
#print("Typedefs", typedefs.keys())
tast = TypedIdTransformer(typedefs).transform(ast)
#tast.show()
ast = tast
initial_state = args.initial
assign_table = []
state_enums = []
func_table = {}
enum_table = {}
state_asmts = []
fsm_funcs = []
tran_table = []
properties = []
state_id = 'm_tState'
#TMR
#input_ids = ['m_bIn', 'm_ulActTime']
input_ids = ['cbEnable', 'cbCondition', 'm_ucEnableUnconditioned']
#conditional_ids = [*input_ids, 'm_ucTimerType']
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]
fast = AstFuncCallInlinerTransformer(func_table).transform(ast)
ast = fast
etv = EnumTypedefVisitor()
etv.visit(ast)
enum_table = etv.enums
states = []
def discover_cals(func):
funcs = []
child_funcs = []
fcv = FuncCallVisitor()
fcv.visit(func)
for fc in fcv.func_calls:
if fc in func_table:
funcs.append(func_table[fc])
child_funcs += discover_cals(func_table[fc])
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)
else:
print(f"Initial State Enum '{args.enum}' not found")
print("")
print("Extracting State Transitions")
sav = StateAssignmentVisitor(func_table, ass)
sav.visit(proc_func)
for assignment in sav.assignments:
state_to = assignment.state
cpa = ConditionalPathAnalyzer()
#print("Path", path_to_str(assignment.path))
cpa.analyze(reversed(assignment.path))
cond_chain = []
conds_state = []
state_from_expr = None
disregard_transition = False
for c in cpa.condition_chain:
# filter by conditional enum
# this is far from correct
cond_blacklist_visitor = ContainsOneOfIdVisitor(cond_blacklist)
cond_blacklist_visitor.visit(c)
if cond_blacklist_visitor.hit:
disregard_transition = True
break
conditional_visitor = ContainsOneOfIdVisitor(input_ids)
conditional_visitor.visit(c)
if conditional_visitor.hit:
cond_chain.append(c)
state_condition_visitor = ContainsOneOfIdVisitor([state_id])
state_condition_visitor.visit(c)
if state_condition_visitor.hit:
conds_state.append(c)
if not(disregard_transition):
if len(conds_state) != 1:
cond_exprs = [cg.visit(x) for x in cpa.condition_chain]
print("OOPS")
print(cond_exprs)
raise Exception("No or too many state conditions found")
# find out from which state the assignment transitions
current_state_visitor = ContainsOneOfIdVisitor(states)
current_state_visitor.visit(conds_state[0])
if not(current_state_visitor.hit):
raise Exception("State assignment does not assign state enum")
state_from = current_state_visitor.name
condition = None
if len(cond_chain) == 1:
condition = cond_chain[0]
elif len(cond_chain) > 1:
condition = cond_chain[0]
for expr in cond_chain[1:len(cond_chain)]:
condition = c_ast.BinaryOp('&&', expr, condition)
cond_exprs = [cg.visit(x) for x in cond_chain]
cond_expr = cg.visit(condition)
tf = NuSmvConditionTransformer()
cond_mod = tf.transform(condition)
mod_expr = cg.visit(cond_mod)
print(f" - {state_from} -> {state_to} on {mod_expr}")
tran_table.append(StateTransition(state_from, state_to, condition))
print("")
# Extract properties
av = AssignmentVisitor(func_table)
av.visit(proc_func)
assigments = []
print("Assignments")
for a in av.assignments:
if a.node.op == '=':
if isinstance(a.node.lvalue, c_ast.StructRef):
if not(check_structref_equivalence(a.node.lvalue, ass)):
assigments.append(a)
for a in assigments:
cpa = ConditionalPathAnalyzer()
#print("Path", path_to_str(assignment.path))
cpa.analyze(reversed(a.path))
cond_chain = []
conds_state = []
state_from_expr = None
disregard_transition = False
for c in cpa.condition_chain:
# filter by conditional enum
# this is far from correct
cond_blacklist_visitor = ContainsOneOfIdVisitor(cond_blacklist)
cond_blacklist_visitor.visit(c)
if cond_blacklist_visitor.hit:
disregard_transition = True
break
conditional_visitor = ContainsOneOfIdVisitor(input_ids)
conditional_visitor.visit(c)
if conditional_visitor.hit:
cond_chain.append(c)
state_condition_visitor = ContainsOneOfIdVisitor([state_id])
state_condition_visitor.visit(c)
if state_condition_visitor.hit:
conds_state.append(c)
if not(disregard_transition):
cond_exprs = [cg.visit(x) for x in cond_chain]
state_cond_exprs = [cg.visit(x) for x in conds_state]
print(f" - {cg.visit(a.node)} {cond_exprs} {state_cond_exprs}")
mod = ModelBuilder(states, initial_state, tran_table, properties)
nusmv = mod.generate()
print(nusmv)