| |
|
|
|
|
| import cython |
| cython.declare(PyrexTypes=object, ExprNodes=object, Nodes=object, Builtin=object, |
| Options=object, TreeVisitor=object, CythonTransform=object, |
| InternalError=object, error=object, warning=object, |
| fake_rhs_expr=object, TypedExprNode=object) |
|
|
| from . import Builtin |
| from . import ExprNodes |
| from . import Nodes |
| from . import Options |
| from . import PyrexTypes |
|
|
| from .Visitor import TreeVisitor, CythonTransform |
| from .Errors import error, warning, InternalError |
|
|
|
|
| class TypedExprNode(ExprNodes.ExprNode): |
| |
| def __init__(self, type, may_be_none=None, pos=None): |
| super().__init__(pos) |
| self.type = type |
| self._may_be_none = may_be_none |
|
|
| def may_be_none(self): |
| return self._may_be_none != False |
|
|
| |
| fake_rhs_expr = TypedExprNode(PyrexTypes.unspecified_type) |
|
|
|
|
| class ControlBlock: |
| """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 = {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: |
| def __init__(self): |
| self.stats = [] |
|
|
|
|
| class ControlFlow: |
| """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 |
| in_try_block int track if we're in a try...except or try...finally block |
| """ |
|
|
| 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 |
| self.in_try_block = 0 |
|
|
| 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_cython_lock_type or |
| (entry.type.is_cpp_class and not entry.is_cpp_optional))): |
| |
| 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, rhs_scope=None): |
| if self.block and self.is_tracked(entry): |
| assignment = NameAssignment(lhs, rhs, entry, rhs_scope=rhs_scope) |
| 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)) |
| |
| |
| |
| |
| |
| self.entries.add(entry) |
|
|
| def normalize(self): |
| """Delete unreachable and orphan blocks.""" |
| queue = {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: set = self.blocks - visited |
| block: ControlBlock |
| for block in unreachable: |
| block.detach() |
|
|
| visited.remove(self.entry_point) |
|
|
| parent: ControlBlock |
| for block in visited: |
| if block.empty(): |
| for parent in block.parents: |
| 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 = {} |
| assmts: AssignmentList |
| block: ControlBlock |
|
|
| bit: int = 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: AssignmentList = 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) |
|
|
| assmt: NameAssignment |
| for assmt in assmts.stats: |
| if istate & assmt.bit: |
| ret.add(assmt) |
| return ret |
|
|
| def reaching_definitions(self): |
| """Per-block reaching definitions analysis.""" |
| block: ControlBlock |
| parent: ControlBlock |
|
|
| 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: |
| def __init__(self, next_block, loop_block): |
| self.next_block = next_block |
| self.loop_block = loop_block |
| self.exceptions = [] |
|
|
|
|
| class ExceptionDescr: |
| """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: |
| def __init__(self, lhs, rhs, entry, rhs_scope=None): |
| 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 |
| |
| self.rhs_scope = rhs_scope |
|
|
| def __repr__(self): |
| return '%s(entry=%r)' % (self.__class__.__name__, self.entry) |
|
|
| def infer_type(self): |
| self.inferred_type = self.rhs.infer_type(self.rhs_scope or self.entry.scope) |
| return self.inferred_type |
|
|
| def type_dependencies(self): |
| return self.rhs.type_dependencies(self.rhs_scope or 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 |
| lhs = TypedExprNode( |
| entry.type, may_be_none=may_be_none, pos=entry.pos) |
| super().__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 PyrexTypes.py_object_type |
| self.inferred_type = inferred_type |
| return inferred_type |
|
|
|
|
| class Uninitialized: |
| """Definitely not initialised yet.""" |
|
|
|
|
| class Unknown: |
| """Coming from outer closure, might be initialised or not.""" |
|
|
|
|
| class NameReference: |
| 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): |
| |
| |
| |
| |
| |
|
|
| 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 |
| |
| super().__init__( |
| [i for i in state if i.rhs is not fake_rhs_expr]) |
|
|
| def one(self): |
| return self[0] |
|
|
|
|
| class GVContext: |
| """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 srcdescr not 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: |
| """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: |
| """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) |
|
|
|
|
| @cython.cfunc |
| def check_definitions(flow: ControlFlow, compiler_directives: dict): |
| flow.initialize() |
| flow.reaching_definitions() |
|
|
| |
| assignments = set() |
| |
| references = {} |
| assmt_nodes = set() |
|
|
| block: ControlBlock |
| assmt: NameAssignment |
| 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) |
| |
| |
| |
| state.discard(Uninitialized) |
| state.discard(Unknown) |
| for assmt in state: |
| assmt.refs.add(stat) |
|
|
| |
| 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() |
|
|
| |
| 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 |
|
|
| |
| 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 |
| and entry.name not in entry.scope.scope_predefined_names): |
| node.cf_is_null = True |
| if (node.allow_null or entry.from_closure |
| or entry.is_pyclass_attr or entry.type.is_error): |
| pass |
| elif node.cf_is_null and not entry.in_closure: |
| 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: |
| msg = "local variable '%s' might be referenced before assignment" % entry.name |
| if entry.in_closure: |
| msg += " (maybe initialized inside a closure)" |
| messages.warning( |
| node.pos, |
| msg) |
| elif Unknown in node.cf_state: |
| |
| |
| |
| |
| node.cf_maybe_null = True |
| else: |
| node.cf_is_null = False |
| node.cf_maybe_null = False |
|
|
| |
| 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 |
|
|
| |
| 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'): |
| |
| 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().__init__() |
| self.assignments = [] |
|
|
| def visit_Node(self): |
| self._visitchildren(self, None, 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 find_in_stack(self, env): |
| if env == self.env: |
| return self.flow |
| for e, flow in reversed(self.stack): |
| if e is env: |
| return flow |
| assert False |
|
|
| def visit_ModuleNode(self, node): |
| dot_output = self.current_directives['control_flow.dot_output'] |
| self.gv_ctx = GVContext() if dot_output else None |
|
|
| from .Optimize import ConstantFolding |
| self.constant_folder = ConstantFolding() |
|
|
| |
| self.reductions = set() |
|
|
| self.in_inplace_assignment = False |
| self.env = node.scope |
| self.flow = ControlFlow() |
| self.stack = [] |
| self.object_expr = TypedExprNode(PyrexTypes.py_object_type, may_be_none=True) |
| self.visitchildren(node) |
|
|
| check_definitions(self.flow, self.current_directives) |
|
|
| if dot_output: |
| annotate_defs = self.current_directives['control_flow.dot_annotate_defs'] |
| with open(dot_output, 'w') as fp: |
| self.gv_ctx.render(fp, 'module', annotate_defs=annotate_defs) |
| return node |
|
|
| def visit_FuncDefNode(self, node): |
| for arg in node.args: |
| if arg.default: |
| self.visitchildren(arg) |
| self.visitchildren(node, ('decorators',)) |
| self.stack.append((self.env, self.flow)) |
| self.env = node.local_scope |
| self.flow = ControlFlow() |
|
|
| |
| for entry in node.local_scope.entries.values(): |
| if self.flow.is_tracked(entry): |
| self.flow.entries.add(entry) |
|
|
| self.mark_position(node) |
| |
| 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) |
| |
| if node.is_generator: |
| self._visit(node.gbody.body) |
|
|
| |
| if self.flow.block: |
| self.flow.block.add_child(self.flow.exit_point) |
|
|
| |
| self.flow.normalize() |
| check_definitions(self.flow, self.current_directives) |
| self.flow.blocks.add(self.flow.entry_point) |
|
|
| if self.gv_ctx is not None: |
| self.gv_ctx.add(GV(node.local_scope.name, self.flow)) |
|
|
| self.env, self.flow = self.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, rhs_scope=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 = self.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: |
| return |
| self.flow.mark_assignment(lhs, rhs, entry, rhs_scope=rhs_scope) |
| elif lhs.is_sequence_constructor: |
| for i, arg in enumerate(lhs.args): |
| if arg.is_starred: |
| |
| item_node = TypedExprNode(Builtin.list_type, may_be_none=False, pos=arg.pos) |
| elif rhs is self.object_expr: |
| item_node = rhs |
| else: |
| item_node = rhs.inferable_item_node(i) |
| self.mark_assignment(arg, item_node, rhs_scope=rhs_scope) |
| 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 %s" % type(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) |
| 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 |
| |
| 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) |
| |
| 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_AssertStatNode(self, node): |
| """Essentially an if-condition that wraps a RaiseStatNode. |
| """ |
| self.mark_position(node) |
| next_block = self.flow.newblock() |
| parent = self.flow.block |
| |
| parent = self.flow.nextblock(parent) |
| self._visit(node.condition) |
| self.flow.nextblock() |
| self._visit(node.exception) |
| if self.flow.block: |
| self.flow.block.add_child(next_block) |
| 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() |
| |
| self.flow.loops.append(LoopDescr(next_block, condition_block)) |
| if node.condition: |
| self._visit(node.condition) |
| |
| self.flow.nextblock() |
| self._visit(node.body) |
| self.flow.loops.pop() |
| |
| if self.flow.block: |
| self.flow.block.add_child(condition_block) |
| self.flow.block.add_child(next_block) |
| |
| 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): |
| |
| is_special = False |
| sequence = node.iterator.sequence |
| target = node.target |
| env = node.iterator.expr_scope or self.env |
| if isinstance(sequence, ExprNodes.SimpleCallNode): |
| function = sequence.function |
| if sequence.self is None and function.is_name: |
| entry = 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(env) |
| if iterator_type.is_builtin_type: |
| |
| self.mark_assignment( |
| target.args[0], |
| ExprNodes.IntNode(target.pos, value='PY_SSIZE_T_MAX', |
| type=PyrexTypes.c_py_ssize_t_type), |
| rhs_scope=node.iterator.expr_scope) |
| 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 = 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, rhs_scope=node.iterator.expr_scope) |
| if len(sequence.args) > 2: |
| self.mark_assignment(target, self.constant_folder( |
| ExprNodes.binop_node(node.pos, |
| '+', |
| sequence.args[0], |
| sequence.args[2])), |
| rhs_scope=node.iterator.expr_scope) |
|
|
| if not is_special: |
| |
| |
| |
| |
| |
|
|
| self.mark_assignment(target, node.item, rhs_scope=node.iterator.expr_scope) |
|
|
| def mark_parallel_forloop_assignment(self, node): |
| target = node.target |
| for arg in node.args[:2]: |
| self.mark_assignment(target, arg) |
| if len(node.args) > 2: |
| self.mark_assignment(target, self.constant_folder( |
| ExprNodes.binop_node(node.pos, |
| '+', |
| node.args[0], |
| node.args[2]))) |
| if not node.args: |
| |
| self.mark_assignment(target) |
|
|
| 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() |
| |
| self.flow.loops.append(LoopDescr(next_block, condition_block)) |
| self._visit(node.iterator) |
| |
| self.flow.nextblock() |
|
|
| if isinstance(node, Nodes.ForInStatNode): |
| self.mark_forloop_target(node) |
| elif isinstance(node, Nodes.AsyncForStatNode): |
| |
| self.mark_assignment(node.target, node.item) |
| elif isinstance(node, Nodes.ParallelRangeNode): |
| self.mark_parallel_forloop_assignment(node) |
| else: |
| assert False, type(node) |
|
|
| |
| if isinstance(node, Nodes.ParallelRangeNode): |
| |
| self._delete_privates(node, exclude=node.target.entry) |
|
|
| self.flow.nextblock() |
| self._visit(node.body) |
| self.flow.loops.pop() |
|
|
| |
| if self.flow.block: |
| self.flow.block.add_child(condition_block) |
| |
| 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 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() |
| |
| 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) |
| |
| 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))) |
| |
| self.flow.nextblock() |
| self._visit(node.body) |
| self.flow.loops.pop() |
| |
| if self.flow.block: |
| self.flow.block.add_child(condition_block) |
| |
| 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): |
| |
| next_block = self.flow.newblock() |
| |
| self.flow.newblock() |
| |
| entry_point = self.flow.newblock() |
| self.flow.exceptions.append(ExceptionDescr(entry_point)) |
| self.flow.nextblock() |
| |
| |
| self.flow.block.add_child(entry_point) |
| self.flow.nextblock() |
| self.flow.in_try_block += 1 |
| self._visit(node.body) |
| self.flow.in_try_block -= 1 |
| self.flow.exceptions.pop() |
|
|
| |
| 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: |
| |
| 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() |
|
|
| |
| 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) |
|
|
| |
| 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.flow.in_try_block += 1 |
| self._visit(node.body) |
| self.flow.in_try_block -= 1 |
| 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 |
| if self.flow.in_try_block: |
| node.in_try_block = True |
| 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: |
| |
| 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: |
| |
| 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: |
| |
| 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.stack.append((self.env, self.flow)) |
| self.env = node.expr_scope |
| |
| self._visit(node.loop) |
| if node.expr_scope: |
| self.env, _ = self.stack.pop() |
| return node |
|
|
| def visit_ScopedExprNode(self, node): |
| |
| |
| assert isinstance(node, (ExprNodes.IteratorNode, ExprNodes.AsyncIteratorNode)), node |
| if node.expr_scope: |
| self.stack.append((self.env, self.flow)) |
| self.flow = self.find_in_stack(node.expr_scope) |
| self.env = node.expr_scope |
| self.visitchildren(node) |
| if node.expr_scope: |
| self.env, self.flow = self.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.target.name)) |
| self.stack.append((self.env, self.flow)) |
| self.env = node.scope |
| self.flow.nextblock() |
| if node.doc_node: |
| self.flow.mark_assignment(node.doc_node, fake_rhs_expr, node.doc_node.entry) |
| self.visitchildren(node, ('body',)) |
| self.flow.nextblock() |
| self.env, _ = self.stack.pop() |
| return node |
|
|
| def visit_CClassDefNode(self, node): |
| |
| self.stack.append((node.scope, self.flow)) |
| self.visitchildren(node) |
| self.stack.pop() |
| return node |
|
|
| def visit_AmpersandNode(self, node): |
| if node.operand.is_name: |
| |
| self.mark_assignment(node.operand, fake_rhs_expr) |
| self.visitchildren(node) |
| return node |
|
|
| def visit_BoolBinopNode(self, node): |
| |
| assert len(node.subexprs) == 2 |
|
|
| next_block = self.flow.newblock() |
| parent = self.flow.block |
|
|
| self._visit(node.operand1) |
|
|
| self.flow.nextblock() |
| self._visit(node.operand2) |
| if self.flow.block: |
| self.flow.block.add_child(next_block) |
|
|
| parent.add_child(next_block) |
|
|
| if next_block.parents: |
| self.flow.block = next_block |
| else: |
| self.flow.block = None |
| return node |
|
|
| def visit_CondExprNode(self, node): |
| assert len(node.subexprs) == 3 |
| self._visit(node.test) |
| parent = self.flow.block |
| next_block = self.flow.newblock() |
| self.flow.nextblock() |
| self._visit(node.true_val) |
| if self.flow.block: |
| self.flow.block.add_child(next_block) |
| self.flow.nextblock(parent=parent) |
| self._visit(node.false_val) |
| if self.flow.block: |
| self.flow.block.add_child(next_block) |
|
|
| if next_block.parents: |
| self.flow.block = next_block |
| else: |
| self.flow.block = None |
| return node |
|
|