Home | History | Annotate | Download | only in Compiler
      1 import cython
      2 cython.declare(PyrexTypes=object, ExprNodes=object, Nodes=object,
      3                Builtin=object, InternalError=object,
      4                error=object, warning=object,
      5                py_object_type=object, unspecified_type=object,
      6                object_expr=object, object_expr_not_none=object,
      7                fake_rhs_expr=object, TypedExprNode=object)
      8 
      9 import Builtin
     10 import ExprNodes
     11 import Nodes
     12 import Options
     13 from PyrexTypes import py_object_type, unspecified_type
     14 import PyrexTypes
     15 
     16 from Visitor import TreeVisitor, CythonTransform
     17 from Errors import error, warning, InternalError
     18 
     19 class TypedExprNode(ExprNodes.ExprNode):
     20     # Used for declaring assignments of a specified type without a known entry.
     21     def __init__(self, type, may_be_none=None, pos=None):
     22         super(TypedExprNode, self).__init__(pos)
     23         self.type = type
     24         self._may_be_none = may_be_none
     25 
     26     def may_be_none(self):
     27         return self._may_be_none != False
     28 
     29 object_expr = TypedExprNode(py_object_type, may_be_none=True)
     30 object_expr_not_none = TypedExprNode(py_object_type, may_be_none=False)
     31 # Fake rhs to silence "unused variable" warning
     32 fake_rhs_expr = TypedExprNode(unspecified_type)
     33 
     34 
     35 class ControlBlock(object):
     36     """Control flow graph node. Sequence of assignments and name references.
     37 
     38        children  set of children nodes
     39        parents   set of parent nodes
     40        positions set of position markers
     41 
     42        stats     list of block statements
     43        gen       dict of assignments generated by this block
     44        bounded   set  of entries that are definitely bounded in this block
     45 
     46        Example:
     47 
     48         a = 1
     49         b = a + c # 'c' is already bounded or exception here
     50 
     51         stats = [Assignment(a), NameReference(a), NameReference(c),
     52                      Assignment(b)]
     53         gen = {Entry(a): Assignment(a), Entry(b): Assignment(b)}
     54         bounded = set([Entry(a), Entry(c)])
     55 
     56     """
     57 
     58     def __init__(self):
     59         self.children = set()
     60         self.parents = set()
     61         self.positions = set()
     62 
     63         self.stats = []
     64         self.gen = {}
     65         self.bounded = set()
     66 
     67         self.i_input = 0
     68         self.i_output = 0
     69         self.i_gen = 0
     70         self.i_kill = 0
     71         self.i_state = 0
     72 
     73     def empty(self):
     74         return (not self.stats and not self.positions)
     75 
     76     def detach(self):
     77         """Detach block from parents and children."""
     78         for child in self.children:
     79             child.parents.remove(self)
     80         for parent in self.parents:
     81             parent.children.remove(self)
     82         self.parents.clear()
     83         self.children.clear()
     84 
     85     def add_child(self, block):
     86         self.children.add(block)
     87         block.parents.add(self)
     88 
     89 
     90 class ExitBlock(ControlBlock):
     91     """Non-empty exit point block."""
     92 
     93     def empty(self):
     94         return False
     95 
     96 
     97 class AssignmentList(object):
     98     def __init__(self):
     99         self.stats = []
    100 
    101 
    102 class ControlFlow(object):
    103     """Control-flow graph.
    104 
    105        entry_point ControlBlock entry point for this graph
    106        exit_point  ControlBlock normal exit point
    107        block       ControlBlock current block
    108        blocks      set    children nodes
    109        entries     set    tracked entries
    110        loops       list   stack for loop descriptors
    111        exceptions  list   stack for exception descriptors
    112     """
    113 
    114     def __init__(self):
    115         self.blocks = set()
    116         self.entries = set()
    117         self.loops = []
    118         self.exceptions = []
    119 
    120         self.entry_point = ControlBlock()
    121         self.exit_point = ExitBlock()
    122         self.blocks.add(self.exit_point)
    123         self.block = self.entry_point
    124 
    125     def newblock(self, parent=None):
    126         """Create floating block linked to `parent` if given.
    127 
    128            NOTE: Block is NOT added to self.blocks
    129         """
    130         block = ControlBlock()
    131         self.blocks.add(block)
    132         if parent:
    133             parent.add_child(block)
    134         return block
    135 
    136     def nextblock(self, parent=None):
    137         """Create block children block linked to current or `parent` if given.
    138 
    139            NOTE: Block is added to self.blocks
    140         """
    141         block = ControlBlock()
    142         self.blocks.add(block)
    143         if parent:
    144             parent.add_child(block)
    145         elif self.block:
    146             self.block.add_child(block)
    147         self.block = block
    148         return self.block
    149 
    150     def is_tracked(self, entry):
    151         if entry.is_anonymous:
    152             return False
    153         return (entry.is_local or entry.is_pyclass_attr or entry.is_arg or
    154                 entry.from_closure or entry.in_closure or
    155                 entry.error_on_uninitialized)
    156 
    157     def is_statically_assigned(self, entry):
    158         if (entry.is_local and entry.is_variable and
    159                 (entry.type.is_struct_or_union or
    160                  entry.type.is_complex or
    161                  entry.type.is_array or
    162                  entry.type.is_cpp_class)):
    163             # stack allocated structured variable => never uninitialised
    164             return True
    165         return False
    166 
    167     def mark_position(self, node):
    168         """Mark position, will be used to draw graph nodes."""
    169         if self.block:
    170             self.block.positions.add(node.pos[:2])
    171 
    172     def mark_assignment(self, lhs, rhs, entry):
    173         if self.block and self.is_tracked(entry):
    174             assignment = NameAssignment(lhs, rhs, entry)
    175             self.block.stats.append(assignment)
    176             self.block.gen[entry] = assignment
    177             self.entries.add(entry)
    178 
    179     def mark_argument(self, lhs, rhs, entry):
    180         if self.block and self.is_tracked(entry):
    181             assignment = Argument(lhs, rhs, entry)
    182             self.block.stats.append(assignment)
    183             self.block.gen[entry] = assignment
    184             self.entries.add(entry)
    185 
    186     def mark_deletion(self, node, entry):
    187         if self.block and self.is_tracked(entry):
    188             assignment = NameDeletion(node, entry)
    189             self.block.stats.append(assignment)
    190             self.block.gen[entry] = Uninitialized
    191             self.entries.add(entry)
    192 
    193     def mark_reference(self, node, entry):
    194         if self.block and self.is_tracked(entry):
    195             self.block.stats.append(NameReference(node, entry))
    196             ## XXX: We don't track expression evaluation order so we can't use
    197             ## XXX: successful reference as initialization sign.
    198             ## # Local variable is definitely bound after this reference
    199             ## if not node.allow_null:
    200             ##     self.block.bounded.add(entry)
    201             self.entries.add(entry)
    202 
    203     def normalize(self):
    204         """Delete unreachable and orphan blocks."""
    205         queue = set([self.entry_point])
    206         visited = set()
    207         while queue:
    208             root = queue.pop()
    209             visited.add(root)
    210             for child in root.children:
    211                 if child not in visited:
    212                     queue.add(child)
    213         unreachable = self.blocks - visited
    214         for block in unreachable:
    215             block.detach()
    216         visited.remove(self.entry_point)
    217         for block in visited:
    218             if block.empty():
    219                 for parent in block.parents: # Re-parent
    220                     for child in block.children:
    221                         parent.add_child(child)
    222                 block.detach()
    223                 unreachable.add(block)
    224         self.blocks -= unreachable
    225 
    226     def initialize(self):
    227         """Set initial state, map assignments to bits."""
    228         self.assmts = {}
    229 
    230         bit = 1
    231         for entry in self.entries:
    232             assmts = AssignmentList()
    233             assmts.mask = assmts.bit = bit
    234             self.assmts[entry] = assmts
    235             bit <<= 1
    236 
    237         for block in self.blocks:
    238             for stat in block.stats:
    239                 if isinstance(stat, NameAssignment):
    240                     stat.bit = bit
    241                     assmts = self.assmts[stat.entry]
    242                     assmts.stats.append(stat)
    243                     assmts.mask |= bit
    244                     bit <<= 1
    245 
    246         for block in self.blocks:
    247             for entry, stat in block.gen.items():
    248                 assmts = self.assmts[entry]
    249                 if stat is Uninitialized:
    250                     block.i_gen |= assmts.bit
    251                 else:
    252                     block.i_gen |= stat.bit
    253                 block.i_kill |= assmts.mask
    254             block.i_output = block.i_gen
    255             for entry in block.bounded:
    256                 block.i_kill |= self.assmts[entry].bit
    257 
    258         for assmts in self.assmts.itervalues():
    259             self.entry_point.i_gen |= assmts.bit
    260         self.entry_point.i_output = self.entry_point.i_gen
    261 
    262     def map_one(self, istate, entry):
    263         ret = set()
    264         assmts = self.assmts[entry]
    265         if istate & assmts.bit:
    266             if self.is_statically_assigned(entry):
    267                 ret.add(StaticAssignment(entry))
    268             elif entry.from_closure:
    269                 ret.add(Unknown)
    270             else:
    271                 ret.add(Uninitialized)
    272         for assmt in assmts.stats:
    273             if istate & assmt.bit:
    274                 ret.add(assmt)
    275         return ret
    276 
    277     def reaching_definitions(self):
    278         """Per-block reaching definitions analysis."""
    279         dirty = True
    280         while dirty:
    281             dirty = False
    282             for block in self.blocks:
    283                 i_input = 0
    284                 for parent in block.parents:
    285                     i_input |= parent.i_output
    286                 i_output = (i_input & ~block.i_kill) | block.i_gen
    287                 if i_output != block.i_output:
    288                     dirty = True
    289                 block.i_input = i_input
    290                 block.i_output = i_output
    291 
    292 
    293 class LoopDescr(object):
    294     def __init__(self, next_block, loop_block):
    295         self.next_block = next_block
    296         self.loop_block = loop_block
    297         self.exceptions = []
    298 
    299 
    300 class ExceptionDescr(object):
    301     """Exception handling helper.
    302 
    303     entry_point   ControlBlock Exception handling entry point
    304     finally_enter ControlBlock Normal finally clause entry point
    305     finally_exit  ControlBlock Normal finally clause exit point
    306     """
    307 
    308     def __init__(self, entry_point, finally_enter=None, finally_exit=None):
    309         self.entry_point = entry_point
    310         self.finally_enter = finally_enter
    311         self.finally_exit = finally_exit
    312 
    313 
    314 class NameAssignment(object):
    315     def __init__(self, lhs, rhs, entry):
    316         if lhs.cf_state is None:
    317             lhs.cf_state = set()
    318         self.lhs = lhs
    319         self.rhs = rhs
    320         self.entry = entry
    321         self.pos = lhs.pos
    322         self.refs = set()
    323         self.is_arg = False
    324         self.is_deletion = False
    325         self.inferred_type = None
    326 
    327     def __repr__(self):
    328         return '%s(entry=%r)' % (self.__class__.__name__, self.entry)
    329 
    330     def infer_type(self):
    331         self.inferred_type = self.rhs.infer_type(self.entry.scope)
    332         return self.inferred_type
    333 
    334     def type_dependencies(self):
    335         return self.rhs.type_dependencies(self.entry.scope)
    336 
    337     @property
    338     def type(self):
    339         if not self.entry.type.is_unspecified:
    340             return self.entry.type
    341         return self.inferred_type
    342 
    343 
    344 class StaticAssignment(NameAssignment):
    345     """Initialised at declaration time, e.g. stack allocation."""
    346     def __init__(self, entry):
    347         if not entry.type.is_pyobject:
    348             may_be_none = False
    349         else:
    350             may_be_none = None  # unknown
    351         lhs = TypedExprNode(
    352             entry.type, may_be_none=may_be_none, pos=entry.pos)
    353         super(StaticAssignment, self).__init__(lhs, lhs, entry)
    354 
    355     def infer_type(self):
    356         return self.entry.type
    357 
    358     def type_dependencies(self):
    359         return ()
    360 
    361 
    362 class Argument(NameAssignment):
    363     def __init__(self, lhs, rhs, entry):
    364         NameAssignment.__init__(self, lhs, rhs, entry)
    365         self.is_arg = True
    366 
    367 
    368 class NameDeletion(NameAssignment):
    369     def __init__(self, lhs, entry):
    370         NameAssignment.__init__(self, lhs, lhs, entry)
    371         self.is_deletion = True
    372 
    373     def infer_type(self):
    374         inferred_type = self.rhs.infer_type(self.entry.scope)
    375         if (not inferred_type.is_pyobject and
    376             inferred_type.can_coerce_to_pyobject(self.entry.scope)):
    377             return py_object_type
    378         self.inferred_type = inferred_type
    379         return inferred_type
    380 
    381 
    382 class Uninitialized(object):
    383     """Definitely not initialised yet."""
    384 
    385 
    386 class Unknown(object):
    387     """Coming from outer closure, might be initialised or not."""
    388 
    389 
    390 class NameReference(object):
    391     def __init__(self, node, entry):
    392         if node.cf_state is None:
    393             node.cf_state = set()
    394         self.node = node
    395         self.entry = entry
    396         self.pos = node.pos
    397 
    398     def __repr__(self):
    399         return '%s(entry=%r)' % (self.__class__.__name__, self.entry)
    400 
    401 
    402 class ControlFlowState(list):
    403     # Keeps track of Node's entry assignments
    404     #
    405     # cf_is_null        [boolean] It is uninitialized
    406     # cf_maybe_null     [boolean] May be uninitialized
    407     # is_single         [boolean] Has only one assignment at this point
    408 
    409     cf_maybe_null = False
    410     cf_is_null = False
    411     is_single = False
    412 
    413     def __init__(self, state):
    414         if Uninitialized in state:
    415             state.discard(Uninitialized)
    416             self.cf_maybe_null = True
    417             if not state:
    418                 self.cf_is_null = True
    419         elif Unknown in state:
    420             state.discard(Unknown)
    421             self.cf_maybe_null = True
    422         else:
    423             if len(state) == 1:
    424                 self.is_single = True
    425         # XXX: Remove fake_rhs_expr
    426         super(ControlFlowState, self).__init__(
    427             [i for i in state if i.rhs is not fake_rhs_expr])
    428 
    429     def one(self):
    430         return self[0]
    431 
    432 
    433 class GVContext(object):
    434     """Graphviz subgraph object."""
    435 
    436     def __init__(self):
    437         self.blockids = {}
    438         self.nextid = 0
    439         self.children = []
    440         self.sources = {}
    441 
    442     def add(self, child):
    443         self.children.append(child)
    444 
    445     def nodeid(self, block):
    446         if block not in self.blockids:
    447             self.blockids[block] = 'block%d' % self.nextid
    448             self.nextid += 1
    449         return self.blockids[block]
    450 
    451     def extract_sources(self, block):
    452         if not block.positions:
    453             return ''
    454         start = min(block.positions)
    455         stop = max(block.positions)
    456         srcdescr = start[0]
    457         if not srcdescr in self.sources:
    458             self.sources[srcdescr] = list(srcdescr.get_lines())
    459         lines = self.sources[srcdescr]
    460         return '\\n'.join([l.strip() for l in lines[start[1] - 1:stop[1]]])
    461 
    462     def render(self, fp, name, annotate_defs=False):
    463         """Render graphviz dot graph"""
    464         fp.write('digraph %s {\n' % name)
    465         fp.write(' node [shape=box];\n')
    466         for child in self.children:
    467             child.render(fp, self, annotate_defs)
    468         fp.write('}\n')
    469 
    470     def escape(self, text):
    471         return text.replace('"', '\\"').replace('\n', '\\n')
    472 
    473 
    474 class GV(object):
    475     """Graphviz DOT renderer."""
    476 
    477     def __init__(self, name, flow):
    478         self.name = name
    479         self.flow = flow
    480 
    481     def render(self, fp, ctx, annotate_defs=False):
    482         fp.write(' subgraph %s {\n' % self.name)
    483         for block in self.flow.blocks:
    484             label = ctx.extract_sources(block)
    485             if annotate_defs:
    486                 for stat in block.stats:
    487                     if isinstance(stat, NameAssignment):
    488                         label += '\n %s [definition]' % stat.entry.name
    489                     elif isinstance(stat, NameReference):
    490                         if stat.entry:
    491                             label += '\n %s [reference]' % stat.entry.name
    492             if not label:
    493                 label = 'empty'
    494             pid = ctx.nodeid(block)
    495             fp.write('  %s [label="%s"];\n' % (pid, ctx.escape(label)))
    496         for block in self.flow.blocks:
    497             pid = ctx.nodeid(block)
    498             for child in block.children:
    499                 fp.write('  %s -> %s;\n' % (pid, ctx.nodeid(child)))
    500         fp.write(' }\n')
    501 
    502 
    503 class MessageCollection(object):
    504     """Collect error/warnings messages first then sort"""
    505     def __init__(self):
    506         self.messages = []
    507 
    508     def error(self, pos, message):
    509         self.messages.append((pos, True, message))
    510 
    511     def warning(self, pos, message):
    512         self.messages.append((pos, False, message))
    513 
    514     def report(self):
    515         self.messages.sort()
    516         for pos, is_error, message in self.messages:
    517             if is_error:
    518                 error(pos, message)
    519             else:
    520                 warning(pos, message, 2)
    521 
    522 
    523 def check_definitions(flow, compiler_directives):
    524     flow.initialize()
    525     flow.reaching_definitions()
    526 
    527     # Track down state
    528     assignments = set()
    529     # Node to entry map
    530     references = {}
    531     assmt_nodes = set()
    532 
    533     for block in flow.blocks:
    534         i_state = block.i_input
    535         for stat in block.stats:
    536             i_assmts = flow.assmts[stat.entry]
    537             state = flow.map_one(i_state, stat.entry)
    538             if isinstance(stat, NameAssignment):
    539                 stat.lhs.cf_state.update(state)
    540                 assmt_nodes.add(stat.lhs)
    541                 i_state = i_state & ~i_assmts.mask
    542                 if stat.is_deletion:
    543                     i_state |= i_assmts.bit
    544                 else:
    545                     i_state |= stat.bit
    546                 assignments.add(stat)
    547                 if stat.rhs is not fake_rhs_expr:
    548                     stat.entry.cf_assignments.append(stat)
    549             elif isinstance(stat, NameReference):
    550                 references[stat.node] = stat.entry
    551                 stat.entry.cf_references.append(stat)
    552                 stat.node.cf_state.update(state)
    553                 ## if not stat.node.allow_null:
    554                 ##     i_state &= ~i_assmts.bit
    555                 ## # after successful read, the state is known to be initialised
    556                 state.discard(Uninitialized)
    557                 state.discard(Unknown)
    558                 for assmt in state:
    559                     assmt.refs.add(stat)
    560 
    561     # Check variable usage
    562     warn_maybe_uninitialized = compiler_directives['warn.maybe_uninitialized']
    563     warn_unused_result = compiler_directives['warn.unused_result']
    564     warn_unused = compiler_directives['warn.unused']
    565     warn_unused_arg = compiler_directives['warn.unused_arg']
    566 
    567     messages = MessageCollection()
    568 
    569     # assignment hints
    570     for node in assmt_nodes:
    571         if Uninitialized in node.cf_state:
    572             node.cf_maybe_null = True
    573             if len(node.cf_state) == 1:
    574                 node.cf_is_null = True
    575             else:
    576                 node.cf_is_null = False
    577         elif Unknown in node.cf_state:
    578             node.cf_maybe_null = True
    579         else:
    580             node.cf_is_null = False
    581             node.cf_maybe_null = False
    582 
    583     # Find uninitialized references and cf-hints
    584     for node, entry in references.iteritems():
    585         if Uninitialized in node.cf_state:
    586             node.cf_maybe_null = True
    587             if not entry.from_closure and len(node.cf_state) == 1:
    588                 node.cf_is_null = True
    589             if (node.allow_null or entry.from_closure
    590                 or entry.is_pyclass_attr or entry.type.is_error):
    591                 pass # Can be uninitialized here
    592             elif node.cf_is_null:
    593                 if entry.error_on_uninitialized or (
    594                         Options.error_on_uninitialized and (
    595                         entry.type.is_pyobject or entry.type.is_unspecified)):
    596                     messages.error(
    597                         node.pos,
    598                         "local variable '%s' referenced before assignment"
    599                         % entry.name)
    600                 else:
    601                     messages.warning(
    602                         node.pos,
    603                         "local variable '%s' referenced before assignment"
    604                         % entry.name)
    605             elif warn_maybe_uninitialized:
    606                 messages.warning(
    607                     node.pos,
    608                     "local variable '%s' might be referenced before assignment"
    609                     % entry.name)
    610         elif Unknown in node.cf_state:
    611             # TODO: better cross-closure analysis to know when inner functions
    612             #       are being called before a variable is being set, and when
    613             #       a variable is known to be set before even defining the
    614             #       inner function, etc.
    615             node.cf_maybe_null = True
    616         else:
    617             node.cf_is_null = False
    618             node.cf_maybe_null = False
    619 
    620     # Unused result
    621     for assmt in assignments:
    622         if (not assmt.refs and not assmt.entry.is_pyclass_attr
    623             and not assmt.entry.in_closure):
    624             if assmt.entry.cf_references and warn_unused_result:
    625                 if assmt.is_arg:
    626                     messages.warning(assmt.pos, "Unused argument value '%s'" %
    627                                      assmt.entry.name)
    628                 else:
    629                     messages.warning(assmt.pos, "Unused result in '%s'" %
    630                                      assmt.entry.name)
    631             assmt.lhs.cf_used = False
    632 
    633     # Unused entries
    634     for entry in flow.entries:
    635         if (not entry.cf_references
    636                 and not entry.is_pyclass_attr):
    637             if entry.name != '_':
    638                 # '_' is often used for unused variables, e.g. in loops
    639                 if entry.is_arg:
    640                     if warn_unused_arg:
    641                         messages.warning(entry.pos, "Unused argument '%s'" %
    642                                          entry.name)
    643                 else:
    644                     if warn_unused:
    645                         messages.warning(entry.pos, "Unused entry '%s'" %
    646                                          entry.name)
    647             entry.cf_used = False
    648 
    649     messages.report()
    650 
    651     for node in assmt_nodes:
    652         node.cf_state = ControlFlowState(node.cf_state)
    653     for node in references:
    654         node.cf_state = ControlFlowState(node.cf_state)
    655 
    656 
    657 class AssignmentCollector(TreeVisitor):
    658     def __init__(self):
    659         super(AssignmentCollector, self).__init__()
    660         self.assignments = []
    661 
    662     def visit_Node(self):
    663         self._visitchildren(self, None)
    664 
    665     def visit_SingleAssignmentNode(self, node):
    666         self.assignments.append((node.lhs, node.rhs))
    667 
    668     def visit_CascadedAssignmentNode(self, node):
    669         for lhs in node.lhs_list:
    670             self.assignments.append((lhs, node.rhs))
    671 
    672 
    673 class ControlFlowAnalysis(CythonTransform):
    674 
    675     def visit_ModuleNode(self, node):
    676         self.gv_ctx = GVContext()
    677 
    678         # Set of NameNode reductions
    679         self.reductions = set()
    680 
    681         self.in_inplace_assignment = False
    682         self.env_stack = []
    683         self.env = node.scope
    684         self.stack = []
    685         self.flow = ControlFlow()
    686         self.visitchildren(node)
    687 
    688         check_definitions(self.flow, self.current_directives)
    689 
    690         dot_output = self.current_directives['control_flow.dot_output']
    691         if dot_output:
    692             annotate_defs = self.current_directives['control_flow.dot_annotate_defs']
    693             fp = open(dot_output, 'wt')
    694             try:
    695                 self.gv_ctx.render(fp, 'module', annotate_defs=annotate_defs)
    696             finally:
    697                 fp.close()
    698         return node
    699 
    700     def visit_FuncDefNode(self, node):
    701         for arg in node.args:
    702             if arg.default:
    703                 self.visitchildren(arg)
    704         self.visitchildren(node, ('decorators',))
    705         self.env_stack.append(self.env)
    706         self.env = node.local_scope
    707         self.stack.append(self.flow)
    708         self.flow = ControlFlow()
    709 
    710         # Collect all entries
    711         for entry in node.local_scope.entries.values():
    712             if self.flow.is_tracked(entry):
    713                 self.flow.entries.add(entry)
    714 
    715         self.mark_position(node)
    716         # Function body block
    717         self.flow.nextblock()
    718 
    719         for arg in node.args:
    720             self._visit(arg)
    721         if node.star_arg:
    722             self.flow.mark_argument(node.star_arg,
    723                                     TypedExprNode(Builtin.tuple_type,
    724                                                   may_be_none=False),
    725                                     node.star_arg.entry)
    726         if node.starstar_arg:
    727             self.flow.mark_argument(node.starstar_arg,
    728                                     TypedExprNode(Builtin.dict_type,
    729                                                   may_be_none=False),
    730                                     node.starstar_arg.entry)
    731         self._visit(node.body)
    732         # Workaround for generators
    733         if node.is_generator:
    734             self._visit(node.gbody.body)
    735 
    736         # Exit point
    737         if self.flow.block:
    738             self.flow.block.add_child(self.flow.exit_point)
    739 
    740         # Cleanup graph
    741         self.flow.normalize()
    742         check_definitions(self.flow, self.current_directives)
    743         self.flow.blocks.add(self.flow.entry_point)
    744 
    745         self.gv_ctx.add(GV(node.local_scope.name, self.flow))
    746 
    747         self.flow = self.stack.pop()
    748         self.env = self.env_stack.pop()
    749         return node
    750 
    751     def visit_DefNode(self, node):
    752         node.used = True
    753         return self.visit_FuncDefNode(node)
    754 
    755     def visit_GeneratorBodyDefNode(self, node):
    756         return node
    757 
    758     def visit_CTypeDefNode(self, node):
    759         return node
    760 
    761     def mark_assignment(self, lhs, rhs=None):
    762         if not self.flow.block:
    763             return
    764         if self.flow.exceptions:
    765             exc_descr = self.flow.exceptions[-1]
    766             self.flow.block.add_child(exc_descr.entry_point)
    767             self.flow.nextblock()
    768 
    769         if not rhs:
    770             rhs = object_expr
    771         if lhs.is_name:
    772             if lhs.entry is not None:
    773                 entry = lhs.entry
    774             else:
    775                 entry = self.env.lookup(lhs.name)
    776             if entry is None: # TODO: This shouldn't happen...
    777                 return
    778             self.flow.mark_assignment(lhs, rhs, entry)
    779         elif isinstance(lhs, ExprNodes.SequenceNode):
    780             for arg in lhs.args:
    781                 self.mark_assignment(arg)
    782         else:
    783             self._visit(lhs)
    784 
    785         if self.flow.exceptions:
    786             exc_descr = self.flow.exceptions[-1]
    787             self.flow.block.add_child(exc_descr.entry_point)
    788             self.flow.nextblock()
    789 
    790     def mark_position(self, node):
    791         """Mark position if DOT output is enabled."""
    792         if self.current_directives['control_flow.dot_output']:
    793             self.flow.mark_position(node)
    794 
    795     def visit_FromImportStatNode(self, node):
    796         for name, target in node.items:
    797             if name != "*":
    798                 self.mark_assignment(target)
    799         self.visitchildren(node)
    800         return node
    801 
    802     def visit_AssignmentNode(self, node):
    803         raise InternalError("Unhandled assignment node")
    804 
    805     def visit_SingleAssignmentNode(self, node):
    806         self._visit(node.rhs)
    807         self.mark_assignment(node.lhs, node.rhs)
    808         return node
    809 
    810     def visit_CascadedAssignmentNode(self, node):
    811         self._visit(node.rhs)
    812         for lhs in node.lhs_list:
    813             self.mark_assignment(lhs, node.rhs)
    814         return node
    815 
    816     def visit_ParallelAssignmentNode(self, node):
    817         collector = AssignmentCollector()
    818         collector.visitchildren(node)
    819         for lhs, rhs in collector.assignments:
    820             self._visit(rhs)
    821         for lhs, rhs in collector.assignments:
    822             self.mark_assignment(lhs, rhs)
    823         return node
    824 
    825     def visit_InPlaceAssignmentNode(self, node):
    826         self.in_inplace_assignment = True
    827         self.visitchildren(node)
    828         self.in_inplace_assignment = False
    829         self.mark_assignment(node.lhs, node.create_binop_node())
    830         return node
    831 
    832     def visit_DelStatNode(self, node):
    833         for arg in node.args:
    834             if arg.is_name:
    835                 entry = arg.entry or self.env.lookup(arg.name)
    836                 if entry.in_closure or entry.from_closure:
    837                     error(arg.pos,
    838                           "can not delete variable '%s' "
    839                           "referenced in nested scope" % entry.name)
    840                 # Mark reference
    841                 self._visit(arg)
    842                 self.flow.mark_deletion(arg, entry)
    843             else:
    844                 self._visit(arg)
    845         return node
    846 
    847     def visit_CArgDeclNode(self, node):
    848         entry = self.env.lookup(node.name)
    849         if entry:
    850             may_be_none = not node.not_none
    851             self.flow.mark_argument(
    852                 node, TypedExprNode(entry.type, may_be_none), entry)
    853         return node
    854 
    855     def visit_NameNode(self, node):
    856         if self.flow.block:
    857             entry = node.entry or self.env.lookup(node.name)
    858             if entry:
    859                 self.flow.mark_reference(node, entry)
    860 
    861                 if entry in self.reductions and not self.in_inplace_assignment:
    862                     error(node.pos,
    863                           "Cannot read reduction variable in loop body")
    864 
    865         return node
    866 
    867     def visit_StatListNode(self, node):
    868         if self.flow.block:
    869             for stat in node.stats:
    870                 self._visit(stat)
    871                 if not self.flow.block:
    872                     stat.is_terminator = True
    873                     break
    874         return node
    875 
    876     def visit_Node(self, node):
    877         self.visitchildren(node)
    878         self.mark_position(node)
    879         return node
    880 
    881     def visit_IfStatNode(self, node):
    882         next_block = self.flow.newblock()
    883         parent = self.flow.block
    884         # If clauses
    885         for clause in node.if_clauses:
    886             parent = self.flow.nextblock(parent)
    887             self._visit(clause.condition)
    888             self.flow.nextblock()
    889             self._visit(clause.body)
    890             if self.flow.block:
    891                 self.flow.block.add_child(next_block)
    892         # Else clause
    893         if node.else_clause:
    894             self.flow.nextblock(parent=parent)
    895             self._visit(node.else_clause)
    896             if self.flow.block:
    897                 self.flow.block.add_child(next_block)
    898         else:
    899             parent.add_child(next_block)
    900 
    901         if next_block.parents:
    902             self.flow.block = next_block
    903         else:
    904             self.flow.block = None
    905         return node
    906 
    907     def visit_WhileStatNode(self, node):
    908         condition_block = self.flow.nextblock()
    909         next_block = self.flow.newblock()
    910         # Condition block
    911         self.flow.loops.append(LoopDescr(next_block, condition_block))
    912         if node.condition:
    913             self._visit(node.condition)
    914         # Body block
    915         self.flow.nextblock()
    916         self._visit(node.body)
    917         self.flow.loops.pop()
    918         # Loop it
    919         if self.flow.block:
    920             self.flow.block.add_child(condition_block)
    921             self.flow.block.add_child(next_block)
    922         # Else clause
    923         if node.else_clause:
    924             self.flow.nextblock(parent=condition_block)
    925             self._visit(node.else_clause)
    926             if self.flow.block:
    927                 self.flow.block.add_child(next_block)
    928         else:
    929             condition_block.add_child(next_block)
    930 
    931         if next_block.parents:
    932             self.flow.block = next_block
    933         else:
    934             self.flow.block = None
    935         return node
    936 
    937     def mark_forloop_target(self, node):
    938         # TODO: Remove redundancy with range optimization...
    939         is_special = False
    940         sequence = node.iterator.sequence
    941         target = node.target
    942         if isinstance(sequence, ExprNodes.SimpleCallNode):
    943             function = sequence.function
    944             if sequence.self is None and function.is_name:
    945                 entry = self.env.lookup(function.name)
    946                 if not entry or entry.is_builtin:
    947                     if function.name == 'reversed' and len(sequence.args) == 1:
    948                         sequence = sequence.args[0]
    949                     elif function.name == 'enumerate' and len(sequence.args) == 1:
    950                         if target.is_sequence_constructor and len(target.args) == 2:
    951                             iterator = sequence.args[0]
    952                             if iterator.is_name:
    953                                 iterator_type = iterator.infer_type(self.env)
    954                                 if iterator_type.is_builtin_type:
    955                                     # assume that builtin types have a length within Py_ssize_t
    956                                     self.mark_assignment(
    957                                         target.args[0],
    958                                         ExprNodes.IntNode(target.pos, value='PY_SSIZE_T_MAX',
    959                                                           type=PyrexTypes.c_py_ssize_t_type))
    960                                     target = target.args[1]
    961                                     sequence = sequence.args[0]
    962         if isinstance(sequence, ExprNodes.SimpleCallNode):
    963             function = sequence.function
    964             if sequence.self is None and function.is_name:
    965                 entry = self.env.lookup(function.name)
    966                 if not entry or entry.is_builtin:
    967                     if function.name in ('range', 'xrange'):
    968                         is_special = True
    969                         for arg in sequence.args[:2]:
    970                             self.mark_assignment(target, arg)
    971                         if len(sequence.args) > 2:
    972                             self.mark_assignment(
    973                                 target,
    974                                 ExprNodes.binop_node(node.pos,
    975                                                      '+',
    976                                                      sequence.args[0],
    977                                                      sequence.args[2]))
    978 
    979         if not is_special:
    980             # A for-loop basically translates to subsequent calls to
    981             # __getitem__(), so using an IndexNode here allows us to
    982             # naturally infer the base type of pointers, C arrays,
    983             # Python strings, etc., while correctly falling back to an
    984             # object type when the base type cannot be handled.
    985 
    986             self.mark_assignment(target, node.item)
    987 
    988     def visit_ForInStatNode(self, node):
    989         condition_block = self.flow.nextblock()
    990         next_block = self.flow.newblock()
    991         # Condition with iterator
    992         self.flow.loops.append(LoopDescr(next_block, condition_block))
    993         self._visit(node.iterator)
    994         # Target assignment
    995         self.flow.nextblock()
    996 
    997         if isinstance(node, Nodes.ForInStatNode):
    998             self.mark_forloop_target(node)
    999         else: # Parallel
   1000             self.mark_assignment(node.target)
   1001 
   1002         # Body block
   1003         if isinstance(node, Nodes.ParallelRangeNode):
   1004             # In case of an invalid
   1005             self._delete_privates(node, exclude=node.target.entry)
   1006 
   1007         self.flow.nextblock()
   1008         self._visit(node.body)
   1009         self.flow.loops.pop()
   1010 
   1011         # Loop it
   1012         if self.flow.block:
   1013             self.flow.block.add_child(condition_block)
   1014         # Else clause
   1015         if node.else_clause:
   1016             self.flow.nextblock(parent=condition_block)
   1017             self._visit(node.else_clause)
   1018             if self.flow.block:
   1019                 self.flow.block.add_child(next_block)
   1020         else:
   1021             condition_block.add_child(next_block)
   1022 
   1023         if next_block.parents:
   1024             self.flow.block = next_block
   1025         else:
   1026             self.flow.block = None
   1027         return node
   1028 
   1029     def _delete_privates(self, node, exclude=None):
   1030         for private_node in node.assigned_nodes:
   1031             if not exclude or private_node.entry is not exclude:
   1032                 self.flow.mark_deletion(private_node, private_node.entry)
   1033 
   1034     def visit_ParallelRangeNode(self, node):
   1035         reductions = self.reductions
   1036 
   1037         # if node.target is None or not a NameNode, an error will have
   1038         # been previously issued
   1039         if hasattr(node.target, 'entry'):
   1040             self.reductions = set(reductions)
   1041 
   1042             for private_node in node.assigned_nodes:
   1043                 private_node.entry.error_on_uninitialized = True
   1044                 pos, reduction = node.assignments[private_node.entry]
   1045                 if reduction:
   1046                     self.reductions.add(private_node.entry)
   1047 
   1048             node = self.visit_ForInStatNode(node)
   1049 
   1050         self.reductions = reductions
   1051         return node
   1052 
   1053     def visit_ParallelWithBlockNode(self, node):
   1054         for private_node in node.assigned_nodes:
   1055             private_node.entry.error_on_uninitialized = True
   1056 
   1057         self._delete_privates(node)
   1058         self.visitchildren(node)
   1059         self._delete_privates(node)
   1060 
   1061         return node
   1062 
   1063     def visit_ForFromStatNode(self, node):
   1064         condition_block = self.flow.nextblock()
   1065         next_block = self.flow.newblock()
   1066         # Condition with iterator
   1067         self.flow.loops.append(LoopDescr(next_block, condition_block))
   1068         self._visit(node.bound1)
   1069         self._visit(node.bound2)
   1070         if node.step is not None:
   1071             self._visit(node.step)
   1072         # Target assignment
   1073         self.flow.nextblock()
   1074         self.mark_assignment(node.target, node.bound1)
   1075         if node.step is not None:
   1076             self.mark_assignment(node.target,
   1077                                  ExprNodes.binop_node(node.pos, '+',
   1078                                                       node.bound1, node.step))
   1079         # Body block
   1080         self.flow.nextblock()
   1081         self._visit(node.body)
   1082         self.flow.loops.pop()
   1083         # Loop it
   1084         if self.flow.block:
   1085             self.flow.block.add_child(condition_block)
   1086         # Else clause
   1087         if node.else_clause:
   1088             self.flow.nextblock(parent=condition_block)
   1089             self._visit(node.else_clause)
   1090             if self.flow.block:
   1091                 self.flow.block.add_child(next_block)
   1092         else:
   1093             condition_block.add_child(next_block)
   1094 
   1095         if next_block.parents:
   1096             self.flow.block = next_block
   1097         else:
   1098             self.flow.block = None
   1099         return node
   1100 
   1101     def visit_LoopNode(self, node):
   1102         raise InternalError("Generic loops are not supported")
   1103 
   1104     def visit_WithTargetAssignmentStatNode(self, node):
   1105         self.mark_assignment(node.lhs, node.rhs)
   1106         return node
   1107 
   1108     def visit_WithStatNode(self, node):
   1109         self._visit(node.manager)
   1110         self._visit(node.enter_call)
   1111         self._visit(node.body)
   1112         return node
   1113 
   1114     def visit_TryExceptStatNode(self, node):
   1115         # After exception handling
   1116         next_block = self.flow.newblock()
   1117         # Body block
   1118         self.flow.newblock()
   1119         # Exception entry point
   1120         entry_point = self.flow.newblock()
   1121         self.flow.exceptions.append(ExceptionDescr(entry_point))
   1122         self.flow.nextblock()
   1123         ## XXX: links to exception handling point should be added by
   1124         ## XXX: children nodes
   1125         self.flow.block.add_child(entry_point)
   1126         self.flow.nextblock()
   1127         self._visit(node.body)
   1128         self.flow.exceptions.pop()
   1129 
   1130         # After exception
   1131         if self.flow.block:
   1132             if node.else_clause:
   1133                 self.flow.nextblock()
   1134                 self._visit(node.else_clause)
   1135             if self.flow.block:
   1136                 self.flow.block.add_child(next_block)
   1137 
   1138         for clause in node.except_clauses:
   1139             self.flow.block = entry_point
   1140             if clause.pattern:
   1141                 for pattern in clause.pattern:
   1142                     self._visit(pattern)
   1143             else:
   1144                 # TODO: handle * pattern
   1145                 pass
   1146             entry_point = self.flow.newblock(parent=self.flow.block)
   1147             self.flow.nextblock()
   1148             if clause.target:
   1149                 self.mark_assignment(clause.target)
   1150             self._visit(clause.body)
   1151             if self.flow.block:
   1152                 self.flow.block.add_child(next_block)
   1153 
   1154         if self.flow.exceptions:
   1155             entry_point.add_child(self.flow.exceptions[-1].entry_point)
   1156 
   1157         if next_block.parents:
   1158             self.flow.block = next_block
   1159         else:
   1160             self.flow.block = None
   1161         return node
   1162 
   1163     def visit_TryFinallyStatNode(self, node):
   1164         body_block = self.flow.nextblock()
   1165 
   1166         # Exception entry point
   1167         entry_point = self.flow.newblock()
   1168         self.flow.block = entry_point
   1169         self._visit(node.finally_clause)
   1170 
   1171         if self.flow.block and self.flow.exceptions:
   1172             self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
   1173 
   1174         # Normal execution
   1175         finally_enter = self.flow.newblock()
   1176         self.flow.block = finally_enter
   1177         self._visit(node.finally_clause)
   1178         finally_exit = self.flow.block
   1179 
   1180         descr = ExceptionDescr(entry_point, finally_enter, finally_exit)
   1181         self.flow.exceptions.append(descr)
   1182         if self.flow.loops:
   1183             self.flow.loops[-1].exceptions.append(descr)
   1184         self.flow.block = body_block
   1185         ## XXX: Is it still required
   1186         body_block.add_child(entry_point)
   1187         self.flow.nextblock()
   1188         self._visit(node.body)
   1189         self.flow.exceptions.pop()
   1190         if self.flow.loops:
   1191             self.flow.loops[-1].exceptions.pop()
   1192 
   1193         if self.flow.block:
   1194             self.flow.block.add_child(finally_enter)
   1195             if finally_exit:
   1196                 self.flow.block = self.flow.nextblock(parent=finally_exit)
   1197             else:
   1198                 self.flow.block = None
   1199         return node
   1200 
   1201     def visit_RaiseStatNode(self, node):
   1202         self.mark_position(node)
   1203         self.visitchildren(node)
   1204         if self.flow.exceptions:
   1205             self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
   1206         self.flow.block = None
   1207         return node
   1208 
   1209     def visit_ReraiseStatNode(self, node):
   1210         self.mark_position(node)
   1211         if self.flow.exceptions:
   1212             self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
   1213         self.flow.block = None
   1214         return node
   1215 
   1216     def visit_ReturnStatNode(self, node):
   1217         self.mark_position(node)
   1218         self.visitchildren(node)
   1219 
   1220         for exception in self.flow.exceptions[::-1]:
   1221             if exception.finally_enter:
   1222                 self.flow.block.add_child(exception.finally_enter)
   1223                 if exception.finally_exit:
   1224                     exception.finally_exit.add_child(self.flow.exit_point)
   1225                 break
   1226         else:
   1227             if self.flow.block:
   1228                 self.flow.block.add_child(self.flow.exit_point)
   1229         self.flow.block = None
   1230         return node
   1231 
   1232     def visit_BreakStatNode(self, node):
   1233         if not self.flow.loops:
   1234             #error(node.pos, "break statement not inside loop")
   1235             return node
   1236         loop = self.flow.loops[-1]
   1237         self.mark_position(node)
   1238         for exception in loop.exceptions[::-1]:
   1239             if exception.finally_enter:
   1240                 self.flow.block.add_child(exception.finally_enter)
   1241                 if exception.finally_exit:
   1242                     exception.finally_exit.add_child(loop.next_block)
   1243                 break
   1244         else:
   1245             self.flow.block.add_child(loop.next_block)
   1246         self.flow.block = None
   1247         return node
   1248 
   1249     def visit_ContinueStatNode(self, node):
   1250         if not self.flow.loops:
   1251             #error(node.pos, "continue statement not inside loop")
   1252             return node
   1253         loop = self.flow.loops[-1]
   1254         self.mark_position(node)
   1255         for exception in loop.exceptions[::-1]:
   1256             if exception.finally_enter:
   1257                 self.flow.block.add_child(exception.finally_enter)
   1258                 if exception.finally_exit:
   1259                     exception.finally_exit.add_child(loop.loop_block)
   1260                 break
   1261         else:
   1262             self.flow.block.add_child(loop.loop_block)
   1263         self.flow.block = None
   1264         return node
   1265 
   1266     def visit_ComprehensionNode(self, node):
   1267         if node.expr_scope:
   1268             self.env_stack.append(self.env)
   1269             self.env = node.expr_scope
   1270         # Skip append node here
   1271         self._visit(node.loop)
   1272         if node.expr_scope:
   1273             self.env = self.env_stack.pop()
   1274         return node
   1275 
   1276     def visit_ScopedExprNode(self, node):
   1277         if node.expr_scope:
   1278             self.env_stack.append(self.env)
   1279             self.env = node.expr_scope
   1280         self.visitchildren(node)
   1281         if node.expr_scope:
   1282             self.env = self.env_stack.pop()
   1283         return node
   1284 
   1285     def visit_PyClassDefNode(self, node):
   1286         self.visitchildren(node, ('dict', 'metaclass',
   1287                                   'mkw', 'bases', 'class_result'))
   1288         self.flow.mark_assignment(node.target, object_expr_not_none,
   1289                                   self.env.lookup(node.name))
   1290         self.env_stack.append(self.env)
   1291         self.env = node.scope
   1292         self.flow.nextblock()
   1293         self.visitchildren(node, ('body',))
   1294         self.flow.nextblock()
   1295         self.env = self.env_stack.pop()
   1296         return node
   1297 
   1298     def visit_AmpersandNode(self, node):
   1299         if node.operand.is_name:
   1300             # Fake assignment to silence warning
   1301             self.mark_assignment(node.operand, fake_rhs_expr)
   1302         self.visitchildren(node)
   1303         return node
   1304