Attempt at a fix for issue #48.

This commit is contained in:
Jon Riehl 2013-01-28 20:08:09 -06:00
commit ffc067f852
6 changed files with 132 additions and 44 deletions

View file

@ -5,13 +5,13 @@ import opcode
import opcode_util
import pprint
from bytecode_visitor import BytecodeFlowVisitor, BenignBytecodeVisitorMixin
from bytecode_visitor import BasicBlockVisitor, BenignBytecodeVisitorMixin
from control_flow import ControlFlowGraph
# ______________________________________________________________________
class ControlFlowBuilder (BenignBytecodeVisitorMixin, BytecodeFlowVisitor):
'''Visitor responsible for traversing a bytecode flow object and
class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
'''Visitor responsible for traversing a bytecode basic block map and
building a control flow graph (CFG).
The primary purpose of this transformation is to create a CFG,
@ -27,21 +27,26 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BytecodeFlowVisitor):
del self.nargs
return ret_val
def enter_flow_object (self, flow):
super(ControlFlowBuilder, self).enter_flow_object(flow)
self.flow = flow
def enter_blocks (self, blocks):
super(ControlFlowBuilder, self).enter_blocks(blocks)
self.blocks = blocks
self.block_list = list(blocks.keys())
self.block_list.sort()
self.cfg = ControlFlowGraph()
for block in flow.keys():
self.cfg.add_block(block, flow[block])
self.loop_stack = []
for block in self.block_list:
self.cfg.add_block(block, blocks[block])
def exit_flow_object (self, flow):
super(ControlFlowBuilder, self).exit_flow_object(flow)
assert self.flow == flow
def exit_blocks (self, blocks):
super(ControlFlowBuilder, self).exit_blocks(blocks)
assert self.blocks == blocks
self.cfg.compute_dataflow()
self.cfg.update_for_ssa()
ret_val = self.cfg
del self.loop_stack
del self.cfg
del self.flow
del self.block_list
del self.blocks
return ret_val
def enter_block (self, block):
@ -58,13 +63,15 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BytecodeFlowVisitor):
def exit_block (self, block):
assert block == self.block
del self.block
i, op, opname, arg, args = self.flow[block][-1]
i, op, arg = self.blocks[block][-1]
opname = opcode.opname[op]
if op in opcode.hasjabs:
self.cfg.add_edge(block, arg)
elif op in opcode.hasjrel:
self.cfg.add_edge(block, i + arg + 3)
elif opname == 'BREAK_LOOP':
self.cfg.add_edge(block, arg)
loop_i, _, loop_arg = self.loop_stack[-1]
self.cfg.add_edge(block, loop_i + loop_arg + 3)
elif opname != 'RETURN_VALUE':
self.cfg.add_edge(block, self._get_next_block(block))
if op in opcode_util.hascbranch:
@ -80,15 +87,24 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BytecodeFlowVisitor):
return super(ControlFlowBuilder, self).op_STORE_FAST(i, op, arg, *args,
**kws)
def op_SETUP_LOOP (self, i, op, arg, *args, **kws):
self.loop_stack.append((i, op, arg))
return super(ControlFlowBuilder, self).op_SETUP_LOOP(i, op, arg, *args,
**kws)
def op_POP_BLOCK (self, i, op, arg, *args, **kws):
self.loop_stack.pop()
return super(ControlFlowBuilder, self).op_POP_BLOCK(i, op, arg, *args,
**kws)
# ______________________________________________________________________
def build_cfg (func):
'''Given a Python function, create a bytecode flow, visit the flow
object, and return a control flow graph.'''
import byte_flow
return ControlFlowBuilder().visit(
byte_flow.build_flow(func),
opcode_util.get_code_object(func).co_argcount)
co_obj = opcode_util.get_code_object(func)
return ControlFlowBuilder().visit(opcode_util.build_basic_blocks(co_obj),
co_obj.co_argcount)
# ______________________________________________________________________
# Main (self-test) routine