def test_extended_arg(self): # Create a code object from arbitrary bytecode co_code = (b'\x90\x12\x904\x90\xabd\xcd' if WORDCODE else b'\x904\x12d\xcd\xab') code = get_code('x=1') code = types.CodeType(code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, co_code, code.co_consts, code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars) # without EXTENDED_ARG opcode bytecode = ConcreteBytecode.from_code(code) self.assertListEqual( list(bytecode), [ConcreteInstr("LOAD_CONST", 0x1234abcd, lineno=1)]) # with EXTENDED_ARG opcode bytecode = ConcreteBytecode.from_code(code, extended_arg=True) if WORDCODE: expected = [ ConcreteInstr('EXTENDED_ARG', 0x12, lineno=1), ConcreteInstr('EXTENDED_ARG', 0x34, lineno=1), ConcreteInstr('EXTENDED_ARG', 0xab, lineno=1), ConcreteInstr('LOAD_CONST', 0xcd, lineno=1) ] else: expected = [ ConcreteInstr('EXTENDED_ARG', 0x1234, lineno=1), ConcreteInstr('LOAD_CONST', 0xabcd, lineno=1) ] self.assertListEqual(list(bytecode), expected)
def test_from_code(self): code = get_code( """ if test: x = 1 else: x = 2 """ ) bytecode = Bytecode.from_code(code) label_else = Label() label_exit = Label() self.assertEqual( bytecode, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label_else, lineno=1), Instr("LOAD_CONST", 1, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", label_exit, lineno=2), label_else, Instr("LOAD_CONST", 2, lineno=4), Instr("STORE_NAME", "x", lineno=4), label_exit, Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ], )
def test_extended_arg(self): # Create a code object from arbitrary bytecode co_code = b'\x904\x12d\xcd\xab' code = get_code('x=1') code = types.CodeType(code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, co_code, code.co_consts, code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars) # without EXTENDED_ARG opcode bytecode = ConcreteBytecode.from_code(code) self.assertListEqual(list(bytecode), [ConcreteInstr("LOAD_CONST", 0x1234abcd, lineno=1)]) # with EXTENDED_ARG opcode bytecode = ConcreteBytecode.from_code(code, extended_arg=True) self.assertListEqual(list(bytecode), [ConcreteInstr('EXTENDED_ARG', 0x1234, lineno=1), ConcreteInstr('LOAD_CONST', 0xabcd, lineno=1)])
def test_extended_arg_make_function(self): code_obj = get_code(''' def foo(x: int, y: int): pass ''') # without EXTENDED_ARG concrete = ConcreteBytecode.from_code(code_obj) func_code = concrete.consts[1] self.assertEqual(concrete.names, ['int', 'foo']) self.assertEqual(concrete.consts, [('x', 'y'), func_code, 'foo', None]) if WORDCODE: expected = [ ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1), ConcreteInstr("LOAD_CONST", 1, lineno=1), ConcreteInstr("LOAD_CONST", 2, lineno=1), ConcreteInstr("MAKE_FUNCTION", 4, lineno=1), ConcreteInstr("STORE_NAME", 1, lineno=1), ConcreteInstr("LOAD_CONST", 3, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1) ] else: expected = [ ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("LOAD_CONST", 1, lineno=1), ConcreteInstr("LOAD_CONST", 2, lineno=1), ConcreteInstr("MAKE_FUNCTION", 3 << 16, lineno=1), ConcreteInstr("STORE_NAME", 1, lineno=1), ConcreteInstr("LOAD_CONST", 3, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1) ] self.assertListEqual(list(concrete), expected) # with EXTENDED_ARG concrete = ConcreteBytecode.from_code(code_obj, extended_arg=True) func_code = concrete.consts[1] self.assertEqual(concrete.names, ['int', 'foo']) self.assertEqual(concrete.consts, [('x', 'y'), func_code, 'foo', None]) if not WORDCODE: expected = [ ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("LOAD_CONST", 1, lineno=1), ConcreteInstr("LOAD_CONST", 2, lineno=1), ConcreteInstr("EXTENDED_ARG", 3, lineno=1), ConcreteInstr("MAKE_FUNCTION", 0, lineno=1), ConcreteInstr("STORE_NAME", 1, lineno=1), ConcreteInstr("LOAD_CONST", 3, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1) ] self.assertListEqual(list(concrete), expected)
def test_attr(self): code_obj = get_code("x = 5") code = ConcreteBytecode.from_code(code_obj) self.assertEqual(code.consts, [5, None]) self.assertEqual(code.names, ['x']) self.assertEqual(code.varnames, []) self.assertEqual(code.freevars, []) self.assertListEqual(list(code), [ConcreteInstr('LOAD_CONST', 0, lineno=1), ConcreteInstr('STORE_NAME', 0, lineno=1), ConcreteInstr('LOAD_CONST', 1, lineno=1), ConcreteInstr('RETURN_VALUE', lineno=1)])
def check(self, source, function=False): ref_code = get_code(source, function=function) code = ConcreteBytecode.from_code(ref_code).to_code() self.assertEqual(code, ref_code) code = Bytecode.from_code(ref_code).to_code() self.assertEqual(code, ref_code) bytecode = Bytecode.from_code(ref_code) blocks = ControlFlowGraph.from_bytecode(bytecode) code = blocks.to_bytecode().to_code() self.assertEqual(code, ref_code)
def test_extended_arg_make_function(self): code_obj = get_code(''' def foo(x: int, y: int): pass ''') # without EXTENDED_ARG concrete = ConcreteBytecode.from_code(code_obj) func_code = concrete.consts[1] self.assertEqual(concrete.names, ['int', 'foo']) self.assertEqual(concrete.consts, [('x', 'y'), func_code, 'foo', None]) if WORDCODE: expected = [ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1), ConcreteInstr("LOAD_CONST", 1, lineno=1), ConcreteInstr("LOAD_CONST", 2, lineno=1), ConcreteInstr("MAKE_FUNCTION", 4, lineno=1), ConcreteInstr("STORE_NAME", 1, lineno=1), ConcreteInstr("LOAD_CONST", 3, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1)] else: expected = [ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("LOAD_CONST", 1, lineno=1), ConcreteInstr("LOAD_CONST", 2, lineno=1), ConcreteInstr("MAKE_FUNCTION", 3 << 16, lineno=1), ConcreteInstr("STORE_NAME", 1, lineno=1), ConcreteInstr("LOAD_CONST", 3, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1)] self.assertListEqual(list(concrete), expected) # with EXTENDED_ARG concrete = ConcreteBytecode.from_code(code_obj, extended_arg=True) func_code = concrete.consts[1] self.assertEqual(concrete.names, ['int', 'foo']) self.assertEqual(concrete.consts, [('x', 'y'), func_code, 'foo', None]) if not WORDCODE: expected = [ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("LOAD_CONST", 1, lineno=1), ConcreteInstr("LOAD_CONST", 2, lineno=1), ConcreteInstr("EXTENDED_ARG", 3, lineno=1), ConcreteInstr("MAKE_FUNCTION", 0, lineno=1), ConcreteInstr("STORE_NAME", 1, lineno=1), ConcreteInstr("LOAD_CONST", 3, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1)] self.assertListEqual(list(concrete), expected)
def test_from_code_load_fast(self): code = get_code(""" def func(): x = 33 y = x """, function=True) code = Bytecode.from_code(code) self.assertEqual(code, [Instr('LOAD_CONST', 33, lineno=2), Instr('STORE_FAST', 'x', lineno=2), Instr('LOAD_FAST', 'x', lineno=3), Instr('STORE_FAST', 'y', lineno=3), Instr('LOAD_CONST', None, lineno=3), Instr('RETURN_VALUE', lineno=3)])
def test_label3(self): """ When you delete ``extended_arg`` that have a value of 0, the following will fail when calling ``Bytecode.to_concrete_bytecode()`` because it cant find a label to correspond to the jump target """ code = get_code(""" def func(x): if x == 1: return x +1 elif x == 2: return x + 1 elif x == 3: return x + 1 elif x == 4: return x + 1 elif x == 5: return x + 1 elif x == 6: return x + 1 elif x == 7: return x + 1 elif x == 8: return x + 1 elif x == 9: return x + 1 elif x == 10: return x + 1 elif x == 11: return x + 1 elif x == 12: return x + 1 elif x == 13: return x + 1 elif x == 14: return x + 1 elif x == 15: return x + 1 elif x == 16: return x + 1 elif x == 17: return x + 1 return -1 """, function=True) code = Bytecode.from_code(code) concrete = code.to_concrete_bytecode() self.assertIsInstance(concrete, ConcreteBytecode)
def test_attr(self): code_obj = get_code("x = 5") code = ConcreteBytecode.from_code(code_obj) self.assertEqual(code.consts, [5, None]) self.assertEqual(code.names, ["x"]) self.assertEqual(code.varnames, []) self.assertEqual(code.freevars, []) self.assertListEqual( list(code), [ ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("STORE_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 1, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1), ], )
def test_extended_arg(self): # Create a code object from arbitrary bytecode co_code = b"\x90\x12\x904\x90\xabd\xcd" if WORDCODE else b"\x904\x12d\xcd\xab" code = get_code("x=1") args = ((code.co_argcount, ) if sys.version_info < (3, 8) else (code.co_argcount, code.co_posonlyargcount)) args += ( code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, co_code, code.co_consts, code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars, ) code = types.CodeType(*args) # without EXTENDED_ARG opcode bytecode = ConcreteBytecode.from_code(code) self.assertListEqual( list(bytecode), [ConcreteInstr("LOAD_CONST", 0x1234ABCD, lineno=1)]) # with EXTENDED_ARG opcode bytecode = ConcreteBytecode.from_code(code, extended_arg=True) if WORDCODE: expected = [ ConcreteInstr("EXTENDED_ARG", 0x12, lineno=1), ConcreteInstr("EXTENDED_ARG", 0x34, lineno=1), ConcreteInstr("EXTENDED_ARG", 0xAB, lineno=1), ConcreteInstr("LOAD_CONST", 0xCD, lineno=1), ] else: expected = [ ConcreteInstr("EXTENDED_ARG", 0x1234, lineno=1), ConcreteInstr("LOAD_CONST", 0xABCD, lineno=1), ] self.assertListEqual(list(bytecode), expected)
def test_from_code(self): code = get_code(""" if test: x = 1 else: x = 2 """) bytecode = Bytecode.from_code(code) label_else = Label() label_exit = Label() if sys.version_info < (3, 10): self.assertEqual( bytecode, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label_else, lineno=1), Instr("LOAD_CONST", 1, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", label_exit, lineno=2), label_else, Instr("LOAD_CONST", 2, lineno=4), Instr("STORE_NAME", "x", lineno=4), label_exit, Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ], ) # Control flow handling appears to have changed under Python 3.10 else: self.assertEqual( bytecode, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label_else, lineno=1), Instr("LOAD_CONST", 1, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("LOAD_CONST", None, lineno=2), Instr("RETURN_VALUE", lineno=2), label_else, Instr("LOAD_CONST", 2, lineno=4), Instr("STORE_NAME", "x", lineno=4), Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ], )
def test_explicit_stacksize(self): # Passing stacksize=... to ConcreteBytecode.to_code should result in a # code object with the specified stacksize. We pass some silly values # and assert that they are honored. code_obj = get_code("print('%s' % (a,b,c))") original_stacksize = code_obj.co_stacksize concrete = ConcreteBytecode.from_code(code_obj) # First with something bigger than necessary. explicit_stacksize = original_stacksize + 42 new_code_obj = concrete.to_code(stacksize=explicit_stacksize) self.assertEqual(new_code_obj.co_stacksize, explicit_stacksize) # Then with something bogus. We probably don't want to advertise this # in the documentation. If this fails then decide if it's for good # reason, and remove if so. explicit_stacksize = 0 new_code_obj = concrete.to_code(stacksize=explicit_stacksize) self.assertEqual(new_code_obj.co_stacksize, explicit_stacksize)
def test_from_code_load_fast(self): code = get_code( """ def func(): x = 33 y = x """, function=True, ) code = Bytecode.from_code(code) self.assertEqual( code, [ Instr("LOAD_CONST", 33, lineno=2), Instr("STORE_FAST", "x", lineno=2), Instr("LOAD_FAST", "x", lineno=3), Instr("STORE_FAST", "y", lineno=3), Instr("LOAD_CONST", None, lineno=3), Instr("RETURN_VALUE", lineno=3), ], )
def test_from_code(self): code = get_code(""" if test: x = 1 else: x = 2 """) bytecode = Bytecode.from_code(code) label_else = Label() label_exit = Label() self.assertEqual(bytecode, [ Instr('LOAD_NAME', 'test', lineno=1), Instr('POP_JUMP_IF_FALSE', label_else, lineno=1), Instr('LOAD_CONST', 1, lineno=2), Instr('STORE_NAME', 'x', lineno=2), Instr('JUMP_FORWARD', label_exit, lineno=2), label_else, Instr('LOAD_CONST', 2, lineno=4), Instr('STORE_NAME', 'x', lineno=4), label_exit, Instr('LOAD_CONST', None, lineno=4), Instr('RETURN_VALUE', lineno=4) ])
def test_from_code(self): code = get_code(""" if test: x = 1 else: x = 2 """) bytecode = Bytecode.from_code(code) label_else = Label() label_exit = Label() self.assertEqual(bytecode, [Instr('LOAD_NAME', 'test', lineno=1), Instr('POP_JUMP_IF_FALSE', label_else, lineno=1), Instr('LOAD_CONST', 1, lineno=2), Instr('STORE_NAME', 'x', lineno=2), Instr('JUMP_FORWARD', label_exit, lineno=2), label_else, Instr('LOAD_CONST', 2, lineno=4), Instr('STORE_NAME', 'x', lineno=4), label_exit, Instr('LOAD_CONST', None, lineno=4), Instr('RETURN_VALUE', lineno=4)])
def test_extended_arg_make_function(self): if (3, 9) <= sys.version_info < (3, 10): from bytecode.tests.util_annotation import get_code as get_code_future code_obj = get_code_future( """ def foo(x: int, y: int): pass """ ) else: code_obj = get_code( """ def foo(x: int, y: int): pass """ ) # without EXTENDED_ARG concrete = ConcreteBytecode.from_code(code_obj) # With future annotation the int annotation is stringified and # stored as constant this the default behavior under Python 3.10 if sys.version_info >= (3, 10): func_code = concrete.consts[3] names = ["foo"] consts = ["x", "int", "y", func_code, "foo", None, ("x", "int", "y", "int")] const_offset = 2 first_instrs = [ ConcreteInstr("LOAD_CONST", 6, lineno=1), ] elif ( sys.version_info >= (3, 7) and concrete.flags & CompilerFlags.FUTURE_ANNOTATIONS ): func_code = concrete.consts[2] names = ["foo"] consts = ["int", ("x", "y"), func_code, "foo", None] const_offset = 1 first_instrs = [ ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0 + const_offset, lineno=1), ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1), ] else: func_code = concrete.consts[1] names = ["int", "foo"] consts = [("x", "y"), func_code, "foo", None] const_offset = 0 first_instrs = [ ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("LOAD_CONST", 0 + const_offset, lineno=1), ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1), ] self.assertEqual(concrete.names, names) self.assertEqual(concrete.consts, consts) expected = first_instrs + [ ConcreteInstr("LOAD_CONST", 1 + const_offset, lineno=1), ConcreteInstr("LOAD_CONST", 2 + const_offset, lineno=1), ConcreteInstr("MAKE_FUNCTION", 4, lineno=1), ConcreteInstr("STORE_NAME", 1 - bool(const_offset), lineno=1), ConcreteInstr("LOAD_CONST", 3 + const_offset, lineno=1), ConcreteInstr("RETURN_VALUE", lineno=1), ] self.assertListEqual(list(concrete), expected) # with EXTENDED_ARG concrete = ConcreteBytecode.from_code(code_obj, extended_arg=True) # With future annotation the int annotation is stringified and # stored as constant this the default behavior under Python 3.10 if sys.version_info >= (3, 10): func_code = concrete.consts[3] names = ["foo"] consts = ["x", "int", "y", func_code, "foo", None, ("x", "int", "y", "int")] elif concrete.flags & CompilerFlags.FUTURE_ANNOTATIONS: func_code = concrete.consts[2] names = ["foo"] consts = ["int", ("x", "y"), func_code, "foo", None] else: func_code = concrete.consts[1] names = ["int", "foo"] consts = [("x", "y"), func_code, "foo", None] self.assertEqual(concrete.names, names) self.assertEqual(concrete.consts, consts) self.assertListEqual(list(concrete), expected)
def test_label3(self): """ CPython generates useless EXTENDED_ARG 0 in some cases. We need to properly track them as otherwise we can end up with broken offset for jumps. """ source = """ def func(x): if x == 1: return x + 0 elif x == 2: return x + 1 elif x == 3: return x + 2 elif x == 4: return x + 3 elif x == 5: return x + 4 elif x == 6: return x + 5 elif x == 7: return x + 6 elif x == 8: return x + 7 elif x == 9: return x + 8 elif x == 10: return x + 9 elif x == 11: return x + 10 elif x == 12: return x + 11 elif x == 13: return x + 12 elif x == 14: return x + 13 elif x == 15: return x + 14 elif x == 16: return x + 15 elif x == 17: return x + 16 return -1 """ code = get_code(source, function=True) bcode = Bytecode.from_code(code) concrete = bcode.to_concrete_bytecode() self.assertIsInstance(concrete, ConcreteBytecode) # Ensure that we do not generate broken code loc = {} exec(textwrap.dedent(source), loc) func = loc["func"] func.__code__ = bcode.to_code() for i, x in enumerate(range(1, 18)): self.assertEqual(func(x), x + i) self.assertEqual(func(18), -1) # Ensure that we properly round trip in such cases self.assertEqual( ConcreteBytecode.from_code(code).to_code().co_code, code.co_code )
def test_label3(self): """ CPython generates useless EXTENDED_ARG 0 in some cases. We need to properly track them as otherwise we can end up with broken offset for jumps. """ source = """ def func(x): if x == 1: return x + 0 elif x == 2: return x + 1 elif x == 3: return x + 2 elif x == 4: return x + 3 elif x == 5: return x + 4 elif x == 6: return x + 5 elif x == 7: return x + 6 elif x == 8: return x + 7 elif x == 9: return x + 8 elif x == 10: return x + 9 elif x == 11: return x + 10 elif x == 12: return x + 11 elif x == 13: return x + 12 elif x == 14: return x + 13 elif x == 15: return x + 14 elif x == 16: return x + 15 elif x == 17: return x + 16 return -1 """ code = get_code(source, function=True) bcode = Bytecode.from_code(code) concrete = bcode.to_concrete_bytecode() self.assertIsInstance(concrete, ConcreteBytecode) # Ensure that we do not generate broken code loc = {} exec(textwrap.dedent(source), loc) func = loc['func'] func.__code__ = bcode.to_code() for i, x in enumerate(range(1, 18)): self.assertEqual(func(x), x + i) self.assertEqual(func(18), -1) # Ensure that we properly round trip in such cases self.assertEqual(ConcreteBytecode.from_code(code).to_code().co_code, code.co_code)