Source code for mythril.analysis.callgraph
"""This module contains the configuration and functions to create call
graphs."""
import re
from jinja2 import Environment, PackageLoader, select_autoescape
from z3 import Z3Exception
from mythril.laser.ethereum.svm import NodeFlags
from mythril.laser.smt import simplify
default_opts = {
"autoResize": True,
"height": "100%",
"width": "100%",
"manipulation": False,
"layout": {
"improvedLayout": True,
"hierarchical": {
"enabled": True,
"levelSeparation": 450,
"nodeSpacing": 200,
"treeSpacing": 100,
"blockShifting": True,
"edgeMinimization": True,
"parentCentralization": False,
"direction": "LR",
"sortMethod": "directed",
},
},
"nodes": {
"color": "#000000",
"borderWidth": 1,
"borderWidthSelected": 2,
"chosen": True,
"shape": "box",
"font": {"align": "left", "color": "#FFFFFF"},
},
"edges": {
"font": {
"color": "#FFFFFF",
"face": "arial",
"background": "none",
"strokeWidth": 0,
"strokeColor": "#ffffff",
"align": "horizontal",
"multi": False,
"vadjust": 0,
}
},
"physics": {"enabled": False},
}
phrack_opts = {
"nodes": {
"color": "#000000",
"borderWidth": 1,
"borderWidthSelected": 1,
"shapeProperties": {"borderDashes": False, "borderRadius": 0},
"chosen": True,
"shape": "box",
"font": {"face": "courier new", "align": "left", "color": "#000000"},
},
"edges": {
"font": {
"color": "#000000",
"face": "courier new",
"background": "none",
"strokeWidth": 0,
"strokeColor": "#ffffff",
"align": "horizontal",
"multi": False,
"vadjust": 0,
}
},
}
default_colors = [
{
"border": "#26996f",
"background": "#2f7e5b",
"highlight": {"border": "#26996f", "background": "#28a16f"},
},
{
"border": "#9e42b3",
"background": "#842899",
"highlight": {"border": "#9e42b3", "background": "#933da6"},
},
{
"border": "#b82323",
"background": "#991d1d",
"highlight": {"border": "#b82323", "background": "#a61f1f"},
},
{
"border": "#4753bf",
"background": "#3b46a1",
"highlight": {"border": "#4753bf", "background": "#424db3"},
},
{
"border": "#26996f",
"background": "#2f7e5b",
"highlight": {"border": "#26996f", "background": "#28a16f"},
},
{
"border": "#9e42b3",
"background": "#842899",
"highlight": {"border": "#9e42b3", "background": "#933da6"},
},
{
"border": "#b82323",
"background": "#991d1d",
"highlight": {"border": "#b82323", "background": "#a61f1f"},
},
{
"border": "#4753bf",
"background": "#3b46a1",
"highlight": {"border": "#4753bf", "background": "#424db3"},
},
]
phrack_color = {
"border": "#000000",
"background": "#ffffff",
"highlight": {"border": "#000000", "background": "#ffffff"},
}
[docs]
def extract_nodes(statespace):
"""
:param statespace:
:param color_map:
:return:
"""
nodes = []
color_map = {}
for node_key in statespace.nodes:
node = statespace.nodes[node_key]
instructions = [state.get_current_instruction() for state in node.states]
code_split = []
for instruction in instructions:
if instruction["opcode"].startswith("PUSH"):
code_line = "%d %s %s" % (
instruction["address"],
instruction["opcode"],
instruction["argument"],
)
elif (
instruction["opcode"].startswith("JUMPDEST")
and NodeFlags.FUNC_ENTRY in node.flags
and instruction["address"] == node.start_addr
):
code_line = node.function_name
else:
code_line = "%d %s" % (instruction["address"], instruction["opcode"])
code_line = re.sub(
"([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code_line
)
code_split.append(code_line)
truncated_code = (
"\n".join(code_split)
if (len(code_split) < 7)
else "\n".join(code_split[:6]) + "\n(click to expand +)"
)
if node.get_cfg_dict()["contract_name"] not in color_map.keys():
color = default_colors[len(color_map) % len(default_colors)]
color_map[node.get_cfg_dict()["contract_name"]] = color
nodes.append(
{
"id": str(node_key),
"color": color_map.get(
node.get_cfg_dict()["contract_name"], default_colors[0]
),
"size": 150,
"fullLabel": "\n".join(code_split),
"label": truncated_code,
"truncLabel": truncated_code,
"isExpanded": False,
}
)
return nodes
[docs]
def extract_edges(statespace):
"""
:param statespace:
:return:
"""
edges = []
for edge in statespace.edges:
if edge.condition is None:
label = ""
else:
try:
label = str(simplify(edge.condition)).replace("\n", "")
except Z3Exception:
label = str(edge.condition).replace("\n", "")
label = re.sub(
r"([^_])([\d]{2}\d+)", lambda m: m.group(1) + hex(int(m.group(2))), label
)
edges.append(
{
"from": str(edge.as_dict["from"]),
"to": str(edge.as_dict["to"]),
"arrows": "to",
"label": label,
"smooth": {"type": "cubicBezier"},
}
)
return edges
[docs]
def generate_graph(
statespace,
title="Mythril / Ethereum LASER Symbolic VM",
physics=False,
phrackify=False,
):
"""
:param statespace:
:param title:
:param physics:
:param phrackify:
:return:
"""
env = Environment(
loader=PackageLoader("mythril.analysis"),
autoescape=select_autoescape(["html", "xml"]),
)
template = env.get_template("callgraph.html")
graph_opts = default_opts
graph_opts["physics"]["enabled"] = physics
return template.render(
title=title,
nodes=extract_nodes(statespace),
edges=extract_edges(statespace),
phrackify=phrackify,
opts=graph_opts,
)