Skip to content

Instantly share code, notes, and snippets.

@tzaffi
Created March 26, 2023 03:45
Show Gist options
  • Save tzaffi/5567e4c3de2fe91a89232bd4ea05b7b3 to your computer and use it in GitHub Desktop.
Save tzaffi/5567e4c3de2fe91a89232bd4ea05b7b3 to your computer and use it in GitHub Desktop.
Static Call Graphs via `pycg`
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}")
# ❯ 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