More work on CFA for try-finally in the presence of loops.
This commit is contained in:
parent
8ae1b70206
commit
c5d95e01e1
3 changed files with 288 additions and 35 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
#! /usr/bin/env python
|
#! /usr/bin/env python
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import opcode
|
import opcode
|
||||||
from . import opcode_util
|
from . import opcode_util
|
||||||
|
|
@ -13,9 +14,10 @@ from .control_flow import ControlFlowGraph
|
||||||
|
|
||||||
# The following opcodes branch based on the control (a.k.a. frame)
|
# The following opcodes branch based on the control (a.k.a. frame)
|
||||||
# stack:
|
# stack:
|
||||||
RETURN_VALUE, CONTINUE_LOOP, BREAK_LOOP, END_FINALLY = (
|
RETURN_VALUE, CONTINUE_LOOP, BREAK_LOOP, END_FINALLY, RAISE_VARARGS = (
|
||||||
opcode.opmap[opname] for opname in (
|
opcode.opmap[opname] for opname in (
|
||||||
'RETURN_VALUE', 'CONTINUE_LOOP', 'BREAK_LOOP', 'END_FINALLY'))
|
'RETURN_VALUE', 'CONTINUE_LOOP', 'BREAK_LOOP', 'END_FINALLY',
|
||||||
|
'RAISE_VARARGS'))
|
||||||
|
|
||||||
# The following opcodes push a new frame on the control stack:
|
# The following opcodes push a new frame on the control stack:
|
||||||
SETUP_EXCEPT, SETUP_FINALLY, SETUP_LOOP, SETUP_WITH = (
|
SETUP_EXCEPT, SETUP_FINALLY, SETUP_LOOP, SETUP_WITH = (
|
||||||
|
|
@ -24,7 +26,11 @@ SETUP_EXCEPT, SETUP_FINALLY, SETUP_LOOP, SETUP_WITH = (
|
||||||
|
|
||||||
WHY_NOT = 1
|
WHY_NOT = 1
|
||||||
WHY_EXCEPTION = WHY_NOT << 1
|
WHY_EXCEPTION = WHY_NOT << 1
|
||||||
WHY_RERAISE = WHY_EXCEPTION << 1
|
WHY_RERAISE = WHY_EXCEPTION << 1 # We don't worry about this code
|
||||||
|
# during CFA, since its primary use
|
||||||
|
# is to log traceback information;
|
||||||
|
# WHY_RERAISE's bytecode control flow
|
||||||
|
# is the same as WHY_EXCEPTION.
|
||||||
WHY_RETURN = WHY_RERAISE << 1
|
WHY_RETURN = WHY_RERAISE << 1
|
||||||
WHY_BREAK = WHY_RETURN << 1
|
WHY_BREAK = WHY_RETURN << 1
|
||||||
WHY_CONTINUE = WHY_BREAK << 1
|
WHY_CONTINUE = WHY_BREAK << 1
|
||||||
|
|
@ -41,10 +47,10 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
which is used by later transformers for dataflow analysis.
|
which is used by later transformers for dataflow analysis.
|
||||||
'''
|
'''
|
||||||
def visit (self, flow, nargs = 0, *args, **kws):
|
def visit (self, flow, nargs = 0, *args, **kws):
|
||||||
'''Given a bytecode flow, and an optional number of arguments,
|
'''Given a map of bytecode basic blocks, and an optional
|
||||||
return a :py:class:`llpython.control_flow.ControlFlowGraph`
|
number of arguments, return a
|
||||||
instance describing the full control flow of the bytecode
|
:py:class:`llpython.control_flow.ControlFlowGraph` instance
|
||||||
flow.'''
|
describing the full control flow of the bytecode flow.'''
|
||||||
self.nargs = nargs
|
self.nargs = nargs
|
||||||
ret_val = super(ControlFlowBuilder, self).visit(flow, *args, **kws)
|
ret_val = super(ControlFlowBuilder, self).visit(flow, *args, **kws)
|
||||||
del self.nargs
|
del self.nargs
|
||||||
|
|
@ -57,6 +63,13 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
self.block_list.sort()
|
self.block_list.sort()
|
||||||
self.cfg = ControlFlowGraph()
|
self.cfg = ControlFlowGraph()
|
||||||
self.control_stack = []
|
self.control_stack = []
|
||||||
|
self.continue_targets = {} # Map from SETUP_LOOP addresses to
|
||||||
|
# start of loop addresses, based on
|
||||||
|
# observed CONTINUE_LOOP opcodes.
|
||||||
|
self.break_targets = set() # Set of SETUP_LOOP address that
|
||||||
|
# have at least one observed
|
||||||
|
# BREAK_LOOP opcode corresponding
|
||||||
|
# to them.
|
||||||
for block in self.block_list:
|
for block in self.block_list:
|
||||||
self.cfg.add_block(block, blocks[block])
|
self.cfg.add_block(block, blocks[block])
|
||||||
|
|
||||||
|
|
@ -66,6 +79,7 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
self.cfg.compute_dataflow()
|
self.cfg.compute_dataflow()
|
||||||
self.cfg.update_for_ssa()
|
self.cfg.update_for_ssa()
|
||||||
ret_val = self.cfg
|
ret_val = self.cfg
|
||||||
|
del self.continue_targets
|
||||||
del self.control_stack
|
del self.control_stack
|
||||||
del self.cfg
|
del self.cfg
|
||||||
del self.block_list
|
del self.block_list
|
||||||
|
|
@ -91,29 +105,37 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
|
|
||||||
Returns True if an edge was added to the CFG, False otherwise.
|
Returns True if an edge was added to the CFG, False otherwise.
|
||||||
Based on the opcode the return result may mean different
|
Based on the opcode the return result may mean different
|
||||||
things (for example: if why == WHY_RETURN, then the function
|
things (for example: if why == WHY_RETURN, then a False return
|
||||||
returns)."""
|
result means the function returned, and no edge was
|
||||||
|
generated)."""
|
||||||
ret_val = False
|
ret_val = False
|
||||||
if len(self.control_stack) > 0:
|
if len(self.control_stack) > 0:
|
||||||
handlers = set((SETUP_FINALLY, SETUP_WITH))
|
handlers = set((SETUP_FINALLY, SETUP_WITH))
|
||||||
if why == WHY_EXCEPTION:
|
if why == WHY_EXCEPTION:
|
||||||
handlers.add(SETUP_EXCEPT)
|
handlers.add(SETUP_EXCEPT)
|
||||||
reverse_stack = self.control_stack[::-1]
|
reversed_stack = reversed(self.control_stack)
|
||||||
target = None
|
target = None
|
||||||
for handler_i, handler_op, handler_arg in reverse_stack:
|
for handler_i, handler_op, handler_arg in reversed_stack:
|
||||||
if handler_op in handlers:
|
if handler_op in handlers:
|
||||||
target = handler_i + handler_arg + 3
|
target = handler_i + handler_arg + 3
|
||||||
elif handler_op == SETUP_LOOP:
|
elif handler_op == SETUP_LOOP:
|
||||||
if why == WHY_CONTINUE:
|
if why == WHY_CONTINUE:
|
||||||
if op == CONTINUE_LOOP:
|
# Only generate a WHY_CONTINUE edge if a continue
|
||||||
target = i + arg + 3
|
# statement has been observed for this loop.
|
||||||
|
if handler_i not in self.continue_targets:
|
||||||
|
break
|
||||||
|
elif op == CONTINUE_LOOP:
|
||||||
|
target = arg
|
||||||
|
assert target == self.continue_targets[handler_i]
|
||||||
else:
|
else:
|
||||||
# XXX This isn't going to be correct for
|
target = self.continue_targets[handler_i]
|
||||||
# for-loops, or really long while-loops
|
|
||||||
# (which use EXTENDED_ARG):
|
|
||||||
target = handler_i + 3
|
|
||||||
elif why == WHY_BREAK:
|
elif why == WHY_BREAK:
|
||||||
target = handler_i + handler_arg + 3
|
# Only generate a WHY_BREAK edge if a break
|
||||||
|
# statement has been observed for this loop.
|
||||||
|
if handler_i not in self.break_targets:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
target = handler_i + handler_arg + 3
|
||||||
if target is not None:
|
if target is not None:
|
||||||
self.cfg.add_edge(block, target)
|
self.cfg.add_edge(block, target)
|
||||||
ret_val = True
|
ret_val = True
|
||||||
|
|
@ -137,11 +159,12 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
WHY_BREAK)
|
WHY_BREAK)
|
||||||
assert branched, ("Attempted to break outside of loop %r" %
|
assert branched, ("Attempted to break outside of loop %r" %
|
||||||
(self.blocks[block][-1],))
|
(self.blocks[block][-1],))
|
||||||
|
elif op == RAISE_VARARGS:
|
||||||
|
self._generate_handler_edge(block, i, op, arg, WHY_EXCEPTION)
|
||||||
elif op == END_FINALLY:
|
elif op == END_FINALLY:
|
||||||
# XXX Should we detect cases where return, continue, and
|
# The following does a lot of redundant traversal of the
|
||||||
# break appear inside the try-block? This would create
|
# simulated frame stack, but it works, and keeps a lot of
|
||||||
# more accurate control flow graphs by eliding edges we
|
# special case logic out of _generate_handler_edge().
|
||||||
# know won't be taken.
|
|
||||||
self._generate_handler_edge(block, i, op, arg, WHY_EXCEPTION)
|
self._generate_handler_edge(block, i, op, arg, WHY_EXCEPTION)
|
||||||
self._generate_handler_edge(block, i, op, arg, WHY_RETURN)
|
self._generate_handler_edge(block, i, op, arg, WHY_RETURN)
|
||||||
self._generate_handler_edge(block, i, op, arg, WHY_BREAK)
|
self._generate_handler_edge(block, i, op, arg, WHY_BREAK)
|
||||||
|
|
@ -156,6 +179,9 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
if op in opcode_util.hascbranch or goto_next:
|
if op in opcode_util.hascbranch or goto_next:
|
||||||
self.cfg.add_edge(block, self._get_next_block(block))
|
self.cfg.add_edge(block, self._get_next_block(block))
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
# LOAD/STORE_FAST
|
||||||
|
|
||||||
def op_LOAD_FAST (self, i, op, arg, *args, **kws):
|
def op_LOAD_FAST (self, i, op, arg, *args, **kws):
|
||||||
self.cfg.blocks_reads[self.block].add(arg)
|
self.cfg.blocks_reads[self.block].add(arg)
|
||||||
return super(ControlFlowBuilder, self).op_LOAD_FAST(i, op, arg, *args,
|
return super(ControlFlowBuilder, self).op_LOAD_FAST(i, op, arg, *args,
|
||||||
|
|
@ -166,6 +192,44 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
return super(ControlFlowBuilder, self).op_STORE_FAST(i, op, arg, *args,
|
return super(ControlFlowBuilder, self).op_STORE_FAST(i, op, arg, *args,
|
||||||
**kws)
|
**kws)
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
# *_LOOP: Special loop control flow.
|
||||||
|
|
||||||
|
def _get_current_loop (self):
|
||||||
|
for handler in reversed(self.control_stack):
|
||||||
|
if handler[1] == SETUP_LOOP:
|
||||||
|
return handler
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
def op_BREAK_LOOP (self, i, op, arg, *args, **kws):
|
||||||
|
handler_i, _, _ = self._get_current_loop()
|
||||||
|
assert handler_i is not None
|
||||||
|
self.break_targets.add(handler_i)
|
||||||
|
|
||||||
|
def op_CONTINUE_LOOP (self, i, op, arg, *args, **kws):
|
||||||
|
"""
|
||||||
|
CONTINUE_LOOP has to be handled differently than BREAK_LOOP,
|
||||||
|
since its argument specifies where the start of the loop is
|
||||||
|
(in the case of for-loops, FOR_ITER defines the true start of
|
||||||
|
the loop, instead of SETUP_LOOP.)
|
||||||
|
"""
|
||||||
|
handler_i, _, _ = self._get_current_loop()
|
||||||
|
assert handler_i is not None
|
||||||
|
if handler_i in self.continue_targets:
|
||||||
|
assert arg == self.continue_targets[handler_i]
|
||||||
|
else:
|
||||||
|
self.continue_targets[handler_i] = arg
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
# POP_BLOCK
|
||||||
|
|
||||||
|
def op_POP_BLOCK (self, i, op, arg, *args, **kws):
|
||||||
|
self.control_stack.pop()
|
||||||
|
return super(ControlFlowBuilder, self).op_POP_BLOCK(i, op, arg, *args,
|
||||||
|
**kws)
|
||||||
|
# ____________________________________________________________
|
||||||
|
# SETUP_*
|
||||||
|
|
||||||
def op_SETUP_EXCEPT (self, i, op, arg, *args, **kws):
|
def op_SETUP_EXCEPT (self, i, op, arg, *args, **kws):
|
||||||
self.control_stack.append((i, op, arg))
|
self.control_stack.append((i, op, arg))
|
||||||
return super(ControlFlowBuilder, self).op_SETUP_EXCEPT(i, op, arg,
|
return super(ControlFlowBuilder, self).op_SETUP_EXCEPT(i, op, arg,
|
||||||
|
|
@ -186,11 +250,6 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||||
return super(ControlFlowBuilder, self).op_SETUP_WITH(i, op, arg, *args,
|
return super(ControlFlowBuilder, self).op_SETUP_WITH(i, op, arg, *args,
|
||||||
**kws)
|
**kws)
|
||||||
|
|
||||||
def op_POP_BLOCK (self, i, op, arg, *args, **kws):
|
|
||||||
self.control_stack.pop()
|
|
||||||
return super(ControlFlowBuilder, self).op_POP_BLOCK(i, op, arg, *args,
|
|
||||||
**kws)
|
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
|
|
||||||
def build_cfg (func):
|
def build_cfg (func):
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ import opcode
|
||||||
|
|
||||||
# Note that opcode.hasjrel and opcode.hasjabs applies only to opcodes
|
# Note that opcode.hasjrel and opcode.hasjabs applies only to opcodes
|
||||||
# that calculate a jump point based on the argument. This ignores
|
# that calculate a jump point based on the argument. This ignores
|
||||||
# jumps that use the frame stack to calculate their targets.
|
# jumps that use the frame stack to calculate their targets, and
|
||||||
|
# exceptions.
|
||||||
|
|
||||||
NON_ARG_JUMP_NAMES = 'BREAK_LOOP', 'RETURN_VALUE', 'END_FINALLY'
|
NON_ARG_JUMP_NAMES = ('BREAK_LOOP', 'RETURN_VALUE', 'END_FINALLY',
|
||||||
|
'RAISE_VARARGS')
|
||||||
NON_ARG_JUMPS = [opcode.opmap[opname]
|
NON_ARG_JUMPS = [opcode.opmap[opname]
|
||||||
for opname in NON_ARG_JUMP_NAMES
|
for opname in NON_ARG_JUMP_NAMES
|
||||||
if opname in opcode.opmap]
|
if opname in opcode.opmap]
|
||||||
|
|
@ -118,7 +120,7 @@ OPCODE_MAP = {
|
||||||
'PRINT_ITEM_TO': (2, None, 1),
|
'PRINT_ITEM_TO': (2, None, 1),
|
||||||
'PRINT_NEWLINE': (0, None, 1),
|
'PRINT_NEWLINE': (0, None, 1),
|
||||||
'PRINT_NEWLINE_TO': (1, None, 1),
|
'PRINT_NEWLINE_TO': (1, None, 1),
|
||||||
'RAISE_VARARGS': (None, None, None),
|
'RAISE_VARARGS': (-1, None, 1),
|
||||||
'RETURN_VALUE': (1, None, 1),
|
'RETURN_VALUE': (1, None, 1),
|
||||||
'ROT_FOUR': (None, None, None),
|
'ROT_FOUR': (None, None, None),
|
||||||
'ROT_THREE': (None, None, None),
|
'ROT_THREE': (None, None, None),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from llpython import byte_control
|
from llpython import byte_control
|
||||||
|
|
@ -10,6 +11,11 @@ from llpython import byte_control
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
# Global data
|
# Global data
|
||||||
|
|
||||||
|
# Technically we could also compute if we need special logic for the
|
||||||
|
# old bytecode compiler by scanning for JUMP_IF_TRUE and JUMP_IF_FALSE
|
||||||
|
# opcodes. These opcodes require additional POP_TOP's be inserted.
|
||||||
|
OLD_BYTECODE_COMPILER = sys.version_info < (2, 7)
|
||||||
|
|
||||||
got_done = 0
|
got_done = 0
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
|
|
@ -19,6 +25,7 @@ def do_something():
|
||||||
global got_done
|
global got_done
|
||||||
got_done += 1
|
got_done += 1
|
||||||
print("Something good got done.")
|
print("Something good got done.")
|
||||||
|
return got_done
|
||||||
|
|
||||||
# ____________________________________________________________
|
# ____________________________________________________________
|
||||||
|
|
||||||
|
|
@ -47,6 +54,62 @@ def try_finally_1(m, n): # why == WHY_BREAK
|
||||||
do_something()
|
do_something()
|
||||||
return i
|
return i
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
|
||||||
|
def try_finally_2(m, n): # why == WHY_CONTINUE
|
||||||
|
i = m
|
||||||
|
while i < n:
|
||||||
|
try:
|
||||||
|
if i == 101:
|
||||||
|
i += 200
|
||||||
|
continue
|
||||||
|
finally:
|
||||||
|
do_something()
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
|
||||||
|
def try_finally_3(m, n): # why == WHY_EXCEPTION (or WHY_RETURN)
|
||||||
|
d = {}
|
||||||
|
try:
|
||||||
|
return d[n] - d[m]
|
||||||
|
finally:
|
||||||
|
do_something()
|
||||||
|
return do_something_else()
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
|
||||||
|
def try_finally_4(m, n): # why == WHY_NOT
|
||||||
|
try:
|
||||||
|
rv = n - m
|
||||||
|
finally:
|
||||||
|
do_something()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
|
||||||
|
def try_finally_5(m, n):
|
||||||
|
for i in range(m, n):
|
||||||
|
try:
|
||||||
|
if i == 99:
|
||||||
|
break
|
||||||
|
elif i == 121:
|
||||||
|
continue
|
||||||
|
elif i == 86:
|
||||||
|
return
|
||||||
|
elif i < -102:
|
||||||
|
raise ValueError(i)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if i < 0:
|
||||||
|
raise ValueError(i)
|
||||||
|
finally:
|
||||||
|
do_something()
|
||||||
|
finally:
|
||||||
|
do_something()
|
||||||
|
return do_something()
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
# Class (test case) definition(s)
|
# Class (test case) definition(s)
|
||||||
|
|
||||||
|
|
@ -55,22 +118,151 @@ class TestByteControl(unittest.TestCase):
|
||||||
assert len(test_cfg.blocks) == block_count
|
assert len(test_cfg.blocks) == block_count
|
||||||
block_keys = list(test_cfg.blocks.keys())
|
block_keys = list(test_cfg.blocks.keys())
|
||||||
block_keys.sort()
|
block_keys.sort()
|
||||||
# TODO: Ensure unexpected edges cause error.
|
expected_blocks_in = dict((block_key, set())
|
||||||
|
for block_key in block_keys)
|
||||||
|
expected_blocks_out = dict((block_key, set())
|
||||||
|
for block_key in block_keys)
|
||||||
for from_block_ofs, to_block_ofs in edges:
|
for from_block_ofs, to_block_ofs in edges:
|
||||||
from_block = block_keys[from_block_ofs]
|
from_block = block_keys[from_block_ofs]
|
||||||
to_block = block_keys[to_block_ofs]
|
to_block = block_keys[to_block_ofs]
|
||||||
assert from_block in test_cfg.blocks_in[to_block]
|
expected_blocks_in[to_block].add(from_block)
|
||||||
assert to_block in test_cfg.blocks_out[from_block]
|
expected_blocks_out[from_block].add(to_block)
|
||||||
|
for block_key in block_keys:
|
||||||
|
expected_in = expected_blocks_in[block_key]
|
||||||
|
test_in = test_cfg.blocks_in[block_key]
|
||||||
|
self.assertEqual(
|
||||||
|
expected_in, test_in, '%r != %r for blocks_in[%d]' % (
|
||||||
|
test_in, expected_in, block_key))
|
||||||
|
expected_out = expected_blocks_out[block_key]
|
||||||
|
test_out = test_cfg.blocks_out[block_key]
|
||||||
|
self.assertEqual(
|
||||||
|
expected_out, test_out, '%r != %r for set blocks_out[%d]' % (
|
||||||
|
test_out, expected_out, block_key))
|
||||||
|
|
||||||
|
def test_raise(self):
|
||||||
|
cfg = byte_control.build_cfg(do_something_else)
|
||||||
|
self.fail_unless_cfg_match(cfg, 2, ())
|
||||||
|
|
||||||
def test_try_finally_0(self):
|
def test_try_finally_0(self):
|
||||||
|
"""
|
||||||
|
Expected CFG (Python 2.7+):
|
||||||
|
digraph CFG_try_finally_0 {
|
||||||
|
BLOCK_0 -> BLOCK_3; // 0 -> 1
|
||||||
|
BLOCK_0 -> BLOCK_15; // 0 -> 3
|
||||||
|
BLOCK_3 -> BLOCK_15; // 1 -> 3
|
||||||
|
BLOCK_11 -> BLOCK_15; // 2 -> 3
|
||||||
|
BLOCK_15 -> BLOCK_23; // 3 -> 4, why == WHY_NOT
|
||||||
|
BLOCK_23; // 4
|
||||||
|
}
|
||||||
|
(Possibly terminal blocks: 15, 23.)
|
||||||
|
"""
|
||||||
cfg = byte_control.build_cfg(try_finally_0)
|
cfg = byte_control.build_cfg(try_finally_0)
|
||||||
self.fail_unless_cfg_match(cfg, 5, ((0, 1), (0, 3), (1, 3), (2, 3),
|
self.fail_unless_cfg_match(cfg, 5, ((0, 1), (0, 3), (1, 3), (2, 3),
|
||||||
(3, 4)))
|
(3, 4)))
|
||||||
|
|
||||||
def test_try_finally_1(self):
|
def test_try_finally_1(self):
|
||||||
|
"""
|
||||||
|
Expected CFG (Python 2.7+):
|
||||||
|
digraph CFG_try_finally_1 {
|
||||||
|
BLOCK_0 -> BLOCK_9; // 0 -> 1
|
||||||
|
BLOCK_0 -> BLOCK_63; // 0 -> 11
|
||||||
|
BLOCK_9 -> BLOCK_22; // 1 -> 2
|
||||||
|
BLOCK_22 -> BLOCK_25; // 2 -> 3
|
||||||
|
BLOCK_22 -> BLOCK_62; // 2 -> 10
|
||||||
|
BLOCK_25 -> BLOCK_31; // 3 -> 4
|
||||||
|
BLOCK_25 -> BLOCK_51; // 3 -> 8
|
||||||
|
BLOCK_31 -> BLOCK_43; // 4 -> 5
|
||||||
|
BLOCK_31 -> BLOCK_47; // 4 -> 7
|
||||||
|
BLOCK_43 -> BLOCK_51; // 5 -> 8
|
||||||
|
BLOCK_44 -> BLOCK_47; // 6 -> 7
|
||||||
|
BLOCK_47 -> BLOCK_51; // 7 -> 8
|
||||||
|
BLOCK_51 -> BLOCK_59; // 8 -> 9, why == WHY_NOT
|
||||||
|
BLOCK_51 -> BLOCK_63; // 8 -> 11, why == WHY_BREAK, WHY_RETURN, ...
|
||||||
|
BLOCK_59 -> BLOCK_22; // 9 -> 2
|
||||||
|
BLOCK_62 -> BLOCK_63; // 10 -> 11
|
||||||
|
BLOCK_63; // 11
|
||||||
|
}
|
||||||
|
(Possibly terminal blocks: 51, 63.)
|
||||||
|
"""
|
||||||
cfg = byte_control.build_cfg(try_finally_1)
|
cfg = byte_control.build_cfg(try_finally_1)
|
||||||
# TODO: Translate known graph to offsets...
|
if not OLD_BYTECODE_COMPILER:
|
||||||
self.fail_unless_cfg_match(cfg, 12, ())
|
self.fail_unless_cfg_match(
|
||||||
|
cfg, 12, ((0, 1), (0, 11), (1, 2), (2, 3), (2, 10), (3, 4),
|
||||||
|
(3, 8), (4, 5), (4, 7), (5, 8), (6, 7), (7, 8),
|
||||||
|
(8, 9), (8, 11), (9, 2), (10, 11)))
|
||||||
|
else:
|
||||||
|
self.fail_unless_cfg_match(
|
||||||
|
cfg, 13, ((0, 1), (0, 12), (1, 2), (2, 3), (2, 11), (3, 4),
|
||||||
|
(3, 9), (4, 5), (4, 7), (5, 9), (6, 8), (7, 8),
|
||||||
|
(8, 9), (9, 10), (9, 12), (10, 2), (11, 12)))
|
||||||
|
|
||||||
|
def test_try_finally_2(self):
|
||||||
|
"""
|
||||||
|
Expected CFG (Python 2.7+):
|
||||||
|
digraph CFG_try_finally_2 {
|
||||||
|
BLOCK_0 -> BLOCK_9; // 0 -> 1
|
||||||
|
BLOCK_0 -> BLOCK_78; // 0 -> 10
|
||||||
|
BLOCK_9 -> BLOCK_21; // 1 -> 2
|
||||||
|
BLOCK_9 -> BLOCK_77; // 1 -> 9
|
||||||
|
BLOCK_21 -> BLOCK_24; // 2 -> 3
|
||||||
|
BLOCK_21 -> BLOCK_56; // 2 -> 7
|
||||||
|
BLOCK_24 -> BLOCK_36; // 3 -> 4
|
||||||
|
BLOCK_24 -> BLOCK_52; // 3 -> 6
|
||||||
|
BLOCK_36 -> BLOCK_56; // 4 -> 7
|
||||||
|
BLOCK_49 -> BLOCK_52; // 5 -> 6
|
||||||
|
BLOCK_52 -> BLOCK_56; // 6 -> 7
|
||||||
|
BLOCK_56 -> BLOCK_9; // 7 -> 1, why == WHY_CONTINUE
|
||||||
|
BLOCK_56 -> BLOCK_64; // 7 -> 8, why == WHY_NOT
|
||||||
|
BLOCK_64 -> BLOCK_9; // 8 -> 1
|
||||||
|
BLOCK_77 -> BLOCK_78; // 9 -> 10
|
||||||
|
BLOCK_78; // 10
|
||||||
|
}
|
||||||
|
(Possibly terminal blocks: 56, 78.)
|
||||||
|
"""
|
||||||
|
cfg = byte_control.build_cfg(try_finally_2)
|
||||||
|
if not OLD_BYTECODE_COMPILER:
|
||||||
|
self.fail_unless_cfg_match(
|
||||||
|
cfg, 11, ((0, 1), (0, 10), (1, 2), (1, 9), (2, 3), (2, 7),
|
||||||
|
(3, 4), (3, 6), (4, 7), (5, 6), (6, 7), (7, 1),
|
||||||
|
(7, 8), (8, 1), (9, 10)))
|
||||||
|
|
||||||
|
def test_try_finally_3(self):
|
||||||
|
"""
|
||||||
|
Expected (Python 2.7+):
|
||||||
|
digraph CFG_foo3 {
|
||||||
|
BLOCK_0 -> BLOCK_9; // 0 -> 1
|
||||||
|
BLOCK_0 -> BLOCK_29; // 0 -> 3
|
||||||
|
BLOCK_9 -> BLOCK_29; // 1 -> 3
|
||||||
|
BLOCK_25 -> BLOCK_29; // 2 -> 3
|
||||||
|
BLOCK_29 -> BLOCK_37; // 3 -> 4, why == WHY_NOT
|
||||||
|
BLOCK_37; // 4
|
||||||
|
}
|
||||||
|
(Possibly terminal blocks: 29, 37.)
|
||||||
|
"""
|
||||||
|
cfg = byte_control.build_cfg(try_finally_3)
|
||||||
|
self.fail_unless_cfg_match(cfg, 5, ((0, 1), (0, 3), (1, 3), (2, 3),
|
||||||
|
(3, 4)))
|
||||||
|
|
||||||
|
def test_try_finally_4(self):
|
||||||
|
"""
|
||||||
|
Expected:
|
||||||
|
digraph CFG_foo4 {
|
||||||
|
BLOCK_0 -> BLOCK_3; // 0 -> 1
|
||||||
|
BLOCK_0 -> BLOCK_17; // 0 -> 2
|
||||||
|
BLOCK_3 -> BLOCK_17; // 1 -> 2
|
||||||
|
BLOCK_17 -> BLOCK_25; // 2 -> 3, why == WHY_NOT
|
||||||
|
BLOCK_25; // 3
|
||||||
|
}
|
||||||
|
(Possibly terminal blocks: 17, 25.)
|
||||||
|
"""
|
||||||
|
cfg = byte_control.build_cfg(try_finally_4)
|
||||||
|
self.fail_unless_cfg_match(cfg, 4, ((0, 1), (0, 2), (1, 2), (2, 3)))
|
||||||
|
|
||||||
|
def test_try_finally_5(self):
|
||||||
|
pass # TODO: fix ControlFlowGraph.update_for_ssa which
|
||||||
|
# diverges on the CFG for the following:
|
||||||
|
#cfg = byte_control.build_cfg(try_finally_5)
|
||||||
|
#self.fail_unless_cfg_match(cfg, XXX, ())
|
||||||
|
|
||||||
# ______________________________________________________________________
|
# ______________________________________________________________________
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue