| Index: third_party/cython/src/Cython/Compiler/FlowControl.py
|
| diff --git a/third_party/cython/src/Cython/Compiler/FlowControl.py b/third_party/cython/src/Cython/Compiler/FlowControl.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a36ffa467614567ef8a3dae747f71abe2331b594
|
| --- /dev/null
|
| +++ b/third_party/cython/src/Cython/Compiler/FlowControl.py
|
| @@ -0,0 +1,1303 @@
|
| +import cython
|
| +cython.declare(PyrexTypes=object, ExprNodes=object, Nodes=object,
|
| + Builtin=object, InternalError=object,
|
| + error=object, warning=object,
|
| + py_object_type=object, unspecified_type=object,
|
| + object_expr=object, object_expr_not_none=object,
|
| + fake_rhs_expr=object, TypedExprNode=object)
|
| +
|
| +import Builtin
|
| +import ExprNodes
|
| +import Nodes
|
| +import Options
|
| +from PyrexTypes import py_object_type, unspecified_type
|
| +import PyrexTypes
|
| +
|
| +from Visitor import TreeVisitor, CythonTransform
|
| +from Errors import error, warning, InternalError
|
| +
|
| +class TypedExprNode(ExprNodes.ExprNode):
|
| + # Used for declaring assignments of a specified type without a known entry.
|
| + def __init__(self, type, may_be_none=None, pos=None):
|
| + super(TypedExprNode, self).__init__(pos)
|
| + self.type = type
|
| + self._may_be_none = may_be_none
|
| +
|
| + 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)
|
| +# Fake rhs to silence "unused variable" warning
|
| +fake_rhs_expr = TypedExprNode(unspecified_type)
|
| +
|
| +
|
| +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()
|
| +
|
| + self.i_input = 0
|
| + self.i_output = 0
|
| + self.i_gen = 0
|
| + self.i_kill = 0
|
| + self.i_state = 0
|
| +
|
| + 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
|
| +
|
| +
|
| +class AssignmentList(object):
|
| + def __init__(self):
|
| + self.stats = []
|
| +
|
| +
|
| +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
|
| + return (entry.is_local or entry.is_pyclass_attr or entry.is_arg or
|
| + entry.from_closure or entry.in_closure or
|
| + entry.error_on_uninitialized)
|
| +
|
| + def is_statically_assigned(self, entry):
|
| + if (entry.is_local and entry.is_variable and
|
| + (entry.type.is_struct_or_union or
|
| + entry.type.is_complex or
|
| + entry.type.is_array or
|
| + entry.type.is_cpp_class)):
|
| + # stack allocated structured variable => never uninitialised
|
| + return True
|
| + return False
|
| +
|
| + def mark_position(self, node):
|
| + """Mark position, will be used to draw graph nodes."""
|
| + if self.block:
|
| + self.block.positions.add(node.pos[:2])
|
| +
|
| + def mark_assignment(self, lhs, rhs, entry):
|
| + if self.block and self.is_tracked(entry):
|
| + 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 = NameDeletion(node, 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))
|
| + ## XXX: We don't track expression evaluation order so we can't use
|
| + ## XXX: successful reference as initialization sign.
|
| + ## # Local variable is definitely bound after this reference
|
| + ## if not node.allow_null:
|
| + ## self.block.bounded.add(entry)
|
| + 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
|
| +
|
| + def initialize(self):
|
| + """Set initial state, map assignments to bits."""
|
| + self.assmts = {}
|
| +
|
| + bit = 1
|
| + for entry in self.entries:
|
| + assmts = AssignmentList()
|
| + assmts.mask = assmts.bit = bit
|
| + self.assmts[entry] = assmts
|
| + bit <<= 1
|
| +
|
| + for block in self.blocks:
|
| + for stat in block.stats:
|
| + if isinstance(stat, NameAssignment):
|
| + stat.bit = bit
|
| + assmts = self.assmts[stat.entry]
|
| + assmts.stats.append(stat)
|
| + assmts.mask |= bit
|
| + bit <<= 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:
|
| + if self.is_statically_assigned(entry):
|
| + ret.add(StaticAssignment(entry))
|
| + elif entry.from_closure:
|
| + ret.add(Unknown)
|
| + else:
|
| + 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
|
| +
|
| +
|
| +class LoopDescr(object):
|
| + def __init__(self, next_block, loop_block):
|
| + self.next_block = next_block
|
| + self.loop_block = loop_block
|
| + self.exceptions = []
|
| +
|
| +
|
| +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):
|
| + if lhs.cf_state is None:
|
| + lhs.cf_state = set()
|
| + self.lhs = lhs
|
| + self.rhs = rhs
|
| + self.entry = entry
|
| + self.pos = lhs.pos
|
| + self.refs = set()
|
| + self.is_arg = False
|
| + self.is_deletion = False
|
| + self.inferred_type = None
|
| +
|
| + def __repr__(self):
|
| + return '%s(entry=%r)' % (self.__class__.__name__, self.entry)
|
| +
|
| + def infer_type(self):
|
| + self.inferred_type = self.rhs.infer_type(self.entry.scope)
|
| + return self.inferred_type
|
| +
|
| + def type_dependencies(self):
|
| + return self.rhs.type_dependencies(self.entry.scope)
|
| +
|
| + @property
|
| + def type(self):
|
| + if not self.entry.type.is_unspecified:
|
| + return self.entry.type
|
| + return self.inferred_type
|
| +
|
| +
|
| +class StaticAssignment(NameAssignment):
|
| + """Initialised at declaration time, e.g. stack allocation."""
|
| + def __init__(self, entry):
|
| + if not entry.type.is_pyobject:
|
| + may_be_none = False
|
| + else:
|
| + may_be_none = None # unknown
|
| + lhs = TypedExprNode(
|
| + entry.type, may_be_none=may_be_none, pos=entry.pos)
|
| + super(StaticAssignment, self).__init__(lhs, lhs, entry)
|
| +
|
| + def infer_type(self):
|
| + return self.entry.type
|
| +
|
| + def type_dependencies(self):
|
| + return ()
|
| +
|
| +
|
| +class Argument(NameAssignment):
|
| + def __init__(self, lhs, rhs, entry):
|
| + NameAssignment.__init__(self, lhs, rhs, entry)
|
| + self.is_arg = True
|
| +
|
| +
|
| +class NameDeletion(NameAssignment):
|
| + def __init__(self, lhs, entry):
|
| + NameAssignment.__init__(self, lhs, lhs, entry)
|
| + self.is_deletion = True
|
| +
|
| + def infer_type(self):
|
| + inferred_type = self.rhs.infer_type(self.entry.scope)
|
| + if (not inferred_type.is_pyobject and
|
| + inferred_type.can_coerce_to_pyobject(self.entry.scope)):
|
| + return py_object_type
|
| + self.inferred_type = inferred_type
|
| + return inferred_type
|
| +
|
| +
|
| +class Uninitialized(object):
|
| + """Definitely not initialised yet."""
|
| +
|
| +
|
| +class Unknown(object):
|
| + """Coming from outer closure, might be initialised or not."""
|
| +
|
| +
|
| +class NameReference(object):
|
| + def __init__(self, node, entry):
|
| + if node.cf_state is None:
|
| + node.cf_state = set()
|
| + self.node = node
|
| + self.entry = entry
|
| + self.pos = node.pos
|
| +
|
| + def __repr__(self):
|
| + return '%s(entry=%r)' % (self.__class__.__name__, self.entry)
|
| +
|
| +
|
| +class ControlFlowState(list):
|
| + # Keeps track of Node's entry assignments
|
| + #
|
| + # cf_is_null [boolean] It is uninitialized
|
| + # cf_maybe_null [boolean] May be uninitialized
|
| + # is_single [boolean] Has only one assignment at this point
|
| +
|
| + cf_maybe_null = False
|
| + cf_is_null = False
|
| + is_single = False
|
| +
|
| + def __init__(self, state):
|
| + if Uninitialized in state:
|
| + state.discard(Uninitialized)
|
| + self.cf_maybe_null = True
|
| + if not state:
|
| + self.cf_is_null = True
|
| + elif Unknown in state:
|
| + state.discard(Unknown)
|
| + self.cf_maybe_null = True
|
| + else:
|
| + if len(state) == 1:
|
| + self.is_single = True
|
| + # XXX: Remove fake_rhs_expr
|
| + super(ControlFlowState, self).__init__(
|
| + [i for i in state if i.rhs is not fake_rhs_expr])
|
| +
|
| + def one(self):
|
| + return self[0]
|
| +
|
| +
|
| +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')
|
| +
|
| +
|
| +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')
|
| +
|
| +
|
| +class MessageCollection(object):
|
| + """Collect error/warnings messages first then sort"""
|
| + def __init__(self):
|
| + self.messages = []
|
| +
|
| + def error(self, pos, message):
|
| + self.messages.append((pos, True, message))
|
| +
|
| + def warning(self, pos, message):
|
| + self.messages.append((pos, False, message))
|
| +
|
| + 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)
|
| +
|
| +
|
| +def check_definitions(flow, compiler_directives):
|
| + flow.initialize()
|
| + flow.reaching_definitions()
|
| +
|
| + # Track down state
|
| + assignments = set()
|
| + # Node to entry map
|
| + references = {}
|
| + assmt_nodes = set()
|
| +
|
| + for block in flow.blocks:
|
| + i_state = block.i_input
|
| + for stat in block.stats:
|
| + i_assmts = flow.assmts[stat.entry]
|
| + state = flow.map_one(i_state, stat.entry)
|
| + if isinstance(stat, NameAssignment):
|
| + stat.lhs.cf_state.update(state)
|
| + assmt_nodes.add(stat.lhs)
|
| + i_state = i_state & ~i_assmts.mask
|
| + if stat.is_deletion:
|
| + i_state |= i_assmts.bit
|
| + else:
|
| + i_state |= stat.bit
|
| + assignments.add(stat)
|
| + if stat.rhs is not fake_rhs_expr:
|
| + stat.entry.cf_assignments.append(stat)
|
| + elif isinstance(stat, NameReference):
|
| + references[stat.node] = stat.entry
|
| + stat.entry.cf_references.append(stat)
|
| + stat.node.cf_state.update(state)
|
| + ## if not stat.node.allow_null:
|
| + ## i_state &= ~i_assmts.bit
|
| + ## # after successful read, the state is known to be initialised
|
| + state.discard(Uninitialized)
|
| + state.discard(Unknown)
|
| + for assmt in state:
|
| + assmt.refs.add(stat)
|
| +
|
| + # Check variable usage
|
| + warn_maybe_uninitialized = compiler_directives['warn.maybe_uninitialized']
|
| + warn_unused_result = compiler_directives['warn.unused_result']
|
| + warn_unused = compiler_directives['warn.unused']
|
| + warn_unused_arg = compiler_directives['warn.unused_arg']
|
| +
|
| + 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
|
| + elif Unknown in node.cf_state:
|
| + node.cf_maybe_null = True
|
| + 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
|
| + if not entry.from_closure and len(node.cf_state) == 1:
|
| + node.cf_is_null = True
|
| + if (node.allow_null or entry.from_closure
|
| + or entry.is_pyclass_attr or entry.type.is_error):
|
| + pass # Can be uninitialized here
|
| + elif node.cf_is_null:
|
| + if entry.error_on_uninitialized or (
|
| + Options.error_on_uninitialized and (
|
| + entry.type.is_pyobject or entry.type.is_unspecified)):
|
| + messages.error(
|
| + node.pos,
|
| + "local variable '%s' referenced before assignment"
|
| + % entry.name)
|
| + else:
|
| + messages.warning(
|
| + node.pos,
|
| + "local variable '%s' referenced before assignment"
|
| + % entry.name)
|
| + elif warn_maybe_uninitialized:
|
| + messages.warning(
|
| + node.pos,
|
| + "local variable '%s' might be referenced before assignment"
|
| + % entry.name)
|
| + elif Unknown in node.cf_state:
|
| + # TODO: better cross-closure analysis to know when inner functions
|
| + # are being called before a variable is being set, and when
|
| + # a variable is known to be set before even defining the
|
| + # inner function, etc.
|
| + node.cf_maybe_null = True
|
| + else:
|
| + node.cf_is_null = False
|
| + node.cf_maybe_null = False
|
| +
|
| + # Unused result
|
| + for assmt in assignments:
|
| + if (not assmt.refs and not assmt.entry.is_pyclass_attr
|
| + and not assmt.entry.in_closure):
|
| + if assmt.entry.cf_references and warn_unused_result:
|
| + if assmt.is_arg:
|
| + messages.warning(assmt.pos, "Unused argument value '%s'" %
|
| + assmt.entry.name)
|
| + else:
|
| + messages.warning(assmt.pos, "Unused result in '%s'" %
|
| + assmt.entry.name)
|
| + assmt.lhs.cf_used = False
|
| +
|
| + # Unused entries
|
| + for entry in flow.entries:
|
| + if (not entry.cf_references
|
| + and not entry.is_pyclass_attr):
|
| + if entry.name != '_':
|
| + # '_' is often used for unused variables, e.g. in loops
|
| + if entry.is_arg:
|
| + if warn_unused_arg:
|
| + messages.warning(entry.pos, "Unused argument '%s'" %
|
| + entry.name)
|
| + else:
|
| + if warn_unused:
|
| + messages.warning(entry.pos, "Unused entry '%s'" %
|
| + entry.name)
|
| + entry.cf_used = False
|
| +
|
| + messages.report()
|
| +
|
| + for node in assmt_nodes:
|
| + node.cf_state = ControlFlowState(node.cf_state)
|
| + for node in references:
|
| + node.cf_state = ControlFlowState(node.cf_state)
|
| +
|
| +
|
| +class AssignmentCollector(TreeVisitor):
|
| + def __init__(self):
|
| + super(AssignmentCollector, self).__init__()
|
| + self.assignments = []
|
| +
|
| + def visit_Node(self):
|
| + self._visitchildren(self, None)
|
| +
|
| + 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 ControlFlowAnalysis(CythonTransform):
|
| +
|
| + def visit_ModuleNode(self, node):
|
| + self.gv_ctx = GVContext()
|
| +
|
| + # Set of NameNode reductions
|
| + self.reductions = set()
|
| +
|
| + self.in_inplace_assignment = False
|
| + self.env_stack = []
|
| + self.env = node.scope
|
| + self.stack = []
|
| + self.flow = ControlFlow()
|
| + self.visitchildren(node)
|
| +
|
| + check_definitions(self.flow, self.current_directives)
|
| +
|
| + 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):
|
| + for arg in node.args:
|
| + if arg.default:
|
| + self.visitchildren(arg)
|
| + self.visitchildren(node, ('decorators',))
|
| + self.env_stack.append(self.env)
|
| + self.env = node.local_scope
|
| + self.stack.append(self.flow)
|
| + self.flow = ControlFlow()
|
| +
|
| + # Collect all entries
|
| + for entry in node.local_scope.entries.values():
|
| + if self.flow.is_tracked(entry):
|
| + self.flow.entries.add(entry)
|
| +
|
| + self.mark_position(node)
|
| + # Function body block
|
| + self.flow.nextblock()
|
| +
|
| + for arg in node.args:
|
| + self._visit(arg)
|
| + if node.star_arg:
|
| + self.flow.mark_argument(node.star_arg,
|
| + TypedExprNode(Builtin.tuple_type,
|
| + may_be_none=False),
|
| + node.star_arg.entry)
|
| + if node.starstar_arg:
|
| + self.flow.mark_argument(node.starstar_arg,
|
| + TypedExprNode(Builtin.dict_type,
|
| + may_be_none=False),
|
| + node.starstar_arg.entry)
|
| + self._visit(node.body)
|
| + # Workaround for generators
|
| + if node.is_generator:
|
| + self._visit(node.gbody.body)
|
| +
|
| + # 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):
|
| + node.used = True
|
| + return self.visit_FuncDefNode(node)
|
| +
|
| + def visit_GeneratorBodyDefNode(self, node):
|
| + return node
|
| +
|
| + 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:
|
| + 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...
|
| + return
|
| + self.flow.mark_assignment(lhs, rhs, entry)
|
| + elif isinstance(lhs, ExprNodes.SequenceNode):
|
| + for arg in lhs.args:
|
| + self.mark_assignment(arg)
|
| + else:
|
| + self._visit(lhs)
|
| +
|
| + 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
|
| +
|
| + def visit_AssignmentNode(self, node):
|
| + raise InternalError("Unhandled assignment node")
|
| +
|
| + def visit_SingleAssignmentNode(self, node):
|
| + self._visit(node.rhs)
|
| + self.mark_assignment(node.lhs, node.rhs)
|
| + 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):
|
| + self.in_inplace_assignment = True
|
| + self.visitchildren(node)
|
| + self.in_inplace_assignment = False
|
| + self.mark_assignment(node.lhs, node.create_binop_node())
|
| + return node
|
| +
|
| + def visit_DelStatNode(self, node):
|
| + for arg in node.args:
|
| + if arg.is_name:
|
| + entry = arg.entry or self.env.lookup(arg.name)
|
| + if entry.in_closure or entry.from_closure:
|
| + error(arg.pos,
|
| + "can not delete variable '%s' "
|
| + "referenced in nested scope" % entry.name)
|
| + # Mark reference
|
| + self._visit(arg)
|
| + self.flow.mark_deletion(arg, entry)
|
| + else:
|
| + self._visit(arg)
|
| + return node
|
| +
|
| + def visit_CArgDeclNode(self, node):
|
| + entry = self.env.lookup(node.name)
|
| + if entry:
|
| + may_be_none = not node.not_none
|
| + self.flow.mark_argument(
|
| + node, TypedExprNode(entry.type, may_be_none), entry)
|
| + 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)
|
| +
|
| + if entry in self.reductions and not self.in_inplace_assignment:
|
| + error(node.pos,
|
| + "Cannot read reduction variable in loop body")
|
| +
|
| + return node
|
| +
|
| + def visit_StatListNode(self, node):
|
| + if self.flow.block:
|
| + for stat in node.stats:
|
| + self._visit(stat)
|
| + if not self.flow.block:
|
| + stat.is_terminator = True
|
| + break
|
| + 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))
|
| + if node.condition:
|
| + self._visit(node.condition)
|
| + # Body block
|
| + self.flow.nextblock()
|
| + self._visit(node.body)
|
| + self.flow.loops.pop()
|
| + # 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)
|
| +
|
| + if next_block.parents:
|
| + self.flow.block = next_block
|
| + else:
|
| + self.flow.block = None
|
| + return node
|
| +
|
| + def mark_forloop_target(self, node):
|
| + # TODO: Remove redundancy with range optimization...
|
| + is_special = False
|
| + sequence = node.iterator.sequence
|
| + target = node.target
|
| + if isinstance(sequence, ExprNodes.SimpleCallNode):
|
| + function = sequence.function
|
| + if sequence.self is None and function.is_name:
|
| + entry = self.env.lookup(function.name)
|
| + if not entry or entry.is_builtin:
|
| + if function.name == 'reversed' and len(sequence.args) == 1:
|
| + sequence = sequence.args[0]
|
| + elif function.name == 'enumerate' and len(sequence.args) == 1:
|
| + if target.is_sequence_constructor and len(target.args) == 2:
|
| + iterator = sequence.args[0]
|
| + if iterator.is_name:
|
| + iterator_type = iterator.infer_type(self.env)
|
| + if iterator_type.is_builtin_type:
|
| + # assume that builtin types have a length within Py_ssize_t
|
| + self.mark_assignment(
|
| + target.args[0],
|
| + ExprNodes.IntNode(target.pos, value='PY_SSIZE_T_MAX',
|
| + type=PyrexTypes.c_py_ssize_t_type))
|
| + target = target.args[1]
|
| + sequence = sequence.args[0]
|
| + if isinstance(sequence, ExprNodes.SimpleCallNode):
|
| + function = sequence.function
|
| + if sequence.self is None and function.is_name:
|
| + entry = self.env.lookup(function.name)
|
| + if not entry or entry.is_builtin:
|
| + if function.name in ('range', 'xrange'):
|
| + is_special = True
|
| + for arg in sequence.args[:2]:
|
| + self.mark_assignment(target, arg)
|
| + if len(sequence.args) > 2:
|
| + self.mark_assignment(
|
| + target,
|
| + ExprNodes.binop_node(node.pos,
|
| + '+',
|
| + sequence.args[0],
|
| + sequence.args[2]))
|
| +
|
| + if not is_special:
|
| + # A for-loop basically translates to subsequent calls to
|
| + # __getitem__(), so using an IndexNode here allows us to
|
| + # naturally infer the base type of pointers, C arrays,
|
| + # Python strings, etc., while correctly falling back to an
|
| + # object type when the base type cannot be handled.
|
| +
|
| + self.mark_assignment(target, node.item)
|
| +
|
| + 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()
|
| +
|
| + if isinstance(node, Nodes.ForInStatNode):
|
| + self.mark_forloop_target(node)
|
| + else: # Parallel
|
| + self.mark_assignment(node.target)
|
| +
|
| + # Body block
|
| + if isinstance(node, Nodes.ParallelRangeNode):
|
| + # In case of an invalid
|
| + self._delete_privates(node, exclude=node.target.entry)
|
| +
|
| + self.flow.nextblock()
|
| + self._visit(node.body)
|
| + self.flow.loops.pop()
|
| +
|
| + # 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)
|
| +
|
| + if next_block.parents:
|
| + self.flow.block = next_block
|
| + else:
|
| + self.flow.block = None
|
| + return node
|
| +
|
| + 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)
|
| +
|
| + def visit_ParallelRangeNode(self, node):
|
| + reductions = self.reductions
|
| +
|
| + # if node.target is None or not a NameNode, an error will have
|
| + # been previously issued
|
| + if hasattr(node.target, 'entry'):
|
| + self.reductions = set(reductions)
|
| +
|
| + 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)
|
| +
|
| + node = self.visit_ForInStatNode(node)
|
| +
|
| + 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)
|
| +
|
| + return node
|
| +
|
| + 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 is not None:
|
| + self._visit(node.step)
|
| + # Target assignment
|
| + self.flow.nextblock()
|
| + self.mark_assignment(node.target, node.bound1)
|
| + if node.step is not None:
|
| + self.mark_assignment(node.target,
|
| + ExprNodes.binop_node(node.pos, '+',
|
| + node.bound1, node.step))
|
| + # Body block
|
| + self.flow.nextblock()
|
| + self._visit(node.body)
|
| + self.flow.loops.pop()
|
| + # 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)
|
| +
|
| + if next_block.parents:
|
| + self.flow.block = next_block
|
| + else:
|
| + self.flow.block = None
|
| + return node
|
| +
|
| + def visit_LoopNode(self, node):
|
| + raise InternalError("Generic loops are not supported")
|
| +
|
| + def visit_WithTargetAssignmentStatNode(self, node):
|
| + self.mark_assignment(node.lhs, node.rhs)
|
| + return node
|
| +
|
| + def visit_WithStatNode(self, node):
|
| + self._visit(node.manager)
|
| + self._visit(node.enter_call)
|
| + self._visit(node.body)
|
| + return node
|
| +
|
| + 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.flow.nextblock()
|
| + 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()
|
| + if clause.target:
|
| + self.mark_assignment(clause.target)
|
| + 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)
|
| +
|
| + if self.flow.block and self.flow.exceptions:
|
| + self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
|
| +
|
| + # Normal execution
|
| + finally_enter = self.flow.newblock()
|
| + self.flow.block = finally_enter
|
| + self._visit(node.finally_clause)
|
| + finally_exit = self.flow.block
|
| +
|
| + descr = ExceptionDescr(entry_point, finally_enter, finally_exit)
|
| + self.flow.exceptions.append(descr)
|
| + if self.flow.loops:
|
| + self.flow.loops[-1].exceptions.append(descr)
|
| + self.flow.block = body_block
|
| + ## XXX: Is it still required
|
| + body_block.add_child(entry_point)
|
| + self.flow.nextblock()
|
| + self._visit(node.body)
|
| + self.flow.exceptions.pop()
|
| + if self.flow.loops:
|
| + self.flow.loops[-1].exceptions.pop()
|
| +
|
| + 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)
|
| + self.visitchildren(node)
|
| + 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)
|
| + for exception in loop.exceptions[::-1]:
|
| + 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)
|
| + for exception in loop.exceptions[::-1]:
|
| + 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.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.visitchildren(node, ('dict', 'metaclass',
|
| + 'mkw', 'bases', 'class_result'))
|
| + self.flow.mark_assignment(node.target, object_expr_not_none,
|
| + self.env.lookup(node.name))
|
| + self.env_stack.append(self.env)
|
| + self.env = node.scope
|
| + self.flow.nextblock()
|
| + self.visitchildren(node, ('body',))
|
| + self.flow.nextblock()
|
| + self.env = self.env_stack.pop()
|
| + return node
|
| +
|
| + def visit_AmpersandNode(self, node):
|
| + if node.operand.is_name:
|
| + # Fake assignment to silence warning
|
| + self.mark_assignment(node.operand, fake_rhs_expr)
|
| + self.visitchildren(node)
|
| + return node
|
|
|