codeanalyzer-python 1.0.3__py3-none-any.whl → 1.1.0__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.
- codeanalyzer/dataflow/scalpel/README.md +34 -0
- codeanalyzer/dataflow/scalpel/SSA/__init__.py +8 -0
- codeanalyzer/dataflow/scalpel/SSA/const.py +398 -0
- codeanalyzer/dataflow/scalpel/__init__.py +11 -0
- codeanalyzer/dataflow/scalpel/cfg/__init__.py +10 -0
- codeanalyzer/dataflow/scalpel/cfg/builder.py +700 -0
- codeanalyzer/dataflow/scalpel/cfg/model.py +331 -0
- codeanalyzer/dataflow/scalpel/core/__init__.py +0 -0
- codeanalyzer/dataflow/scalpel/core/func_call_visitor.py +234 -0
- codeanalyzer/dataflow/scalpel/core/vars_visitor.py +205 -0
- codeanalyzer/dataflow/scalpel_oracle.py +9 -12
- {codeanalyzer_python-1.0.3.dist-info → codeanalyzer_python-1.1.0.dist-info}/METADATA +11 -14
- {codeanalyzer_python-1.0.3.dist-info → codeanalyzer_python-1.1.0.dist-info}/RECORD +18 -7
- codeanalyzer_python-1.1.0.dist-info/licenses/LICENSE +201 -0
- {codeanalyzer_python-1.0.3.dist-info → codeanalyzer_python-1.1.0.dist-info}/licenses/NOTICE +7 -0
- {codeanalyzer_python-1.0.3.dist-info/licenses → codeanalyzer/dataflow/scalpel}/LICENSE +0 -0
- {codeanalyzer_python-1.0.3.dist-info → codeanalyzer_python-1.1.0.dist-info}/WHEEL +0 -0
- {codeanalyzer_python-1.0.3.dist-info → codeanalyzer_python-1.1.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Control flow graph for Python programs.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import sys
|
|
7
|
+
import re
|
|
8
|
+
import token
|
|
9
|
+
import tokenize
|
|
10
|
+
import astor
|
|
11
|
+
|
|
12
|
+
try: # PATCH (codeanalyzer): graphviz is only used by build_visual(), which
|
|
13
|
+
import graphviz as gv # codeanalyzer never calls. Keep the module importable
|
|
14
|
+
except ImportError: # without the graphviz dependency.
|
|
15
|
+
gv = None
|
|
16
|
+
|
|
17
|
+
__all__ = ["Block", "Link", "CFG"]
|
|
18
|
+
|
|
19
|
+
class Block(object):
|
|
20
|
+
"""
|
|
21
|
+
Basic block in a control flow graph.
|
|
22
|
+
|
|
23
|
+
Contains a list of statements executed in a program without any control
|
|
24
|
+
jumps. A block of statements is exited through one of its exits. Exits are
|
|
25
|
+
a list of Links that represent control flow jumps.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__slots__ = ["id", "statements", "func_calls", "predecessors", "exits"]
|
|
29
|
+
|
|
30
|
+
def __init__(self, id):
|
|
31
|
+
# Id of the block.
|
|
32
|
+
self.id = id
|
|
33
|
+
# Statements in the block.
|
|
34
|
+
self.statements = []
|
|
35
|
+
# Calls to functions inside the block (represents context switches to
|
|
36
|
+
# some functions' CFGs).
|
|
37
|
+
self.func_calls = []
|
|
38
|
+
# Links to predecessors in a control flow graph.
|
|
39
|
+
self.predecessors = []
|
|
40
|
+
# Links to the next blocks in a control flow graph.
|
|
41
|
+
self.exits = []
|
|
42
|
+
|
|
43
|
+
def __del__(self):
|
|
44
|
+
self.statements.clear()
|
|
45
|
+
self.func_calls.clear()
|
|
46
|
+
self.predecessors.clear()
|
|
47
|
+
self.exits.clear()
|
|
48
|
+
|
|
49
|
+
def __str__(self):
|
|
50
|
+
if self.statements:
|
|
51
|
+
return "block:{}@{}".format(self.id, self.at())
|
|
52
|
+
return "empty block:{}".format(self.id)
|
|
53
|
+
|
|
54
|
+
def __repr__(self):
|
|
55
|
+
txt = "{} with {} exits".format(str(self), len(self.exits))
|
|
56
|
+
if self.statements:
|
|
57
|
+
txt += ", body=["
|
|
58
|
+
txt += ", ".join([ast.dump(node) for node in self.statements])
|
|
59
|
+
txt += "]"
|
|
60
|
+
return txt
|
|
61
|
+
|
|
62
|
+
def at(self):
|
|
63
|
+
"""
|
|
64
|
+
Get the line number of the first statement of the block in the program.
|
|
65
|
+
"""
|
|
66
|
+
if self.statements and self.statements[0].lineno >= 0:
|
|
67
|
+
return self.statements[0].lineno
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def is_empty(self):
|
|
71
|
+
"""
|
|
72
|
+
Check if the block is empty.
|
|
73
|
+
Returns:
|
|
74
|
+
A boolean indicating if the block is empty (True) or not (False).
|
|
75
|
+
"""
|
|
76
|
+
return len(self.statements) == 0
|
|
77
|
+
'''
|
|
78
|
+
def strip_comment(self, src):
|
|
79
|
+
clean_src = ""
|
|
80
|
+
|
|
81
|
+
prev_toktype = token.INDENT
|
|
82
|
+
first_line = None
|
|
83
|
+
last_lineno = -1
|
|
84
|
+
last_col = 0
|
|
85
|
+
|
|
86
|
+
tokgen = tokenize.generate_tokens(src)
|
|
87
|
+
for toktype, ttext, (slineno, scol), (elineno, ecol), ltext in tokgen:
|
|
88
|
+
if 0: # Change to if 1 to see the tokens fly by.
|
|
89
|
+
print("%10s %-14s %-20r %r" % (
|
|
90
|
+
tokenize.tok_name.get(toktype, toktype),
|
|
91
|
+
"%d.%d-%d.%d" % (slineno, scol, elineno, ecol),
|
|
92
|
+
ttext, ltext
|
|
93
|
+
))
|
|
94
|
+
if slineno > last_lineno:
|
|
95
|
+
last_col = 0
|
|
96
|
+
if scol > last_col:
|
|
97
|
+
mod.write(" " * (scol - last_col))
|
|
98
|
+
if toktype == token.STRING and prev_toktype == token.INDENT:
|
|
99
|
+
# Docstring
|
|
100
|
+
mod.write("#--")
|
|
101
|
+
elif toktype == tokenize.COMMENT:
|
|
102
|
+
# Comment
|
|
103
|
+
mod.write("##\n")
|
|
104
|
+
else:
|
|
105
|
+
mod.write(ttext)
|
|
106
|
+
prev_toktype = toktype
|
|
107
|
+
last_col = ecol
|
|
108
|
+
last_lineno = elineno
|
|
109
|
+
'''
|
|
110
|
+
def get_source(self):
|
|
111
|
+
"""
|
|
112
|
+
Get a string containing the Python source code corresponding to the
|
|
113
|
+
statements in the block.
|
|
114
|
+
Returns:
|
|
115
|
+
A string containing the source code of the statements.
|
|
116
|
+
"""
|
|
117
|
+
src = "#" + str(self.id)+'\n'
|
|
118
|
+
for statement in self.statements:
|
|
119
|
+
if type(statement) in [ast.If, ast.For, ast.While, ast.With]:
|
|
120
|
+
src += (astor.to_source(statement)).split('\n')[0] + "\n"
|
|
121
|
+
elif type(statement) == ast.Try:
|
|
122
|
+
src += (astor.to_source(statement)).split('\n')[0] + "\n"
|
|
123
|
+
#elif type(statement) == ast.If:
|
|
124
|
+
# src += (astor.to_source(statement)).split('\n')[0] + "\n"
|
|
125
|
+
elif type(statement) in [ast.FunctionDef,ast.AsyncFunctionDef,
|
|
126
|
+
ast.ClassDef]:
|
|
127
|
+
src += (astor.to_source(statement)).split('\n')[0] + "...\n"
|
|
128
|
+
elif type(statement) == ast.ClassDef:
|
|
129
|
+
src += (astor.to_source(statement)).split('\n')[0] + "...\n"
|
|
130
|
+
else:
|
|
131
|
+
src += astor.to_source(statement)
|
|
132
|
+
return src
|
|
133
|
+
|
|
134
|
+
def get_calls(self):
|
|
135
|
+
"""
|
|
136
|
+
Get a string containing the calls to other functions inside the block.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
A string containing the names of the functions called inside the
|
|
140
|
+
block.
|
|
141
|
+
"""
|
|
142
|
+
txt = ""
|
|
143
|
+
for func_call_entry in self.func_calls:
|
|
144
|
+
txt += func_call_entry['name'] + '\n'
|
|
145
|
+
return txt
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Link(object):
|
|
149
|
+
"""
|
|
150
|
+
Link between blocks in a control flow graph.
|
|
151
|
+
|
|
152
|
+
Represents a control flow jump between two blocks. Contains an exitcase in
|
|
153
|
+
the form of an expression, representing the case in which the associated
|
|
154
|
+
control jump is made.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
__slots__ = ["source", "target", "exitcase"]
|
|
158
|
+
|
|
159
|
+
def __init__(self, source, target, exitcase=None):
|
|
160
|
+
assert type(source) == Block, "Source of a link must be a block"
|
|
161
|
+
assert type(target) == Block, "Target of a link must be a block"
|
|
162
|
+
# Block from which the control flow jump was made.
|
|
163
|
+
self.source = source
|
|
164
|
+
# Target block of the control flow jump.
|
|
165
|
+
self.target = target
|
|
166
|
+
# 'Case' leading to a control flow jump through this link.
|
|
167
|
+
self.exitcase = exitcase
|
|
168
|
+
|
|
169
|
+
def __str__(self):
|
|
170
|
+
return "link from {} to {}".format(str(self.source), str(self.target))
|
|
171
|
+
|
|
172
|
+
def __repr__(self):
|
|
173
|
+
if self.exitcase is not None:
|
|
174
|
+
return "{}, with exitcase {}".format(str(self),
|
|
175
|
+
ast.dump(self.exitcase))
|
|
176
|
+
return str(self)
|
|
177
|
+
|
|
178
|
+
def get_exitcase(self):
|
|
179
|
+
"""
|
|
180
|
+
Get a string containing the Python source code corresponding to the
|
|
181
|
+
exitcase of the Link.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
A string containing the source code.
|
|
185
|
+
"""
|
|
186
|
+
if self.exitcase:
|
|
187
|
+
return astor.to_source(self.exitcase)
|
|
188
|
+
return ""
|
|
189
|
+
def __del__(self):
|
|
190
|
+
self.source = None
|
|
191
|
+
# Target block of the control flow jump.
|
|
192
|
+
self.target = None
|
|
193
|
+
# 'Case' leading to a control flow jump through this link.
|
|
194
|
+
self.exitcase = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class CFG(object):
|
|
199
|
+
"""
|
|
200
|
+
Control flow graph (CFG).
|
|
201
|
+
|
|
202
|
+
A control flow graph is composed of basic blocks and links between them
|
|
203
|
+
representing control flow jumps. It has a unique entry block and several
|
|
204
|
+
possible 'final' blocks (blocks with no exits representing the end of the
|
|
205
|
+
CFG).
|
|
206
|
+
"""
|
|
207
|
+
def __init__(self, name, asynchr=False):
|
|
208
|
+
"""
|
|
209
|
+
The constructor of CFG class. Only name of this graph is required.
|
|
210
|
+
"""
|
|
211
|
+
assert type(name) == str, "Name of a CFG must be a string"
|
|
212
|
+
assert type(asynchr) == bool, "Async must be a boolean value"
|
|
213
|
+
# Name of the function or module being represented.
|
|
214
|
+
self.name = name
|
|
215
|
+
# Type of function represented by the CFG (sync or async). A Python
|
|
216
|
+
# program is considered as a synchronous function (main).
|
|
217
|
+
self.asynchr = asynchr
|
|
218
|
+
# Entry block of the CFG.
|
|
219
|
+
self.entryblock = None
|
|
220
|
+
# Final blocks of the CFG.
|
|
221
|
+
self.finalblocks = []
|
|
222
|
+
# Sub-CFGs for functions defined inside the current CFG.
|
|
223
|
+
self.functioncfgs = {}
|
|
224
|
+
self.class_cfgs = {}
|
|
225
|
+
|
|
226
|
+
self.function_args = {}
|
|
227
|
+
self.class_args = {}
|
|
228
|
+
|
|
229
|
+
def __del__(self):
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
def __str__(self):
|
|
233
|
+
return "CFG for {}".format(self.name)
|
|
234
|
+
|
|
235
|
+
def remove_comments(self, src):
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_all_blocks(self):
|
|
241
|
+
"""
|
|
242
|
+
Get a list of code blocks in this CFG; This is generated by BFS order.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
A list of code blocks.
|
|
246
|
+
"""
|
|
247
|
+
import queue
|
|
248
|
+
all_blocks = []
|
|
249
|
+
|
|
250
|
+
visited = set()
|
|
251
|
+
working_queue = queue.Queue()
|
|
252
|
+
working_queue.put(self.entryblock)
|
|
253
|
+
|
|
254
|
+
while not working_queue.empty():
|
|
255
|
+
block = working_queue.get()
|
|
256
|
+
# this block has been visited
|
|
257
|
+
if block.id in visited:
|
|
258
|
+
continue
|
|
259
|
+
all_blocks.append(block)
|
|
260
|
+
visited.add(block.id)
|
|
261
|
+
for suc_link in block.exits:
|
|
262
|
+
if suc_link.target.id not in visited:
|
|
263
|
+
working_queue.put(suc_link.target)
|
|
264
|
+
return all_blocks
|
|
265
|
+
#def dfs(start_block):
|
|
266
|
+
# # non-recurisve implementation of DFS search
|
|
267
|
+
|
|
268
|
+
def __iter__(self):
|
|
269
|
+
"""
|
|
270
|
+
Generator that yields all the blocks in the current graph, then
|
|
271
|
+
recursively yields from any sub graphs
|
|
272
|
+
"""
|
|
273
|
+
visited = set()
|
|
274
|
+
to_visit = [self.entryblock]
|
|
275
|
+
|
|
276
|
+
while to_visit:
|
|
277
|
+
block = to_visit.pop(0)
|
|
278
|
+
visited.add(block)
|
|
279
|
+
for exit_ in block.exits:
|
|
280
|
+
if exit_.target in visited or exit_.target in to_visit:
|
|
281
|
+
continue
|
|
282
|
+
to_visit.append(exit_.target)
|
|
283
|
+
yield block
|
|
284
|
+
|
|
285
|
+
for subcfg in self.functioncfgs.values():
|
|
286
|
+
yield from subcfg
|
|
287
|
+
|
|
288
|
+
def _visit_blocks(self, graph, block, visited=[], calls=True):
|
|
289
|
+
# Don't visit blocks twice.
|
|
290
|
+
if block.id in visited:
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
nodelabel = block.get_source()
|
|
294
|
+
graph.node(str(block.id), label=nodelabel)
|
|
295
|
+
|
|
296
|
+
visited.append(block.id)
|
|
297
|
+
# Show the block's function calls in a node.
|
|
298
|
+
if calls and block.func_calls:
|
|
299
|
+
calls_node = str(block.id)+"_calls"
|
|
300
|
+
calls_label = block.get_calls().strip()
|
|
301
|
+
graph.node(calls_node, label=calls_label,
|
|
302
|
+
_attributes={'shape': 'box'})
|
|
303
|
+
graph.edge(str(block.id), calls_node, label="calls",
|
|
304
|
+
_attributes={'style': 'dashed'})
|
|
305
|
+
# Recursively visit all the blocks of the CFG.
|
|
306
|
+
for exit in block.exits:
|
|
307
|
+
self._visit_blocks(graph, exit.target, visited, calls=calls)
|
|
308
|
+
edgelabel = exit.get_exitcase().strip()
|
|
309
|
+
graph.edge(str(block.id), str(exit.target.id), label=edgelabel)
|
|
310
|
+
|
|
311
|
+
def _build_visual(self, format='pdf', calls=True):
|
|
312
|
+
graph = gv.Digraph(name='cluster'+self.name, format=format,
|
|
313
|
+
graph_attr={'label': self.name})
|
|
314
|
+
self._visit_blocks(graph, self.entryblock, visited=[], calls=False)
|
|
315
|
+
return graph
|
|
316
|
+
|
|
317
|
+
def build_visual(self, format, calls=True, show=True):
|
|
318
|
+
"""
|
|
319
|
+
Build a visualisation of the CFG with graphviz and output it in a DOT
|
|
320
|
+
file.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
filename: The name of the output file in which the visualisation
|
|
324
|
+
must be saved.
|
|
325
|
+
format: The format to use for the output file (PDF, ...).
|
|
326
|
+
show: A boolean indicating whether to automatically open the output
|
|
327
|
+
file after building the visualisation.
|
|
328
|
+
"""
|
|
329
|
+
graph = self._build_visual(format, calls)
|
|
330
|
+
return graph
|
|
331
|
+
|
|
File without changes
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import ast
|
|
3
|
+
from collections import deque
|
|
4
|
+
from ast import NodeVisitor
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_py38_or_higher():
|
|
9
|
+
if sys.version_info.major == 3 and sys.version_info.minor >= 8:
|
|
10
|
+
return True
|
|
11
|
+
return False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
NAMECONSTANT_TYPE = ast.Constant if is_py38_or_higher() else ast.NameConstant
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CallTransformer(ast.NodeTransformer):
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.call_names = []
|
|
20
|
+
|
|
21
|
+
def visit_Attribute(self, node):
|
|
22
|
+
# self.generic_visit(node.value)
|
|
23
|
+
return node
|
|
24
|
+
|
|
25
|
+
def param2str(self, param):
|
|
26
|
+
|
|
27
|
+
def get_func(node):
|
|
28
|
+
if type(node) == ast.Name:
|
|
29
|
+
return node.id
|
|
30
|
+
elif type(node) == ast.Constant:
|
|
31
|
+
# ingore such as "this is a constant".join()
|
|
32
|
+
return ""
|
|
33
|
+
elif type(node) == ast.BinOp:
|
|
34
|
+
# ingore such as (a+b+c).fun()
|
|
35
|
+
return ""
|
|
36
|
+
elif type(node) == ast.Str:
|
|
37
|
+
# ingore such as "xxx".fun()
|
|
38
|
+
return ""
|
|
39
|
+
elif type(node) == ast.JoinedStr:
|
|
40
|
+
# ingore such as "xxx".fun()
|
|
41
|
+
return ""
|
|
42
|
+
elif type(node) == ast.Bytes:
|
|
43
|
+
# ingore such as "xxx".fun()
|
|
44
|
+
return ""
|
|
45
|
+
elif type(node) == ast.Compare:
|
|
46
|
+
# example "(x.matrix_exp() == torch.eye(20, 20, dtype=dtype, device=device)).all().item()"
|
|
47
|
+
# tests/test-cases/cfg-tests/pytorch-test-test_linalg.py
|
|
48
|
+
# ignore for now
|
|
49
|
+
return ""
|
|
50
|
+
elif type(node) == ast.Subscript:
|
|
51
|
+
# currently, we will ignore the slices because we cannot track the type of the value.
|
|
52
|
+
# for instance, a[something].fun() -> a.fun()
|
|
53
|
+
# this sacrifice
|
|
54
|
+
return get_func(node.value)
|
|
55
|
+
#elif type(node) == ast.JoinedStr:
|
|
56
|
+
# return ""
|
|
57
|
+
elif type(node) == ast.Attribute:
|
|
58
|
+
if type(node.value) in [ast.JoinedStr, ast.Constant]:
|
|
59
|
+
return node.attr
|
|
60
|
+
else:
|
|
61
|
+
return get_func(node.value) + "." + node.attr
|
|
62
|
+
elif type(node) == ast.Call:
|
|
63
|
+
return get_func(node.func)
|
|
64
|
+
elif type(node) == ast.IfExp:
|
|
65
|
+
return ""
|
|
66
|
+
elif type(node) == ast.Compare:
|
|
67
|
+
return ""
|
|
68
|
+
elif type(node) == ast.UnaryOp:
|
|
69
|
+
return ""
|
|
70
|
+
#ast.UnaryOp
|
|
71
|
+
else:
|
|
72
|
+
#import astor
|
|
73
|
+
#print(astor.to_source(node))
|
|
74
|
+
raise Exception(str(type(node)))
|
|
75
|
+
|
|
76
|
+
if isinstance(param, ast.Subscript):
|
|
77
|
+
return self.param2str(param.value)
|
|
78
|
+
if isinstance(param, ast.Call):
|
|
79
|
+
return get_func(param)
|
|
80
|
+
elif isinstance(param, ast.Name):
|
|
81
|
+
return param.id
|
|
82
|
+
elif isinstance(param, ast.Num):
|
|
83
|
+
# python 3.6
|
|
84
|
+
return param.n
|
|
85
|
+
#return param.value
|
|
86
|
+
elif isinstance(param, ast.List):
|
|
87
|
+
return "List"
|
|
88
|
+
elif isinstance(param, ast.ListComp):
|
|
89
|
+
return "List"
|
|
90
|
+
elif isinstance(param, ast.Tuple):
|
|
91
|
+
return "Tuple"
|
|
92
|
+
elif isinstance(param, (ast.Dict, ast.DictComp)):
|
|
93
|
+
return "Dict"
|
|
94
|
+
elif isinstance(param, (ast.Set, ast.SetComp)):
|
|
95
|
+
return "Set"
|
|
96
|
+
elif isinstance(param, ast.Str):
|
|
97
|
+
return param.s
|
|
98
|
+
elif isinstance(param, ast.NameConstant):
|
|
99
|
+
return param.value
|
|
100
|
+
elif isinstance(param, ast.Constant):
|
|
101
|
+
return param.value
|
|
102
|
+
elif isinstance(param, ast.Expr):
|
|
103
|
+
return "Expr"
|
|
104
|
+
else:
|
|
105
|
+
return "unknown"
|
|
106
|
+
|
|
107
|
+
def visit_Call(self, node):
|
|
108
|
+
|
|
109
|
+
tmp_fun_node = deepcopy(node)
|
|
110
|
+
tmp_fun_node.args = []
|
|
111
|
+
tmp_fun_node.keywords = []
|
|
112
|
+
|
|
113
|
+
callvisitor = FuncCallVisitor()
|
|
114
|
+
callvisitor.visit(tmp_fun_node)
|
|
115
|
+
|
|
116
|
+
call_info = {"name": callvisitor.name,
|
|
117
|
+
"lineno": tmp_fun_node.lineno,
|
|
118
|
+
"col_offset": tmp_fun_node.col_offset,
|
|
119
|
+
"params": []
|
|
120
|
+
}
|
|
121
|
+
self.call_names += [call_info]
|
|
122
|
+
for arg in node.args:
|
|
123
|
+
call_info["params"] += [self.param2str(arg)]
|
|
124
|
+
self.generic_visit(arg)
|
|
125
|
+
|
|
126
|
+
for kw in node.keywords:
|
|
127
|
+
call_info["params"] += [self.param2str(kw.value)]
|
|
128
|
+
self.generic_visit(kw)
|
|
129
|
+
self.generic_visit(tmp_fun_node)
|
|
130
|
+
|
|
131
|
+
return node
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class FuncCallVisitor(ast.NodeVisitor):
|
|
135
|
+
def __init__(self):
|
|
136
|
+
self._name = deque()
|
|
137
|
+
self.call_names = []
|
|
138
|
+
|
|
139
|
+
def clear(self):
|
|
140
|
+
self._name = deque()
|
|
141
|
+
self.call_names = []
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def name(self):
|
|
145
|
+
return '.'.join(self._name)
|
|
146
|
+
|
|
147
|
+
@name.deleter
|
|
148
|
+
def name(self):
|
|
149
|
+
self._name.clear()
|
|
150
|
+
|
|
151
|
+
def visit_Name(self, node):
|
|
152
|
+
self._name.appendleft(node.id)
|
|
153
|
+
|
|
154
|
+
def visit_Attribute(self, node):
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
self._name.appendleft(node.attr)
|
|
158
|
+
self._name.appendleft(node.value.id)
|
|
159
|
+
except AttributeError as e:
|
|
160
|
+
self.generic_visit(node)
|
|
161
|
+
|
|
162
|
+
def visit_Call(self, node):
|
|
163
|
+
node.args = []
|
|
164
|
+
node.keywords = []
|
|
165
|
+
self.generic_visit(node)
|
|
166
|
+
return node
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def visit_Subscript(self, node):
|
|
170
|
+
# ingore subscription slice
|
|
171
|
+
self.visit(node.value)
|
|
172
|
+
return node
|
|
173
|
+
|
|
174
|
+
def get_args(node):
|
|
175
|
+
arg_type = []
|
|
176
|
+
for arg in node.args:
|
|
177
|
+
if isinstance(arg, ast.Name):
|
|
178
|
+
arg_type.append(arg.id)
|
|
179
|
+
elif isinstance(arg, ast.Num):
|
|
180
|
+
arg_type.append("Num")
|
|
181
|
+
elif isinstance(arg, ast.List):
|
|
182
|
+
arg_type.append("List")
|
|
183
|
+
elif isinstance(arg, ast.ListComp):
|
|
184
|
+
arg_type.append("List")
|
|
185
|
+
elif isinstance(arg, ast.Tuple):
|
|
186
|
+
arg_type.append("Tuple")
|
|
187
|
+
elif isinstance(arg, ast.Dict):
|
|
188
|
+
arg_type.append("Dict")
|
|
189
|
+
elif isinstance(arg, ast.DictComp):
|
|
190
|
+
arg_type.append("Dict")
|
|
191
|
+
elif isinstance(arg, ast.Set):
|
|
192
|
+
arg_type.append("Set")
|
|
193
|
+
elif isinstance(arg, ast.SetComp):
|
|
194
|
+
arg_type.append("Set")
|
|
195
|
+
elif isinstance(arg, ast.Str):
|
|
196
|
+
arg_type.append("Str")
|
|
197
|
+
elif isinstance(arg, ast.NameConstant):
|
|
198
|
+
arg_type.append("NameConstant")
|
|
199
|
+
elif isinstance(arg, ast.Constant):
|
|
200
|
+
arg_type.append("Constant")
|
|
201
|
+
elif isinstance(arg, ast.Call):
|
|
202
|
+
arg_type.append(("Call", get_func_calls(arg)[0]))
|
|
203
|
+
else:
|
|
204
|
+
arg_type.append("Other")
|
|
205
|
+
return arg_type
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_call_type(tree):
|
|
209
|
+
# how to remove
|
|
210
|
+
func_calls = []
|
|
211
|
+
for node in ast.walk(tree):
|
|
212
|
+
if isinstance(node, ast.Call):
|
|
213
|
+
callvisitor = FuncCallVisitor()
|
|
214
|
+
callvisitor.visit(node.func)
|
|
215
|
+
func_calls += [(callvisitor.name, get_args(node))]
|
|
216
|
+
return func_calls
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def get_call_type(tree):
|
|
220
|
+
# how to remove
|
|
221
|
+
func_calls = []
|
|
222
|
+
for node in ast.walk(tree):
|
|
223
|
+
if isinstance(node, ast.Call):
|
|
224
|
+
callvisitor = FuncCallVisitor()
|
|
225
|
+
callvisitor.visit(node.func)
|
|
226
|
+
func_calls += [(callvisitor.name, get_args(node))]
|
|
227
|
+
return func_calls
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def get_func_calls(tree):
|
|
231
|
+
node = deepcopy(tree)
|
|
232
|
+
transformer = CallTransformer()
|
|
233
|
+
transformer.visit(node)
|
|
234
|
+
return transformer.call_names
|