438 lines
15 KiB
Python
438 lines
15 KiB
Python
import re
|
|
from pycparser import c_ast, c_generator
|
|
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_contains(p, t):
|
|
for n in p:
|
|
if isinstance(n, t):
|
|
return True
|
|
return False
|
|
|
|
def path_select_parent(child, p):
|
|
parent = None
|
|
for n in p:
|
|
if n == child:
|
|
return parent
|
|
parent = n
|
|
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)]
|
|
|
|
|
|
def path_filter(p, t):
|
|
elems = []
|
|
for n in p:
|
|
if isinstance(n, t):
|
|
elems.append(n)
|
|
return elems
|
|
|
|
def path_filter_multi(p, ts):
|
|
elems = []
|
|
for n in p:
|
|
for t in ts:
|
|
if isinstance(n, t):
|
|
elems.append(n)
|
|
return elems
|
|
|
|
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
|
|
|
|
class CaseLabelExtractionVisitor(c_ast.NodeVisitor):
|
|
def __init__(self):
|
|
self.label = None
|
|
|
|
def visit_ID(self, node):
|
|
self.label = node.name
|
|
|
|
|
|
def expr_list_to_str(exprl):
|
|
elsv = ExprListSerializerVisitor()
|
|
elsv.visit(exprl)
|
|
return ','.join(elsv.serial)
|
|
|
|
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 NodeVisitorFuncCallForward(object):
|
|
def __init__(self, ast):
|
|
self.current_parent = None
|
|
self.ast = ast
|
|
fdv = FuncDefVisitor()
|
|
fdv.visit(ast)
|
|
self.func_table = fdv.func_table
|
|
|
|
def visit_FuncCall(self, node):
|
|
print("Visiting FuncCall")
|
|
print(node.show())
|
|
print('---- parent ----')
|
|
print(self.current_parent.show())
|
|
|
|
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):
|
|
self.func_table = {}
|
|
|
|
def visit_FuncDef(self, node):
|
|
self.func_table[node.decl.name] = node
|
|
|
|
class FuncCallVisitor(c_ast.NodeVisitor):
|
|
def __init__(self):
|
|
self.func_calls = []
|
|
|
|
def visit_FuncCall(self, node):
|
|
self.func_calls.append(node.children()[0][1].name)
|
|
|
|
class StateAssignmentVisitor(NodeVisitorFuncCallForward):
|
|
def __init__(self, ast, state, config_cond_blacklist=None, config_cond_whitelist=None):
|
|
super().__init__(ast)
|
|
self._method_cache = {}
|
|
self.state = state
|
|
self.ccb = config_cond_blacklist
|
|
self.ccw = config_cond_whitelist
|
|
self.assignments = []
|
|
|
|
def visit(self, node, path = None, invariants = None):
|
|
""" Visit a node.
|
|
"""
|
|
if path is None:
|
|
path = []
|
|
if invariants is None:
|
|
invariants = []
|
|
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, invariants)
|
|
|
|
def generic_visit(self, node, path, invariants):
|
|
""" 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, invariants)
|
|
|
|
def visit_FuncCall(self, node, path, invariants):
|
|
fcall = node.name.name
|
|
#print("CAL path", path_to_str(path))
|
|
cases = path_filter(path, c_ast.Case)
|
|
#print(cases)
|
|
#new_invariants = [x.expr.name for x in cases]
|
|
new_invariants = []
|
|
for x in cases:
|
|
clev = CaseLabelExtractionVisitor()
|
|
clev.visit(x.expr)
|
|
new_invariants.append(clev.label)
|
|
|
|
invariants = invariants.copy()
|
|
for ni in new_invariants:
|
|
if not(ni in invariants):
|
|
invariants.append(ni)
|
|
#print("invariants:", invariants)
|
|
#print(f"Visiting FuncCall {fcall}")
|
|
if fcall in self.func_table:
|
|
#print("->deferring!")
|
|
self.visit(self.func_table[fcall], path, invariants)
|
|
#print('---- path ----')
|
|
|
|
def visit_Assignment(self, n, path, invariants):
|
|
# fallthrough detection
|
|
case_node = path_select_last('Case', path)
|
|
fallthrough_case_names = []
|
|
if case_node is not None:
|
|
case_parent = path_select_parent(case_node, path)
|
|
siblings = [x for x in case_parent.block_items if not isinstance(x, c_ast.Default)]
|
|
#for s in siblings:
|
|
# s.show()
|
|
sibling_names = [(x.expr.expr.name if isinstance(x.expr, c_ast.Cast) else 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 = ''
|
|
|
|
# conditional assignment detection
|
|
asm_if = path_select_last('If', path)
|
|
asm_case = path_select_last('Case', path)
|
|
asm_ifs = path_filter(path, c_ast.If)
|
|
asm_condchain = path_filter_multi(path, [c_ast.If, c_ast.Case])
|
|
asm_cases = path_filter(path, c_ast.Case)
|
|
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)
|
|
type_condition_antivalent = False
|
|
|
|
# for ccn in asm_condchain:
|
|
# condition = None
|
|
# if isinstance(ccn, c_ast.If):
|
|
# condition = ccn.cond
|
|
# else:
|
|
# condition = ccn.expr
|
|
# cg = c_generator.CGenerator()
|
|
# expr = cg.visit(condition)
|
|
# print(f" - {expr}")
|
|
|
|
#if asm_case is not None:
|
|
for case in asm_cases:
|
|
elsv = ExprListSerializerVisitor()
|
|
elsv.visit(case.expr)
|
|
if elsv.serial[0] in self.ccb:
|
|
type_condition_antivalent = True
|
|
break
|
|
|
|
if not(type_condition_antivalent):
|
|
for if_node in asm_ifs:
|
|
cg = c_generator.CGenerator()
|
|
if_expr = cg.visit(if_node.cond)
|
|
for incl_cond in self.ccw:
|
|
match = re.search(f"(\!=|==)[\(\)\w\d\s]+{incl_cond}", if_expr)
|
|
if match is not None:
|
|
op = match.groups()[0]
|
|
if op == '!=':
|
|
type_condition_antivalent = True
|
|
break
|
|
|
|
for excl_cond in self.ccb:
|
|
match = re.search(f"(\!=|==)[\(\)\w\d\s]+{excl_cond}", if_expr)
|
|
if match is not None:
|
|
op = match.groups()[0]
|
|
if op == '==':
|
|
type_condition_antivalent = True
|
|
break
|
|
|
|
if type_condition_antivalent:
|
|
break
|
|
|
|
if isinstance(n.rvalue, c_ast.TernaryOp):
|
|
# a ternary op -> we have to dissect it
|
|
cg = c_generator.CGenerator()
|
|
expr = cg.visit(n.rvalue)
|
|
elsv = ExprListSerializerVisitor()
|
|
elsv.visit(n.rvalue)
|
|
if (self.state in elsv.serial) and not(type_condition_antivalent):
|
|
self.assignments.append((n,path,fallthrough_case_names,is_conditional,invariants))
|
|
print(f">>>> {self.state} ∈ {expr} antivalent={type_condition_antivalent}")
|
|
#n.rvalue.show()
|
|
#print(expr)
|
|
else:
|
|
if not(isinstance(n.rvalue, c_ast.Constant) or isinstance(n.rvalue, c_ast.BinaryOp)):
|
|
rval_str = n.rvalue.name
|
|
print(f">>>> {rval_str} == {self.state} antivalent={type_condition_antivalent}")
|
|
if (rval_str == self.state) and not(type_condition_antivalent):
|
|
self.assignments.append((n,path,fallthrough_case_names,is_conditional,invariants))
|
|
|
|
class EnumDefVisitor(c_ast.NodeVisitor):
|
|
def __init__(self, name):
|
|
super().__init__()
|
|
self._name = name
|
|
self.enums = {}
|
|
|
|
def visit_Enum(self, node):
|
|
self.enums[self._name] = node
|
|
|
|
class EnumTypedefVisitor(c_ast.NodeVisitor):
|
|
def __init__(self):
|
|
self.enums = {}
|
|
|
|
def visit_Typedef(self, node):
|
|
ev = EnumDefVisitor(node.name)
|
|
ev.visit(node)
|
|
self.enums = {**self.enums, **ev.enums}
|
|
|
|
|
|
class EnumVisitor(c_ast.NodeVisitor):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.enum_names = []
|
|
|
|
def visit_Enumerator(self, node):
|
|
self.enum_names.append(node.name)
|
|
|
|
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, path, states, fallthrough_states, is_conditional, invariants):
|
|
super().__init__()
|
|
self.states = states
|
|
self.path = path
|
|
self.invariants = invariants
|
|
self.fallthrough_states = fallthrough_states
|
|
self.is_conditional = is_conditional
|
|
self._asm_node = asm_node
|
|
self.tran_table = []
|
|
|
|
def visit_Case(self, node):
|
|
clev = CaseLabelExtractionVisitor()
|
|
clev.visit(node.children()[0][1])
|
|
state_from = clev.label
|
|
# highly inefficient but it is what it is
|
|
hit = False
|
|
#for n in self.path:
|
|
sctv = SwitchCaseTermVisitor(self._asm_node)
|
|
#node.show()
|
|
sctv.visit(node)
|
|
hit = sctv.hit
|
|
|
|
#print(state_from, "->", self._asm_node.rvalue.name, "? hit=", hit, "isstate=", (state_from in self.states), "invar=", self.invariants)
|
|
if (hit or (state_from in self.invariants)) 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
|
|
if isinstance(self._asm_node.rvalue, c_ast.TernaryOp):
|
|
elsv = ExprListSerializerVisitor()
|
|
elsv.visit(self._asm_node.rvalue)
|
|
ids = elsv.serial
|
|
for i in ids:
|
|
if i in self.states:
|
|
self.tran_table.append((state_from, i))
|
|
for ft in self.fallthrough_states:
|
|
self.tran_table.append((ft, i))
|
|
else:
|
|
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, func_blacklist = None):
|
|
super().__init__()
|
|
self._func_bl = func_blacklist
|
|
self._sas = state_asmts
|
|
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})"
|
|
if not(name in self._func_bl):
|
|
self.properties.append(fcall)
|
|
|
|
def visit_Assignment(self, node):
|
|
if not(node in self._sas):
|
|
lvalue = None
|
|
rvalue = None
|
|
if isinstance(node.lvalue, c_ast.StructRef):
|
|
lvalue = f"{node.lvalue.children()[0][1].name}->{node.lvalue.children()[1][1].name}";
|
|
else:
|
|
lvalue = node.lvalue.name
|
|
|
|
#if isinstance(node.rvalue, c_ast.Constant):
|
|
# rvalue = f"{node.rvalue.type }({node.rvalue.value})"
|
|
#else:
|
|
# rvalue = node.rvalue.name
|
|
cg = c_generator.CGenerator()
|
|
rvalue = cg.visit(node.rvalue)
|
|
|
|
prop = f"{lvalue}<={rvalue}"
|
|
self.properties.append(prop)
|
|
|
|
|
|
class SwitchCaseCodePropertyVisitor(c_ast.NodeVisitor):
|
|
def __init__(self, case, state_asmts, func_blacklist):
|
|
super().__init__()
|
|
self._func_bl = func_blacklist
|
|
self._case = case
|
|
self._sas = state_asmts
|
|
self.properties = []
|
|
|
|
def visit_Case(self, node):
|
|
label = node.children()[0][1]
|
|
block = node
|
|
clev = CaseLabelExtractionVisitor()
|
|
clev.visit(label)
|
|
if clev.label == self._case:
|
|
scpv = SwitchCasePropertyVisitor(self._sas, self._func_bl)
|
|
scpv.visit(block)
|
|
self.properties += scpv.properties
|
|
|