Skip to content
This repository has been archived by the owner on Oct 14, 2024. It is now read-only.

Commit

Permalink
get_start_control_flow_node, next_from_end, raise edges, and labels i…
Browse files Browse the repository at this point in the history
…n branches (#6)

* Adds get_start_control_flow_node to ControlFlowGraph
* Adds next_from_end to ControlFlowNode
* Uses labels (e.g. '<exit>' and '<raise>' strings) to indicate these special nodes
* Support keyword only arguments without defaults
* Add non-interrupting edges from raise statements
* Bump version number
  • Loading branch information
dbieber authored Oct 7, 2021
1 parent 6bdce0d commit 44c15b9
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 9 deletions.
54 changes: 46 additions & 8 deletions python_graphs/control_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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."""
Expand Down Expand Up @@ -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 '<exit>' or '<raise>' 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.
Expand All @@ -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):
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
TEST_DEPENDENCIES = [
]

VERSION = '1.2.1'
VERSION = '1.2.3'
URL = 'https://github.com/google-research/python-graphs'

setup(
Expand Down

0 comments on commit 44c15b9

Please sign in to comment.