diff --git a/python_graphs/control_flow.py b/python_graphs/control_flow.py index 5853e0c..19110a4 100644 --- a/python_graphs/control_flow.py +++ b/python_graphs/control_flow.py @@ -103,6 +103,17 @@ def get_instructions(self): for node in block.control_flow_nodes: yield node.instruction + def get_start_control_flow_node(self): + if self.start_block.control_flow_nodes: + return self.start_block.control_flow_nodes[0] + if self.start_block.exits_from_end: + assert len(self.start_block.exits_from_end) == 1 + first_block = next(iter(self.start_block.exits_from_end)) + if first_block.control_flow_nodes: + return first_block.control_flow_nodes[0] + else: + return first_block.label + def get_control_flow_nodes_by_ast_node(self, node): return six.moves.filter( lambda control_flow_node: control_flow_node.instruction.node == node, @@ -503,7 +514,10 @@ def __init__(self, graph, block, instruction): @property def next(self): - """Returns the set of possible next instructions.""" + """Returns the set of possible next instructions. + + This allows for taking exits from the middle (exceptions). + """ if self.block is None: return None index_in_block = self.block.index_of(self) @@ -520,6 +534,29 @@ def next(self): assert not next_block.next return control_flow_nodes + @property + def next_from_end(self): + """Returns the set of possible next instructions. + + This does not allow for taking exits from the middle (exceptions). + """ + if self.block is None: + return None + index_in_block = self.block.index_of(self) + if len(self.block.control_flow_nodes) > index_in_block + 1: + return {self.block.control_flow_nodes[index_in_block + 1]} + control_flow_nodes = set() + for next_block in self.block.exits_from_end: + if next_block.control_flow_nodes: + control_flow_nodes.add(next_block.control_flow_nodes[0]) + else: + # If next_block is empty, it isn't the case that some downstream block + # is nonempty. This is guaranteed by the pruning phase of control flow + # graph construction. + assert not next_block.next + control_flow_nodes.add(next_block.label) + return control_flow_nodes + @property def prev(self): """Returns the set of possible previous instructions.""" @@ -562,9 +599,9 @@ def get_branches(self, include_except_branches=False, include_reraise_branches=F A dictionary with possible keys True and False, and values given by the node that is reached by taking the True/False branch. An empty dictionary indicates that there are no branches to take, and so self.next gives the - next node (in a set of size 1). A value of None indicates that taking that - branch leads to the exit, since there are no exit ControlFlowNodes in a - ControlFlowGraph. + next node (in a set of size 1). A value of '' or '' indicates that + taking that branch leads to the exit or raise block, since there are no exit + ControlFlowNodes in a ControlFlowGraph. """ if self.block is None: return {} # We're not in a block. No branch decision. @@ -588,7 +625,7 @@ def get_branches(self, include_except_branches=False, include_reraise_branches=F # is nonempty. This is guaranteed by the pruning phase of control flow # graph construction. assert not next_block.next - branches[key] = None # Indicates exit; there is no node to return. + branches[key] = next_block.label # Indicates exit or raise; there is no node to return. return branches def has_label(self, label): @@ -992,6 +1029,8 @@ def handle_argument_defaults(self, node, current_block): for default in node.defaults: self.add_new_instruction(current_block, default) for default in node.kw_defaults: + if default is None: + continue self.add_new_instruction(current_block, default) return current_block @@ -1148,8 +1187,6 @@ def visit_Try(self, node, current_block): bare_handler_block = None if node.finalbody: - # TODO(dbieber): Move final_block and all blocks from visiting it - # to after try blocks. final_block = self.new_block(node=node, label='final_block') final_block_end = self.visit_list(node.finalbody, final_block) # "False" indicates the path taken after finally if no error has been raised. @@ -1343,11 +1380,12 @@ def visit_Raise(self, node, current_block): after_block: An unreachable block for code that follows the raise statement. """ - del current_block + self.raise_through_frames(current_block, interrupting=False) # The Raise statement is an Instruction. Don't visit children. # Note there is no exit to the after_block. It is unreachable. after_block = self.new_block(node=node, label='after_block') + return after_block def handle_ExitStatement(self, node, next_block, try_finally_frames, diff --git a/setup.py b/setup.py index ab4d796..e597fd1 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ TEST_DEPENDENCIES = [ ] -VERSION = '1.2.1' +VERSION = '1.2.3' URL = 'https://github.com/google-research/python-graphs' setup(