codeanalyzer-python 1.0.2__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/core.py +161 -8
- 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.2.dist-info → codeanalyzer_python-1.1.0.dist-info}/METADATA +12 -14
- {codeanalyzer_python-1.0.2.dist-info → codeanalyzer_python-1.1.0.dist-info}/RECORD +19 -8
- codeanalyzer_python-1.1.0.dist-info/licenses/LICENSE +201 -0
- {codeanalyzer_python-1.0.2.dist-info → codeanalyzer_python-1.1.0.dist-info}/licenses/NOTICE +7 -0
- {codeanalyzer_python-1.0.2.dist-info/licenses → codeanalyzer/dataflow/scalpel}/LICENSE +0 -0
- {codeanalyzer_python-1.0.2.dist-info → codeanalyzer_python-1.1.0.dist-info}/WHEEL +0 -0
- {codeanalyzer_python-1.0.2.dist-info → codeanalyzer_python-1.1.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This implementation is partly adapted from the static cfg project
|
|
3
|
+
https://github.com/coetaur0/staticfg
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
from .model import Block, Link, CFG
|
|
8
|
+
import sys
|
|
9
|
+
from ..core.func_call_visitor import get_func_calls
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_py38_or_higher():
|
|
13
|
+
if sys.version_info.major == 3 and sys.version_info.minor >= 8:
|
|
14
|
+
return True
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
NAMECONSTANT_TYPE = ast.Constant if is_py38_or_higher() else ast.NameConstant
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def invert(node):
|
|
22
|
+
"""
|
|
23
|
+
Invert the operation in an ast node object (get its negation).
|
|
24
|
+
Args:
|
|
25
|
+
node: An ast node object.
|
|
26
|
+
Returns:
|
|
27
|
+
An ast node object containing the inverse (negation) of the input node.
|
|
28
|
+
"""
|
|
29
|
+
inverse = {ast.Eq: ast.NotEq,
|
|
30
|
+
ast.NotEq: ast.Eq,
|
|
31
|
+
ast.Lt: ast.GtE,
|
|
32
|
+
ast.LtE: ast.Gt,
|
|
33
|
+
ast.Gt: ast.LtE,
|
|
34
|
+
ast.GtE: ast.Lt,
|
|
35
|
+
ast.Is: ast.IsNot,
|
|
36
|
+
ast.IsNot: ast.Is,
|
|
37
|
+
ast.In: ast.NotIn,
|
|
38
|
+
ast.NotIn: ast.In}
|
|
39
|
+
|
|
40
|
+
if type(node) == ast.Compare:
|
|
41
|
+
op = type(node.ops[0])
|
|
42
|
+
inverse_node = ast.Compare(left=node.left, ops=[inverse[op]()],
|
|
43
|
+
comparators=node.comparators)
|
|
44
|
+
elif isinstance(node, ast.BinOp) and type(node.op) in inverse:
|
|
45
|
+
op = type(node.op)
|
|
46
|
+
inverse_node = ast.BinOp(node.left, inverse[op](), node.right)
|
|
47
|
+
elif type(node) == NAMECONSTANT_TYPE and node.value in [True, False]:
|
|
48
|
+
inverse_node = NAMECONSTANT_TYPE(value=not node.value)
|
|
49
|
+
else:
|
|
50
|
+
inverse_node = ast.UnaryOp(op=ast.Not(), operand=node)
|
|
51
|
+
return inverse_node
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def merge_exitcases(exit1, exit2):
|
|
55
|
+
"""
|
|
56
|
+
Merge the exitcases of two Links.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
exit1: The exitcase of a Link object.
|
|
60
|
+
exit2: Another exitcase to merge with exit1.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The merged exitcases.
|
|
64
|
+
"""
|
|
65
|
+
if exit1:
|
|
66
|
+
if exit2:
|
|
67
|
+
return ast.BoolOp(ast.And(), values=[exit1, exit2])
|
|
68
|
+
return exit1
|
|
69
|
+
return exit2
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class CFGBuilder(ast.NodeVisitor):
|
|
73
|
+
"""
|
|
74
|
+
Control flow graph builder.
|
|
75
|
+
|
|
76
|
+
A control flow graph builder is an ast.NodeVisitor that can walk through
|
|
77
|
+
a program's AST and iteratively build the corresponding CFG.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
__all__ = ["build", "build_from_src", "build_from_file"]
|
|
81
|
+
|
|
82
|
+
def __init__(self, separate=False):
|
|
83
|
+
super().__init__()
|
|
84
|
+
self.after_loop_block_stack = []
|
|
85
|
+
self.curr_loop_guard_stack = []
|
|
86
|
+
self.current_block = None
|
|
87
|
+
self.separate_node_blocks = separate
|
|
88
|
+
self.enter_func_def = False
|
|
89
|
+
|
|
90
|
+
# ---------- CFG building methods ---------- #
|
|
91
|
+
def build(self, name, tree, asynchr=False, entry_id=0, flattened=False):
|
|
92
|
+
"""
|
|
93
|
+
Build a CFG from an AST.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
name: The name of the CFG being built.
|
|
97
|
+
tree: The root of the AST from which the CFG must be built.
|
|
98
|
+
async: Boolean indicating whether the CFG being built represents an
|
|
99
|
+
asynchronous function or not. When the CFG of a Python
|
|
100
|
+
program is being built, it is considered like a synchronous
|
|
101
|
+
'main' function.
|
|
102
|
+
entry_id: Value for the id of the entry block of the CFG.
|
|
103
|
+
flattened: if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The CFG produced from the AST.
|
|
107
|
+
"""
|
|
108
|
+
self.cfg = CFG(name, asynchr=asynchr)
|
|
109
|
+
# Tracking of the current block while building the CFG.
|
|
110
|
+
self.current_id = entry_id
|
|
111
|
+
self.current_block = self.new_block()
|
|
112
|
+
self.cfg.entryblock = self.current_block
|
|
113
|
+
# Actual building of the CFG is done here.
|
|
114
|
+
self.visit(tree)
|
|
115
|
+
visited = []
|
|
116
|
+
self.clean_cfg(self.cfg.entryblock,visited)
|
|
117
|
+
|
|
118
|
+
if flattened:
|
|
119
|
+
self.cfg = self._flatten_cfg(self.cfg)
|
|
120
|
+
pass
|
|
121
|
+
return self.cfg
|
|
122
|
+
|
|
123
|
+
def _flatten_cfg(self, mod_cfg):
|
|
124
|
+
flattend_cfg = {}
|
|
125
|
+
|
|
126
|
+
def process_cfg(cfg, dotted_name=["mod"], name_type="mod"):
|
|
127
|
+
fully_qualified_name = ".".join(dotted_name)
|
|
128
|
+
flattend_cfg[fully_qualified_name] = cfg
|
|
129
|
+
for fun_name_tup, fun_cfg in cfg.functioncfgs.items():
|
|
130
|
+
process_cfg(fun_cfg, dotted_name = dotted_name +[fun_name_tup[1]], name_type= "func")
|
|
131
|
+
|
|
132
|
+
for cls_name, cls_cfg in cfg.class_cfgs.items():
|
|
133
|
+
process_cfg(cls_cfg, dotted_name = dotted_name +[cls_name], name_type = "cls")
|
|
134
|
+
|
|
135
|
+
process_cfg(mod_cfg)
|
|
136
|
+
|
|
137
|
+
return flattend_cfg
|
|
138
|
+
def build_from_src(self, name, src, flattened=False):
|
|
139
|
+
"""
|
|
140
|
+
Build a CFG from some Python source code.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
name: The name of the CFG being built.
|
|
144
|
+
src: A string containing the source code to build the CFG from.
|
|
145
|
+
flattened: if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The CFG produced from the source code.
|
|
150
|
+
"""
|
|
151
|
+
tree = ast.parse(src, mode='exec')
|
|
152
|
+
return self.build(name, tree, flattened=flattened)
|
|
153
|
+
|
|
154
|
+
def build_from_file(self, name, filepath, flattened=False):
|
|
155
|
+
"""
|
|
156
|
+
Build a CFG from some Python source file.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
name: The name of the CFG being built.
|
|
160
|
+
filepath: The path to the file containing the Python source code to build the CFG from.
|
|
161
|
+
flattened: if use k-v format for all CFGs while hiding its nested information. Key will be fully-qualified names.
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
The CFG produced from the source file.
|
|
166
|
+
"""
|
|
167
|
+
with open(filepath, 'r',encoding="utf8") as src_file:
|
|
168
|
+
src = src_file.read()
|
|
169
|
+
return self.build_from_src(name, src, flattened=flattened)
|
|
170
|
+
|
|
171
|
+
# ---------- Graph management methods ---------- #
|
|
172
|
+
def new_block(self):
|
|
173
|
+
"""
|
|
174
|
+
Create a new block with a new id.
|
|
175
|
+
Returns:
|
|
176
|
+
A Block object with a new unique id.
|
|
177
|
+
"""
|
|
178
|
+
self.current_id += 1
|
|
179
|
+
return Block(self.current_id)
|
|
180
|
+
|
|
181
|
+
def add_statement(self, block, statement):
|
|
182
|
+
"""
|
|
183
|
+
Add a statement to a block.
|
|
184
|
+
Args:
|
|
185
|
+
block: A Block object to which a statement must be added.
|
|
186
|
+
statement: An AST node representing the statement that must be
|
|
187
|
+
added to the current block.
|
|
188
|
+
"""
|
|
189
|
+
# remove function def nodes
|
|
190
|
+
block.statements.append(statement)
|
|
191
|
+
|
|
192
|
+
def add_exit(self, block, nextblock, exitcase=None):
|
|
193
|
+
"""
|
|
194
|
+
Add a new exit to a block.
|
|
195
|
+
Args:
|
|
196
|
+
block: A block to which an exit must be added.
|
|
197
|
+
nextblock: The block to which control jumps from the new exit.
|
|
198
|
+
exitcase: An AST node representing the 'case' (or condition)
|
|
199
|
+
leading to the exit from the block in the program.
|
|
200
|
+
"""
|
|
201
|
+
newlink = Link(block, nextblock, exitcase)
|
|
202
|
+
block.exits.append(newlink)
|
|
203
|
+
nextblock.predecessors.append(newlink)
|
|
204
|
+
|
|
205
|
+
def new_loopguard(self):
|
|
206
|
+
"""
|
|
207
|
+
Create a new block for a loop's guard if the current block is not
|
|
208
|
+
empty. Links the current block to the new loop guard.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
The block to be used as new loop guard.
|
|
212
|
+
"""
|
|
213
|
+
if (self.current_block.is_empty() and
|
|
214
|
+
len(self.current_block.exits) == 0):
|
|
215
|
+
# If the current block is empty and has no exits, it is used as
|
|
216
|
+
# entry block (condition test) for the loop.
|
|
217
|
+
loopguard = self.current_block
|
|
218
|
+
else:
|
|
219
|
+
# Jump to a new block for the loop's guard if the current block
|
|
220
|
+
# isn't empty or has exits.
|
|
221
|
+
loopguard = self.new_block()
|
|
222
|
+
self.add_exit(self.current_block, loopguard)
|
|
223
|
+
return loopguard
|
|
224
|
+
|
|
225
|
+
def new_functionCFG(self, node, asynchr=False, enclosing_block_id=-1):
|
|
226
|
+
"""
|
|
227
|
+
Create a new sub-CFG for a function definition and add it to the
|
|
228
|
+
function CFGs of the CFG being built.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
node: The AST node containing the function definition.
|
|
232
|
+
async: Boolean indicating whether the function for which the CFG is
|
|
233
|
+
being built is asynchronous or not.
|
|
234
|
+
"""
|
|
235
|
+
self.current_id += 1
|
|
236
|
+
# A new sub-CFG is created for the body of the function definition and
|
|
237
|
+
# added to the function CFGs of the current CFG.
|
|
238
|
+
func_body = ast.Module(body=node.body)
|
|
239
|
+
func_builder = CFGBuilder()
|
|
240
|
+
self.cfg.functioncfgs[(enclosing_block_id,node.name)] = func_builder.build(node.name,
|
|
241
|
+
func_body,
|
|
242
|
+
asynchr,
|
|
243
|
+
self.current_id)
|
|
244
|
+
|
|
245
|
+
def get_arg_names(argument_node):
|
|
246
|
+
arg_names = []
|
|
247
|
+
for node in ast.walk(argument_node):
|
|
248
|
+
if isinstance(node, ast.arg):
|
|
249
|
+
arg_names.append( node.arg)
|
|
250
|
+
return arg_names
|
|
251
|
+
|
|
252
|
+
self.cfg.function_args[(enclosing_block_id, node.name)] = get_arg_names(node.args)
|
|
253
|
+
self.current_id = func_builder.current_id + 1
|
|
254
|
+
|
|
255
|
+
def new_ClassCFG(self, node, asynchr=False):
|
|
256
|
+
"""
|
|
257
|
+
Create a new sub-CFG for a class definition and add it to the
|
|
258
|
+
function CFGs of the CFG being built.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
node: The AST node containing the function definition.
|
|
262
|
+
asynchr: Boolean indicating whether the function for which the CFG is
|
|
263
|
+
being built is asynchronous or not.
|
|
264
|
+
"""
|
|
265
|
+
self.current_id += 1
|
|
266
|
+
# A new sub-CFG is created for the body of the function definition and
|
|
267
|
+
# added to the function CFGs of the current CFG.
|
|
268
|
+
func_body = ast.Module(body=node.body)
|
|
269
|
+
func_builder = CFGBuilder()
|
|
270
|
+
base_names = []
|
|
271
|
+
for base in node.bases:
|
|
272
|
+
if not isinstance(base, ast.Name):
|
|
273
|
+
continue
|
|
274
|
+
base_names.append(base.id)
|
|
275
|
+
if node.name in self.cfg.class_cfgs and node.name in base_names:
|
|
276
|
+
existing_class_cfg = self.cfg.class_cfgs[node.name]
|
|
277
|
+
new_class_cfg = func_builder.build(node.name,
|
|
278
|
+
func_body,
|
|
279
|
+
asynchr,
|
|
280
|
+
self.current_id)
|
|
281
|
+
new_class_cfg.entryblock.statements = new_class_cfg.entryblock.statements+existing_class_cfg.entryblock.statements
|
|
282
|
+
new_class_cfg.functioncfgs.update(existing_class_cfg.functioncfgs)
|
|
283
|
+
new_class_cfg.function_args.update(existing_class_cfg.function_args)
|
|
284
|
+
self.cfg.class_cfgs[node.name]=new_class_cfg
|
|
285
|
+
else:
|
|
286
|
+
self.cfg.class_cfgs[node.name] = func_builder.build(node.name,
|
|
287
|
+
func_body,
|
|
288
|
+
asynchr,
|
|
289
|
+
self.current_id)
|
|
290
|
+
|
|
291
|
+
self.current_id = func_builder.current_id + 1
|
|
292
|
+
|
|
293
|
+
def clean_cfg(self, block, visited):
|
|
294
|
+
"""
|
|
295
|
+
Remove the useless (empty) blocks from a CFG.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
block: The block from which to start traversing the CFG to clean
|
|
299
|
+
it.
|
|
300
|
+
visited: A list of blocks that already have been visited by
|
|
301
|
+
clean_cfg (recursive function).
|
|
302
|
+
"""
|
|
303
|
+
# Don't visit blocks twice.
|
|
304
|
+
if block.id in visited:
|
|
305
|
+
return
|
|
306
|
+
visited.append(block.id)
|
|
307
|
+
|
|
308
|
+
# Empty blocks are removed from the CFG.
|
|
309
|
+
if block.is_empty():
|
|
310
|
+
for pred in block.predecessors:
|
|
311
|
+
for exit in block.exits:
|
|
312
|
+
self.add_exit(pred.source, exit.target,
|
|
313
|
+
merge_exitcases(pred.exitcase,
|
|
314
|
+
exit.exitcase))
|
|
315
|
+
# Check if the exit hasn't yet been removed from
|
|
316
|
+
# the predecessors of the target block.
|
|
317
|
+
if exit in exit.target.predecessors:
|
|
318
|
+
exit.target.predecessors.remove(exit)
|
|
319
|
+
# Check if the predecessor hasn't yet been removed from
|
|
320
|
+
# the exits of the source block.
|
|
321
|
+
if pred in pred.source.exits:
|
|
322
|
+
pred.source.exits.remove(pred)
|
|
323
|
+
|
|
324
|
+
block.predecessors = []
|
|
325
|
+
# as the exits may be modified during the recursive call, it is unsafe to iterate on block.exits
|
|
326
|
+
# Created a copy of block.exits before calling clean cfg , and iterate over it instead.
|
|
327
|
+
for exit in block.exits[:]:
|
|
328
|
+
self.clean_cfg(exit.target, visited)
|
|
329
|
+
block.exits = []
|
|
330
|
+
else:
|
|
331
|
+
for exit in block.exits[:]:
|
|
332
|
+
self.clean_cfg(exit.target, visited)
|
|
333
|
+
|
|
334
|
+
def goto_new_block(self, node):
|
|
335
|
+
if self.separate_node_blocks:
|
|
336
|
+
newblock = self.new_block()
|
|
337
|
+
self.add_exit(self.current_block, newblock)
|
|
338
|
+
self.current_block = newblock
|
|
339
|
+
self.generic_visit(node)
|
|
340
|
+
|
|
341
|
+
# start visting all statements in AST tree
|
|
342
|
+
def visit_Expr(self, node):
|
|
343
|
+
self.add_statement(self.current_block, node)
|
|
344
|
+
self.goto_new_block(node)
|
|
345
|
+
|
|
346
|
+
def visit_Call(self, node):
|
|
347
|
+
def visit_func(node):
|
|
348
|
+
if type(node) == ast.Name:
|
|
349
|
+
return node.id
|
|
350
|
+
elif type(node) == ast.Attribute:
|
|
351
|
+
# Recursion on series of calls to attributes.
|
|
352
|
+
func_name = visit_func(node.value)
|
|
353
|
+
func_name += "." + node.attr
|
|
354
|
+
return func_name
|
|
355
|
+
elif type(node) == ast.Str:
|
|
356
|
+
return node.s
|
|
357
|
+
elif type(node) == ast.Subscript:
|
|
358
|
+
return node.value.id
|
|
359
|
+
|
|
360
|
+
#func = node.func
|
|
361
|
+
#func_name = visit_func(func)
|
|
362
|
+
func_name = get_func_calls(node)[0]
|
|
363
|
+
self.current_block.func_calls.append(func_name)
|
|
364
|
+
|
|
365
|
+
def visit_Assign(self, node):
|
|
366
|
+
self.add_statement(self.current_block, node)
|
|
367
|
+
self.goto_new_block(node)
|
|
368
|
+
|
|
369
|
+
def visit_AnnAssign(self, node):
|
|
370
|
+
self.add_statement(self.current_block, node)
|
|
371
|
+
self.goto_new_block(node)
|
|
372
|
+
|
|
373
|
+
def visit_AugAssign(self, node):
|
|
374
|
+
self.add_statement(self.current_block, node)
|
|
375
|
+
self.goto_new_block(node)
|
|
376
|
+
|
|
377
|
+
def visit_Global(self, node):
|
|
378
|
+
self.add_statement(self.current_block, node)
|
|
379
|
+
self.goto_new_block(node)
|
|
380
|
+
|
|
381
|
+
def visit_Nonlocal(self, node):
|
|
382
|
+
self.add_statement(self.current_block, node)
|
|
383
|
+
self.goto_new_block(node)
|
|
384
|
+
|
|
385
|
+
def visit_Pass(self, node):
|
|
386
|
+
self.add_statement(self.current_block, node)
|
|
387
|
+
self.goto_new_block(node)
|
|
388
|
+
|
|
389
|
+
def visit_Delete(self, node):
|
|
390
|
+
self.add_statement(self.current_block, node)
|
|
391
|
+
self.goto_new_block(node)
|
|
392
|
+
|
|
393
|
+
def visit_Raise(self, node):
|
|
394
|
+
self.add_statement(self.current_block, node)
|
|
395
|
+
self.cfg.finalblocks.append(self.current_block)
|
|
396
|
+
self.current_block = self.new_block()
|
|
397
|
+
|
|
398
|
+
def visit_Assert(self, node):
|
|
399
|
+
self.add_statement(self.current_block, node)
|
|
400
|
+
# New block for the case in which the assertion 'fails'.
|
|
401
|
+
failblock = self.new_block()
|
|
402
|
+
self.add_exit(self.current_block, failblock, invert(node.test))
|
|
403
|
+
# If the assertion fails, the current flow ends, so the fail block is a
|
|
404
|
+
# final block of the CFG.
|
|
405
|
+
self.cfg.finalblocks.append(failblock)
|
|
406
|
+
# If the assertion is True, continue the flow of the program.
|
|
407
|
+
successblock = self.new_block()
|
|
408
|
+
self.add_exit(self.current_block, successblock, node.test)
|
|
409
|
+
self.current_block = successblock
|
|
410
|
+
self.goto_new_block(node)
|
|
411
|
+
|
|
412
|
+
def visit_Try(self, node):
|
|
413
|
+
# Add the try statement at the end of the current block.
|
|
414
|
+
self.add_statement(self.current_block, node)
|
|
415
|
+
|
|
416
|
+
# Create a new block for the body of try.
|
|
417
|
+
try_block = self.new_block()
|
|
418
|
+
self.add_exit(self.current_block, try_block, ast.Constant(True))
|
|
419
|
+
n_else_stmts = len(node.orelse)
|
|
420
|
+
#else_block = self.new_block()
|
|
421
|
+
#self.add_exit(self.current_block, try_block, ast.Constant(True))
|
|
422
|
+
|
|
423
|
+
# Create blocks for handlers
|
|
424
|
+
n_handlers = len(node.handlers)
|
|
425
|
+
handler_blocks = []
|
|
426
|
+
for i in range(n_handlers):
|
|
427
|
+
h_block = self.new_block()
|
|
428
|
+
handler_blocks += [h_block]
|
|
429
|
+
after_try_block = self.new_block()
|
|
430
|
+
#self.add_exit(self.current_block, after_try_block, ast.Constant(False))
|
|
431
|
+
# keep the original block
|
|
432
|
+
current_block = self.current_block
|
|
433
|
+
#
|
|
434
|
+
self.current_block = try_block
|
|
435
|
+
|
|
436
|
+
for child in node.body:
|
|
437
|
+
self.visit(child)
|
|
438
|
+
|
|
439
|
+
if n_else_stmts>0:
|
|
440
|
+
else_block = self.new_block()
|
|
441
|
+
self.add_exit(self.current_block, else_block)
|
|
442
|
+
self.current_block = else_block
|
|
443
|
+
# create else block
|
|
444
|
+
for child in node.orelse:
|
|
445
|
+
self.visit(child)
|
|
446
|
+
self.add_exit(self.current_block, after_try_block)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
for i in range(n_handlers):
|
|
450
|
+
self.current_block = current_block
|
|
451
|
+
handler = node.handlers[i]
|
|
452
|
+
self.add_exit(self.current_block, handler_blocks[i], handler.type)
|
|
453
|
+
self.current_block = handler_blocks[i]
|
|
454
|
+
self.visit(handler)
|
|
455
|
+
# If encountered a break, exit will have already been added
|
|
456
|
+
if not self.current_block.exits:
|
|
457
|
+
self.add_exit(self.current_block, after_try_block)
|
|
458
|
+
#self.add_exit(self.current_block, after_try_block)
|
|
459
|
+
|
|
460
|
+
#if not self.current_block.exits:
|
|
461
|
+
# self.add_exit(self.current_block, after_try_block)
|
|
462
|
+
# Continue building the CFG in the after-if block.
|
|
463
|
+
|
|
464
|
+
self.current_block = after_try_block
|
|
465
|
+
|
|
466
|
+
#populate the block in the try
|
|
467
|
+
#self.current_block = try_block
|
|
468
|
+
#for child in node.body:
|
|
469
|
+
# self.visit(child)
|
|
470
|
+
#if not self.current_block.exits:
|
|
471
|
+
# self.add_exit(self.current_block, after_try_block)
|
|
472
|
+
#self.current_block = after_try_block
|
|
473
|
+
|
|
474
|
+
def visit_If(self, node):
|
|
475
|
+
# Add the If statement at the end of the current block.
|
|
476
|
+
self.add_statement(self.current_block, node)
|
|
477
|
+
|
|
478
|
+
# Create a new block for the body of the if.
|
|
479
|
+
if_block = self.new_block()
|
|
480
|
+
self.add_exit(self.current_block, if_block, node.test)
|
|
481
|
+
|
|
482
|
+
# Create a block for the code after the if-else.
|
|
483
|
+
afterif_block = self.new_block()
|
|
484
|
+
|
|
485
|
+
# New block for the body of the else if there is an else clause.
|
|
486
|
+
if len(node.orelse) != 0:
|
|
487
|
+
else_block = self.new_block()
|
|
488
|
+
self.add_exit(self.current_block, else_block, invert(node.test))
|
|
489
|
+
self.current_block = else_block
|
|
490
|
+
# Visit the children in the body of the else to populate the block.
|
|
491
|
+
for child in node.orelse:
|
|
492
|
+
self.visit(child)
|
|
493
|
+
# If encountered a break, exit will have already been added
|
|
494
|
+
if not self.current_block.exits:
|
|
495
|
+
self.add_exit(self.current_block, afterif_block)
|
|
496
|
+
else:
|
|
497
|
+
self.add_exit(self.current_block, afterif_block, invert(node.test))
|
|
498
|
+
|
|
499
|
+
# Visit children to populate the if block.
|
|
500
|
+
self.current_block = if_block
|
|
501
|
+
for child in node.body:
|
|
502
|
+
self.visit(child)
|
|
503
|
+
if not self.current_block.exits:
|
|
504
|
+
self.add_exit(self.current_block, afterif_block)
|
|
505
|
+
|
|
506
|
+
# Continue building the CFG in the after-if block.
|
|
507
|
+
self.current_block = afterif_block
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def visit_While(self, node):
|
|
511
|
+
loop_guard = self.new_loopguard()
|
|
512
|
+
self.current_block = loop_guard
|
|
513
|
+
self.add_statement(self.current_block, node)
|
|
514
|
+
self.curr_loop_guard_stack.append(loop_guard)
|
|
515
|
+
# New block for the case where the test in the while is True.
|
|
516
|
+
while_block = self.new_block()
|
|
517
|
+
self.add_exit(self.current_block, while_block, node.test)
|
|
518
|
+
|
|
519
|
+
# New block for the case where the test in the while is False.
|
|
520
|
+
afterwhile_block = self.new_block()
|
|
521
|
+
self.after_loop_block_stack.append(afterwhile_block)
|
|
522
|
+
inverted_test = invert(node.test)
|
|
523
|
+
# Skip shortcut loop edge if while True:
|
|
524
|
+
if not (isinstance(inverted_test, NAMECONSTANT_TYPE) and
|
|
525
|
+
inverted_test.value is False):
|
|
526
|
+
self.add_exit(self.current_block, afterwhile_block, inverted_test)
|
|
527
|
+
# Populate the while block.
|
|
528
|
+
self.current_block = while_block
|
|
529
|
+
for child in node.body:
|
|
530
|
+
self.visit(child)
|
|
531
|
+
if not self.current_block.exits:
|
|
532
|
+
# Did not encounter a break statement, loop back
|
|
533
|
+
self.add_exit(self.current_block, loop_guard)
|
|
534
|
+
|
|
535
|
+
# Continue building the CFG in the after-while block.
|
|
536
|
+
self.current_block = afterwhile_block
|
|
537
|
+
self.after_loop_block_stack.pop()
|
|
538
|
+
self.curr_loop_guard_stack.pop()
|
|
539
|
+
|
|
540
|
+
def visit_For(self, node):
|
|
541
|
+
loop_guard = self.new_loopguard()
|
|
542
|
+
self.current_block = loop_guard
|
|
543
|
+
self.add_statement(self.current_block, node)
|
|
544
|
+
self.curr_loop_guard_stack.append(loop_guard)
|
|
545
|
+
# New block for the body of the for-loop.
|
|
546
|
+
for_block = self.new_block()
|
|
547
|
+
self.add_exit(self.current_block, for_block, node.iter)
|
|
548
|
+
|
|
549
|
+
# Block of code after the for loop.
|
|
550
|
+
afterfor_block = self.new_block()
|
|
551
|
+
self.add_exit(self.current_block, afterfor_block)
|
|
552
|
+
self.after_loop_block_stack.append(afterfor_block)
|
|
553
|
+
self.current_block = for_block
|
|
554
|
+
|
|
555
|
+
# Populate the body of the for loop.
|
|
556
|
+
for child in node.body:
|
|
557
|
+
self.visit(child)
|
|
558
|
+
if not self.current_block.exits:
|
|
559
|
+
# Did not encounter a break
|
|
560
|
+
self.add_exit(self.current_block, loop_guard)
|
|
561
|
+
|
|
562
|
+
# Continue building the CFG in the after-for block.
|
|
563
|
+
self.current_block = afterfor_block
|
|
564
|
+
# Popping the current after loop stack,taking care of errors in case of nested for loops
|
|
565
|
+
self.after_loop_block_stack.pop()
|
|
566
|
+
self.curr_loop_guard_stack.pop()
|
|
567
|
+
|
|
568
|
+
# Async for loops and async with context managers.
|
|
569
|
+
# They have the same fields as For and With, respectively.
|
|
570
|
+
# Only valid in the body of an AsyncFunctionDef.
|
|
571
|
+
# https://docs.python.org/3/library/ast.html
|
|
572
|
+
def visit_AsyncFor(self, node):
|
|
573
|
+
loop_guard = self.new_loopguard()
|
|
574
|
+
self.current_block = loop_guard
|
|
575
|
+
self.add_statement(self.current_block, node)
|
|
576
|
+
self.curr_loop_guard_stack.append(loop_guard)
|
|
577
|
+
# New block for the body of the for-loop.
|
|
578
|
+
for_block = self.new_block()
|
|
579
|
+
self.add_exit(self.current_block, for_block, node.iter)
|
|
580
|
+
|
|
581
|
+
# Block of code after the for loop.
|
|
582
|
+
afterfor_block = self.new_block()
|
|
583
|
+
self.add_exit(self.current_block, afterfor_block)
|
|
584
|
+
self.after_loop_block_stack.append(afterfor_block)
|
|
585
|
+
self.current_block = for_block
|
|
586
|
+
|
|
587
|
+
# Populate the body of the for loop.
|
|
588
|
+
for child in node.body:
|
|
589
|
+
self.visit(child)
|
|
590
|
+
if not self.current_block.exits:
|
|
591
|
+
# Did not encounter a break
|
|
592
|
+
self.add_exit(self.current_block, loop_guard)
|
|
593
|
+
|
|
594
|
+
# Continue building the CFG in the after-for block.
|
|
595
|
+
self.current_block = afterfor_block
|
|
596
|
+
# Popping the current after loop stack,taking care of errors in case of nested for loops
|
|
597
|
+
self.after_loop_block_stack.pop()
|
|
598
|
+
self.curr_loop_guard_stack.pop()
|
|
599
|
+
def visit_Break(self, node):
|
|
600
|
+
assert len(self.after_loop_block_stack), "Found break not inside loop"
|
|
601
|
+
self.add_exit(self.current_block, self.after_loop_block_stack[-1])
|
|
602
|
+
|
|
603
|
+
def visit_Continue(self, node):
|
|
604
|
+
assert len(self.curr_loop_guard_stack), "Found continue outside loop"
|
|
605
|
+
self.add_exit(self.current_block, self.curr_loop_guard_stack[-1])
|
|
606
|
+
|
|
607
|
+
def visit_Import(self, node):
|
|
608
|
+
self.add_statement(self.current_block, node)
|
|
609
|
+
|
|
610
|
+
def visit_ImportFrom(self, node):
|
|
611
|
+
self.add_statement(self.current_block, node)
|
|
612
|
+
|
|
613
|
+
def visit_FunctionDef(self, node):
|
|
614
|
+
self.add_statement(self.current_block, node)
|
|
615
|
+
self.new_functionCFG(node, asynchr=False, enclosing_block_id=self.current_block.id)
|
|
616
|
+
|
|
617
|
+
def visit_AsyncFunctionDef(self, node):
|
|
618
|
+
self.add_statement(self.current_block, node)
|
|
619
|
+
self.new_functionCFG(node, asynchr=True,enclosing_block_id=self.current_block.id)
|
|
620
|
+
|
|
621
|
+
def visit_ClassDef(self, node):
|
|
622
|
+
self.add_statement(self.current_block, node)
|
|
623
|
+
self.new_ClassCFG(node, asynchr=True)
|
|
624
|
+
return node
|
|
625
|
+
|
|
626
|
+
def visit_Await(self, node):
|
|
627
|
+
afterawait_block = self.new_block()
|
|
628
|
+
self.add_exit(self.current_block, afterawait_block)
|
|
629
|
+
self.goto_new_block(node)
|
|
630
|
+
self.current_block = afterawait_block
|
|
631
|
+
|
|
632
|
+
def visit_Return(self, node):
|
|
633
|
+
self.add_statement(self.current_block, node)
|
|
634
|
+
self.cfg.finalblocks.append(self.current_block)
|
|
635
|
+
# Continue in a new block but without any jump to it -> all code after
|
|
636
|
+
# the return statement will not be included in the CFG.
|
|
637
|
+
self.current_block = self.new_block()
|
|
638
|
+
|
|
639
|
+
def visit_Yield(self, node):
|
|
640
|
+
self.cfg.asynchr = True
|
|
641
|
+
afteryield_block = self.new_block()
|
|
642
|
+
self.add_exit(self.current_block, afteryield_block)
|
|
643
|
+
self.current_block = afteryield_block
|
|
644
|
+
|
|
645
|
+
def visit_With(self, node):
|
|
646
|
+
# add with statement to the current block
|
|
647
|
+
self.add_statement(self.current_block, node)
|
|
648
|
+
# New block for the body of the with.
|
|
649
|
+
with_block = self.new_block()
|
|
650
|
+
# link current block to with block
|
|
651
|
+
self.add_exit(self.current_block, with_block)
|
|
652
|
+
|
|
653
|
+
# Block of code after the with.
|
|
654
|
+
afterwith_block = self.new_block()
|
|
655
|
+
# no branch here
|
|
656
|
+
# link with block and body of with
|
|
657
|
+
#print(with_block, afterwith_block)
|
|
658
|
+
#self.add_exit(with_block, afterwith_block)
|
|
659
|
+
# go to with block and create more
|
|
660
|
+
self.current_block = with_block
|
|
661
|
+
|
|
662
|
+
# Populate the body of the with loop.
|
|
663
|
+
for child in node.body:
|
|
664
|
+
self.visit(child)
|
|
665
|
+
|
|
666
|
+
if not self.current_block.exits:
|
|
667
|
+
self.add_exit(self.current_block, afterwith_block)
|
|
668
|
+
# Continue building the CFG in the after-with block.
|
|
669
|
+
self.current_block = afterwith_block
|
|
670
|
+
|
|
671
|
+
# Async for loops and async with context managers.
|
|
672
|
+
# They have the same fields as For and With, respectively.
|
|
673
|
+
# Only valid in the body of an AsyncFunctionDef.
|
|
674
|
+
# https://docs.python.org/3/library/ast.html
|
|
675
|
+
def visit_AsyncWith(self, node):
|
|
676
|
+
# add with statement to the current block
|
|
677
|
+
self.add_statement(self.current_block, node)
|
|
678
|
+
# New block for the body of the with.
|
|
679
|
+
with_block = self.new_block()
|
|
680
|
+
# link current block to with block
|
|
681
|
+
self.add_exit(self.current_block, with_block)
|
|
682
|
+
|
|
683
|
+
# Block of code after the with.
|
|
684
|
+
afterwith_block = self.new_block()
|
|
685
|
+
# no branch here
|
|
686
|
+
# link with block and body of with
|
|
687
|
+
#print(with_block, afterwith_block)
|
|
688
|
+
#self.add_exit(with_block, afterwith_block)
|
|
689
|
+
# go to with block and create more
|
|
690
|
+
self.current_block = with_block
|
|
691
|
+
|
|
692
|
+
# Populate the body of the with loop.
|
|
693
|
+
for child in node.body:
|
|
694
|
+
self.visit(child)
|
|
695
|
+
|
|
696
|
+
if not self.current_block.exits:
|
|
697
|
+
self.add_exit(self.current_block, afterwith_block)
|
|
698
|
+
# Continue building the CFG in the after-with block.
|
|
699
|
+
self.current_block = afterwith_block
|
|
700
|
+
|