12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325 |
- from __future__ import absolute_import
- 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, fake_rhs_expr=object, TypedExprNode=object)
- from . import Builtin
- from . import ExprNodes
- from . import Nodes
- from . import Options
- from .PyrexTypes import py_object_type, unspecified_type
- from . import PyrexTypes
- from .Visitor import TreeVisitor, CythonTransform
- from .Errors import error, warning, InternalError
- from .Optimize import ConstantFolding
- 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)
- # 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.values():
- 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 [%s %s]' % (
- stat.entry.name, 'deletion' if stat.is_deletion else 'definition', stat.pos[1])
- elif isinstance(stat, NameReference):
- if stat.entry:
- label += '\n %s [reference %s]' % (stat.entry.name, stat.pos[1])
- 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 = set()
- def error(self, pos, message):
- self.messages.add((pos, True, message))
- def warning(self, pos, message):
- self.messages.add((pos, False, message))
- def report(self):
- for pos, is_error, message in sorted(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.items():
- 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 != '_' and not entry.name.startswith('unused'):
- # '_' 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()
- self.constant_folder = ConstantFolding()
- # 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 lhs.is_sequence_constructor:
- for i, arg in enumerate(lhs.args):
- if not rhs or arg.is_starred:
- item_node = None
- else:
- item_node = rhs.inferable_item_node(i)
- self.mark_assignment(arg, item_node)
- 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, self.constant_folder(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)
- if not node.ignore_nonexisting:
- self._visit(arg) # mark reference
- 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_SizeofVarNode(self, node):
- return node
- def visit_TypeidNode(self, 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, self.constant_folder(
- 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_AsyncForStatNode(self, node):
- return self.visit_ForInStatNode(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()
- if isinstance(node, Nodes.ForInStatNode):
- self.mark_forloop_target(node)
- elif isinstance(node, Nodes.AsyncForStatNode):
- # not entirely correct, but good enough for now
- self.mark_assignment(node.target, node.item)
- 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, self.constant_folder(
- 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.with_node.enter_call)
- 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_except_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
- 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)
- outer_exception_handlers = iter(self.flow.exceptions[::-1])
- for handler in outer_exception_handlers:
- if handler.finally_enter:
- self.flow.block.add_child(handler.finally_enter)
- if handler.finally_exit:
- # 'return' goes to function exit, or to the next outer 'finally' clause
- exit_point = self.flow.exit_point
- for next_handler in outer_exception_handlers:
- if next_handler.finally_enter:
- exit_point = next_handler.finally_enter
- break
- handler.finally_exit.add_child(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, node.classobj,
- 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
|