• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Control flow graph (CFG) structure for Python AST representation.
16
17The CFG is a digraph with edges representing valid control flow. Each
18node is associated with exactly one AST node, but not all AST nodes may have
19a corresponding CFG counterpart.
20
21Once built, the CFG itself is immutable, but the values it holds need not be;
22they are usually annotated with information extracted by walking the graph.
23"""
24
25# TODO(mdan): The notion of 'statements' below is inaccurate.
26# They should rather be called 'block statements', because they include
27# statements that may have a body, e.g. if and while.
28
29from __future__ import absolute_import
30from __future__ import division
31from __future__ import print_function
32
33import collections
34import weakref
35from enum import Enum
36
37# pylint:disable=g-bad-import-order
38import gast
39# pylint:enable=g-bad-import-order
40
41from tensorflow.python.autograph.pyct import compiler
42
43
44class Node(object):
45  """A node in the CFG.
46
47  Although new instances of this class are mutable, the objects that a user
48  finds in the CFG are typically not.
49
50  The nodes represent edges in the CFG graph, and maintain pointers to allow
51  efficient walking in both forward and reverse order. The following property
52  holds for all nodes: "child in node.next" iff "node in child.prev".
53
54  Attributes:
55    next: FrozenSet[Node, ...], the nodes that follow this node, in control
56      flow order
57    prev: FrozenSet[Node, ...], the nodes that precede this node, in reverse
58      control flow order
59    ast_node: ast.AST, the AST node corresponding to this CFG node
60  """
61
62  def __init__(self, next_, prev, ast_node):
63    self.next = next_
64    self.prev = prev
65    self.ast_node = ast_node
66
67  def freeze(self):
68    self.next = frozenset(self.next)
69    # Assumption: All CFG nodes have identical life spans, because the graph
70    # owns them. Nodes should never be used outside the context of an existing
71    # graph.
72    self.prev = weakref.WeakSet(self.prev)
73
74  def __repr__(self):
75    if isinstance(self.ast_node, gast.FunctionDef):
76      return 'def %s' % self.ast_node.name
77    elif isinstance(self.ast_node, gast.withitem):
78      return compiler.ast_to_source(self.ast_node.context_expr).strip()
79    return compiler.ast_to_source(self.ast_node).strip()
80
81
82class Graph(
83    collections.namedtuple(
84        'Graph',
85        ['entry', 'exit', 'error', 'index', 'stmt_prev', 'stmt_next'])):
86  """A Control Flow Graph.
87
88  The CFG maintains an index to allow looking up a CFG node by the AST node to
89  which it is associated. The index can also be enumerated in top-down, depth
90  first order.
91
92  Walking the graph in forward or reverse order is supported by double
93  parent-child links.
94
95  Note: the error nodes are not wired to their corresponding finally guards,
96  because these are shared, and wiring them would create a reverse path from
97  normal control flow into the error nodes, which we want to avoid.
98
99  The graph also maintains edges corresponding to higher level statements
100  like for-else loops. A node is considered successor of a statement if there
101  is an edge from a node that is lexically a child of that statement to a node
102  that is not. Statement predecessors are analogously defined.
103
104  Attributes:
105    entry: Node, the entry node
106    exit: FrozenSet[Node, ...], the exit nodes
107    error: FrozenSet[Node, ...], nodes that exit due to an explicitly raised
108        error (errors propagated from function calls are not accounted)
109    index: Dict[ast.Node, Node], mapping AST nodes to the respective CFG
110        node
111    stmt_prev: Dict[ast.Node, FrozenSet[Node, ...]], mapping statement AST
112        nodes to their predecessor CFG nodes
113    stmt_next: Dict[ast.Node, FrozenSet[Node, ...]], mapping statement AST
114        nodes to their successor CFG nodes
115  """
116
117  def __repr__(self):
118    result = 'digraph CFG {\n'
119    for node in self.index.values():
120      result += '  %s [label="%s"];\n' % (id(node), node)
121    for node in self.index.values():
122      for next_ in node.next:
123        result += '  %s -> %s;\n' % (id(node), id(next_))
124    result += '}'
125    return result
126
127
128class _WalkMode(Enum):
129  FORWARD = 1
130  REVERSE = 2
131
132
133# TODO(mdan): Rename to DataFlowAnalyzer.
134# TODO(mdan): Consider specializations that use gen/kill/transfer abstractions.
135class GraphVisitor(object):
136  """Base class for a CFG visitors.
137
138  This implementation is not thread safe.
139
140  The visitor has some facilities to simplify dataflow analyses. In particular,
141  it allows revisiting the nodes at the decision of the subclass. This can be
142  used to visit the graph until the state reaches a fixed point.
143
144  For more details on dataflow analysis, see
145  https://www.seas.harvard.edu/courses/cs252/2011sp/slides/Lec02-Dataflow.pdf
146
147  Note: the literature generally suggests visiting successor nodes only when the
148  state of the current node changed, regardless of whether that successor has
149  ever been visited. This implementation visits every successor at least once.
150
151  Attributes:
152    graph: Graph
153    in_: Dict[Node, Any], stores node-keyed state during a visit
154    out: Dict[Node, Any], stores node-keyed state during a visit
155  """
156
157  def __init__(self, graph):
158    self.graph = graph
159    self.reset()
160
161  def init_state(self, node):
162    """State initialization function. Optional to overload.
163
164    An in/out state slot will be created for each node in the graph. Subclasses
165    must overload this to control what that is initialized to.
166
167    Args:
168      node: Node
169    """
170    raise NotImplementedError('Subclasses must implement this.')
171
172  # TODO(mdan): Rename to flow?
173  def visit_node(self, node):
174    """Visitor function.
175
176    Args:
177      node: Node
178    Returns:
179      bool, whether the node should be revisited; subclasses can visit every
180          reachable node exactly once by always returning False
181    """
182    raise NotImplementedError('Subclasses must implement this.')
183
184  def reset(self):
185    self.in_ = {
186        node: self.init_state(node) for node in self.graph.index.values()
187    }
188    self.out = {
189        node: self.init_state(node) for node in self.graph.index.values()
190    }
191
192  def _visit_internal(self, mode):
193    """Visits the CFG, depth-first."""
194    assert mode in (_WalkMode.FORWARD, _WalkMode.REVERSE)
195    if mode == _WalkMode.FORWARD:
196      open_ = [self.graph.entry]
197    elif mode == _WalkMode.REVERSE:
198      open_ = list(self.graph.exit)
199    closed = set()
200
201    while open_:
202      node = open_.pop(0)
203      closed.add(node)
204
205      should_revisit = self.visit_node(node)
206
207      if mode == _WalkMode.FORWARD:
208        children = node.next
209      elif mode == _WalkMode.REVERSE:
210        children = node.prev
211
212      for next_ in children:
213        if should_revisit or next_ not in closed:
214          open_.append(next_)
215
216  def visit_forward(self):
217    self._visit_internal(_WalkMode.FORWARD)
218
219  def visit_reverse(self):
220    self._visit_internal(_WalkMode.REVERSE)
221
222
223class GraphBuilder(object):
224  """Builder that constructs a CFG from a given AST.
225
226  This GraphBuilder facilitates constructing the DAG that forms the CFG when
227  nodes
228  are supplied in lexical order (i.e., top-down, depth first). Under these
229  conditions, it supports building patterns found in typical structured
230  programs.
231
232  This builder ignores the flow generated by exceptions, which are assumed to
233  always be catastrophic and present purely for diagnostic purposes (e.g. to
234  print debug information). Statements like raise and try/catch sections are
235  allowed and will generate control flow edges, but ordinaty statements are
236  assumed not to raise exceptions.
237
238  Finally sections are also correctly interleaved between break/continue/return
239  nodes and their subsequent statements.
240
241  Important concepts:
242   * nodes - nodes refer refer to CFG nodes; AST nodes are qualified explicitly
243   * leaf set - since the graph is constructed gradually, a leaf set maintains
244     the CFG nodes that will precede the node that the builder expects to
245     receive next; when an ordinary node is added, it is connected to the
246     existing leaves and it in turn becomes the new leaf
247   * jump nodes - nodes that should generate edges other than what
248     ordinary nodes would; these correspond to break, continue and return
249     statements
250   * sections - logical delimiters for subgraphs that require special
251     edges; there are various types of nodes, each admitting various
252     types of jump nodes; sections are identified by their corresponding AST
253     node
254  """
255
256  # TODO(mdan): Perhaps detail this in a markdown doc.
257  # TODO(mdan): Add exception support.
258
259  def __init__(self, parent_ast_node):
260    self.reset()
261    self.parent = parent_ast_node
262
263  def reset(self):
264    """Resets the state of this factory."""
265    self.head = None
266    self.errors = set()
267    self.node_index = {}
268
269    # TODO(mdan): Too many primitives. Use classes.
270    self.leaves = set()
271
272    # Note: This mechanism requires that nodes are added in lexical order (top
273    # to bottom, depth first).
274    self.active_stmts = set()
275    self.owners = {}  # type: Set[any]
276    self.forward_edges = set()  # type: Tuple[Node, Node] # (from, to)
277
278    self.finally_sections = {}
279    # Dict values represent (entry, exits)
280    self.finally_section_subgraphs = {
281    }  # type: Dict[ast.AST, Tuple[Node, Set[Node]]]
282    # Whether the guard section can be reached from the statement that precedes
283    # it.
284    self.finally_section_has_direct_flow = {}
285    # Finally sections that await their first node.
286    self.pending_finally_sections = set()
287
288    # Exit jumps keyed by the section they affect.
289    self.exits = {}
290
291    # The entry of loop sections, keyed by the section.
292    self.section_entry = {}
293    # Continue jumps keyed by the section they affect.
294    self.continues = {}
295
296    # The entry of conditional sections, keyed by the section.
297    self.cond_entry = {}
298    # Lists of leaf nodes corresponding to each branch in the section.
299    self.cond_leaves = {}
300
301  def _connect_nodes(self, first, second):
302    """Connects nodes to signify that control flows from first to second.
303
304    Args:
305      first: Union[Set[Node, ...], Node]
306      second: Node
307    """
308    if isinstance(first, Node):
309      first.next.add(second)
310      second.prev.add(first)
311      self.forward_edges.add((first, second))
312    else:
313      for node in first:
314        self._connect_nodes(node, second)
315
316  def _add_new_node(self, ast_node):
317    """Grows the graph by adding a CFG node following the current leaves."""
318    if ast_node is self.node_index:
319      raise ValueError('%s added twice' % ast_node)
320    # Assumption: All CFG nodes have identical life spans, because the graph
321    # owns them. Nodes should never be used outside the context of an existing
322    # graph.
323    node = Node(next_=set(), prev=weakref.WeakSet(), ast_node=ast_node)
324    self.node_index[ast_node] = node
325    self.owners[node] = frozenset(self.active_stmts)
326
327    if self.head is None:
328      self.head = node
329
330    for leaf in self.leaves:
331      self._connect_nodes(leaf, node)
332
333    # If any finally section awaits its first node, populate it.
334    for section_id in self.pending_finally_sections:
335      self.finally_section_subgraphs[section_id][0] = node
336    self.pending_finally_sections = set()
337
338    return node
339
340  def begin_statement(self, stmt):
341    """Marks the beginning of a statement.
342
343    Args:
344      stmt: Hashable, a key by which the statement can be identified in
345          the CFG's stmt_prev and stmt_next attributes
346    """
347    self.active_stmts.add(stmt)
348
349  def end_statement(self, stmt):
350    """Marks the end of a statement.
351
352    Args:
353      stmt: Hashable, a key by which the statement can be identified in
354          the CFG's stmt_prev and stmt_next attributes; must match a key
355          previously passed to begin_statement.
356    """
357    self.active_stmts.remove(stmt)
358
359  def add_ordinary_node(self, ast_node):
360    """Grows the graph by adding an ordinary CFG node.
361
362    Ordinary nodes are followed by the next node, in lexical order, that is,
363    they become the new leaf set.
364
365    Args:
366      ast_node: ast.AST
367    Returns:
368      Node
369    """
370    node = self._add_new_node(ast_node)
371    self.leaves = set((node,))
372    return node
373
374  def _add_jump_node(self, ast_node, guards):
375    """Grows the graph by adding a jump node.
376
377    Jump nodes are added to the current leaf set, and the leaf set becomes
378    empty. If the jump node is the last in a cond section, then it may be added
379    back to the leaf set by a separate mechanism.
380
381    Args:
382      ast_node: ast.AST
383      guards: Tuple[ast.AST, ...], the finally sections active for this node
384    Returns:
385      Node
386    """
387    node = self._add_new_node(ast_node)
388    self.leaves = set()
389    # The guards themselves may not yet be complete, and will be wired later.
390    self.finally_sections[node] = guards
391    return node
392
393  def _connect_jump_to_finally_sections(self, node):
394    """Connects a jump node to the finally sections protecting it."""
395    cursor = set((node,))
396    if node not in self.finally_sections:
397      return cursor
398    for guard_section_id in self.finally_sections[node]:
399      guard_begin, guard_ends = self.finally_section_subgraphs[guard_section_id]
400      self._connect_nodes(cursor, guard_begin)
401      cursor = guard_ends
402    del self.finally_sections[node]
403    # TODO(mdan): Should garbage-collect finally_section_subgraphs.
404    return cursor
405
406  def add_exit_node(self, ast_node, section_id, guards):
407    """Grows the graph by adding an exit node.
408
409    This node becomes an exit for the current section.
410
411    Args:
412      ast_node: ast.AST
413      section_id: Hashable, the node for which ast_node should be considered
414          to be an exit node
415      guards: Tuple[ast.AST, ...], the finally sections that guard ast_node
416    """
417    node = self._add_jump_node(ast_node, guards)
418    self.exits[section_id].add(node)
419
420  def add_continue_node(self, ast_node, section_id, guards):
421    """Grows the graph by adding a reentry node.
422
423    This node causes control flow to go back to the loop section's entry.
424
425    Args:
426      ast_node: ast.AST
427      section_id: Hashable, the node for which ast_node should be considered
428          to be an exit node
429      guards: Tuple[ast.AST, ...], the finally sections that guard ast_node
430    """
431    node = self._add_jump_node(ast_node, guards)
432    self.continues[section_id].add(node)
433
434  def add_error_node(self, ast_node, guards):
435    """Grows the graph by adding an error node.
436
437    This node becomes an exit for the entire graph.
438
439    Args:
440      ast_node: ast.AST
441      guards: Tuple[ast.AST, ...], the finally sections that guard ast_node
442    """
443    node = self._add_jump_node(ast_node, guards)
444    self.errors.add(node)
445    self.leaves = set()
446
447  def enter_section(self, section_id):
448    """Enters a regular section.
449
450    Regular sections admit exit jumps, which end the section.
451
452    Args:
453      section_id: Hashable, the same node that will be used in calls to the
454          ast_node arg passed to add_exit_node
455    """
456    assert section_id not in self.exits
457    self.exits[section_id] = set()
458
459  def exit_section(self, section_id):
460    """Exits a regular section."""
461
462    # Exits are jump nodes, which may be protected.
463    for exit_ in self.exits[section_id]:
464      self.leaves |= self._connect_jump_to_finally_sections(exit_)
465
466    del self.exits[section_id]
467
468  def enter_loop_section(self, section_id, entry_node):
469    """Enters a loop section.
470
471    Loop sections define an entry node. The end of the section always flows back
472    to the entry node. These admit continue jump nodes which also flow to the
473    entry node.
474
475    Args:
476      section_id: Hashable, the same node that will be used in calls to the
477          ast_node arg passed to add_continue_node
478      entry_node: ast.AST, the entry node into the loop (e.g. the test node
479          for while loops)
480    """
481    assert section_id not in self.section_entry
482    assert section_id not in self.continues
483    self.continues[section_id] = set()
484    node = self.add_ordinary_node(entry_node)
485    self.section_entry[section_id] = node
486
487  def exit_loop_section(self, section_id):
488    """Exits a loop section."""
489    self._connect_nodes(self.leaves, self.section_entry[section_id])
490
491    # continues are jump nodes, which may be protected.
492    for reentry in self.continues[section_id]:
493      guard_ends = self._connect_jump_to_finally_sections(reentry)
494      self._connect_nodes(guard_ends, self.section_entry[section_id])
495
496    # Loop nodes always loop back.
497    self.leaves = set((self.section_entry[section_id],))
498
499    del self.continues[section_id]
500    del self.section_entry[section_id]
501
502  def enter_cond_section(self, section_id):
503    """Enters a conditional section.
504
505    Conditional sections define an entry node, and one or more branches.
506
507    Args:
508      section_id: Hashable, the same node that will be used in calls to the
509          section_id arg passed to new_cond_branch
510    """
511
512    assert section_id not in self.cond_entry
513    assert section_id not in self.cond_leaves
514    self.cond_leaves[section_id] = []
515
516  def new_cond_branch(self, section_id):
517    """Begins a new branch in a cond section."""
518    assert section_id in self.cond_leaves
519
520    if section_id in self.cond_entry:
521      # Subsequent splits move back to the split point, and memorize the
522      # current leaves.
523      self.cond_leaves[section_id].append(self.leaves)
524      self.leaves = self.cond_entry[section_id]
525    else:
526      # If this is the first time we split a section, just remember the split
527      # point.
528      self.cond_entry[section_id] = self.leaves
529
530  def exit_cond_section(self, section_id):
531    """Exits a conditional section."""
532    for split in self.cond_leaves[section_id]:
533      self.leaves |= split
534    del self.cond_entry[section_id]
535    del self.cond_leaves[section_id]
536
537  def enter_finally_section(self, section_id):
538    """Enters a finally section."""
539    # TODO(mdan): This, not the caller, should track the active sections.
540    self.finally_section_subgraphs[section_id] = [None, None]
541    if self.leaves:
542      self.finally_section_has_direct_flow[section_id] = True
543    else:
544      self.finally_section_has_direct_flow[section_id] = False
545    self.pending_finally_sections.add(section_id)
546
547  def exit_finally_section(self, section_id):
548    """Exits a finally section."""
549    assert section_id not in self.pending_finally_sections, 'Empty finally?'
550    self.finally_section_subgraphs[section_id][1] = self.leaves
551    # If the guard can only be reached by a jump, then it will not flow
552    # into the statement that follows it.
553    if not self.finally_section_has_direct_flow[section_id]:
554      self.leaves = set()
555    del self.finally_section_has_direct_flow[section_id]
556
557  def build(self):
558    """Returns the CFG accumulated so far and resets the builder.
559
560    Returns:
561      Graph
562    """
563    # Freeze the nodes.
564    for node in self.node_index.values():
565      node.freeze()
566
567    # Build the statement edges.
568    stmt_next = {}
569    stmt_prev = {}
570    for node, _ in self.forward_edges:
571      for stmt in self.owners[node]:
572        if stmt not in stmt_next:
573          stmt_next[stmt] = set()
574        if stmt not in stmt_prev:
575          stmt_prev[stmt] = set()
576    for first, second in self.forward_edges:
577      stmts_exited = self.owners[first] - self.owners[second]
578      for stmt in stmts_exited:
579        stmt_next[stmt].add(second)
580      stmts_entered = self.owners[second] - self.owners[first]
581      for stmt in stmts_entered:
582        stmt_prev[stmt].add(first)
583    for stmt in stmt_next:
584      stmt_next[stmt] = frozenset(stmt_next[stmt])
585    for stmt in stmt_prev:
586      stmt_prev[stmt] = frozenset(stmt_prev[stmt])
587
588    # Construct the final graph object.
589    result = Graph(
590        entry=self.head,
591        exit=self.leaves,
592        error=self.errors,
593        index=self.node_index,
594        stmt_prev=stmt_prev,
595        stmt_next=stmt_next)
596
597    # Reset the state.
598    self.reset()
599
600    return result
601
602
603class AstToCfg(gast.NodeVisitor):
604  """Converts an AST to CFGs.
605
606  A separate CFG will be constructed for each function.
607  """
608
609  def __init__(self):
610    super(AstToCfg, self).__init__()
611
612    self.builder_stack = []
613    self.builder = None
614    self.cfgs = {}
615
616    self.lexical_scopes = []
617
618  def _enter_lexical_scope(self, node):
619    self.lexical_scopes.append(node)
620
621  def _exit_lexical_scope(self, node):
622    leaving_node = self.lexical_scopes.pop()
623    assert node == leaving_node
624
625  def _get_enclosing_finally_scopes(self, stop_at):
626    included = []
627    for node in reversed(self.lexical_scopes):
628      if isinstance(node, gast.Try) and node.finalbody:
629        included.append(node)
630      if isinstance(node, stop_at):
631        return node, included
632    return None, included
633
634  def _process_basic_statement(self, node):
635    self.generic_visit(node)
636    self.builder.add_ordinary_node(node)
637
638  def _process_exit_statement(self, node, *exits_nodes_of_type):
639    # Note: this is safe because we process functions separately.
640    try_node, guards = self._get_enclosing_finally_scopes(
641        tuple(exits_nodes_of_type))
642    if try_node is None:
643      raise ValueError(
644          '%s that is not enclosed by any of %s' % (node, exits_nodes_of_type))
645    self.builder.add_exit_node(node, try_node, guards)
646
647  def _process_continue_statement(self, node, *loops_to_nodes_of_type):
648    # Note: this is safe because we process functions separately.
649    try_node, guards = self._get_enclosing_finally_scopes(
650        tuple(loops_to_nodes_of_type))
651    if try_node is None:
652      raise ValueError('%s that is not enclosed by any of %s' %
653                       (node, loops_to_nodes_of_type))
654    self.builder.add_continue_node(node, try_node, guards)
655
656  def visit_FunctionDef(self, node):
657    # We also keep the FunctionDef node in the CFG. This allows us to determine
658    # things like reaching definitions via closure. Note that the function body
659    # will be stored in a separate graph, because function definitions are not
660    # the same as function calls.
661    if self.builder is not None:
662      self.builder.add_ordinary_node(node)
663
664    self.builder_stack.append(self.builder)
665    self.builder = GraphBuilder(node)
666
667    self._enter_lexical_scope(node)
668    self.builder.enter_section(node)
669
670    self._process_basic_statement(node.args)
671    for stmt in node.body:
672      self.visit(stmt)
673
674    self.builder.exit_section(node)
675    self._exit_lexical_scope(node)
676
677    self.cfgs[node] = self.builder.build()
678    self.builder = self.builder_stack.pop()
679
680  def visit_Return(self, node):
681    self._process_exit_statement(node, gast.FunctionDef)
682
683  def visit_Expr(self, node):
684    self._process_basic_statement(node)
685
686  def visit_Assign(self, node):
687    self._process_basic_statement(node)
688
689  def visit_AnnAssign(self, node):
690    self._process_basic_statement(node)
691
692  def visit_AugAssign(self, node):
693    self._process_basic_statement(node)
694
695  def visit_Print(self, node):
696    self._process_basic_statement(node)
697
698  def visit_Raise(self, node):
699    try_node, guards = self._get_enclosing_finally_scopes((gast.FunctionDef,))
700    if try_node is None:
701      raise ValueError('%s that is not enclosed by any FunctionDef' % node)
702    self.builder.add_error_node(node, guards)
703
704  def visit_Assert(self, node):
705    # Ignoring the effect of exceptions.
706    self._process_basic_statement(node)
707
708  def visit_Delete(self, node):
709    self._process_basic_statement(node)
710
711  def visit_If(self, node):
712    # No need to track ifs as lexical scopes, for now.
713    # Lexical scopes are generally tracked in order to be able to resolve the
714    # targets of jump statements like break/continue/etc. Since there is no
715    # statement that can interrupt a conditional, we don't need to track their
716    # lexical scope. That may change in the future.
717    self.builder.begin_statement(node)
718
719    self.builder.enter_cond_section(node)
720    self._process_basic_statement(node.test)
721
722    self.builder.new_cond_branch(node)
723    for stmt in node.body:
724      self.visit(stmt)
725
726    self.builder.new_cond_branch(node)
727    for stmt in node.orelse:
728      self.visit(stmt)
729
730    self.builder.exit_cond_section(node)
731    self.builder.end_statement(node)
732
733  def visit_While(self, node):
734    self.builder.begin_statement(node)
735    self._enter_lexical_scope(node)
736
737    self.builder.enter_section(node)
738
739    self.builder.enter_loop_section(node, node.test)
740    for stmt in node.body:
741      self.visit(stmt)
742    self.builder.exit_loop_section(node)
743
744    # Note: although the orelse is technically part of the loop node,
745    # the statements inside it don't affect the loop itself. For example, a
746    # break in the loop's orelse will not affect the loop itself.
747    self._exit_lexical_scope(node)
748
749    for stmt in node.orelse:
750      self.visit(stmt)
751
752    self.builder.exit_section(node)
753    self.builder.end_statement(node)
754
755  def visit_For(self, node):
756    self.builder.begin_statement(node)
757    self._enter_lexical_scope(node)
758
759    self.builder.enter_section(node)
760
761    # Note: Strictly speaking, this should be node.target + node.iter.
762    # However, the activity analysis accounts for this inconsistency,
763    # so dataflow analysis produces the correct values.
764    self.builder.enter_loop_section(node, node.iter)
765    for stmt in node.body:
766      self.visit(stmt)
767    self.builder.exit_loop_section(node)
768
769    # Note: although the orelse is technically part of the loop node,
770    # they don't count as loop bodies.  For example, a break in the loop's
771    # orelse will affect the parent loop, not the current one.
772    self._exit_lexical_scope(node)
773
774    for stmt in node.orelse:
775      self.visit(stmt)
776
777    self.builder.exit_section(node)
778    self.builder.end_statement(node)
779
780  def visit_Break(self, node):
781    self._process_exit_statement(node, gast.While, gast.For)
782
783  def visit_Continue(self, node):
784    self._process_continue_statement(node, gast.While, gast.For)
785
786  def visit_Try(self, node):
787    self._enter_lexical_scope(node)
788
789    for stmt in node.body:
790      self.visit(stmt)
791    # Unlike loops, the orelse is a simple continuation of the body.
792    for stmt in node.orelse:
793      self.visit(stmt)
794
795    self._exit_lexical_scope(node)
796
797    if node.finalbody:
798      self.builder.enter_finally_section(node)
799      for stmt in node.finalbody:
800        self.visit(stmt)
801      self.builder.exit_finally_section(node)
802
803  def visit_With(self, node):
804    # TODO(mdan): Mark the context manager's exit call as exit guard.
805    for item in node.items:
806      self._process_basic_statement(item)
807    for stmt in node.body:
808      self.visit(stmt)
809
810
811def build(node):
812  visitor = AstToCfg()
813  visitor.visit(node)
814  return visitor.cfgs
815