def test_call_without_arguments(self): module_block = BasicBlock([ # def callee(): Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="callee"), Instr("MAKE_FUNCTION", arg=0), Instr("STORE_NAME", arg="callee"), # result = callee() Instr("LOAD_NAME", arg="callee"), Instr("CALL_FUNCTION", arg=0), Instr("STORE_NAME", arg="result"), # return result Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE") ]) callee_block = BasicBlock([ Instr("LOAD_CONST", arg=0), Instr("RETURN_VALUE") ]) expected_instructions = [] expected_instructions.extend(module_block) expected_instructions.extend(callee_block) module_file = "simple_call.py" module_path = example_modules_path + module_file dynamic_slice = slice_module_at_return(module_path) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_simple_control_dependency_2(self): # If condition evaluated to false, with two relevant variables (but no influence on result) def func() -> int: foo = 1 bar = 2 result = 3 if foo == bar: result = 1 return result init_basic_block = BasicBlock([ # result = 3 Instr("LOAD_CONST", arg=3), Instr("STORE_FAST", arg="result"), ]) return_basic_block = BasicBlock([ # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE") ]) expected_instructions = [] expected_instructions.extend(init_basic_block) expected_instructions.extend(return_basic_block) dynamic_slice = slice_function_at_return(func.__code__) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_simple_control_dependency_4(self): # If-elif-else with else branch true def func() -> int: foo = 1 bar = 2 if foo == bar: result = 1 elif foo > bar: result = 2 else: result = 3 return result return_block = BasicBlock([ # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE") ]) else_block = BasicBlock([ # result = 3 Instr("LOAD_CONST", arg=3), Instr("STORE_FAST", arg="result") ]) elif_cond = BasicBlock([ # elif foo == 1: Instr("LOAD_FAST", arg="foo"), Instr("LOAD_FAST", arg="bar"), Instr("COMPARE_OP", arg=Compare.GT), Instr("POP_JUMP_IF_FALSE", arg=else_block), ]) if_cond = BasicBlock([ # if foo == bar Instr("LOAD_FAST", arg="foo"), Instr("LOAD_FAST", arg="bar"), Instr("COMPARE_OP", arg=Compare.EQ), Instr("POP_JUMP_IF_FALSE", arg=elif_cond), ]) init_block = BasicBlock([ # foo = 1 Instr("LOAD_CONST", arg=1), Instr("STORE_FAST", arg="foo"), # bar = 2 Instr("LOAD_CONST", arg=2), Instr("STORE_FAST", arg="bar"), ]) expected_instructions = [] expected_instructions.extend(init_block) expected_instructions.extend(if_cond) expected_instructions.extend(elif_cond) expected_instructions.extend(else_block) expected_instructions.extend(return_block) dynamic_slice = slice_function_at_return(func.__code__) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def _create_consecutive_blocks( bytecode_cfg: ControlFlowGraph, first: BasicBlock, amount: int ) -> Tuple[BasicBlock, ...]: """Split the given basic block into more blocks. The blocks are consecutive in the list of basic blocks. Args: bytecode_cfg: The control-flow graph first: The first basic block amount: The amount of consecutive blocks that should be created. Returns: A tuple of consecutive basic blocks """ assert amount > 0, "Amount of created basic blocks must be positive." current: BasicBlock = first nodes: List[BasicBlock] = [] # Can be any instruction, as it is discarded anyway. dummy_instruction = Instr("POP_TOP") for _ in range(amount): # Insert dummy instruction, which we can use to split off another block current.insert(0, dummy_instruction) current = bytecode_cfg.split_block(current, 1) nodes.append(current) # Move instructions back to first block. first.clear() first.extend(current) # Clear instructions in all created blocks. for node in nodes: node.clear() return tuple(nodes)
def test_nested_class(self): def func(): # STORE_DEREF, LOAD_CLOSURE, LOAD_CLASSDEREF x = [] class NestedClass: y = x class_attr = NestedClass.y result = class_attr return result freevar_x = FreeVar("x") cellvar_x = CellVar("x") function_block = BasicBlock([ # x = [] Instr("BUILD_LIST", arg=0), Instr("STORE_DEREF", arg=cellvar_x), # class NestedClass: Instr("LOAD_BUILD_CLASS"), Instr("LOAD_CLOSURE", arg=cellvar_x), Instr("BUILD_TUPLE", arg=1), Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="NestedClass"), Instr("MAKE_FUNCTION", arg=8), Instr("LOAD_CONST", arg="NestedClass"), Instr("CALL_FUNCTION", arg=2), Instr("STORE_FAST", arg="NestedClass"), # class_attr = NestedClass.y Instr("LOAD_FAST", arg="NestedClass"), Instr("LOAD_ATTR", arg="y"), Instr("STORE_FAST", arg="class_attr"), # result = class_attr Instr("LOAD_FAST", arg="class_attr"), Instr("STORE_FAST", arg="result"), # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE"), ]) nested_class_block = BasicBlock([ # y = x Instr("LOAD_CLASSDEREF", arg=freevar_x), Instr("STORE_NAME", arg="y"), Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE"), ]) expected_instructions = [] expected_instructions.extend(function_block) expected_instructions.extend(nested_class_block) dynamic_slice = slice_function_at_return(func.__code__, test_name="test_nested_class") self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_closures(self): # Closure function freevar_foo = FreeVar("foo") cellvar_foo = CellVar("foo") module_block = BasicBlock([ # def outer_function(foo): Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="outer_function"), Instr("MAKE_FUNCTION", arg=0), Instr("STORE_NAME", arg="outer_function"), # inner = outer_function('a') Instr("LOAD_NAME", arg="outer_function"), Instr("LOAD_CONST", arg="a"), Instr("CALL_FUNCTION", arg=1), Instr("STORE_NAME", arg="inner"), # result = inner("abc") Instr("LOAD_NAME", arg="inner"), Instr("LOAD_CONST", arg="abc"), Instr("CALL_FUNCTION", arg=1), Instr("STORE_NAME", arg="result"), Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE") ]) outer_function_block = BasicBlock([ # def inner_function(bar): Instr("LOAD_CLOSURE", arg=cellvar_foo), Instr("BUILD_TUPLE", arg=1), Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="outer_function.<locals>.inner_function"), Instr("MAKE_FUNCTION", arg=8), Instr("STORE_FAST", arg="inner_function"), # return inner Instr("LOAD_FAST", arg="inner_function"), Instr("RETURN_VALUE"), ]) inner_function_block = BasicBlock([ # return foo in bar Instr("LOAD_DEREF", arg=freevar_foo), Instr("LOAD_FAST", arg="bar"), Instr("COMPARE_OP", arg=Compare.IN), Instr("RETURN_VALUE"), ]) expected_instructions = [] expected_instructions.extend(module_block) expected_instructions.extend(outer_function_block) expected_instructions.extend(inner_function_block) module_file = "closure.py" module_path = example_modules_path + module_file dynamic_slice = slice_module_at_return(module_path) self.assertEqual(len(expected_instructions), len(dynamic_slice.sliced_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_data_dependency_4(self): # Explicit attribute dependencies (full cover) module_block = BasicBlock([ # class Foo: Instr("LOAD_BUILD_CLASS"), Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="Foo"), Instr("MAKE_FUNCTION", arg=0), Instr("LOAD_CONST", arg="Foo"), Instr("CALL_FUNCTION", arg=2), Instr("STORE_NAME", arg="Foo"), # ob.attr1 = 1 Instr("LOAD_CONST", arg=1), Instr("LOAD_NAME", arg="ob"), Instr("STORE_ATTR", arg="attr1"), # ob.attr2 = ob.attr2.append(ob.attr1) Instr("LOAD_NAME", arg="ob"), Instr("LOAD_ATTR", arg="attr2"), Instr("LOAD_METHOD", arg="append"), Instr("LOAD_NAME", arg="ob"), Instr("LOAD_ATTR", arg="attr1"), Instr("CALL_METHOD", arg=1), Instr("LOAD_NAME", arg="ob"), Instr("STORE_ATTR", arg="attr2"), # result = ob.attr2 Instr("LOAD_NAME", arg="ob"), Instr("LOAD_ATTR", arg="attr2"), Instr("STORE_NAME", arg="result"), # return Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE") ]) class_attr_block = BasicBlock([ # attr2 = [1, 2, 3] Instr("LOAD_CONST", arg=1), Instr("LOAD_CONST", arg=2), Instr("LOAD_CONST", arg=3), Instr("BUILD_LIST", arg=3), Instr("STORE_NAME", arg="attr2"), # return Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE") ]) expected_instructions = [] expected_instructions.extend(module_block) expected_instructions.extend(class_attr_block) module_file = "attribute_dependencies.py" module_path = example_modules_path + module_file dynamic_slice = slice_module_at_return(module_path) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_call_unused_argument(self): # Call with two arguments, one of which is used in the callee module_block = BasicBlock([ # def callee(): Instr("LOAD_NAME", arg="int"), Instr("LOAD_NAME", arg="int"), Instr("LOAD_CONST", arg=('a', 'b')), Instr("BUILD_CONST_KEY_MAP", arg=2), Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="callee"), Instr("MAKE_FUNCTION", arg=4), Instr("STORE_NAME", arg="callee"), # foo = 1 Instr("LOAD_CONST", arg=1), Instr("STORE_NAME", arg="foo"), # bar = 2 # This argument is not used by the callee and should therefore be excluded. # But it is an implicit data dependency of the call and is incorrectly and imprecisely included. # Instr("LOAD_CONST", arg=2), # Instr("STORE_NAME", arg="bar"), # result = callee() Instr("LOAD_NAME", arg="callee"), Instr("LOAD_NAME", arg="foo"), # Instr("LOAD_NAME", arg="bar"), Instr("CALL_FUNCTION", arg=2), Instr("STORE_NAME", arg="result"), # return result Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE") ]) callee_block = BasicBlock([ # return a Instr("LOAD_FAST", arg="a"), Instr("RETURN_VALUE") ]) expected_instructions = [] expected_instructions.extend(module_block) expected_instructions.extend(callee_block) module_file = "simple_call_arg.py" module_path = example_modules_path + module_file dynamic_slice = slice_module_at_return(module_path) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def _add_is_function_check( self, function_expr: bytecode.Parameter, add_to_block: bytecode.BasicBlock) -> None: typeof_var = bytecode.Var(next(self.var_names)) typeof_instr = bytecode.TypeofInst( typeof_var, function_expr) is_function_var = bytecode.Var(next(self.var_names)) is_function_instr = bytecode.BinopInst( is_function_var, bytecode.Binop.SYM_EQ, typeof_var, bytecode.SymLit(sexp.SSym('function')) ) branch_instr = bytecode.BrnInst(is_function_var, IS_FUNCTION_TRAP) for instr in [typeof_instr, is_function_instr, branch_instr]: add_to_block.add_inst(instr)
def test_builtin_addresses(self): def func(): test_dict = {1: "one", 2: "two"} # noinspection PyListCreation test_list = [1, 2] test_list.append(3) result = test_dict.get(1) return result function_block = BasicBlock([ # test_dict = {1: "one", 2: "two"} Instr("LOAD_CONST", arg="one"), Instr("LOAD_CONST", arg="two"), Instr("LOAD_CONST", arg=(1, 2)), Instr("BUILD_CONST_KEY_MAP", arg=2), Instr("STORE_FAST", arg="test_dict"), # result = test_dict.get(1) Instr("LOAD_FAST", arg="test_dict"), Instr("LOAD_METHOD", arg="get"), Instr("LOAD_CONST", arg=1), Instr("CALL_METHOD", arg=1), Instr("STORE_FAST", arg="result"), # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE"), ]) expected_instructions = [] expected_instructions.extend(function_block) dynamic_slice = slice_function_at_return(func.__code__, test_name="test_builtin_addresses") self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_simple_loop(self): def func(): result = 0 for i in range(0, 3): result += i return result return_block = BasicBlock([ # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE") ]) loop_header = BasicBlock([ Instr("FOR_ITER", arg=return_block), ]) loop_block = BasicBlock([ # result += i Instr("STORE_FAST", arg="i"), Instr("LOAD_FAST", arg="result"), Instr("LOAD_FAST", arg="i"), Instr("INPLACE_ADD"), Instr("STORE_FAST", arg="result"), Instr("JUMP_ABSOLUTE", arg=loop_header), ]) loop_setup = BasicBlock([ # for i in range(0, 3): Instr("LOAD_GLOBAL", arg="range"), Instr("LOAD_CONST", arg=0), Instr("LOAD_CONST", arg=3), Instr("CALL_FUNCTION", arg=2), Instr("GET_ITER"), ]) init_block = BasicBlock([ Instr("LOAD_CONST", arg=0), Instr("STORE_FAST", arg="result"), ]) expected_instructions = [] expected_instructions.extend(init_block) expected_instructions.extend(loop_setup) expected_instructions.extend(loop_header) expected_instructions.extend(loop_block) expected_instructions.extend(return_block) dynamic_slice = slice_function_at_return(func.__code__, test_name="test_simple_loop") self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_call_with_arguments(self): # Call with two arguments, one of which is used in the callee module_block = BasicBlock([ # def callee(): Instr("LOAD_NAME", arg="int"), Instr("LOAD_NAME", arg="int"), Instr("LOAD_CONST", arg=('a', 'b')), Instr("BUILD_CONST_KEY_MAP", arg=2), Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="callee"), Instr("MAKE_FUNCTION", arg=4), Instr("STORE_NAME", arg="callee"), # foo = 1 Instr("LOAD_CONST", arg=1), Instr("STORE_NAME", arg="foo"), # bar = 2 Instr("LOAD_CONST", arg=2), Instr("STORE_NAME", arg="bar"), # result = callee() Instr("LOAD_NAME", arg="callee"), Instr("LOAD_NAME", arg="foo"), Instr("LOAD_NAME", arg="bar"), Instr("CALL_FUNCTION", arg=2), Instr("STORE_NAME", arg="result"), # return result Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE") ]) callee_block = BasicBlock([ # return a Instr("LOAD_FAST", arg="a"), Instr("RETURN_VALUE") ]) expected_instructions = [] expected_instructions.extend(module_block) expected_instructions.extend(callee_block) module_file = "simple_call_arg.py" module_path = example_modules_path + module_file dynamic_slice = slice_module_at_return(module_path) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_simple_control_dependency_1(self): # If condition evaluated to true, with relevant variable foo def func() -> int: foo = 1 result = 3 if foo == 1: result = 1 return result return_basic_block = BasicBlock([ # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE") ]) if_basic_block = BasicBlock([ # result = 1 Instr("LOAD_CONST", arg=1), Instr("STORE_FAST", arg="result"), ]) init_basic_block = BasicBlock([ # foo = 1 Instr("LOAD_CONST", arg=1), Instr("STORE_FAST", arg="foo"), # if foo == 1 Instr("LOAD_FAST", arg="foo"), Instr("LOAD_CONST", arg=1), Instr("COMPARE_OP", arg=Compare.EQ), Instr("POP_JUMP_IF_FALSE", arg=return_basic_block), ]) expected_instructions = [] expected_instructions.extend(init_basic_block) expected_instructions.extend(if_basic_block) expected_instructions.extend(return_basic_block) dynamic_slice = slice_function_at_return(func.__code__) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def copy_propagate_block(self, block: BasicBlock) -> None: copies: Dict[Var, Parameter] = {} for i, inst in enumerate(block.instructions): if isinstance(inst, CopyInst): value = inst.value while value in copies: assert isinstance(value, Var) value = copies[value] copies[inst.dest] = value else: block.instructions[i] = inst.copy_prop(copies)
def test_blocks_broken_jump(self): block = BasicBlock() code = ControlFlowGraph() code[0].append(Instr('JUMP_ABSOLUTE', block)) expected = textwrap.dedent(""" block1: JUMP_ABSOLUTE <error: unknown block> """).lstrip("\n") self.check_dump_bytecode(code, expected)
def test_get_block_index(self): blocks = ControlFlowGraph() block0 = blocks[0] block1 = blocks.add_block() block2 = blocks.add_block() self.assertEqual(blocks.get_block_index(block0), 0) self.assertEqual(blocks.get_block_index(block1), 1) self.assertEqual(blocks.get_block_index(block2), 2) other_block = BasicBlock() self.assertRaises(ValueError, blocks.get_block_index, other_block)
def mergable(block: BasicBlock) -> bool: # @TODO: Branch switching for conditional+unconditional jumps. # It would be nice to be able to use the better one of the two # tails. assert self.preds last = block.instructions[-1] if isinstance(last, JmpInst): return len(self.preds[id(last.target)]) == 1 elif isinstance(last, TrapInst) and len(block.instructions) > 1: prev = block.instructions[-2] if isinstance(prev, (BrInst, BrnInst)): if not len(self.preds[id(prev.target)]) == 1: return False new_target = block.split_after(-2) block.instructions[-1] = JmpInst(prev.target) if isinstance(prev, BrInst): block.instructions[-2] = BrnInst(prev.cond, new_target) else: block.instructions[-2] = BrInst(prev.cond, new_target) return True return False
def test_import_star(self): # IMPORT_STAR with access to immutable variable main_module_block = BasicBlock([ # from tests.slicer.example_modules.import_star_def import * Instr("IMPORT_NAME", "tests.slicer.example_modules.import_star_def"), Instr("IMPORT_STAR"), # result = Foo.test Instr("LOAD_NAME", arg="star_imported"), Instr("STORE_NAME", arg="result"), Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE"), ]) dependency_module_block = BasicBlock([ # star_imported = "test" Instr("LOAD_CONST", arg="test"), Instr("STORE_NAME", arg="star_imported"), Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE") ]) expected_instructions = [] expected_instructions.extend(main_module_block) expected_instructions.extend(dependency_module_block) module_dependency_file = "import_star_def.py" module_dependency_path = example_modules_path + module_dependency_file instrument_module(module_dependency_path) module_file = "import_star_main.py" module_path = example_modules_path + module_file dynamic_slice = slice_module_at_return(module_path) compile_module(module_dependency_path) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_lambda(self): def func(): x = lambda a: a + 10 result = x(1) return result function_block = BasicBlock([ # x = lambda a: a + 10 Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="IntegrationTestLanguageFeatures.test_lambda.<locals>.func.<locals>.<lambda>"), Instr("MAKE_FUNCTION", arg=0), Instr("STORE_FAST", arg="x"), # result = x(1) Instr("LOAD_FAST", arg="x"), Instr("LOAD_CONST", arg=1), Instr("CALL_FUNCTION", arg=1), Instr("STORE_FAST", arg="result"), # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE"), ]) lambda_block = BasicBlock([ # lambda a: a + 10 Instr("LOAD_FAST", arg="a"), Instr("LOAD_CONST", arg=10), Instr("BINARY_ADD"), Instr("RETURN_VALUE"), ]) expected_instructions = [] expected_instructions.extend(function_block) expected_instructions.extend(lambda_block) dynamic_slice = slice_function_at_return(func.__code__, test_name="test_lambda") self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_invalid_arg(self): label = Label() block = BasicBlock() # EXTENDED_ARG self.assertRaises(ValueError, Instr, "EXTENDED_ARG", 0) # has_jump() self.assertRaises(TypeError, Instr, "JUMP_ABSOLUTE", 1) self.assertRaises(TypeError, Instr, "JUMP_ABSOLUTE", 1.0) Instr("JUMP_ABSOLUTE", label) Instr("JUMP_ABSOLUTE", block) # hasfree self.assertRaises(TypeError, Instr, "LOAD_DEREF", "x") Instr("LOAD_DEREF", CellVar("x")) Instr("LOAD_DEREF", FreeVar("x")) # haslocal self.assertRaises(TypeError, Instr, "LOAD_FAST", 1) Instr("LOAD_FAST", "x") # hasname self.assertRaises(TypeError, Instr, "LOAD_NAME", 1) Instr("LOAD_NAME", "x") # hasconst self.assertRaises(ValueError, Instr, "LOAD_CONST") # UNSET self.assertRaises(ValueError, Instr, "LOAD_CONST", label) self.assertRaises(ValueError, Instr, "LOAD_CONST", block) Instr("LOAD_CONST", 1.0) Instr("LOAD_CONST", object()) # hascompare self.assertRaises(TypeError, Instr, "COMPARE_OP", 1) Instr("COMPARE_OP", Compare.EQ) # HAVE_ARGUMENT self.assertRaises(ValueError, Instr, "CALL_FUNCTION", -1) self.assertRaises(TypeError, Instr, "CALL_FUNCTION", 3.0) Instr("CALL_FUNCTION", 3) # test maximum argument self.assertRaises(ValueError, Instr, "CALL_FUNCTION", 2147483647 + 1) instr = Instr("CALL_FUNCTION", 2147483647) self.assertEqual(instr.arg, 2147483647) # not HAVE_ARGUMENT self.assertRaises(ValueError, Instr, "NOP", 0) Instr("NOP")
def test_iter_invalid_types(self): # Labels are not allowed in basic blocks block = BasicBlock() block.append(Label()) with self.assertRaises(ValueError): list(block) # Only one jump allowed and only at the end block = BasicBlock() block2 = BasicBlock() block.extend([Instr('JUMP_ABSOLUTE', block2), Instr('NOP')]) with self.assertRaises(ValueError): list(block) # jump target must be a BasicBlock block = BasicBlock() label = Label() block.extend([Instr('JUMP_ABSOLUTE', label)]) with self.assertRaises(ValueError): list(block)
def _add_arity_check( self, function_expr: bytecode.Parameter, add_to_block: bytecode.BasicBlock, arity: int) -> None: arity_var = bytecode.Var(next(self.var_names)) add_to_block.add_inst(bytecode.ArityInst(arity_var, function_expr)) correct_arity_var = bytecode.Var(next(self.var_names)) add_to_block.add_inst(bytecode.BinopInst( correct_arity_var, bytecode.Binop.NUM_EQ, arity_var, bytecode.NumLit(sexp.SNum(arity)) )) add_to_block.add_inst(bytecode.BrnInst(correct_arity_var, ARITY_TRAP))
def inst_function( name: SSym, params: List[Var], return_val: Optional[bytecode.Parameter], *insts: Inst, ) -> SFunction: """Create a function out of the instructions in insts.""" begin = BasicBlock('bb0') for inst in insts: begin.add_inst(inst) if return_val is not None: begin.add_inst(bytecode.ReturnInst(return_val)) code = Function(params, begin) param_syms = [SSym(p.name) for p in params] return SFunction(name, param_syms, Nil, code, False)
def test_with_extended_arg(self): def func(): p = [1, 2, 3, 4, 5, 6] # noinspection PyUnusedLocal unused = p q, r, *s, t = p # With extended argument result = q, r return result module_block = BasicBlock([ # p = [1, 2, 3, 4, 5, 6] Instr("LOAD_CONST", arg=1), Instr("LOAD_CONST", arg=2), Instr("LOAD_CONST", arg=3), Instr("LOAD_CONST", arg=4), Instr("LOAD_CONST", arg=5), Instr("LOAD_CONST", arg=6), Instr("BUILD_LIST", arg=6), Instr("STORE_FAST", arg="p"), # q, r, *s, t = p Instr("LOAD_FAST", arg="p"), # Instr("EXTENDED_ARG", arg=1), # EXTENDED_ARG can not be in a slice Instr("UNPACK_EX", arg=258), Instr("STORE_FAST", arg="q"), Instr("STORE_FAST", arg="r"), # result = q Instr("LOAD_FAST", arg="q"), Instr("LOAD_FAST", arg="r"), Instr("BUILD_TUPLE", arg=2), Instr("STORE_FAST", arg="result"), # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE"), ]) expected_instructions = [] expected_instructions.extend(module_block) dynamic_slice = slice_function_at_return(func.__code__, test_name="test_with_extended_arg") self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue(compare(dynamic_slice.sliced_instructions, expected_instructions))
def block_transfer( self, env: EvalEnv, block: BasicBlock, types: TypeMap, values: ValueMap, ) -> List[Tuple[TypeMap, ValueMap]]: abstract = [] for i, inst in enumerate(block.instructions): abstract.append((copy.copy(types), copy.copy(values))) inst.run_abstract(env, types, values) inst = inst.constant_fold(types, values) block.instructions[i] = inst if isinstance(inst, CallInst): func = values[inst.func] if (isinstance(func, SFunction) and self.should_inline( env, func, inst.specialization)): # noqa code = func.get_specialized(inst.specialization) code = copy.deepcopy(code) opt = FunctionOptimizer(code) opt.specialization = inst.specialization opt.inputs = tuple(values[x] for x in inst.args) opt.banned_from_inline = (self.banned_from_inline | {func.name}) opt.optimize(env) if opt.result is not None: ty, val = opt.result types[inst.dest] = ty values[inst.dest] = val if id(block) not in self.inlines: self.inlines[id(block)] = (block, {}) self.inlines[id(block)][1][i] = code if isinstance(inst, ReturnInst): ret_ty = types[inst.ret] ret_val = values[inst.ret] if self.result is None: self.result = (ret_ty, ret_val) else: self.result = (self.result[0].join(ret_ty), self.result[1] if self.result[1] == ret_val else None) abstract.append((copy.copy(types), copy.copy(values))) return abstract
def test_mod_untraced_object(self): def func(): lst = [('foo', '3'), ('bar', '1'), ('foobar', '2')] lst.sort( ) # This is incorrectly excluded, since it is not known that the method modifies the list result = lst return result function_block = BasicBlock([ # lst = [('foo', '3'), ('bar', '1'), ('foobar', '2')] Instr("LOAD_CONST", arg=('foo', '3')), Instr("LOAD_CONST", arg=('bar', '1')), Instr("LOAD_CONST", arg=('foobar', '2')), Instr("BUILD_LIST", arg=3), Instr("STORE_FAST", arg="lst"), # lst.sort() # This is incorrectly excluded, since it is not known that the method modifies the list Instr("LOAD_FAST", arg="lst"), Instr("LOAD_METHOD", arg="sort"), Instr("CALL_METHOD", arg=0), Instr("POP_TOP"), # result = lst Instr("LOAD_FAST", arg="lst"), Instr("STORE_FAST", arg="result"), # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE"), ]) expected_instructions = [] expected_instructions.extend(function_block) dynamic_slice = slice_function_at_return( func.__code__, test_name="test_mod_untraced_object") self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_dunder_definition(self): def func(): class NestedClass: def __init__( self ): # Definition of dunder methods wrongly excluded, these are not explicitly loaded self.x = 1 result = NestedClass() return result function_block = BasicBlock([ # class NestedClass: Instr("LOAD_BUILD_CLASS"), Instr("LOAD_CONST", arg=dummy_code_object), Instr("LOAD_CONST", arg="NestedClass"), Instr("MAKE_FUNCTION", arg=0), Instr("LOAD_CONST", arg="NestedClass"), Instr("CALL_FUNCTION", arg=2), Instr("STORE_FAST", arg="NestedClass"), # result = NestedClass() Instr("LOAD_FAST", arg="NestedClass"), Instr("CALL_FUNCTION", arg=0), Instr("STORE_FAST", arg="result"), # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE"), ]) nested_class_block = BasicBlock([ # Definition of dunder methods are wrongly excluded, since these are not explicitly loaded # def __init__(self): Instr("LOAD_CONST", arg=dummy_code_object), Instr( "LOAD_CONST", arg= "IntegrationTestLanguageFeatures.test_object_modification_call.<locals>." "func.<locals>.NestedClass.__init__"), Instr("MAKE_FUNCTION", arg=0), Instr("STORE_NAME", arg="__init__"), Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE"), ]) init_block = BasicBlock([ # self.x = 1 Instr("LOAD_CONST", arg=1), Instr("LOAD_FAST", arg="self"), Instr("STORE_ATTR", arg="x"), Instr("LOAD_CONST", arg=None), Instr("RETURN_VALUE"), ]) expected_instructions = [] expected_instructions.extend(function_block) expected_instructions.extend(nested_class_block) expected_instructions.extend(init_block) dynamic_slice = slice_function_at_return( func.__code__, test_name="test_dunder_definition") self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_exception(self): # Exception def func(): foo = 1 bar = 0 try: result = 0 / 0 except ZeroDivisionError: result = foo + bar return result self.assertEqual(func(), 1) dummy_block = BasicBlock([]) return_block = BasicBlock([ # return result Instr("LOAD_FAST", arg="result"), Instr("RETURN_VALUE") ]) try_block = BasicBlock([ # result = foo / bar <- did somehow effect slicing criterion... Instr("LOAD_FAST", arg="foo"), Instr("LOAD_FAST", arg="bar"), Instr("BINARY_TRUE_DIVIDE"), # except ZeroDivisionError: Instr("DUP_TOP"), Instr("LOAD_GLOBAL", arg="ZeroDivisionError"), Instr("COMPARE_OP", arg=Compare.EXC_MATCH), Instr("POP_JUMP_IF_FALSE", arg=dummy_block), ]) except_block = BasicBlock([ # result = foo + bar Instr("LOAD_FAST", arg="foo"), Instr("LOAD_FAST", arg="bar"), Instr("BINARY_ADD"), Instr("STORE_FAST", arg="result"), Instr("JUMP_FORWARD", arg=dummy_block), ]) function_block = BasicBlock([ # foo = 1 Instr("LOAD_CONST", arg=1), # <- excluded because no stack simulation Instr("STORE_FAST", arg="foo"), # bar = 0 Instr("LOAD_CONST", arg=0), # <- excluded because no stack simulation Instr("STORE_FAST", arg="bar"), # try: # Instr("SETUP_FINALLY", arg=try_block), ]) expected_instructions = [] expected_instructions.extend(return_block) expected_instructions.extend(except_block) expected_instructions.extend(function_block) expected_instructions.extend(try_block) dynamic_slice = slice_function_at_return(func.__code__) self.assertEqual(len(dynamic_slice.sliced_instructions), len(expected_instructions)) self.assertTrue( compare(dynamic_slice.sliced_instructions, expected_instructions))
def test_copy(self): block = BasicBlock([Instr("NOP")]) next_block = BasicBlock() block.next_block = next_block self.assertEqual(block, block.copy()) self.assertIs(next_block, block.copy().next_block)
def test_slice(self): block = BasicBlock([Instr("NOP")]) next_block = BasicBlock() block.next_block = next_block self.assertEqual(block, block[:]) self.assertIs(next_block, block[:].next_block)