diff --git a/analyze.py b/analyze.py index cc267a1..304bf4b 100644 --- a/analyze.py +++ b/analyze.py @@ -14,6 +14,10 @@ if __name__ == "__main__": 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('--func', help='process function') argparser.add_argument('--enum', help='state enum') argparser.add_argument('--initial', help='initial state') @@ -29,19 +33,29 @@ if __name__ == "__main__": 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() + prep_source = oh.getvalue()#unicode(oh.getvalue(), errors='ignore') - #print(prep_source) - #exit() + 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() + assign_table = [] state_enums = [] func_table = {} @@ -57,21 +71,26 @@ if __name__ == "__main__": etv = EnumTypedefVisitor() etv.visit(ast) enum_table = etv.enums - + states = [] if not(args.func in func_table): raise Exception(f"Function name '{args.func}' not found!") else: proc_func = func_table[args.func] fsm_funcs.append(proc_func) - func_table[args.func].show() fcv = FuncCallVisitor() fcv.visit(proc_func) - fsm_funcs += [ func_table[x] for x in fcv.func_calls ] - + for fc in fcv.func_calls: + if fc in func_table: + fsm_funcs.append(func_table[fc]) + #fsm_funcs += [ func_table[x] for x in fcv.func_calls ] + + 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(ename) sav.visit(f) @@ -89,7 +108,7 @@ if __name__ == "__main__": for sa in state_asmts: for f in fsm_funcs: - sctv = SwitchCaseTranVisitor(sa[0]) + sctv = SwitchCaseTranVisitor(sa[0], states, sa[2]) sctv.visit(f) tran_table += sctv.tran_table @@ -98,13 +117,15 @@ if __name__ == "__main__": for t in tran_table: print(f"{t[0]}->{t[1]}") if t[0] in comp_tt: - comp_tt[t[0]].append(t[1]) + 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] - states = comp_tt.keys() + #todo recomment once fixed + #states = comp_tt.keys() print("States: ", ",".join(states)) print("") @@ -156,13 +177,15 @@ if __name__ == "__main__": g = gv.Digraph('G') for state in states: + shape = 'circle' + if state == args.initial: + shape = 'doublecircle' + if state in props_by_state: pstr = ",".join(props_by_state[state]) - if state == args.initial: - g.attr('node', shape='doublecircle') - else: - g.attr('node', shape='circle') - g.node(state, label=state, xlabel=f"{{{pstr}}}") + g.node(state, label=state, xlabel=f"{{{pstr}}}", shape=shape) + else: + g.node(state, label=state, shape=shape) diff --git a/astvisitors.py b/astvisitors.py index 97d36a8..84b4f18 100644 --- a/astvisitors.py +++ b/astvisitors.py @@ -1,4 +1,43 @@ from pycparser import c_ast +from itertools import pairwise + +def path_to_str(p): + return "->".join([str(n.__class__.__name__) for n in p]) + +def path_select_last(classname, p): + for n in reversed(p): + if n.__class__.__name__ == classname: + return n + return None + +def path_select_parent(child, p): + parent = None + for n in p: + if n == child: + return parent + parent = n + return None + +class NodeVisitorWithParent(object): + def __init__(self): + self.current_parent = None + + def visit(self, node): + """ Visit a node. + """ + method = 'visit_' + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + return visitor(node) + + def generic_visit(self, node): + """ Called if no explicit visitor function exists for a + node. Implements preorder visiting of the node. + """ + oldparent = self.current_parent + self.current_parent = node + for c in node.children(): + self.visit(c) + self.current_parent = oldparent class FuncDefVisitor(c_ast.NodeVisitor): def __init__(self): @@ -46,12 +85,38 @@ class StateAssignmentVisitor(c_ast.NodeVisitor): self.visit(c, path) def visit_Assignment(self, n, path): - n.show() + case_node = path_select_last('Case', path) + fallthrough_case_names = [] + if case_node is not None: + case_parent = path_select_parent(case_node, path) #path_select_last('Compound', path) + siblings = [x for x in case_parent.block_items if not isinstance(x, c_ast.Default)] + sibling_names = [x.expr.name for x in siblings] + sibling_empty = [(len(x.stmts) == 0) for x in siblings] + + in_fallthrough = False + slice_start = None + slice_end = None + for i,sibling in enumerate(zip(reversed(sibling_names),reversed(sibling_empty), reversed(siblings))): + if sibling[0] == self.state: + slice_start = i + 1 + if (slice_start is not None) and sibling[1] and not(in_fallthrough) and (slice_start == i): + in_fallthrough = True + if in_fallthrough: + if sibling[1]: + slice_end = i + else: + in_fallthrough = False + if (slice_start is not None) and (slice_end is not None): + slice_start_temp = slice_start + slice_start = len(siblings) - slice_end + slice_end = len(siblings) - slice_start_temp + fallthrough_case_names = sibling_names[slice_start-1:slice_end] rval_str = '' - if not isinstance(n.rvalue, c_ast.Constant): + + if not(isinstance(n.rvalue, c_ast.Constant) or isinstance(n.rvalue, c_ast.BinaryOp)): rval_str = n.rvalue.name if rval_str == self.state: - self.assignments.append((n,path)) + self.assignments.append((n,path,fallthrough_case_names)) class EnumDefVisitor(c_ast.NodeVisitor): def __init__(self, name): @@ -91,16 +156,21 @@ class SwitchCaseTermVisitor(c_ast.NodeVisitor): self.hit = True class SwitchCaseTranVisitor(c_ast.NodeVisitor): - def __init__(self, asm_node): + def __init__(self, asm_node, states, fallthrough_states): super().__init__() + self.states = states + self.fallthrough_states = fallthrough_states self._asm_node = asm_node self.tran_table = [] def visit_Case(self, node): + state_from = node.children()[0][1].name sctv = SwitchCaseTermVisitor(self._asm_node) sctv.visit(node) - if sctv.hit: - self.tran_table.append((node.children()[0][1].name, self._asm_node.rvalue.name)) + if sctv.hit and (state_from in self.states): + self.tran_table.append((state_from, self._asm_node.rvalue.name)) + for ft in self.fallthrough_states: + self.tran_table.append((ft, self._asm_node.rvalue.name)) class SwitchCasePropertyVisitor(c_ast.NodeVisitor): def __init__(self, state_asmts): @@ -115,7 +185,7 @@ class SwitchCasePropertyVisitor(c_ast.NodeVisitor): if isinstance(node.lvalue, c_ast.StructRef): lvalue = f"{node.lvalue.children()[0][1].name}->{node.lvalue.children()[1][1].name}"; else: - lvalue = f"{node.lvalue.name}<={node.rvalue.name}" + lvalue = node.lvalue.name if isinstance(node.rvalue, c_ast.Constant): rvalue = f"{node.rvalue.type }({node.rvalue.value})"