lambda-graphs 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lambda_graphs/__init__.py +18 -0
- lambda_graphs/__main__.py +7 -0
- lambda_graphs/cli.py +183 -0
- lambda_graphs/codeviews/AST/AST.py +120 -0
- lambda_graphs/codeviews/AST/AST_driver.py +37 -0
- lambda_graphs/codeviews/AST/__init__.py +0 -0
- lambda_graphs/codeviews/CFG/CFG.py +42 -0
- lambda_graphs/codeviews/CFG/CFG_c.py +1182 -0
- lambda_graphs/codeviews/CFG/CFG_cpp.py +5604 -0
- lambda_graphs/codeviews/CFG/CFG_driver.py +45 -0
- lambda_graphs/codeviews/CFG/CFG_java.py +1722 -0
- lambda_graphs/codeviews/CFG/__init__.py +0 -0
- lambda_graphs/codeviews/CST/CST_driver.py +70 -0
- lambda_graphs/codeviews/CST/__init__.py +0 -0
- lambda_graphs/codeviews/DFG/DFG_driver.py +42 -0
- lambda_graphs/codeviews/DFG/__init__.py +0 -0
- lambda_graphs/codeviews/SDFG/SDFG.py +135 -0
- lambda_graphs/codeviews/SDFG/SDFG_c.py +2041 -0
- lambda_graphs/codeviews/SDFG/SDFG_cpp.py +4030 -0
- lambda_graphs/codeviews/SDFG/SDFG_java.py +1618 -0
- lambda_graphs/codeviews/SDFG/__init__.py +0 -0
- lambda_graphs/codeviews/__init__.py +0 -0
- lambda_graphs/codeviews/combined_graph/__init__.py +0 -0
- lambda_graphs/codeviews/combined_graph/combined_driver.py +163 -0
- lambda_graphs/tree_parser/__init__.py +0 -0
- lambda_graphs/tree_parser/c_parser.py +565 -0
- lambda_graphs/tree_parser/cpp_parser.py +424 -0
- lambda_graphs/tree_parser/custom_parser.py +66 -0
- lambda_graphs/tree_parser/java_parser.py +254 -0
- lambda_graphs/tree_parser/parser_driver.py +54 -0
- lambda_graphs/utils/DFG_utils.py +44 -0
- lambda_graphs/utils/__init__.py +0 -0
- lambda_graphs/utils/c_nodes.py +431 -0
- lambda_graphs/utils/cpp_nodes.py +1331 -0
- lambda_graphs/utils/java_nodes.py +756 -0
- lambda_graphs/utils/multi_file_merger.py +444 -0
- lambda_graphs/utils/postprocessor.py +83 -0
- lambda_graphs/utils/preprocessor.py +68 -0
- lambda_graphs/utils/src_parser.py +23 -0
- lambda_graphs-0.1.4.dist-info/METADATA +380 -0
- lambda_graphs-0.1.4.dist-info/RECORD +45 -0
- lambda_graphs-0.1.4.dist-info/WHEEL +5 -0
- lambda_graphs-0.1.4.dist-info/entry_points.txt +2 -0
- lambda_graphs-0.1.4.dist-info/licenses/LICENSE +21 -0
- lambda_graphs-0.1.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from tree_sitter import Language
|
|
2
|
+
|
|
3
|
+
import tree_sitter_c
|
|
4
|
+
import tree_sitter_cpp
|
|
5
|
+
import tree_sitter_java
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_language_map():
|
|
9
|
+
"""Return a mapping from language name to tree-sitter Language object.
|
|
10
|
+
|
|
11
|
+
Uses pre-compiled grammar libraries from pip packages instead of
|
|
12
|
+
cloning and building grammars at runtime.
|
|
13
|
+
"""
|
|
14
|
+
return {
|
|
15
|
+
"c": Language(tree_sitter_c.language()),
|
|
16
|
+
"cpp": Language(tree_sitter_cpp.language()),
|
|
17
|
+
"java": Language(tree_sitter_java.language()),
|
|
18
|
+
}
|
lambda_graphs/cli.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""CLI for lambda-graphs"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
from .codeviews.combined_graph.combined_driver import CombinedDriver
|
|
13
|
+
from .utils.multi_file_merger import merge_files
|
|
14
|
+
from . import get_language_map
|
|
15
|
+
|
|
16
|
+
get_language_map()
|
|
17
|
+
app = typer.Typer()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.callback(invoke_without_command=True)
|
|
21
|
+
def main(
|
|
22
|
+
lang: str = typer.Option(..., help="c, cpp, java"),
|
|
23
|
+
code: Optional[str] = typer.Option(
|
|
24
|
+
None,
|
|
25
|
+
help="""
|
|
26
|
+
int main() {
|
|
27
|
+
int x = 3;
|
|
28
|
+
x = x + 3;
|
|
29
|
+
int y = 4;
|
|
30
|
+
y += 1;
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
""",
|
|
34
|
+
),
|
|
35
|
+
code_file: Optional[Path] = typer.Option(None, help="./test_file.c"),
|
|
36
|
+
code_folder: Optional[Path] = typer.Option(
|
|
37
|
+
None, help="./project_folder/ - combines all source files"
|
|
38
|
+
),
|
|
39
|
+
combined_name: Optional[str] = typer.Option(
|
|
40
|
+
None,
|
|
41
|
+
help="Custom name for combined file (without extension), e.g., 'myproject'",
|
|
42
|
+
),
|
|
43
|
+
graphs: str = typer.Option("ast,dfg", help="ast, cfg, dfg"),
|
|
44
|
+
output: str = typer.Option("dot", help="all/json/dot (dot generates png as well)"),
|
|
45
|
+
blacklisted: str = typer.Option("", help="Nodes to be removed from the AST"),
|
|
46
|
+
collapsed: bool = typer.Option(
|
|
47
|
+
False, help="Collapses all occurrences of a variable into one node"
|
|
48
|
+
),
|
|
49
|
+
last_def: bool = typer.Option(
|
|
50
|
+
False, help="Adds last definition information to the DFG"
|
|
51
|
+
),
|
|
52
|
+
last_use: bool = typer.Option(False, help="Adds last use information to the DFG"),
|
|
53
|
+
throw_parse_error: bool = typer.Option(
|
|
54
|
+
False, help="Throws an error if the code cannot be parsed"
|
|
55
|
+
),
|
|
56
|
+
debug: bool = typer.Option(False, help="Enables debug logs"),
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
lambda-graphs
|
|
60
|
+
|
|
61
|
+
Generates, customizes and combines multiple source code representations (AST, CFG, DFG)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
if debug:
|
|
66
|
+
level = "DEBUG"
|
|
67
|
+
else:
|
|
68
|
+
level = "WARNING"
|
|
69
|
+
|
|
70
|
+
config = {
|
|
71
|
+
"handlers": [{"sink": sys.stderr, "level": level}],
|
|
72
|
+
}
|
|
73
|
+
logger.configure(**config)
|
|
74
|
+
|
|
75
|
+
codeviews = {
|
|
76
|
+
"AST": {
|
|
77
|
+
"exists": False,
|
|
78
|
+
"collapsed": collapsed,
|
|
79
|
+
"minimized": bool(blacklisted),
|
|
80
|
+
"blacklisted": blacklisted.split(","),
|
|
81
|
+
},
|
|
82
|
+
"DFG": {
|
|
83
|
+
"exists": False,
|
|
84
|
+
"collapsed": collapsed,
|
|
85
|
+
"minimized": False,
|
|
86
|
+
"statements": True,
|
|
87
|
+
"last_def": last_def,
|
|
88
|
+
"last_use": last_use,
|
|
89
|
+
},
|
|
90
|
+
"CFG": {
|
|
91
|
+
"exists": False,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if "ast" in graphs.lower():
|
|
96
|
+
codeviews["AST"] = {
|
|
97
|
+
"exists": True,
|
|
98
|
+
"collapsed": collapsed,
|
|
99
|
+
"minimized": bool(blacklisted),
|
|
100
|
+
"blacklisted": blacklisted.split(","),
|
|
101
|
+
}
|
|
102
|
+
if "dfg" in graphs.lower():
|
|
103
|
+
codeviews["DFG"] = {
|
|
104
|
+
"exists": True,
|
|
105
|
+
"collapsed": collapsed,
|
|
106
|
+
"minimized": False,
|
|
107
|
+
"statements": True,
|
|
108
|
+
"last_def": last_def,
|
|
109
|
+
"last_use": last_use,
|
|
110
|
+
}
|
|
111
|
+
if "cfg" in graphs.lower():
|
|
112
|
+
codeviews["CFG"] = {
|
|
113
|
+
"exists": True,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
output_dir = Path("output")
|
|
118
|
+
output_dir.mkdir(exist_ok=True)
|
|
119
|
+
|
|
120
|
+
if code_folder:
|
|
121
|
+
logger.info(f"Combining source files from folder: {code_folder}")
|
|
122
|
+
|
|
123
|
+
temp_dir = Path("temp")
|
|
124
|
+
temp_dir.mkdir(exist_ok=True)
|
|
125
|
+
|
|
126
|
+
if combined_name:
|
|
127
|
+
output_base_name = combined_name
|
|
128
|
+
else:
|
|
129
|
+
output_base_name = code_folder.name
|
|
130
|
+
|
|
131
|
+
if lang == "c":
|
|
132
|
+
combined_source_file = str(temp_dir / f"{output_base_name}.c")
|
|
133
|
+
elif lang == "java":
|
|
134
|
+
combined_source_file = str(temp_dir / f"{output_base_name}.java")
|
|
135
|
+
else:
|
|
136
|
+
combined_source_file = str(temp_dir / f"{output_base_name}.cpp")
|
|
137
|
+
|
|
138
|
+
combined_file_path = merge_files(
|
|
139
|
+
str(code_folder), lang, combined_source_file
|
|
140
|
+
)
|
|
141
|
+
logger.info(f"Combined source written to: {combined_file_path}")
|
|
142
|
+
|
|
143
|
+
with open(combined_file_path, "r") as file_handle:
|
|
144
|
+
src_code = file_handle.read()
|
|
145
|
+
|
|
146
|
+
output_file_name = str(output_dir / f"{output_base_name}.json")
|
|
147
|
+
CombinedDriver(
|
|
148
|
+
src_language=lang,
|
|
149
|
+
src_code=src_code,
|
|
150
|
+
output_file=output_file_name,
|
|
151
|
+
graph_format=output,
|
|
152
|
+
codeviews=codeviews,
|
|
153
|
+
)
|
|
154
|
+
elif code_file:
|
|
155
|
+
file_handle = open(code_file, "r")
|
|
156
|
+
src_code = file_handle.read()
|
|
157
|
+
file_handle.close()
|
|
158
|
+
output_base_name = code_file.stem
|
|
159
|
+
output_file_name = str(output_dir / f"{output_base_name}.json")
|
|
160
|
+
CombinedDriver(
|
|
161
|
+
src_language=lang,
|
|
162
|
+
src_code=src_code,
|
|
163
|
+
output_file=output_file_name,
|
|
164
|
+
graph_format=output,
|
|
165
|
+
codeviews=codeviews,
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
if not code:
|
|
169
|
+
raise Exception("No code provided")
|
|
170
|
+
output_file_name = str(output_dir / "output.json")
|
|
171
|
+
CombinedDriver(
|
|
172
|
+
src_language=lang,
|
|
173
|
+
src_code=code,
|
|
174
|
+
output_file=output_file_name,
|
|
175
|
+
graph_format=output,
|
|
176
|
+
codeviews=codeviews,
|
|
177
|
+
)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
try:
|
|
180
|
+
logger.error(e.msg)
|
|
181
|
+
except AttributeError:
|
|
182
|
+
logger.error(e)
|
|
183
|
+
sys.exit(-1)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ASTGraph:
|
|
5
|
+
def __init__(self, src_language, src_code, properties, root_node, parser):
|
|
6
|
+
self.src_language = src_language
|
|
7
|
+
self.src_code = src_code
|
|
8
|
+
self.properties = properties
|
|
9
|
+
self.root_node = root_node
|
|
10
|
+
self.parser = parser
|
|
11
|
+
self.index = self.parser.index
|
|
12
|
+
self.all_tokens = self.parser.all_tokens
|
|
13
|
+
self.label = self.parser.label
|
|
14
|
+
self.method_map = self.parser.method_map
|
|
15
|
+
self.method_calls = self.parser.method_calls
|
|
16
|
+
self.start_line = self.parser.start_line
|
|
17
|
+
self.graph = self.to_networkx()
|
|
18
|
+
|
|
19
|
+
def get_AST_nodes(self, root_node, AST, AST_index):
|
|
20
|
+
|
|
21
|
+
if root_node.is_named:
|
|
22
|
+
if root_node.type in ["string_content", "escape_sequence"]:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
current_node_id = AST_index[
|
|
26
|
+
(root_node.start_point, root_node.end_point, root_node.type)
|
|
27
|
+
]
|
|
28
|
+
if current_node_id in self.all_tokens:
|
|
29
|
+
|
|
30
|
+
label = self.label[current_node_id]
|
|
31
|
+
else:
|
|
32
|
+
label = root_node.type
|
|
33
|
+
AST.add_node(
|
|
34
|
+
current_node_id,
|
|
35
|
+
node_type=root_node.type,
|
|
36
|
+
label=label,
|
|
37
|
+
shape="box",
|
|
38
|
+
style="rounded, filled",
|
|
39
|
+
fillcolor="#BFE6D3",
|
|
40
|
+
color="white",
|
|
41
|
+
)
|
|
42
|
+
for child in root_node.children:
|
|
43
|
+
if child.is_named:
|
|
44
|
+
child_id = self.get_AST_nodes(child, AST, AST_index)
|
|
45
|
+
if child_id != None:
|
|
46
|
+
AST.add_edge(current_node_id, child_id)
|
|
47
|
+
return current_node_id
|
|
48
|
+
|
|
49
|
+
def merge_nodes(self, G, nodes, new_node):
|
|
50
|
+
"""
|
|
51
|
+
Merges the selected `nodes` of the graph G into one `new_node`,
|
|
52
|
+
meaning that all the edges that pointed to or from one of these
|
|
53
|
+
`nodes` will point to or from the `new_node`.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
nodes.remove(new_node)
|
|
57
|
+
|
|
58
|
+
incoming_edges = G.in_edges(nodes, data=True)
|
|
59
|
+
outgoing_edges = G.out_edges(nodes, data=True)
|
|
60
|
+
for i in incoming_edges:
|
|
61
|
+
G.add_edge(i[0], new_node, **i[2])
|
|
62
|
+
for i in outgoing_edges:
|
|
63
|
+
G.add_edge(new_node, i[1], **i[2])
|
|
64
|
+
|
|
65
|
+
for n in nodes:
|
|
66
|
+
G.remove_node(n)
|
|
67
|
+
|
|
68
|
+
def collapse(self, G):
|
|
69
|
+
name_to_index_map = {}
|
|
70
|
+
node_attributes = {}
|
|
71
|
+
|
|
72
|
+
for variable in self.all_tokens:
|
|
73
|
+
if variable not in self.method_map:
|
|
74
|
+
name_to_index_map[self.label[variable]] = set()
|
|
75
|
+
|
|
76
|
+
for node, properties in G.nodes(data=True):
|
|
77
|
+
name = properties["label"]
|
|
78
|
+
if name in name_to_index_map.keys():
|
|
79
|
+
name_to_index_map[name].add(node)
|
|
80
|
+
|
|
81
|
+
for name, indexes in name_to_index_map.items():
|
|
82
|
+
node_list = list(indexes)
|
|
83
|
+
chosen_node = min(node_list)
|
|
84
|
+
node_attributes[chosen_node] = G.nodes(data=True)[chosen_node]
|
|
85
|
+
self.merge_nodes(G, node_list, chosen_node)
|
|
86
|
+
nx.set_node_attributes(G, node_attributes)
|
|
87
|
+
|
|
88
|
+
def minimize(self, root_node, blacklisted_nodes):
|
|
89
|
+
if root_node.type in self.properties.get("blacklisted", []):
|
|
90
|
+
blacklisted_nodes.append(
|
|
91
|
+
self.index[(root_node.start_point, root_node.end_point, root_node.type)]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
for child in root_node.children:
|
|
95
|
+
self.minimize(child, blacklisted_nodes)
|
|
96
|
+
|
|
97
|
+
return blacklisted_nodes
|
|
98
|
+
|
|
99
|
+
def remove_blacklisted_nodes(self, G):
|
|
100
|
+
blacklisted_nodes = self.minimize(self.root_node, [])
|
|
101
|
+
|
|
102
|
+
for node in blacklisted_nodes:
|
|
103
|
+
for predecessor in G.predecessors(node):
|
|
104
|
+
for successor in G.successors(node):
|
|
105
|
+
G.add_edge(predecessor, successor)
|
|
106
|
+
|
|
107
|
+
G.remove_node(node)
|
|
108
|
+
|
|
109
|
+
def to_networkx(self):
|
|
110
|
+
G = nx.MultiDiGraph()
|
|
111
|
+
self.get_AST_nodes(self.root_node, G, self.index)
|
|
112
|
+
if self.properties.get("collapsed", False) == True:
|
|
113
|
+
self.collapse(G)
|
|
114
|
+
if self.properties.get("minimized", False) == True:
|
|
115
|
+
self.remove_blacklisted_nodes(G)
|
|
116
|
+
|
|
117
|
+
nx.set_edge_attributes(G, "AST_edge", "edge_type")
|
|
118
|
+
nx.set_edge_attributes(G, "indigo", "color")
|
|
119
|
+
nx.set_edge_attributes(G, "vee", "shape")
|
|
120
|
+
return G
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from .AST import ASTGraph
|
|
2
|
+
from ...tree_parser.parser_driver import ParserDriver
|
|
3
|
+
from ...utils import postprocessor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ASTDriver:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
src_language="c",
|
|
10
|
+
src_code="",
|
|
11
|
+
output_file="AST_output.json",
|
|
12
|
+
properties={},
|
|
13
|
+
):
|
|
14
|
+
self.src_language = src_language
|
|
15
|
+
self.src_code = src_code
|
|
16
|
+
self.properties = properties
|
|
17
|
+
self.parser = ParserDriver(src_language, src_code).parser
|
|
18
|
+
self.root_node = self.parser.root_node
|
|
19
|
+
self.AST = ASTGraph(
|
|
20
|
+
self.src_language,
|
|
21
|
+
self.src_code,
|
|
22
|
+
self.properties,
|
|
23
|
+
self.root_node,
|
|
24
|
+
self.parser,
|
|
25
|
+
)
|
|
26
|
+
self.graph = self.AST.graph
|
|
27
|
+
if output_file:
|
|
28
|
+
self.json = postprocessor.write_networkx_to_json(self.graph, output_file)
|
|
29
|
+
try:
|
|
30
|
+
postprocessor.write_to_dot(
|
|
31
|
+
self.graph,
|
|
32
|
+
output_file.split(".")[0] + ".dot",
|
|
33
|
+
output_png=True,
|
|
34
|
+
src_language=self.src_language,
|
|
35
|
+
)
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CFGGraph:
|
|
5
|
+
def __init__(self, src_language, src_code, properties, root_node, parser):
|
|
6
|
+
self.src_language = src_language
|
|
7
|
+
self.src_code = src_code
|
|
8
|
+
self.properties = properties
|
|
9
|
+
self.root_node = root_node
|
|
10
|
+
self.parser = parser
|
|
11
|
+
self.index = self.parser.index
|
|
12
|
+
self.all_tokens = self.parser.all_tokens
|
|
13
|
+
|
|
14
|
+
def to_networkx(self, CFG_node_list, CFG_edge_list):
|
|
15
|
+
G = nx.MultiDiGraph()
|
|
16
|
+
for node in CFG_node_list:
|
|
17
|
+
label = str(node[1] + 1) + "_ " + node[2]
|
|
18
|
+
G.add_node(node[0], label=label, type_label=node[3])
|
|
19
|
+
for edge in CFG_edge_list:
|
|
20
|
+
additional_data = None
|
|
21
|
+
if len(edge) == 4:
|
|
22
|
+
additional_data = edge[3]
|
|
23
|
+
if edge[2].startswith("constructor_call"):
|
|
24
|
+
normal_label = "constructor_call"
|
|
25
|
+
elif edge[2].startswith("method_call"):
|
|
26
|
+
normal_label = "method_call"
|
|
27
|
+
elif edge[2].startswith("virtual_call"):
|
|
28
|
+
normal_label = "virtual_call"
|
|
29
|
+
else:
|
|
30
|
+
normal_label = edge[2]
|
|
31
|
+
G.add_edge(
|
|
32
|
+
edge[0],
|
|
33
|
+
edge[1],
|
|
34
|
+
controlflow_type=edge[2],
|
|
35
|
+
edge_type="CFG_edge",
|
|
36
|
+
label=normal_label,
|
|
37
|
+
color="red",
|
|
38
|
+
)
|
|
39
|
+
if additional_data:
|
|
40
|
+
for key, value in additional_data.items():
|
|
41
|
+
G.edges[edge[0], edge[1], 0][key] = value
|
|
42
|
+
return G
|