Skip to content

Commit f2fbbba

Browse files
committed
refactor graph visualization
1 parent 8198d32 commit f2fbbba

File tree

8 files changed

+197
-201
lines changed

8 files changed

+197
-201
lines changed

app.py

Lines changed: 0 additions & 188 deletions
This file was deleted.

dashviz.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from viz.app import app
2+
from viz.layout import layout
3+
import logging
4+
5+
app.layout = layout
6+
7+
if __name__ == '__main__':
8+
logging.basicConfig()
9+
logging.getLogger("graph_import").setLevel(logging.INFO)
10+
app.run_server(debug=True)

graph_import.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,30 +89,31 @@ def igraph_to_cytoscape_fig(graph: igraph.Graph, max_nodes: int, max_edges: int)
8989
vdf = graph.get_vertex_dataframe()
9090
vdf.sort_values(by="pr", ascending=False, inplace=True)
9191

92+
93+
chosen_ix = vdf[:max_nodes].index
94+
95+
# now, pick up to 'max_edges' edges with highest weights.
96+
# nodes with no edges will be removed
97+
subgraph = graph.induced_subgraph(chosen_ix)
98+
edf = subgraph.get_edge_dataframe()
99+
edf.sort_values(by="weight", ascending=False, inplace=True)
100+
101+
subgraph = subgraph.subgraph_edges(edf[:max_edges].index, delete_vertices=True)
102+
103+
92104
inferred_hierch = None
93105
if "name" in vdf.columns:
94106
inferred_hierch = HierarchyType.method
95107
elif "class" in vdf.columns:
96108
inferred_hierch = HierarchyType.type_def
97109
else:
98110
inferred_hierch = HierarchyType.package
99-
100-
chosen_ix = vdf[:max_nodes].index
101-
102-
# now, delete edges with the lowest weights so we hav
103-
# no more than 'max_edges' edges.
104-
subgraph = graph.induced_subgraph(chosen_ix)
105-
edf = subgraph.get_edge_dataframe()
106-
edf.sort_values(by="weight", ascending=True, inplace=True)
107-
n_edges_to_delete = max(len(edf) - max_edges, 0)
108-
subgraph.delete_edges(edf[:n_edges_to_delete].index)
109-
110111
# converting to cytoscape element format
111112
def vertex_name(v):
112113
if inferred_hierch == HierarchyType.method:
113-
return v["name"]
114+
return v["class"] + "." + v["method"]
114115
elif inferred_hierch == HierarchyType.type_def:
115-
return f"{v['package']}.{v['class']}"
116+
return v["class"]
116117
return v["package"]
117118

118119
classes = inferred_hierch.name

viz/__init__.py

Whitespace-only changes.

viz/app.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dash import Dash, html
2+
from dash.development.base_component import Component
3+
import dash_cytoscape as cyto
4+
5+
cyto.load_extra_layouts()
6+
app = Dash(__name__)
7+
8+
def field(label: str, input: Component):
9+
return html.Div(children=[
10+
html.Label(children=label, htmlFor=input.id),
11+
input
12+
], className="formField")

viz/callgraph_creation.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
""" A very basic interface for generating a Java callgraph using
2+
a web UI instead of CLI
3+
"""
4+
5+
from dash import html, dcc, Input, Output, State
6+
from dash.development.base_component import Component
7+
from dash.exceptions import PreventUpdate
8+
from graph_import import Args, GEN_CALLGRAPH_JAR
9+
from pathlib import Path
10+
from .app import app, field
11+
12+
graph_create_status = html.Div(children=[
13+
dcc.Loading(id="creating", type="default", children=[
14+
html.H3(id="graph_state")
15+
])
16+
])
17+
18+
graph_create = html.Div(children=[
19+
html.H2(children="Callgraph generation options"),
20+
21+
# html.Label(children="Graph directory where the call graph will be created and imported from", htmlFor="out_dir"),
22+
# dcc.Input(id="out_dir", type="text", placeholder="Folder that will contain graph data",
23+
# value="graphImport-jadx"),
24+
field("Output folder for generated callgraph (where it can be imported from)",
25+
dcc.Input(id="out_dir", type="text", placeholder="Folder that will contain graph data",
26+
value="graphImport-jadx")),
27+
28+
field("Folder that contains .jar files for generating a call graph",
29+
dcc.Input(id="jar_dir", type="text", placeholder="Folder containing .jar files",
30+
value="C:\\dev\\genCallgraph\\toAnalyze")),
31+
32+
field("Identifier of main class (that contains the 'main' function)",
33+
dcc.Input(id="main_identifier", type="text", placeholder="Type a fully qualified class name, e.g, com.foo.MainClass",
34+
value="jadx.gui.JadxGUI")),
35+
36+
field("Words that should appear in a .jar file. A file without any of these words will be skipped",
37+
dcc.Dropdown(id="jar_filter", multi=True, placeholder="Type any word, e.g 'jadx' ",
38+
value=["jadx"])),
39+
40+
41+
field("Words that should appear in an edge's source and target identifiers. An edge without any of these words will be skipped",
42+
dcc.Dropdown(id="edge_filter", multi=True, placeholder="Type any word, e.g 'jadx' ",
43+
value=["jadx"])),
44+
45+
html.Button("Create callgraph", id="create_cg"),
46+
graph_create_status,
47+
])
48+
49+
50+
# a hacky way of using a dropdown input
51+
# for arbitrary input
52+
def multi_input(search_value, value):
53+
if not search_value:
54+
raise PreventUpdate
55+
existing = {v: v for v in value} if value else {}
56+
return {**existing, search_value: search_value}
57+
58+
jar_filter = app.callback(
59+
Output("jar_filter", "options"),
60+
Input("jar_filter", "search_value"),
61+
State("jar_filter", "value")
62+
)(multi_input)
63+
64+
edge_filter = app.callback(
65+
Output("edge_filter", "options"),
66+
Input("edge_filter", "search_value"),
67+
State("edge_filter", "value")
68+
)(multi_input)
69+
70+
@app.callback(
71+
dict(graph_state=Output("graph_state", "children"),
72+
import_dir=Output("import_dir", "value")
73+
),
74+
Input("create_cg", "n_clicks"),
75+
State("out_dir", "value"),
76+
State("jar_dir", "value"),
77+
State("edge_filter", "value"),
78+
State("jar_filter", "value"),
79+
State("main_identifier", "value"),
80+
State("graph_state", "children"),
81+
State("import_dir", "value")
82+
)
83+
def create_callgraph(n_clicks, out_dir, jar_dir, edge_filter, jar_filter, main_identifier, graph_state, import_dir):
84+
if n_clicks:
85+
print("Trying to fetch callgraph")
86+
args = Args(
87+
input_jar_folder=Path(jar_dir).resolve(),
88+
edge_filter=list(edge_filter) if edge_filter else [],
89+
jar_filter=list(jar_filter) if jar_filter else [],
90+
main_class_identifier=main_identifier,
91+
graph_output_folder=Path(out_dir).resolve(),
92+
)
93+
cg = args.run_callgraph(GEN_CALLGRAPH_JAR)
94+
return dict(graph_state=f"Created a graph with {len(cg.node_props)} nodes and {len(cg.edges)} edges ",
95+
import_dir=out_dir)
96+
return dict(graph_state=graph_state, import_dir=import_dir)

0 commit comments

Comments
 (0)