125 lines
3 KiB
Python
125 lines
3 KiB
Python
|
#!/usr/bin/env python3
|
||
|
|
||
|
""" Generates the call graph (in dot format) of a C code generated by CSmith.
|
||
|
|
||
|
This does not parse C code, but performs only string lookups. In particular, it
|
||
|
assumes functions are named `func_[0-9]+` or `main`, and that a function
|
||
|
implementation is of the form (whitespaces meaningful)
|
||
|
|
||
|
(static)? RETURN_TYPE func_[0-9]+(.*)
|
||
|
{
|
||
|
...
|
||
|
}
|
||
|
"""
|
||
|
|
||
|
import sys
|
||
|
import re
|
||
|
|
||
|
|
||
|
def build_graph(prog):
|
||
|
func_declare_re = re.compile(r'(?:static )?\S.* (func_\d+|main) ?\(.*\)$')
|
||
|
func_call_re = re.compile(r'func_\d+')
|
||
|
|
||
|
graph = {}
|
||
|
cur_function = None
|
||
|
|
||
|
for line in prog:
|
||
|
func_declare_groups = func_declare_re.match(line)
|
||
|
if func_declare_groups:
|
||
|
func_name = func_declare_groups.group(1)
|
||
|
cur_function = func_name
|
||
|
graph[func_name] = []
|
||
|
|
||
|
elif line == '}':
|
||
|
cur_function = None
|
||
|
|
||
|
else:
|
||
|
if cur_function is None:
|
||
|
continue # Not interresting outside of functions
|
||
|
|
||
|
last_find_pos = 0
|
||
|
call_match = func_call_re.search(line, pos=last_find_pos)
|
||
|
|
||
|
while call_match is not None:
|
||
|
graph[cur_function].append(call_match.group(0))
|
||
|
last_find_pos = call_match.end()
|
||
|
call_match = func_call_re.search(line, pos=last_find_pos)
|
||
|
|
||
|
reachable = set()
|
||
|
def mark_reachable(node):
|
||
|
nonlocal reachable, graph
|
||
|
if node in reachable:
|
||
|
return
|
||
|
reachable.add(node)
|
||
|
|
||
|
for child in graph[node]:
|
||
|
mark_reachable(child)
|
||
|
mark_reachable('main')
|
||
|
|
||
|
delete = []
|
||
|
for node in graph:
|
||
|
if node not in reachable:
|
||
|
delete.append(node)
|
||
|
for node in delete:
|
||
|
print('> Deleted: {}'.format(node), file=sys.stderr)
|
||
|
graph.pop(node)
|
||
|
|
||
|
return graph
|
||
|
|
||
|
|
||
|
def dump_graph(graph):
|
||
|
print('digraph prog {')
|
||
|
|
||
|
for node in graph:
|
||
|
for call in graph[node]:
|
||
|
if call in graph:
|
||
|
print('\t{} -> {}'.format(node, call))
|
||
|
else:
|
||
|
print('Wtf is {} (called from {})?'.format(node, call),
|
||
|
file=sys.stderr)
|
||
|
print('}')
|
||
|
|
||
|
|
||
|
def dump_stats(graph, out_file):
|
||
|
entry_point = 'main'
|
||
|
|
||
|
depth_of = {}
|
||
|
def find_depth(node):
|
||
|
nonlocal depth_of
|
||
|
|
||
|
if node in depth_of:
|
||
|
return depth_of[node]
|
||
|
|
||
|
callees = graph[node]
|
||
|
if callees:
|
||
|
depth = max(map(find_depth, callees)) + 1
|
||
|
else:
|
||
|
depth = 1
|
||
|
depth_of[node] = depth
|
||
|
return depth
|
||
|
|
||
|
print("Call chain max depth: {}".format(find_depth(entry_point)),
|
||
|
file=out_file)
|
||
|
|
||
|
|
||
|
def get_prog_lines():
|
||
|
def do_read(handle):
|
||
|
return handle.readlines()
|
||
|
|
||
|
if len(sys.argv) > 1:
|
||
|
with open(sys.argv[1], 'r') as handle:
|
||
|
return do_read(handle)
|
||
|
else:
|
||
|
return do_read(sys.stdin)
|
||
|
|
||
|
|
||
|
def main():
|
||
|
prog = get_prog_lines()
|
||
|
graph = build_graph(prog)
|
||
|
dump_graph(graph)
|
||
|
dump_stats(graph, out_file=sys.stderr)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|