mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
Most system headers contain functions (e.g. `memcpy` in `string.h`) which aren't needed in our own header files. For the most part, our own headers should only include types needed to declare our own types and functions. We now enforce this so we think twice about which headers we really need in the .h files.
116 lines
3.1 KiB
Python
Executable File
116 lines
3.1 KiB
Python
Executable File
#!/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[str, List[str]]:
|
|
"""
|
|
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: Dict[str, Set[str]] = 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[str],
|
|
callgraph: Dict[str, List[str]],
|
|
cycles: Set[str],
|
|
stack: List[str],
|
|
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[str]) -> 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[str] = set()
|
|
visited: Set[str] = 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",
|
|
})
|