diff --git a/mythril/analysis/callgraph.py b/mythril/analysis/callgraph.py
index 48c3d366..fd619c9b 100644
--- a/mythril/analysis/callgraph.py
+++ b/mythril/analysis/callgraph.py
@@ -1,217 +1,96 @@
-from z3 import Z3Exception, simplify
-from laser.ethereum.svm import NodeFlags
import re
-default_title = '
Mythril / Ethereum LASER Symbolic VM
'
-
-default_style = '''
-
-'''
+from jinja2 import Environment, PackageLoader, select_autoescape
+from laser.ethereum.svm import NodeFlags
+from z3 import Z3Exception, simplify
-default_opts = '''
- var options = {
- autoResize: true,
- height: '100%',
- width: '100%',
- manipulation: false,
- height: '90%',
- layout: {
- randomSeed: undefined,
- improvedLayout:true,
- hierarchical: {
- enabled:true,
- levelSeparation: 450,
- nodeSpacing: 200,
- treeSpacing: 100,
- blockShifting: true,
- edgeMinimization: true,
- parentCentralization: false,
- direction: 'LR', // UD, DU, LR, RL
- sortMethod: 'directed' // hubsize, directed
+default_opts = {
+ 'autoResize': True,
+ 'height': '100%',
+ 'width': '100%',
+ 'manipulation': False,
+ 'height': '90%',
+ 'layout': {
+ 'improvedLayout': True,
+ 'hierarchical': {
+ 'enabled': True,
+ 'levelSeparation': 450,
+ 'nodeSpacing': 200,
+ 'treeSpacing': 100,
+ 'blockShifting': True,
+ 'edgeMinimization': True,
+ 'parentCentralization': False,
+ 'direction': 'LR',
+ 'sortMethod': 'directed'
}
- },
- nodes:{
- borderWidth: 1,
- borderWidthSelected: 2,
- chosen: true,
- shape: 'box',
- font: {
- align: 'left',
- color: '#FFFFFF',
- },
- },
- edges:{
- font: {
- color: '#ffffff',
- size: 12, // px
- face: 'arial',
- background: 'none',
- strokeWidth: 0, // px
- strokeColor: '#ffffff',
- align: 'horizontal',
- multi: false,
- vadjust: 0,
- }
- },
-
- physics:{
- enabled: [ENABLE_PHYSICS],
- }
-
- }
-'''
-
-
-phrack_style = '''
-
-'''
-
-phrack_opts = '''
- var options = {
- autoResize: true,
- height: '100%',
- width: '100%',
- manipulation: false,
- height: '90%',
- layout: {
- randomSeed: undefined,
- improvedLayout:true,
- hierarchical: {
- enabled:true,
- levelSeparation: 450,
- nodeSpacing: 200,
- treeSpacing: 100,
- blockShifting: true,
- edgeMinimization: true,
- parentCentralization: false,
- direction: 'LR', // UD, DU, LR, RL
- sortMethod: 'directed' // hubsize, 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,
}
- },
- nodes:{
- color: '#000000',
- borderWidth: 1,
- borderWidthSelected: 1,
- shapeProperties: {
- borderDashes: false, // only for borders
- borderRadius: 0, // only for box shape
- },
- chosen: true,
- shape: 'box',
- font: {
- face: 'courier new',
- align: 'left',
- color: '#000000',
+ },
+ 'physics': {'enabled': False}
+}
+
+phrack_opts = {
+ 'nodes': {
+ 'color': '#000000',
+ 'borderWidth': 1,
+ 'borderWidthSelected': 1,
+ 'shapeProperties': {
+ 'borderDashes': False,
+ 'borderRadius': 0,
},
- },
- edges:{
- font: {
- color: '#000000',
- face: 'courier new',
- background: 'none',
- strokeWidth: 0, // px
- strokeColor: '#ffffff',
- align: 'horizontal',
- multi: false,
- vadjust: 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,
}
- },
-
- physics:{
- enabled: [ENABLE_PHYSICS],
- }
- }
-'''
-
-graph_html = '''
-
-
- [STYLE]
-
-
-
-
-
-
-Mythril / LASER Symbolic VM
-
-
-
-
-'''
-
-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'}}",
+}
+
+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'}}
-def serialize(statespace, color_map):
+def extract_nodes(statespace, color_map):
nodes = []
- edges = []
-
for node_key in statespace.nodes:
node = statespace.nodes[node_key]
@@ -223,16 +102,31 @@ def serialize(statespace, color_map):
code = re.sub("JUMPDEST", node.function_name, code)
code_split = code.split("\\n")
+ code_split = [x for x in code_split if x]
+ full_code = '\n'.join(code_split)
- truncated_code = code if (len(code_split) < 7) else "\\n".join(code_split[:6]) + "\\n(click to expand +)"
+ truncated_code = '\n'.join(code_split) if (len(code_split) < 7) else '\n'.join(code_split[:6]) + "\n(click to expand +)"
color = color_map[node.get_cfg_dict()['contract_name']]
- nodes.append("{id: '" + str(node_key) + "', color: " + color + ", size: 150, 'label': '" + truncated_code + "', 'fullLabel': '" + code + "', 'truncLabel': '" + truncated_code + "', 'isExpanded': false}")
+ nodes.append({
+ 'id': str(node_key),
+ 'color': color,
+ 'size': 150,
+ 'label': truncated_code,
+ 'fullLabel': full_code,
+ 'truncLabel': truncated_code,
+ 'isExpanded': False
+ })
+ return nodes
+
+
+def extract_edges(statespace):
+ edges = []
for edge in statespace.edges:
- if (edge.condition is None):
+ if edge.condition is None:
label = ""
else:
@@ -242,38 +136,52 @@ def serialize(statespace, color_map):
label = str(edge.condition).replace("\n", "")
label = re.sub("([^_])([\d]{2}\d+)", lambda m: m.group(1) + hex(int(m.group(2))), label)
- code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code)
-
- edges.append("{from: '" + str(edge.as_dict()['from']) + "', to: '" + str(edge.as_dict()['to']) + "', 'arrows': 'to', 'label': '" + label + "', 'smooth': {'type': 'cubicBezier'}}")
- return "var nodes = [\n" + ",\n".join(nodes) + "\n];\nvar edges = [\n" + ",\n".join(edges) + "\n];"
+ edges.append({
+ 'from': str(edge.as_dict()['from']),
+ 'to': str(edge.as_dict()['to']),
+ 'arrows': 'to',
+ 'label': label,
+ 'smooth': {'type': 'cubicBezier'}
+ })
+ return edges
-def generate_graph(statespace, physics=False, phrackify=False):
+def generate_graph(statespace, physics=False, phrackify=False, opts=None):
'''
This is some of the the ugliest code in the whole project.
At some point someone needs to write a templating system.
'''
+ if opts is None:
+ opts = {}
+ env = Environment(
+ loader=PackageLoader('mythril.analysis'),
+ autoescape=select_autoescape(['html', 'xml'])
+ )
+ template = env.get_template('graph.html')
+
color_map = {}
+ graph_opts = default_opts
if phrackify:
-
+ graph_opts.update(phrack_opts)
for k in statespace.accounts:
- color_map[statespace.accounts[k].contract_name] = "{border: '#000000', background: '#ffffff', highlight: {border: '#000000', background: '#ffffff'}}"
-
- html = graph_html.replace("[STYLE]", phrack_style).replace("[OPTS]", phrack_opts)
-
+ color_map[statespace.accounts[k].contract_name] = phrack_color
else:
-
i = 0
-
for k in statespace.accounts:
- color_map[statespace.accounts[k].contract_name] = colors[i]
+ color_map[statespace.accounts[k].contract_name] = default_colors[i]
i += 1
- html = graph_html.replace("[STYLE]", default_style).replace("[OPTS]", default_opts)
+ graph_opts.update(opts)
+ graph_opts['physics']['enabled'] = physics
- html = html.replace("[JS]", serialize(statespace, color_map)).replace("[ENABLE_PHYSICS]", str(physics).lower())
+ nodes = extract_nodes(statespace, color_map)
- return html
+ return template.render(title="Mythril / Ethereum LASER Symbolic VM",
+ nodes=extract_nodes(statespace, color_map),
+ edges=extract_edges(statespace),
+ phrackify=phrackify,
+ opts=graph_opts
+ )
diff --git a/mythril/analysis/templates/graph.html b/mythril/analysis/templates/graph.html
new file mode 100644
index 00000000..58b0e000
--- /dev/null
+++ b/mythril/analysis/templates/graph.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+ {% if not phrackify %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+{{ title }}
+
+
+
+
diff --git a/requirements.txt b/requirements.txt
index d517f1f8..d63c7c86 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,3 +15,4 @@ eth-keys>=0.2.0b3
eth-rlp>=0.1.0
eth-tester>=0.1.0b21
coverage
+jinja2