added function calls as properties, now noting conditional state assignments as implicit self-transitions
This commit is contained in:
parent
a5a44098b8
commit
a7e3c59a94
28
analyze.py
28
analyze.py
@ -109,7 +109,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
for sa in state_asmts:
|
for sa in state_asmts:
|
||||||
for f in fsm_funcs:
|
for f in fsm_funcs:
|
||||||
sctv = SwitchCaseTranVisitor(sa[0], states, sa[2])
|
sctv = SwitchCaseTranVisitor(sa[0], states, sa[2], sa[3])
|
||||||
sctv.visit(f)
|
sctv.visit(f)
|
||||||
tran_table += sctv.tran_table
|
tran_table += sctv.tran_table
|
||||||
|
|
||||||
@ -170,7 +170,12 @@ if __name__ == "__main__":
|
|||||||
ss = ','.join(pstates)
|
ss = ','.join(pstates)
|
||||||
print(f" - '{full_prop}' when {ss}")
|
print(f" - '{full_prop}' when {ss}")
|
||||||
print("")
|
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 = []
|
ltls = []
|
||||||
if args.ltlfile is not None:
|
if args.ltlfile is not None:
|
||||||
with open(args.ltlfile) as f:
|
with open(args.ltlfile) as f:
|
||||||
@ -189,21 +194,20 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
if args.dot is not None:
|
if args.dot is not None:
|
||||||
g = gv.Digraph('G')
|
g = gv.Digraph('G')
|
||||||
print(props_by_state)
|
for state in reachable_states:
|
||||||
print(property_alias)
|
state_short = state_to_short[state]
|
||||||
for state in states:
|
shape = 'oval'
|
||||||
shape = 'circle'
|
|
||||||
if state == initial_state:
|
if state == initial_state:
|
||||||
shape = 'doublecircle'
|
shape = 'doubleoctagon'
|
||||||
|
|
||||||
if state in props_by_state:
|
if state in props_by_state:
|
||||||
pstr = ",".join([property_alias[x] for x in props_by_state[state]])
|
pstr = ",".join([property_alias[x] for x in props_by_state[state]])
|
||||||
g.node(state, label=f"{state}\n\n{{{pstr}}}", shape=shape)
|
g.node(state_short, label=f"{state_short}\n\n{{{pstr}}}", shape=shape)
|
||||||
else:
|
else:
|
||||||
g.node(state, label=state, shape=shape)
|
g.node(state_short, label=state_short, shape=shape)
|
||||||
|
|
||||||
|
|
||||||
|
for n,ms in comp_tt.items():
|
||||||
for t in tran_table:
|
for m in ms:
|
||||||
g.edge(t[0], t[1])
|
g.edge(state_to_short[n], state_to_short[m])
|
||||||
g.render(filename=args.dot)
|
g.render(filename=args.dot)
|
||||||
|
|||||||
@ -10,6 +10,12 @@ def path_select_last(classname, p):
|
|||||||
return n
|
return n
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def path_contains(p, t):
|
||||||
|
for n in p:
|
||||||
|
if isinstance(n, t):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def path_select_parent(child, p):
|
def path_select_parent(child, p):
|
||||||
parent = None
|
parent = None
|
||||||
for n in p:
|
for n in p:
|
||||||
@ -18,6 +24,33 @@ def path_select_parent(child, p):
|
|||||||
parent = n
|
parent = n
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def path_slice_back(node, p):
|
||||||
|
s = []
|
||||||
|
for n in reversed(p):
|
||||||
|
if n == node:
|
||||||
|
break
|
||||||
|
s.append(n)
|
||||||
|
return [x for x in reversed(s)]
|
||||||
|
|
||||||
|
class ExprListSerializerVisitor(c_ast.NodeVisitor):
|
||||||
|
def __init__(self):
|
||||||
|
self.serial = []
|
||||||
|
|
||||||
|
def visit_Constant(self, node):
|
||||||
|
expr = node.value
|
||||||
|
self.serial.append(expr)
|
||||||
|
|
||||||
|
def visit_ID(self, node):
|
||||||
|
expr = node.name
|
||||||
|
self.serial.append(expr)
|
||||||
|
|
||||||
|
#todo: expand
|
||||||
|
|
||||||
|
def expr_list_to_str(exprl):
|
||||||
|
elsv = ExprListSerializerVisitor()
|
||||||
|
elsv.visit(exprl)
|
||||||
|
return ','.join(elsv.serial)
|
||||||
|
|
||||||
class NodeVisitorWithParent(object):
|
class NodeVisitorWithParent(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.current_parent = None
|
self.current_parent = None
|
||||||
@ -85,6 +118,7 @@ class StateAssignmentVisitor(c_ast.NodeVisitor):
|
|||||||
self.visit(c, path)
|
self.visit(c, path)
|
||||||
|
|
||||||
def visit_Assignment(self, n, path):
|
def visit_Assignment(self, n, path):
|
||||||
|
# fallthrough detection
|
||||||
case_node = path_select_last('Case', path)
|
case_node = path_select_last('Case', path)
|
||||||
fallthrough_case_names = []
|
fallthrough_case_names = []
|
||||||
if case_node is not None:
|
if case_node is not None:
|
||||||
@ -113,10 +147,16 @@ class StateAssignmentVisitor(c_ast.NodeVisitor):
|
|||||||
fallthrough_case_names = sibling_names[slice_start-1:slice_end]
|
fallthrough_case_names = sibling_names[slice_start-1:slice_end]
|
||||||
rval_str = ''
|
rval_str = ''
|
||||||
|
|
||||||
|
# conditional assignment detection
|
||||||
|
asm_if = path_select_last('If', path)
|
||||||
|
subpath_case = path_slice_back(case_node, path)
|
||||||
|
is_exhaustive_conditional = True if asm_if is None else (asm_if.iftrue is not None) and (asm_if.iffalse is not None)
|
||||||
|
is_conditional = path_contains(subpath_case, c_ast.If) and not(is_exhaustive_conditional)
|
||||||
|
|
||||||
if not(isinstance(n.rvalue, c_ast.Constant) or isinstance(n.rvalue, c_ast.BinaryOp)):
|
if not(isinstance(n.rvalue, c_ast.Constant) or isinstance(n.rvalue, c_ast.BinaryOp)):
|
||||||
rval_str = n.rvalue.name
|
rval_str = n.rvalue.name
|
||||||
if rval_str == self.state:
|
if rval_str == self.state:
|
||||||
self.assignments.append((n,path,fallthrough_case_names))
|
self.assignments.append((n,path,fallthrough_case_names,is_conditional))
|
||||||
|
|
||||||
class EnumDefVisitor(c_ast.NodeVisitor):
|
class EnumDefVisitor(c_ast.NodeVisitor):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
@ -156,10 +196,11 @@ class SwitchCaseTermVisitor(c_ast.NodeVisitor):
|
|||||||
self.hit = True
|
self.hit = True
|
||||||
|
|
||||||
class SwitchCaseTranVisitor(c_ast.NodeVisitor):
|
class SwitchCaseTranVisitor(c_ast.NodeVisitor):
|
||||||
def __init__(self, asm_node, states, fallthrough_states):
|
def __init__(self, asm_node, states, fallthrough_states, is_conditional):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.states = states
|
self.states = states
|
||||||
self.fallthrough_states = fallthrough_states
|
self.fallthrough_states = fallthrough_states
|
||||||
|
self.is_conditional = is_conditional
|
||||||
self._asm_node = asm_node
|
self._asm_node = asm_node
|
||||||
self.tran_table = []
|
self.tran_table = []
|
||||||
|
|
||||||
@ -168,6 +209,10 @@ class SwitchCaseTranVisitor(c_ast.NodeVisitor):
|
|||||||
sctv = SwitchCaseTermVisitor(self._asm_node)
|
sctv = SwitchCaseTermVisitor(self._asm_node)
|
||||||
sctv.visit(node)
|
sctv.visit(node)
|
||||||
if sctv.hit and (state_from in self.states):
|
if sctv.hit and (state_from in self.states):
|
||||||
|
#if conditional, state remains in state sometimes
|
||||||
|
if self.is_conditional:
|
||||||
|
self.tran_table.append((state_from,state_from))
|
||||||
|
# process state assignment
|
||||||
self.tran_table.append((state_from, self._asm_node.rvalue.name))
|
self.tran_table.append((state_from, self._asm_node.rvalue.name))
|
||||||
for ft in self.fallthrough_states:
|
for ft in self.fallthrough_states:
|
||||||
self.tran_table.append((ft, self._asm_node.rvalue.name))
|
self.tran_table.append((ft, self._asm_node.rvalue.name))
|
||||||
@ -178,6 +223,12 @@ class SwitchCasePropertyVisitor(c_ast.NodeVisitor):
|
|||||||
self._sas = state_asmts
|
self._sas = state_asmts
|
||||||
self.properties = []
|
self.properties = []
|
||||||
|
|
||||||
|
def visit_FuncCall(self, node):
|
||||||
|
name = node.name.name
|
||||||
|
args = expr_list_to_str(node.args) if node.args is not None else ""
|
||||||
|
fcall = f"{name}({args})"
|
||||||
|
self.properties.append(fcall)
|
||||||
|
|
||||||
def visit_Assignment(self, node):
|
def visit_Assignment(self, node):
|
||||||
if not(node in self._sas):
|
if not(node in self._sas):
|
||||||
lvalue = None
|
lvalue = None
|
||||||
|
|||||||
3
utils.py
3
utils.py
@ -25,6 +25,9 @@ def base_10_to_alphabet(number):
|
|||||||
for part in _decompose(number)
|
for part in _decompose(number)
|
||||||
)[::-1]
|
)[::-1]
|
||||||
|
|
||||||
|
def remove_prefix(s, prefix):
|
||||||
|
return s[len(prefix):] if s.startswith(prefix) else s
|
||||||
|
|
||||||
def find_longest_path(paths):
|
def find_longest_path(paths):
|
||||||
ms = 0
|
ms = 0
|
||||||
mp = None
|
mp = None
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user