diff --git a/scripts/maint/analyze_callgraph.py b/scripts/maint/analyze_callgraph.py new file mode 100755 index 0000000000..afafe1430b --- /dev/null +++ b/scripts/maint/analyze_callgraph.py @@ -0,0 +1,72 @@ +#!/usr/bin/python + +import re +import sys +import copy +import cPickle +import os + +class Parser: + def __init__(self): + self.calls = {} + + def enter_func(self, name): + if self.infunc and not self.extern: + self.calls.setdefault(self.infunc, set()).update( self.calledfns ) + + self.calledfns = set() + self.infunc = name + self.extern = False + + def parse_callgraph_file(self, inp): + self.infunc = None + self.extern = False + self.calledfns = set() + for line in inp: + m = re.match(r"Call graph node for function: '([^']+)'", line) + if m: + self.enter_func(m.group(1)) + continue + m = re.match(r" CS<[^>]+> calls external node", line) + if m: + self.extern = True + m = re.match(r" CS<[^>]+> calls function '([^']+)'", line) + if m: + self.calledfns.add(m.group(1)) + self.enter_func(None) + + def extract_callgraph(self): + c = self.calls + self.calls = {} + return c + + +def transitive_closure(g): + changed = True + g = copy.deepcopy(g) + while changed: + changed = False + print "X" + for k in g.keys(): + newset = g[k].copy() + for fn in g[k]: + newset.update(g.get(fn, set())) + if len(newset) != len(g[k]): + g[k].update( newset ) + changed = True + return g + +if __name__ == '__main__': + p = Parser() + for fname in sys.argv[1:]: + with open(fname, 'r') as f: + p.parse_callgraph_file(f) + callgraph = p.extract_callgraph() + + closure = transitive_closure(callgraph) + + with open('callgraph.cp', 'w') as f: + cPickle.dump(callgraph, f) + + with open('callgraph_closure.cp', 'w') as f: + cPickle.dump(closure, f) diff --git a/scripts/maint/display_callgraph.py b/scripts/maint/display_callgraph.py new file mode 100755 index 0000000000..4e4ea637ff --- /dev/null +++ b/scripts/maint/display_callgraph.py @@ -0,0 +1,9 @@ +#!/usr/bin/python + +import cPickle + +callgraph = cPickle.load(open("callgraph.cp")) +closure = cPickle.load(open("callgraph_closure.cp")) + +for n_reachable, fn in sorted(list((len(r), fn) for fn, r in closure.iteritems())): + print "%s can reach %s other functions." %(fn, n_reachable) diff --git a/scripts/maint/generate_callgraph.sh b/scripts/maint/generate_callgraph.sh new file mode 100755 index 0000000000..97330cc949 --- /dev/null +++ b/scripts/maint/generate_callgraph.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +C_FILES=`echo src/common/*.c src/or/*.c src/tools/*.c` +CFLAGS="-Isrc/ext/trunnel -Isrc/trunnel -I. -Isrc/ext -Isrc/common -DLOCALSTATEDIR=\"\" -DSHARE_DATADIR=\"\" -Dinline=" + +mkdir -p callgraph/src/common +mkdir -p callgraph/src/or +mkdir -p callgraph/src/tools + +for fn in $C_FILES; do + clang $CFLAGS -S -emit-llvm -fno-inline -o - $fn | \ + opt -analyze -print-callgraph 2> "callgraph/${fn}allgraph" +done