Moved most of llpython.tests.test_addr_flow into llpython.tests.test_byte_flow, made some modifications to various modules to facilitate proper handling of try-finally.
This commit is contained in:
parent
4837bf194e
commit
bba305774a
4 changed files with 150 additions and 40 deletions
|
|
@ -10,6 +10,7 @@ from . import opcode_util
|
|||
from . import byte_control
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class definition(s)
|
||||
|
||||
class BytecodeFlowBuilder (BasicBlockVisitor):
|
||||
'''Transforms a CFG into a bytecode "flow tree".
|
||||
|
|
@ -116,10 +117,17 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_BINARY_XOR = _op
|
||||
|
||||
def op_BREAK_LOOP (self, i, op, arg):
|
||||
# XXX Not sure this is correct.
|
||||
loop_i, _, loop_arg, _ = self.control_stack[-1]
|
||||
assert arg is None
|
||||
return self._op(i, op, loop_i + loop_arg + 3)
|
||||
if self.opnames[op] == 'BREAK_LOOP':
|
||||
# Break target was already computed in control flow analysis;
|
||||
# reuse that, replacing the opcode argument.
|
||||
blocks_out = tuple(self.cfg.blocks_out[self.block_no])
|
||||
assert len(blocks_out) == 1
|
||||
assert arg is None
|
||||
arg = blocks_out[0]
|
||||
# else: Continue target is already in the argument. Note that
|
||||
# the argument might not be the same as CFG destination block,
|
||||
# since we might have a finally block to visit first.
|
||||
return self._op(i, op, arg)
|
||||
|
||||
op_BUILD_CLASS = _op
|
||||
op_BUILD_LIST = _op
|
||||
|
|
@ -131,7 +139,7 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_CALL_FUNCTION_VAR = _op
|
||||
op_CALL_FUNCTION_VAR_KW = _op
|
||||
op_COMPARE_OP = _op
|
||||
#op_CONTINUE_LOOP = _not_implemented
|
||||
op_CONTINUE_LOOP = op_BREAK_LOOP
|
||||
op_DELETE_ATTR = _op
|
||||
op_DELETE_FAST = _op
|
||||
op_DELETE_GLOBAL = _op
|
||||
|
|
@ -146,7 +154,11 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
self.stack += self.stack[-arg:]
|
||||
|
||||
#op_DUP_TOP_TWO = _not_implemented
|
||||
#op_END_FINALLY = _not_implemented
|
||||
|
||||
# See the note regarding END_FINALLY in the definition of
|
||||
# opcope_util.OPCODE_MAP.
|
||||
op_END_FINALLY = _op
|
||||
|
||||
op_EXEC_STMT = _op
|
||||
|
||||
def op_EXTENDED_ARG (self, i, op, arg):
|
||||
|
|
@ -249,7 +261,20 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_SETUP_FINALLY = _op_SETUP
|
||||
op_SETUP_LOOP = _op_SETUP
|
||||
|
||||
#op_SETUP_WITH = _not_implemented
|
||||
def op_SETUP_WITH (self, i, op, arg):
|
||||
assert arg is not None
|
||||
# Care has to be taken here. SETUP_WITH pushes two things on
|
||||
# the value stack (the exit ), and once on the handler frame.
|
||||
ctx = self.stack.pop()
|
||||
# We signal that the value is an exit handler by setting arg to None
|
||||
exit_handler = i, op, self.opnames[op], None, [ctx]
|
||||
self.stack.append(exit_handler)
|
||||
ret_val = i, op, self.opnames[op], arg, [ctx]
|
||||
self.control_stack.append((i, op, arg, len(self.stack)))
|
||||
self.stack.append(ret_val)
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
op_SET_ADD = op_LIST_APPEND
|
||||
op_SLICE = _op
|
||||
#op_STOP_CODE = _not_implemented
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ OPCODE_MAP = {
|
|||
'CALL_FUNCTION_VAR': OpcodeData(-3, 1, None),
|
||||
'CALL_FUNCTION_VAR_KW': OpcodeData(-4, 1, None),
|
||||
'COMPARE_OP': OpcodeData(2, 1, None),
|
||||
'CONTINUE_LOOP': NO_OPCODE_DATA,
|
||||
'CONTINUE_LOOP': OpcodeData(0, None, 1),
|
||||
'DELETE_ATTR': OpcodeData(1, None, 1),
|
||||
'DELETE_DEREF': NO_OPCODE_DATA,
|
||||
'DELETE_FAST': OpcodeData(0, None, 1),
|
||||
|
|
@ -74,7 +74,15 @@ OPCODE_MAP = {
|
|||
'DUP_TOP': NO_OPCODE_DATA,
|
||||
'DUP_TOPX': NO_OPCODE_DATA,
|
||||
'DUP_TOP_TWO': NO_OPCODE_DATA,
|
||||
'END_FINALLY': NO_OPCODE_DATA,
|
||||
|
||||
# The data for END_FINALLY is a total fabrication; END_FINALLY may
|
||||
# pop 1 or 3 values off the value stack, based on the type of the
|
||||
# top of the value stack. If, however, a value stack simulator
|
||||
# ignores the part of the CPython evaluator loop that pushes the
|
||||
# why code on the value stack for WHY_RETURN and WHY_CONTINUE (as
|
||||
# this table does), this should work out fine.
|
||||
'END_FINALLY': OpcodeData(0, 0, 1),
|
||||
|
||||
'EXEC_STMT': OpcodeData(3, 0, 1),
|
||||
'EXTENDED_ARG': NO_OPCODE_DATA,
|
||||
'FOR_ITER': OpcodeData(1, 1, 1),
|
||||
|
|
@ -109,7 +117,7 @@ OPCODE_MAP = {
|
|||
'LOAD_DEREF': OpcodeData(0, 1, None),
|
||||
'LOAD_FAST': OpcodeData(0, 1, None),
|
||||
'LOAD_GLOBAL': OpcodeData(0, 1, None),
|
||||
'LOAD_LOCALS': NO_OPCODE_DATA,
|
||||
'LOAD_LOCALS': OpcodeData(0, 1, None),
|
||||
'LOAD_NAME': OpcodeData(0, 1, None),
|
||||
'MAKE_CLOSURE': NO_OPCODE_DATA,
|
||||
'MAKE_FUNCTION': OpcodeData(-2, 1, None),
|
||||
|
|
|
|||
|
|
@ -5,42 +5,21 @@ from __future__ import absolute_import
|
|||
|
||||
import unittest
|
||||
|
||||
from llpython import addr_flow, opcode_util
|
||||
from llpython import addr_flow
|
||||
|
||||
from . import test_byte_control as tbc
|
||||
from . import test_byte_flow
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class (test case) definition(s)
|
||||
|
||||
class TestAddrFlow(unittest.TestCase):
|
||||
def fail_unless_valid_flow(self, flow):
|
||||
raise NotImplementedError("XXX")
|
||||
# TODO: Make sure child indices are valid bytecode addresses
|
||||
# TODO: Make sure opcode has a "reasonable" number of child indices
|
||||
class TestAddressFlowBuilder(unittest.TestCase, test_byte_flow.FlowTestMixin):
|
||||
BUILDER_CLS = addr_flow.AddressFlowBuilder
|
||||
|
||||
def test_try_finally_0(self):
|
||||
self.fail_unless_valid_flow(
|
||||
addr_flow.build_addr_flow(tbc.try_finally_0))
|
||||
|
||||
def test_try_finally_1(self):
|
||||
self.fail_unless_valid_flow(
|
||||
addr_flow.build_addr_flow(tbc.try_finally_1))
|
||||
|
||||
def test_try_finally_2(self):
|
||||
self.fail_unless_valid_flow(
|
||||
addr_flow.build_addr_flow(tbc.try_finally_2))
|
||||
|
||||
def test_try_finally_3(self):
|
||||
self.fail_unless_valid_flow(
|
||||
addr_flow.build_addr_flow(tbc.try_finally_3))
|
||||
|
||||
def test_try_finally_4(self):
|
||||
self.fail_unless_valid_flow(
|
||||
addr_flow.build_addr_flow(tbc.try_finally_4))
|
||||
|
||||
def test_try_finally_5(self):
|
||||
self.fail_unless_valid_flow(
|
||||
addr_flow.build_addr_flow(tbc.try_finally_5))
|
||||
def fail_unless_valid_instruction(self, instr):
|
||||
super(TestAddressFlowBuilder, self).fail_unless_valid_instruction(
|
||||
instr)
|
||||
for arg_addr in instr[-1]:
|
||||
self.fail_unless_valid_address(arg_addr)
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (unit test) routine
|
||||
|
|
|
|||
98
llpython/tests/test_byte_flow.py
Normal file
98
llpython/tests/test_byte_flow.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from llpython import byte_flow
|
||||
from llpython import opcode_util
|
||||
|
||||
from . import test_byte_control as tbc
|
||||
from . import llfuncs
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class (test case) definition(s)
|
||||
|
||||
class FlowTestMixin(object):
|
||||
|
||||
def fail_unless_valid_address(self, address):
|
||||
self.failUnless(address >= 0)
|
||||
self.failUnless(address < self.max_addr)
|
||||
self.failUnless(address in self.valid_addrs)
|
||||
|
||||
def fail_unless_valid_instruction(self, instr):
|
||||
address = instr[0]
|
||||
self.visited.add(address)
|
||||
self.fail_unless_valid_address(instr[0])
|
||||
|
||||
def fail_unless_valid_flow(self, flow, func):
|
||||
self.failUnless(len(flow) > 0)
|
||||
func_code = opcode_util.get_code_object(func).co_code
|
||||
self.valid_addrs = set(addr for addr, _, _ in
|
||||
opcode_util.itercode(func_code))
|
||||
self.visited = set()
|
||||
self.max_addr = len(func_code)
|
||||
for block_index, block_instrs in flow.items():
|
||||
self.failUnless(block_index < self.max_addr)
|
||||
for instr in block_instrs:
|
||||
self.fail_unless_valid_instruction(instr)
|
||||
del self.max_addr
|
||||
# Make sure that all instructions identified by itercode were
|
||||
# checked at least once; they should be represented in the
|
||||
# resulting flow, even if their basic block is unreachable.
|
||||
self.failUnless(self.valid_addrs == self.visited,
|
||||
'Failed to visit following addresses: %r' %
|
||||
(self.valid_addrs - self.visited))
|
||||
del self.visited
|
||||
del self.valid_addrs
|
||||
|
||||
def build_and_test_flow(self, func):
|
||||
self.fail_unless_valid_flow(self.BUILDER_CLS.build_flow(func), func)
|
||||
|
||||
def test_doslice(self):
|
||||
self.build_and_test_flow(llfuncs.doslice)
|
||||
|
||||
def test_ipow(self):
|
||||
self.build_and_test_flow(llfuncs.ipow)
|
||||
|
||||
def test_pymod(self):
|
||||
self.build_and_test_flow(llfuncs.pymod)
|
||||
|
||||
def test_try_finally_0(self):
|
||||
self.build_and_test_flow(tbc.try_finally_0)
|
||||
|
||||
def test_try_finally_1(self):
|
||||
self.build_and_test_flow(tbc.try_finally_1)
|
||||
|
||||
def test_try_finally_2(self):
|
||||
self.build_and_test_flow(tbc.try_finally_2)
|
||||
|
||||
def test_try_finally_3(self):
|
||||
self.build_and_test_flow(tbc.try_finally_3)
|
||||
|
||||
def test_try_finally_4(self):
|
||||
self.build_and_test_flow(tbc.try_finally_4)
|
||||
|
||||
def test_try_finally_5(self):
|
||||
self.build_and_test_flow(tbc.try_finally_5)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
class TestBytecodeFlowBuilder(unittest.TestCase, FlowTestMixin):
|
||||
BUILDER_CLS = byte_flow.BytecodeFlowBuilder
|
||||
|
||||
def fail_unless_valid_instruction(self, instr):
|
||||
super(TestBytecodeFlowBuilder, self).fail_unless_valid_instruction(
|
||||
instr)
|
||||
for child_instr in instr[-1]:
|
||||
self.fail_unless_valid_instruction(child_instr)
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (unit test) routine
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of test_byte_flow.py
|
||||
Loading…
Add table
Add a link
Reference in a new issue