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.
@@ -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
+