toxcore/other/analysis/check_recursion

116 lines
3.0 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env python3
"""
Tool to check for recursive calls in toxcore C code.
Usage:
cat toxav/*.c toxcore/*.c toxencryptsave/*.c \
| clang `pkg-config --cflags libsodium opus vpx` \
-Itoxav -Itoxcore -Itoxencryptsave -S -emit-llvm -xc - -o- \
| opt -analyze -print-callgraph 2>&1 \
| other/analysis/check_recursion
"""
import collections
import fileinput
import re
import sys
import time
from typing import Dict
from typing import List
from typing import Set
def load_callgraph() -> Dict:
"""
Parses the output from opt -print-callgraph from stdin or argv.
Returns graph as dict[str, list[str]] containing nodes with their outgoing
edges.
"""
graph = collections.defaultdict(set)
cur = None
for line in fileinput.input():
found = re.search("Call graph node for function: '(.*)'", line)
if found:
cur = found.group(1)
if cur:
found = re.search("calls function '(.*)'", line)
if found:
graph[cur].add(found.group(1))
return {k: sorted(v) for k, v in graph.items()}
def walk(
visited: Set,
callgraph: Dict,
cycles: Set,
stack: List,
cur: str,
) -> None:
"""
Detects cycles in the callgraph and adds them to the cycles parameter.
"""
if cur in visited:
return
stack.append(cur)
for callee in callgraph.get(cur, ()):
try:
cycles.add(" -> ".join(stack[stack.index(callee):] + [callee]))
except ValueError:
walk(visited, callgraph, cycles, stack, callee)
visited.add(callee)
stack.pop()
def get_time() -> int:
"""
Return the current time in milliseconds.
"""
return int(round(time.time() * 1000))
def find_recursion(expected: Set) -> None:
"""
Main function: detects cycles and prints them.
Takes a set of expected cycles. If any of the expected cycles was not found,
or any unexpected cycle was found, the program exits with an error.
"""
start = prev = get_time()
print("[+0000=0000] Generating callgraph")
callgraph = load_callgraph()
now = get_time()
print("[+%04d=%04d] Finding recursion" % (now - prev, now - start))
prev = now
cycles = set()
visited = set()
for func in sorted(callgraph.keys()):
walk(visited, callgraph, cycles, [], func)
now = get_time()
if cycles:
print("[+%04d=%04d] Recursion detected:" % (now - prev, now - start))
for cycle in sorted(cycles):
if cycle in expected:
print(" - " + cycle + " (expected)")
expected.remove(cycle)
cycles.remove(cycle)
else:
print(" - " + cycle)
else:
print("[+%04d=%04d] No recursion detected" % (now - prev, now - start))
if expected:
print("Expected recursion no longer present: " + str(list(expected)))
if expected or cycles:
sys.exit(1)
find_recursion(expected={
"add_to_closest -> add_to_closest",
"add_to_list -> add_to_list",
})