FlowControl.py 36.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import cython
cython.declare(PyrexTypes=object, Naming=object, ExprNodes=object, Nodes=object,
               Options=object, UtilNodes=object, ModuleNode=object,
               LetNode=object, LetRefNode=object, TreeFragment=object,
               TemplateTransform=object, EncodedString=object,
               error=object, warning=object, copy=object)

import Builtin
import ExprNodes
import Nodes
from PyrexTypes import py_object_type, unspecified_type

from Visitor import TreeVisitor, CythonTransform
from Errors import error, warning, CompileError, InternalError

class TypedExprNode(ExprNodes.ExprNode):
17 18
    # Used for declaring assignments of a specified type without a known entry.
    def __init__(self, type, may_be_none=None):
19
        self.type = type
20
        self._may_be_none = may_be_none
21

22 23 24 25 26
    def may_be_none(self):
        return self._may_be_none != False

object_expr = TypedExprNode(py_object_type, may_be_none=True)
object_expr_not_none = TypedExprNode(py_object_type, may_be_none=False)
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

class ControlBlock(object):
    """Control flow graph node. Sequence of assignments and name references.

       children  set of children nodes
       parents   set of parent nodes
       positions set of position markers

       stats     list of block statements
       gen       dict of assignments generated by this block
       bounded   set  of entries that are definitely bounded in this block

       Example:

        a = 1
        b = a + c # 'c' is already bounded or exception here

        stats = [Assignment(a), NameReference(a), NameReference(c),
                     Assignment(b)]
        gen = {Entry(a): Assignment(a), Entry(b): Assignment(b)}
        bounded = set([Entry(a), Entry(c)])

    """

    def __init__(self):
        self.children = set()
        self.parents = set()
        self.positions = set()

        self.stats = []
        self.gen = {}
        self.bounded = set()

60 61 62 63 64 65
        self.i_input = 0
        self.i_output = 0
        self.i_gen = 0
        self.i_kill = 0
        self.i_state = 0

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
    def empty(self):
        return (not self.stats and not self.positions)

    def detach(self):
        """Detach block from parents and children."""
        for child in self.children:
            child.parents.remove(self)
        for parent in self.parents:
            parent.children.remove(self)
        self.parents.clear()
        self.children.clear()

    def add_child(self, block):
        self.children.add(block)
        block.parents.add(self)


class ExitBlock(ControlBlock):
    """Non-empty exit point block."""

    def empty(self):
        return False


90 91 92 93 94
class AssignmentList:
    def __init__(self):
        self.stats = []


95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
class ControlFlow(object):
    """Control-flow graph.

       entry_point ControlBlock entry point for this graph
       exit_point  ControlBlock normal exit point
       block       ControlBlock current block
       blocks      set    children nodes
       entries     set    tracked entries
       loops       list   stack for loop descriptors
       exceptions  list   stack for exception descriptors

    """

    def __init__(self):
        self.blocks = set()
        self.entries = set()
        self.loops = []
        self.exceptions = []

        self.entry_point = ControlBlock()
        self.exit_point = ExitBlock()
        self.blocks.add(self.exit_point)
        self.block = self.entry_point

    def newblock(self, parent=None):
        """Create floating block linked to `parent` if given.

           NOTE: Block is NOT added to self.blocks
        """
        block = ControlBlock()
        self.blocks.add(block)
        if parent:
            parent.add_child(block)
        return block

    def nextblock(self, parent=None):
        """Create block children block linked to current or `parent` if given.

           NOTE: Block is added to self.blocks
        """
        block = ControlBlock()
        self.blocks.add(block)
        if parent:
            parent.add_child(block)
        elif self.block:
            self.block.add_child(block)
        self.block = block
        return self.block

    def is_tracked(self, entry):
        if entry.is_anonymous:
            return False
147 148
        if (entry.type.is_array or entry.type.is_struct_or_union or
                entry.type.is_cpp_class):
149
            return False
Vitja Makarov's avatar
Vitja Makarov committed
150
        return (entry.is_local or entry.is_pyclass_attr or entry.is_arg or
Vitja Makarov's avatar
Vitja Makarov committed
151 152
                entry.from_closure or entry.in_closure or
                entry.error_on_uninitialized)
153 154 155 156 157 158

    def mark_position(self, node):
        """Mark position, will be used to draw graph nodes."""
        if self.block:
            self.block.positions.add(node.pos[:2])

159
    def mark_assignment(self, lhs, rhs, entry):
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
        if self.block:
            if not self.is_tracked(entry):
                return
            assignment = NameAssignment(lhs, rhs, entry)
            self.block.stats.append(assignment)
            self.block.gen[entry] = assignment
            self.entries.add(entry)

    def mark_argument(self, lhs, rhs, entry):
        if self.block and self.is_tracked(entry):
            assignment = Argument(lhs, rhs, entry)
            self.block.stats.append(assignment)
            self.block.gen[entry] = assignment
            self.entries.add(entry)

    def mark_deletion(self, node, entry):
        if self.block and self.is_tracked(entry):
            assignment = NameAssignment(node, None, entry)
            self.block.stats.append(assignment)
            self.block.gen[entry] = Uninitialized
            self.entries.add(entry)

    def mark_reference(self, node, entry):
        if self.block and self.is_tracked(entry):
            self.block.stats.append(NameReference(node, entry))
            # Local variable is definitely bound after this reference
186 187
            if not node.allow_null:
                self.block.bounded.add(entry)
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
            self.entries.add(entry)

    def normalize(self):
        """Delete unreachable and orphan blocks."""
        queue = set([self.entry_point])
        visited = set()
        while queue:
            root = queue.pop()
            visited.add(root)
            for child in root.children:
                if child not in visited:
                    queue.add(child)
        unreachable = self.blocks - visited
        for block in unreachable:
            block.detach()
        visited.remove(self.entry_point)
        for block in visited:
            if block.empty():
                for parent in block.parents: # Re-parent
                    for child in block.children:
                        parent.add_child(child)
                block.detach()
                unreachable.add(block)
        self.blocks -= unreachable

213 214 215 216 217 218 219
    def initialize(self):
        """Set initial state, map assignments to bits."""
        self.assmts = {}

        offset = 0
        for entry in self.entries:
            assmts = AssignmentList()
Robert Bradshaw's avatar
Robert Bradshaw committed
220
            assmts.bit = 1 << offset
221 222 223 224 225 226 227
            assmts.mask = assmts.bit
            self.assmts[entry] = assmts
            offset += 1

        for block in self.blocks:
            for stat in block.stats:
                if isinstance(stat, NameAssignment):
Robert Bradshaw's avatar
Robert Bradshaw committed
228
                    stat.bit = 1 << offset
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
                    assmts = self.assmts[stat.entry]
                    assmts.stats.append(stat)
                    assmts.mask |= stat.bit
                    offset += 1

        for block in self.blocks:
            for entry, stat in block.gen.items():
                assmts = self.assmts[entry]
                if stat is Uninitialized:
                    block.i_gen |= assmts.bit
                else:
                    block.i_gen |= stat.bit
                block.i_kill |= assmts.mask
            block.i_output = block.i_gen
            for entry in block.bounded:
                block.i_kill |= self.assmts[entry].bit

        for assmts in self.assmts.itervalues():
            self.entry_point.i_gen |= assmts.bit
        self.entry_point.i_output = self.entry_point.i_gen

    def map_one(self, istate, entry):
        ret = set()
        assmts = self.assmts[entry]
        if istate & assmts.bit:
            ret.add(Uninitialized)
        for assmt in assmts.stats:
            if istate & assmt.bit:
                ret.add(assmt)
        return ret

    def reaching_definitions(self):
        """Per-block reaching definitions analysis."""
        dirty = True
        while dirty:
            dirty = False
            for block in self.blocks:
                i_input = 0
                for parent in block.parents:
                    i_input |= parent.i_output
                i_output = (i_input & ~block.i_kill) | block.i_gen
                if i_output != block.i_output:
                    dirty = True
                block.i_input = i_input
                block.i_output = i_output

275 276 277 278 279

class LoopDescr(object):
    def __init__(self, next_block, loop_block):
        self.next_block = next_block
        self.loop_block = loop_block
280
        self.exceptions = []
281

282

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
class ExceptionDescr(object):
    """Exception handling helper.

    entry_point   ControlBlock Exception handling entry point
    finally_enter ControlBlock Normal finally clause entry point
    finally_exit  ControlBlock Normal finally clause exit point
    """

    def __init__(self, entry_point, finally_enter=None, finally_exit=None):
        self.entry_point = entry_point
        self.finally_enter = finally_enter
        self.finally_exit = finally_exit

class NameAssignment(object):
    def __init__(self, lhs, rhs, entry):
298 299
        if lhs.cf_state is None:
            lhs.cf_state = set()
300 301 302 303 304
        self.lhs = lhs
        self.rhs = rhs
        self.entry = entry
        self.pos = lhs.pos
        self.refs = set()
305
        self.is_arg = False
306 307 308 309 310

    def __repr__(self):
        return '%s(entry=%r)' % (self.__class__.__name__, self.entry)

class Argument(NameAssignment):
Vitja Makarov's avatar
Vitja Makarov committed
311 312 313
    def __init__(self, lhs, rhs, entry):
        NameAssignment.__init__(self, lhs, rhs, entry)
        self.is_arg = True
314 315 316 317 318 319

class Uninitialized(object):
    pass

class NameReference(object):
    def __init__(self, node, entry):
320 321
        if node.cf_state is None:
            node.cf_state = set()
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
        self.node = node
        self.entry = entry
        self.pos = node.pos

    def __repr__(self):
        return '%s(entry=%r)' % (self.__class__.__name__, self.entry)


class GVContext(object):
    """Graphviz subgraph object."""

    def __init__(self):
        self.blockids = {}
        self.nextid = 0
        self.children = []
        self.sources = {}

    def add(self, child):
        self.children.append(child)

    def nodeid(self, block):
        if block not in self.blockids:
            self.blockids[block] = 'block%d' % self.nextid
            self.nextid += 1
        return self.blockids[block]

    def extract_sources(self, block):
        if not block.positions:
            return ''
        start = min(block.positions)
        stop = max(block.positions)
        srcdescr = start[0]
        if not srcdescr in self.sources:
            self.sources[srcdescr] = list(srcdescr.get_lines())
        lines = self.sources[srcdescr]
        return '\\n'.join([l.strip() for l in lines[start[1] - 1:stop[1]]])

    def render(self, fp, name, annotate_defs=False):
        """Render graphviz dot graph"""
        fp.write('digraph %s {\n' % name)
        fp.write(' node [shape=box];\n')
        for child in self.children:
            child.render(fp, self, annotate_defs)
        fp.write('}\n')

    def escape(self, text):
        return text.replace('"', '\\"').replace('\n', '\\n')

370

371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
class GV(object):
    """Graphviz DOT renderer."""

    def __init__(self, name, flow):
        self.name = name
        self.flow = flow

    def render(self, fp, ctx, annotate_defs=False):
        fp.write(' subgraph %s {\n' % self.name)
        for block in self.flow.blocks:
            label = ctx.extract_sources(block)
            if annotate_defs:
                for stat in block.stats:
                    if isinstance(stat, NameAssignment):
                        label += '\n %s [definition]' % stat.entry.name
                    elif isinstance(stat, NameReference):
                        if stat.entry:
                            label += '\n %s [reference]' % stat.entry.name
            if not label:
                label = 'empty'
            pid = ctx.nodeid(block)
            fp.write('  %s [label="%s"];\n' % (pid, ctx.escape(label)))
        for block in self.flow.blocks:
            pid = ctx.nodeid(block)
            for child in block.children:
                fp.write('  %s -> %s;\n' % (pid, ctx.nodeid(child)))
        fp.write(' }\n')

399

400
class MessageCollection:
401
    """Collect error/warnings messages first then sort"""
402 403
    def __init__(self):
        self.messages = []
404 405

    def error(self, pos, message):
406
        self.messages.append((pos, True, message))
407 408

    def warning(self, pos, message):
409
        self.messages.append((pos, False, message))
410

411 412 413 414 415 416 417
    def report(self):
        self.messages.sort()
        for pos, is_error, message in self.messages:
            if is_error:
                error(pos, message)
            else:
                warning(pos, message, 2)
418 419 420


def check_definitions(flow, compiler_directives):
421 422
    flow.initialize()
    flow.reaching_definitions()
423 424 425

    # Track down state
    assignments = set()
426 427 428 429
    # Node to entry map
    references = {}
    assmt_nodes = set()

430
    for block in flow.blocks:
431
        i_state = block.i_input
432
        for stat in block.stats:
433 434
            i_assmts = flow.assmts[stat.entry]
            state = flow.map_one(i_state, stat.entry)
435
            if isinstance(stat, NameAssignment):
436
                stat.lhs.cf_state.update(state)
437
                assmt_nodes.add(stat.lhs)
438
                i_state = i_state & ~i_assmts.mask
439
                if stat.rhs:
440
                    i_state |= stat.bit
441
                else:
442
                    i_state |= i_assmts.bit
443
                assignments.add(stat)
444
                stat.entry.cf_assignments.append(stat)
445
            elif isinstance(stat, NameReference):
446
                references[stat.node] = stat.entry
447
                stat.entry.cf_references.append(stat)
448
                stat.node.cf_state.update(state)
449
                if not stat.node.allow_null:
450 451 452 453
                    i_state &= ~i_assmts.bit
                state.discard(Uninitialized)
                for assmt in state:
                    assmt.refs.add(stat)
454 455

    # Check variable usage
456
    warn_maybe_uninitialized = compiler_directives['warn.maybe_uninitialized']
457 458 459 460
    warn_unused_result = compiler_directives['warn.unused_result']
    warn_unused = compiler_directives['warn.unused']
    warn_unused_arg = compiler_directives['warn.unused_arg']

461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
    messages = MessageCollection()

    # assignment hints
    for node in assmt_nodes:
        if Uninitialized in node.cf_state:
            node.cf_maybe_null = True
            if len(node.cf_state) == 1:
                node.cf_is_null = True
            else:
                node.cf_is_null = False
        else:
            node.cf_is_null = False
            node.cf_maybe_null = False

    # Find uninitialized references and cf-hints
    for node, entry in references.iteritems():
        if Uninitialized in node.cf_state:
            node.cf_maybe_null = True
479 480
            if not entry.from_closure and len(node.cf_state) == 1:
                node.cf_is_null = True
481
            if node.allow_null or entry.from_closure or entry.is_pyclass_attr:
482
                pass # Can be uninitialized here
483
            elif node.cf_is_null:
484 485
                if (entry.type.is_pyobject or entry.type.is_unspecified or
                        entry.error_on_uninitialized):
486 487 488 489
                    messages.error(
                        node.pos,
                        "local variable '%s' referenced before assignment"
                        % entry.name)
490 491 492 493 494
                else:
                    messages.warning(
                        node.pos,
                        "local variable '%s' referenced before assignment"
                        % entry.name)
495 496 497 498 499
            elif warn_maybe_uninitialized:
                messages.warning(
                    node.pos,
                    "local variable '%s' might be referenced before assignment"
                    % entry.name)
500 501 502 503 504
        else:
            node.cf_is_null = False
            node.cf_maybe_null = False

    # Unused result
505
    for assmt in assignments:
Vitja Makarov's avatar
Vitja Makarov committed
506 507
        if (not assmt.refs and not assmt.entry.is_pyclass_attr
            and not assmt.entry.in_closure):
508
            if assmt.entry.cf_references and warn_unused_result:
509
                if assmt.is_arg:
Vitja Makarov's avatar
Vitja Makarov committed
510 511
                    messages.warning(assmt.pos, "Unused argument value '%s'" %
                                     assmt.entry.name)
512
                else:
Vitja Makarov's avatar
Vitja Makarov committed
513 514
                    messages.warning(assmt.pos, "Unused result in '%s'" %
                                     assmt.entry.name)
515
            assmt.lhs.cf_used = False
516

517
    # Unused entries
518
    for entry in flow.entries:
Vitja Makarov's avatar
Vitja Makarov committed
519 520
        if (not entry.cf_references and not entry.is_pyclass_attr
            and not entry.in_closure):
Vitja Makarov's avatar
Vitja Makarov committed
521
            if entry.is_arg:
522
                if warn_unused_arg:
Vitja Makarov's avatar
Vitja Makarov committed
523 524
                    messages.warning(entry.pos, "Unused argument '%s'" %
                                     entry.name)
525 526
            else:
                if warn_unused:
Vitja Makarov's avatar
Vitja Makarov committed
527 528
                    messages.warning(entry.pos, "Unused entry '%s'" %
                                     entry.name)
529
            entry.cf_used = False
530

531
    messages.report()
532

533 534 535 536 537 538
    # Remove Uninitialized from cf_state
    for node in assmt_nodes:
        node.cf_state.discard(Uninitialized)
    for node in references:
        node.cf_state.discard(Uninitialized)

539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558

class AssignmentCollector(TreeVisitor):
    def __init__(self):
        super(AssignmentCollector, self).__init__()
        self.assignments = []

    def visit_Node(self):
        self.visitchildren(self)

    def visit_SingleAssignmentNode(self, node):
        self.assignments.append((node.lhs, node.rhs))

    def visit_CascadedAssignmentNode(self, node):
        for lhs in node.lhs_list:
            self.assignments.append((lhs, node.rhs))


class CreateControlFlowGraph(CythonTransform):
    """Create NameNode use and assignment graph."""

559 560
    in_inplace_assignment = False

561 562 563
    def visit_ModuleNode(self, node):
        self.gv_ctx = GVContext()

564
        # Set of NameNode reductions
Robert Bradshaw's avatar
Robert Bradshaw committed
565
        self.reductions = set()
566

567 568 569 570 571 572
        self.env_stack = []
        self.env = node.scope
        self.stack = []
        self.flow = ControlFlow()
        self.visitchildren(node)

573 574
        check_definitions(self.flow, self.current_directives)

575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
        dot_output = self.current_directives['control_flow.dot_output']
        if dot_output:
            annotate_defs = self.current_directives['control_flow.dot_annotate_defs']
            fp = open(dot_output, 'wt')
            try:
                self.gv_ctx.render(fp, 'module', annotate_defs=annotate_defs)
            finally:
                fp.close()
        return node

    def visit_FuncDefNode(self, node):
        self.env_stack.append(self.env)
        self.env = node.local_scope
        self.stack.append(self.flow)
        self.flow = ControlFlow()

591 592 593 594 595
        # Collect all entries
        for entry in node.local_scope.entries.values():
            if self.flow.is_tracked(entry):
                self.flow.entries.add(entry)

596 597 598 599 600 601
        self.mark_position(node)
        # Function body block
        self.flow.nextblock()

        if node.star_arg:
            self.flow.mark_argument(node.star_arg,
602 603
                                    TypedExprNode(Builtin.tuple_type,
                                                  may_be_none=False),
604 605 606
                                    node.star_arg.entry)
        if node.starstar_arg:
            self.flow.mark_argument(node.starstar_arg,
607 608
                                    TypedExprNode(Builtin.dict_type,
                                                  may_be_none=False),
609 610
                                    node.starstar_arg.entry)
        self.visitchildren(node)
Vitja Makarov's avatar
Vitja Makarov committed
611 612 613
        # Workaround for generators
        if node.is_generator:
            self.visit(node.gbody.body)
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632

        # Exit point
        if self.flow.block:
            self.flow.block.add_child(self.flow.exit_point)

        # Cleanup graph
        self.flow.normalize()
        check_definitions(self.flow, self.current_directives)
        self.flow.blocks.add(self.flow.entry_point)

        self.gv_ctx.add(GV(node.local_scope.name, self.flow))

        self.flow = self.stack.pop()
        self.env = self.env_stack.pop()
        return node

    def visit_DefNode(self, node):
        ## XXX: no target name node here
        node.used = True
633 634 635
        entry = node.entry
        if entry.is_anonymous:
            entry = self.env.lookup(node.name)
636
        if entry:
637
            self.flow.mark_assignment(node, object_expr_not_none, entry)
638 639
        return self.visit_FuncDefNode(node)

Vitja Makarov's avatar
Vitja Makarov committed
640 641 642
    def visit_GeneratorBodyDefNode(self, node):
        return node

643 644 645 646 647 648 649 650 651 652 653 654 655 656
    def visit_CTypeDefNode(self, node):
        return node

    def mark_assignment(self, lhs, rhs=None):
        if not self.flow.block:
            return
        if self.flow.exceptions:
            exc_descr = self.flow.exceptions[-1]
            self.flow.block.add_child(exc_descr.entry_point)
            self.flow.nextblock()

        if not rhs:
            rhs = object_expr
        if lhs.is_name:
657 658 659 660 661
            if lhs.entry is not None:
                entry = lhs.entry
            else:
                entry = self.env.lookup(lhs.name)
            if entry is None: # TODO: This shouldn't happen...
662
                return
663
            self.flow.mark_assignment(lhs, rhs, entry)
664 665 666 667
        elif isinstance(lhs, ExprNodes.SequenceNode):
            for arg in lhs.args:
                self.mark_assignment(arg)
        else:
668
            self.visit(lhs)
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686

        if self.flow.exceptions:
            exc_descr = self.flow.exceptions[-1]
            self.flow.block.add_child(exc_descr.entry_point)
            self.flow.nextblock()

    def mark_position(self, node):
        """Mark position if DOT output is enabled."""
        if self.current_directives['control_flow.dot_output']:
            self.flow.mark_position(node)

    def visit_FromImportStatNode(self, node):
        for name, target in node.items:
            if name != "*":
                self.mark_assignment(target)
        self.visitchildren(node)
        return node

687 688 689
    def visit_AssignmentNode(self, node):
        raise InternalError, "Unhandled assignment node"

690 691
    def visit_SingleAssignmentNode(self, node):
        self.visit(node.rhs)
692
        self.mark_assignment(node.lhs, node.rhs)
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
        return node

    def visit_CascadedAssignmentNode(self, node):
        self.visit(node.rhs)
        for lhs in node.lhs_list:
            self.mark_assignment(lhs, node.rhs)
        return node

    def visit_ParallelAssignmentNode(self, node):
        collector = AssignmentCollector()
        collector.visitchildren(node)
        for lhs, rhs in collector.assignments:
            self.visit(rhs)
        for lhs, rhs in collector.assignments:
            self.mark_assignment(lhs, rhs)
        return node

    def visit_InPlaceAssignmentNode(self, node):
Mark Florisson's avatar
Mark Florisson committed
711
        self.in_inplace_assignment = True
712
        self.visitchildren(node)
Mark Florisson's avatar
Mark Florisson committed
713
        self.in_inplace_assignment = False
714
        self.mark_assignment(node.lhs, node.create_binop_node())
715 716 717 718 719
        return node

    def visit_DelStatNode(self, node):
        for arg in node.args:
            if arg.is_name:
Vitja Makarov's avatar
Vitja Makarov committed
720 721
                entry = arg.entry or self.env.lookup(arg.name)
                if entry.in_closure or entry.from_closure:
Vitja Makarov's avatar
Vitja Makarov committed
722 723 724
                    error(arg.pos,
                          "can not delete variable '%s' "
                          "referenced in nested scope" % entry.name)
Vitja Makarov's avatar
Vitja Makarov committed
725
                # Mark reference
726
                self.visit(arg)
Vitja Makarov's avatar
Vitja Makarov committed
727
                self.flow.mark_deletion(arg, entry)
728 729 730 731
        return node

    def visit_CArgDeclNode(self, node):
        entry = self.env.lookup(node.name)
732
        if entry:
733 734
            may_be_none = not node.not_none
            self.flow.mark_argument(node, TypedExprNode(entry.type, may_be_none), entry)
735 736 737 738 739 740 741
        return node

    def visit_NameNode(self, node):
        if self.flow.block:
            entry = node.entry or self.env.lookup(node.name)
            if entry:
                self.flow.mark_reference(node, entry)
Mark Florisson's avatar
Mark Florisson committed
742 743 744 745 746

                if entry in self.reductions and not self.in_inplace_assignment:
                    error(node.pos,
                          "Cannot read reduction variable in loop body")

747 748 749
        return node

    def visit_StatListNode(self, node):
750 751 752 753 754 755
        if self.flow.block:
            for stat in node.stats:
                self.visit(stat)
                if not self.flow.block:
                    stat.is_terminator = True
                    break
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
        return node

    def visit_Node(self, node):
        self.visitchildren(node)
        self.mark_position(node)
        return node

    def visit_IfStatNode(self, node):
        next_block = self.flow.newblock()
        parent = self.flow.block
        # If clauses
        for clause in node.if_clauses:
            parent = self.flow.nextblock(parent)
            self.visit(clause.condition)
            self.flow.nextblock()
            self.visit(clause.body)
            if self.flow.block:
                self.flow.block.add_child(next_block)
        # Else clause
        if node.else_clause:
            self.flow.nextblock(parent=parent)
            self.visit(node.else_clause)
            if self.flow.block:
                self.flow.block.add_child(next_block)
        else:
            parent.add_child(next_block)

        if next_block.parents:
            self.flow.block = next_block
        else:
            self.flow.block = None
        return node

    def visit_WhileStatNode(self, node):
        condition_block = self.flow.nextblock()
        next_block = self.flow.newblock()
        # Condition block
        self.flow.loops.append(LoopDescr(next_block, condition_block))
        self.visit(node.condition)
        # Body block
        self.flow.nextblock()
        self.visit(node.body)
798
        self.flow.loops.pop()
799 800 801 802 803 804 805 806 807 808 809 810
        # Loop it
        if self.flow.block:
            self.flow.block.add_child(condition_block)
            self.flow.block.add_child(next_block)
        # Else clause
        if node.else_clause:
            self.flow.nextblock(parent=condition_block)
            self.visit(node.else_clause)
            if self.flow.block:
                self.flow.block.add_child(next_block)
        else:
            condition_block.add_child(next_block)
811 812 813 814 815

        if next_block.parents:
            self.flow.block = next_block
        else:
            self.flow.block = None
816 817 818 819 820 821 822 823 824 825 826
        return node

    def visit_ForInStatNode(self, node):
        condition_block = self.flow.nextblock()
        next_block = self.flow.newblock()
        # Condition with iterator
        self.flow.loops.append(LoopDescr(next_block, condition_block))
        self.visit(node.iterator)
        # Target assignment
        self.flow.nextblock()
        self.mark_assignment(node.target)
Mark Florisson's avatar
Mark Florisson committed
827

828
        # Body block
Mark Florisson's avatar
Mark Florisson committed
829 830 831 832
        if isinstance(node, Nodes.ParallelRangeNode):
            # In case of an invalid
            self._delete_privates(node, exclude=node.target.entry)

833 834
        self.flow.nextblock()
        self.visit(node.body)
835
        self.flow.loops.pop()
Mark Florisson's avatar
Mark Florisson committed
836

837 838 839 840 841 842 843 844 845 846 847
        # Loop it
        if self.flow.block:
            self.flow.block.add_child(condition_block)
        # Else clause
        if node.else_clause:
            self.flow.nextblock(parent=condition_block)
            self.visit(node.else_clause)
            if self.flow.block:
                self.flow.block.add_child(next_block)
        else:
            condition_block.add_child(next_block)
848 849 850 851 852

        if next_block.parents:
            self.flow.block = next_block
        else:
            self.flow.block = None
853 854
        return node

Mark Florisson's avatar
Mark Florisson committed
855 856 857 858 859
    def _delete_privates(self, node, exclude=None):
        for private_node in node.assigned_nodes:
            if not exclude or private_node.entry is not exclude:
                self.flow.mark_deletion(private_node, private_node.entry)

860
    def visit_ParallelRangeNode(self, node):
Mark Florisson's avatar
Mark Florisson committed
861 862 863 864 865
        reductions = self.reductions

        # if node.target is None or not a NameNode, an error will have
        # been previously issued
        if hasattr(node.target, 'entry'):
Robert Bradshaw's avatar
Robert Bradshaw committed
866
            self.reductions = set(reductions)
Mark Florisson's avatar
Mark Florisson committed
867 868 869 870 871 872 873

            for private_node in node.assigned_nodes:
                private_node.entry.error_on_uninitialized = True
                pos, reduction = node.assignments[private_node.entry]
                if reduction:
                    self.reductions.add(private_node.entry)

874 875
            node = self.visit_ForInStatNode(node)

Mark Florisson's avatar
Mark Florisson committed
876 877 878 879 880 881 882 883 884 885 886
        self.reductions = reductions
        return node

    def visit_ParallelWithBlockNode(self, node):
        for private_node in node.assigned_nodes:
            private_node.entry.error_on_uninitialized = True

        self._delete_privates(node)
        self.visitchildren(node)
        self._delete_privates(node)

887 888
        return node

889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
    def visit_ForFromStatNode(self, node):
        condition_block = self.flow.nextblock()
        next_block = self.flow.newblock()
        # Condition with iterator
        self.flow.loops.append(LoopDescr(next_block, condition_block))
        self.visit(node.bound1)
        self.visit(node.bound2)
        if node.step:
            self.visit(node.step)
        # Target assignment
        self.flow.nextblock()
        self.mark_assignment(node.target)

        # Body block
        self.flow.nextblock()
        self.visit(node.body)
905
        self.flow.loops.pop()
906 907 908 909 910 911 912 913 914 915 916
        # Loop it
        if self.flow.block:
            self.flow.block.add_child(condition_block)
        # Else clause
        if node.else_clause:
            self.flow.nextblock(parent=condition_block)
            self.visit(node.else_clause)
            if self.flow.block:
                self.flow.block.add_child(next_block)
        else:
            condition_block.add_child(next_block)
917 918 919 920 921

        if next_block.parents:
            self.flow.block = next_block
        else:
            self.flow.block = None
922 923 924 925 926
        return node

    def visit_LoopNode(self, node):
        raise InternalError, "Generic loops are not supported"

927 928 929 930
    def visit_WithTargetAssignmentStatNode(self, node):
        self.mark_assignment(node.lhs)
        return node

931
    def visit_WithStatNode(self, node):
932 933 934
        self.visit(node.manager)
        self.visit(node.body)
        return node
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968

    def visit_TryExceptStatNode(self, node):
        # After exception handling
        next_block = self.flow.newblock()
        # Body block
        self.flow.newblock()
        # Exception entry point
        entry_point = self.flow.newblock()
        self.flow.exceptions.append(ExceptionDescr(entry_point))
        self.flow.nextblock()
        ## XXX: links to exception handling point should be added by
        ## XXX: children nodes
        self.flow.block.add_child(entry_point)
        self.visit(node.body)
        self.flow.exceptions.pop()

        # After exception
        if self.flow.block:
            if node.else_clause:
                self.flow.nextblock()
                self.visit(node.else_clause)
            if self.flow.block:
                self.flow.block.add_child(next_block)

        for clause in node.except_clauses:
            self.flow.block = entry_point
            if clause.pattern:
                for pattern in clause.pattern:
                    self.visit(pattern)
            else:
                # TODO: handle * pattern
                pass
            entry_point = self.flow.newblock(parent=self.flow.block)
            self.flow.nextblock()
969 970
            if clause.target:
                self.mark_assignment(clause.target)
971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
            self.visit(clause.body)
            if self.flow.block:
                self.flow.block.add_child(next_block)

        if self.flow.exceptions:
            entry_point.add_child(self.flow.exceptions[-1].entry_point)

        if next_block.parents:
            self.flow.block = next_block
        else:
            self.flow.block = None
        return node

    def visit_TryFinallyStatNode(self, node):
        body_block = self.flow.nextblock()

        # Exception entry point
        entry_point = self.flow.newblock()
        self.flow.block = entry_point
        self.visit(node.finally_clause)

992 993 994
        if self.flow.block and self.flow.exceptions:
            self.flow.block.add_child(self.flow.exceptions[-1].entry_point)

995 996 997 998 999 1000
        # Normal execution
        finally_enter = self.flow.newblock()
        self.flow.block = finally_enter
        self.visit(node.finally_clause)
        finally_exit = self.flow.block

1001 1002 1003 1004
        descr = ExceptionDescr(entry_point, finally_enter, finally_exit)
        self.flow.exceptions.append(descr)
        if self.flow.loops:
            self.flow.loops[-1].exceptions.append(descr)
1005 1006 1007 1008 1009
        self.flow.block = body_block
        ## XXX: Is it still required
        body_block.add_child(entry_point)
        self.visit(node.body)
        self.flow.exceptions.pop()
1010 1011
        if self.flow.loops:
            self.flow.loops[-1].exceptions.pop()
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022

        if self.flow.block:
            self.flow.block.add_child(finally_enter)
            if finally_exit:
                self.flow.block = self.flow.nextblock(parent=finally_exit)
            else:
                self.flow.block = None
        return node

    def visit_RaiseStatNode(self, node):
        self.mark_position(node)
Vitja Makarov's avatar
Vitja Makarov committed
1023
        self.visitchildren(node)
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
        if self.flow.exceptions:
            self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
        self.flow.block = None
        return node

    def visit_ReraiseStatNode(self, node):
        self.mark_position(node)
        if self.flow.exceptions:
            self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
        self.flow.block = None
        return node

    def visit_ReturnStatNode(self, node):
        self.mark_position(node)
        self.visitchildren(node)

        for exception in self.flow.exceptions[::-1]:
            if exception.finally_enter:
                self.flow.block.add_child(exception.finally_enter)
                if exception.finally_exit:
                    exception.finally_exit.add_child(self.flow.exit_point)
                break
        else:
            if self.flow.block:
                self.flow.block.add_child(self.flow.exit_point)
        self.flow.block = None
        return node

    def visit_BreakStatNode(self, node):
        if not self.flow.loops:
            #error(node.pos, "break statement not inside loop")
            return node
        loop = self.flow.loops[-1]
        self.mark_position(node)
1058
        for exception in loop.exceptions[::-1]:
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
            if exception.finally_enter:
                self.flow.block.add_child(exception.finally_enter)
                if exception.finally_exit:
                    exception.finally_exit.add_child(loop.next_block)
                break
        else:
            self.flow.block.add_child(loop.next_block)
        self.flow.block = None
        return node

    def visit_ContinueStatNode(self, node):
        if not self.flow.loops:
            #error(node.pos, "continue statement not inside loop")
            return node
        loop = self.flow.loops[-1]
        self.mark_position(node)
1075
        for exception in loop.exceptions[::-1]:
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
            if exception.finally_enter:
                self.flow.block.add_child(exception.finally_enter)
                if exception.finally_exit:
                    exception.finally_exit.add_child(loop.loop_block)
                break
        else:
            self.flow.block.add_child(loop.loop_block)
        self.flow.block = None
        return node

    def visit_ComprehensionNode(self, node):
        if node.expr_scope:
            self.env_stack.append(self.env)
            self.env = node.expr_scope
        # Skip append node here
        self.visit(node.target)
        self.visit(node.loop)
        if node.expr_scope:
            self.env = self.env_stack.pop()
        return node

    def visit_ScopedExprNode(self, node):
        if node.expr_scope:
            self.env_stack.append(self.env)
            self.env = node.expr_scope
        self.visitchildren(node)
        if node.expr_scope:
            self.env = self.env_stack.pop()
        return node

    def visit_PyClassDefNode(self, node):
        self.flow.mark_assignment(node.target,
1108
                                  object_expr_not_none, self.env.lookup(node.name))
1109
        # TODO: add negative attribute list to "visitchildren"?
Vitja Makarov's avatar
Vitja Makarov committed
1110 1111
        self.visitchildren(node, attrs=['dict', 'metaclass',
                                        'mkw', 'bases', 'classobj'])
1112 1113 1114 1115 1116 1117 1118
        self.env_stack.append(self.env)
        self.env = node.scope
        self.flow.nextblock()
        self.visitchildren(node, attrs=['body'])
        self.flow.nextblock()
        self.env = self.env_stack.pop()
        return node
1119 1120 1121 1122 1123 1124 1125

    def visit_AmpersandNode(self, node):
        if node.operand.is_name:
            # Fake assignment to silence warning
            self.mark_assignment(node.operand)
        self.visitchildren(node)
        return node