dwarf-assembly/benching/csmith/gen_call_graph.py

124 lines
3 KiB
Python
Executable file

#!/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()