Created
March 26, 2023 03:45
-
-
Save tzaffi/5567e4c3de2fe91a89232bd4ea05b7b3 to your computer and use it in GitHub Desktop.
Static Call Graphs via `pycg`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import networkx as nx | |
REACHABLE = "reachable.json" | |
EVERYTHING = "everything.json" | |
def to_ntwx_json(data: dict) -> nx.DiGraph: | |
nt = nx.DiGraph() | |
def _ensure_key(name): | |
if name not in nt: | |
nt.add_node(name, size=50) | |
for node in data: | |
_ensure_key(node) | |
for child in data[node]: | |
_ensure_key(child) | |
nt.add_edge(node, child) | |
return nt | |
color_filter = { | |
"menus": "red", | |
"loader": "purple", | |
"forms": "darkblue", | |
"popups": "blue", | |
"default": "black", | |
} | |
def ntw_pyvis(ntx: nx.DiGraph, root, size0=5, loosen=2): | |
nt = Network(width="1200px", height="800px", directed=True) | |
for node in ntx.nodes: | |
mass = ntx.nodes[node]["size"] / (loosen * size0) | |
size = size0 * ntx.nodes[node]["size"] ** 0.5 | |
label = node | |
color = color_filter["default"] | |
for key in color_filter: | |
if key in node: | |
color = color_filter[key] | |
kwargs = { | |
"label": label, | |
"mass": mass, | |
"size": size, | |
"color": color, | |
} | |
nt.add_node( | |
node, | |
**kwargs, | |
) | |
for link in ntx.edges: | |
try: | |
depth = nx.shortest_path_length(ntx, source=root, target=link[0]) | |
width = max(size0, size0 * (12 - 4 * depth)) | |
except: | |
width = 5 | |
nt.add_edge(link[0], link[1], width=width) | |
nt.show_buttons(filter_=["physics"]) | |
nt.show("nodes.html") | |
with open(REACHABLE, "r") as f: | |
reachable = json.load(f) | |
ntx = to_ntwx_json(reachable) | |
# print(ntx.number_of_nodes()) # 1278 nodes | |
with open(EVERYTHING, "r") as f: | |
everything = json.load(f) | |
rvals = set.union(*[set(v) for v in reachable.values()]) | |
evals = set.union(*[set(v) for v in everything.values()]) | |
unreachable = list( | |
sorted((set(everything.keys()) | evals) - (set(reachable.keys()) | rvals)) | |
) | |
EXCLUDES = [ | |
".", | |
"<builtin>", | |
"Cryptodome", | |
] | |
everything = list( | |
filter(lambda s: s.split(".")[0] not in EXCLUDES, everything) | |
) | |
unreachable = list( | |
filter(lambda s: s.split(".")[0] not in EXCLUDES, unreachable) | |
) | |
reachable = list( | |
filter(lambda s: s.split(".")[0] not in EXCLUDES, sorted(reachable)) | |
) | |
def fold_paths(paths): | |
tree = {} | |
for path in paths: | |
parts = path.split(".") | |
current_node = tree | |
for part in parts: | |
if part not in current_node: | |
current_node[part] = {} | |
current_node = current_node[part] | |
return tree | |
def get_objects(paths): | |
objects = [] | |
prev_node = "" | |
for path in paths: | |
parts = path.split(".") | |
current_node = ".".join(parts[:2]) | |
if not current_node.startswith(prev_node): | |
objects.append(prev_node) | |
prev_node = current_node | |
objects.append(prev_node) | |
return objects | |
def unfolded(folded): | |
result = [] | |
for key, value in folded.items(): | |
if value: | |
result.append([key, unfolded(value)]) | |
else: | |
result.append(key) | |
return result | |
roots = list(fold_paths(unreachable).keys()) | |
trees = unfolded(fold_paths(unreachable)) | |
o_reachable = get_objects(reachable) | |
o_unreachable = get_objects(unreachable) | |
s_unreachable = set(o_unreachable) | |
o_everything = get_objects(everything) | |
summary = { | |
p: "UNTESTED" if p in s_unreachable else "" for p in sorted(o_everything) | |
} | |
print( | |
json.dumps( | |
[ | |
# roots, | |
# unreachable, | |
# trees, | |
# o_unreachable, | |
summary, | |
], | |
indent=2, | |
) | |
) | |
for k,v in summary.items(): | |
print(f"{k},{v}") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ❯ pip freeze | grep pycg | |
# pycg==0.0.6 | |
clean: | |
rm reachable.json everything.json diff.json | |
reachable.json: | |
pycg --package algosdk tests/steps/account_v2_steps.py tests/steps/application_v2_steps.py tests/steps/other_v2_steps.py tests/steps/steps.py -o reachable.json | |
EVERYTHING := $(shell find algosdk -name "*.py") | |
everything.json: | |
pycg --package algosdk $(EVERYTHING) -o everything.json | |
diff.json: reachable.json everything.json | |
python algosdk_cg.py > diff.json |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment