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_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_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 make_bytecode(self, symbol_table): code = [ Instr("LOAD_CONST", symbol_table["write_func"]), Instr("LOAD_NAME", "_output"), Instr("LOAD_NAME", "str") ] compiled_expr = compile(self.variable_name, filename="<none>", mode="eval") concrete_bytecode = ConcreteBytecode.from_code(compiled_expr) inner = concrete_bytecode.to_bytecode() # The compiler drops a return statement at the end of the # expression, which we want to strip off so that we can use # the result inner.pop() code += inner code += [ Instr("CALL_FUNCTION", 1), Instr("CALL_FUNCTION", 2), Instr("POP_TOP") ] return code
def test_cellvar_freevar(self): concrete = ConcreteBytecode() concrete.cellvars = ["cell"] concrete.freevars = ["free"] concrete.append(ConcreteInstr("LOAD_DEREF", 0)) concrete.append(ConcreteInstr("LOAD_DEREF", 1)) code = concrete.to_code() concrete = ConcreteBytecode.from_code(code) self.assertEqual(concrete.cellvars, ["cell"]) self.assertEqual(concrete.freevars, ["free"]) self.assertEqual( list(concrete), [ ConcreteInstr("LOAD_DEREF", 0, lineno=1), ConcreteInstr("LOAD_DEREF", 1, lineno=1), ], ) bytecode = concrete.to_bytecode() self.assertEqual(bytecode.cellvars, ["cell"]) self.assertEqual( list(bytecode), [ Instr("LOAD_DEREF", CellVar("cell"), lineno=1), Instr("LOAD_DEREF", FreeVar("free"), lineno=1), ], )
def instrument_code_recursive( self, code: CodeType, parent_code_object_id: Optional[int] = None) -> CodeType: """Instrument the given Code Object recursively. :param code: The code object to be instrumented. :param parent_code_object_id: Internal id of the code object to which this code object belongs (can be None if `code` is the highest node, i.e. the module node). :return The instrumented code object. """ # The original bytecode should match the disassembly, so EXTENDED_ARG is included # original_cfg = CFG.from_bytecode(ConcreteBytecode.from_code(code, extended_arg=False).to_bytecode()) original_cfg = CFG.from_bytecode( ConcreteBytecode.from_code(code, extended_arg=False).to_bytecode()) original_cdg = ControlDependenceGraph.compute(original_cfg) cfg = CFG.from_bytecode(Bytecode.from_code(code)) code_object_id = self._tracer.register_code_object( CodeObjectMetaData(code.co_filename, code, parent_code_object_id, original_cfg, cfg, original_cdg)) assert cfg.entry_node is not None, "Entry node cannot be None." module_entry = False if not parent_code_object_id: # This is the module entry, in which we want to import the ExecutionTracer self._instrument_import(cfg) module_entry = True self._instrument_cfg(cfg, original_cfg, code_object_id, module_entry) return self._instrument_inner_code_objects( cfg.bytecode_cfg().to_code(), code_object_id)
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_packing_lines(self): from bytecode.tests.long_lines_example import long_lines import dis line_starts = list(dis.findlinestarts(long_lines.__code__)) concrete = ConcreteBytecode.from_code(long_lines.__code__) as_code = concrete.to_code() self.assertEqual(line_starts, list(dis.findlinestarts(as_code)))
def test_extended_arg_unpack_ex(self): def test(): p = [1, 2, 3, 4, 5, 6] q, r, *s, t = p return q, r, s, t test.__code__ = ConcreteBytecode.from_code(test.__code__, extended_arg=True).to_code() self.assertEqual(test.__code__.co_stacksize, 6) self.assertEqual(test(), (1, 2, [3, 4, 5], 6))
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_freevar(self): concrete = ConcreteBytecode() concrete.freevars = ["x"] concrete.append(ConcreteInstr("LOAD_DEREF", 0)) code = concrete.to_code() concrete = ConcreteBytecode.from_code(code) self.assertEqual(concrete.cellvars, []) self.assertEqual(concrete.freevars, ["x"]) self.assertEqual(list(concrete), [ConcreteInstr("LOAD_DEREF", 0, lineno=1)]) bytecode = concrete.to_bytecode() self.assertEqual(bytecode.cellvars, []) self.assertEqual(list(bytecode), [Instr("LOAD_DEREF", FreeVar("x"), lineno=1)])
def test_extended_arg(self): # Create a code object from arbitrary bytecode co_code = b"%c4\x12d\xcd\xab" % EXTENDED_ARG code = get_code("x=1") args = ( code.co_argcount, 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) expected = [ ConcreteInstr("EXTENDED_ARG", 0x1234, lineno=1), ConcreteInstr("LOAD_CONST", 0xABCD, lineno=1), ] self.assertListEqual(list(bytecode), 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 test_cellvar(self): concrete = ConcreteBytecode() concrete.cellvars = ['x'] concrete.append(ConcreteInstr('LOAD_DEREF', 0)) code = concrete.to_code() concrete = ConcreteBytecode.from_code(code) self.assertEqual(concrete.cellvars, ['x']) self.assertEqual(concrete.freevars, []) self.assertEqual(list(concrete), [ConcreteInstr('LOAD_DEREF', 0, lineno=1)]) bytecode = concrete.to_bytecode() self.assertEqual(bytecode.cellvars, ['x']) self.assertEqual(list(bytecode), [Instr('LOAD_DEREF', CellVar('x'), lineno=1)])
def test_freevar(self): concrete = ConcreteBytecode() concrete.freevars = ['x'] concrete.append(ConcreteInstr('LOAD_DEREF', 0)) code = concrete.to_code() concrete = ConcreteBytecode.from_code(code) self.assertEqual(concrete.cellvars, []) self.assertEqual(concrete.freevars, ['x']) self.assertEqual(list(concrete), [ConcreteInstr('LOAD_DEREF', 0, lineno=1)]) bytecode = concrete.to_bytecode() self.assertEqual(bytecode.cellvars, []) self.assertEqual(list(bytecode), [Instr('LOAD_DEREF', FreeVar('x'), lineno=1)])
def extract_code(frame, start=None, end=None, name="<withhack>"): """Extract a Code object corresponding to the given frame. Given a frame object, this function returns a byteplay Code object with containing the code being executed by the frame. If the optional "start" "start" and/or "end" arguments are given, they are used as indices to return only a slice of the code. """ code = frame.f_code if start is None: start = 0 if end is None: end = len(code.co_code) # convert the byte indices into ConcreteBytecode indices start_c = 0 end_c = 0 at = 0 concrete_bc = ConcreteBytecode.from_code(code) for c in concrete_bc: at += c.size if at < start: start_c += 1 if at < end: end_c += 1 # convert the ConcreteBytecode indices into Bytecode indices # assumes that instructions map one-to-one bc = concrete_bc.to_bytecode() start_b = None end_b = None at = 0 for i, b in enumerate(bc): if at == start_c: start_b = i if at == end_c: end_b = i if isinstance(b, bytecode.instr.Instr): at += 1 assert at == len(concrete_bc) bc[:] = bc[start_b:end_b] return bc
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 make_bytecode(self, symbol_table): label_end = Label() compiled_expr = compile(self.condition, filename="<none>", mode="eval") concrete_bytecode = ConcreteBytecode.from_code(compiled_expr) inner = concrete_bytecode.to_bytecode() # The compiler drops a return statement at the end of the # expression, which we want to strip off so that we can use # the result inner.pop() inner += [Instr("POP_JUMP_IF_FALSE", label_end)] for element in self.sequence.elements: inner += element.make_bytecode(symbol_table) inner += [label_end] return inner
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 f(a, b): # res = a + b return def g(a, b): res = a + b if a < b else b + a r = 0 for a in range(res): r += 1 return r or 2 for x in (f, g): #get byte code for f dis(x) print(f.__code__.co_code) c = Bytecode.from_code(x.__code__) cc = ConcreteBytecode.from_code(x.__code__) dump_bytecode(c) dump_bytecode(cc) #generate byte code cnew = c.to_code() x.__code__ = cnew dis(x) print(x(3, 5))
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 )
def test_expected_arg_with_many_consts(self): def test(): var = 0 var = 1 var = 2 var = 3 var = 4 var = 5 var = 6 var = 7 var = 8 var = 9 var = 10 var = 11 var = 12 var = 13 var = 14 var = 15 var = 16 var = 17 var = 18 var = 19 var = 20 var = 21 var = 22 var = 23 var = 24 var = 25 var = 26 var = 27 var = 28 var = 29 var = 30 var = 31 var = 32 var = 33 var = 34 var = 35 var = 36 var = 37 var = 38 var = 39 var = 40 var = 41 var = 42 var = 43 var = 44 var = 45 var = 46 var = 47 var = 48 var = 49 var = 50 var = 51 var = 52 var = 53 var = 54 var = 55 var = 56 var = 57 var = 58 var = 59 var = 60 var = 61 var = 62 var = 63 var = 64 var = 65 var = 66 var = 67 var = 68 var = 69 var = 70 var = 71 var = 72 var = 73 var = 74 var = 75 var = 76 var = 77 var = 78 var = 79 var = 80 var = 81 var = 82 var = 83 var = 84 var = 85 var = 86 var = 87 var = 88 var = 89 var = 90 var = 91 var = 92 var = 93 var = 94 var = 95 var = 96 var = 97 var = 98 var = 99 var = 100 var = 101 var = 102 var = 103 var = 104 var = 105 var = 106 var = 107 var = 108 var = 109 var = 110 var = 111 var = 112 var = 113 var = 114 var = 115 var = 116 var = 117 var = 118 var = 119 var = 120 var = 121 var = 122 var = 123 var = 124 var = 125 var = 126 var = 127 var = 128 var = 129 var = 130 var = 131 var = 132 var = 133 var = 134 var = 135 var = 136 var = 137 var = 138 var = 139 var = 140 var = 141 var = 142 var = 143 var = 144 var = 145 var = 146 var = 147 var = 148 var = 149 var = 150 var = 151 var = 152 var = 153 var = 154 var = 155 var = 156 var = 157 var = 158 var = 159 var = 160 var = 161 var = 162 var = 163 var = 164 var = 165 var = 166 var = 167 var = 168 var = 169 var = 170 var = 171 var = 172 var = 173 var = 174 var = 175 var = 176 var = 177 var = 178 var = 179 var = 180 var = 181 var = 182 var = 183 var = 184 var = 185 var = 186 var = 187 var = 188 var = 189 var = 190 var = 191 var = 192 var = 193 var = 194 var = 195 var = 196 var = 197 var = 198 var = 199 var = 200 var = 201 var = 202 var = 203 var = 204 var = 205 var = 206 var = 207 var = 208 var = 209 var = 210 var = 211 var = 212 var = 213 var = 214 var = 215 var = 216 var = 217 var = 218 var = 219 var = 220 var = 221 var = 222 var = 223 var = 224 var = 225 var = 226 var = 227 var = 228 var = 229 var = 230 var = 231 var = 232 var = 233 var = 234 var = 235 var = 236 var = 237 var = 238 var = 239 var = 240 var = 241 var = 242 var = 243 var = 244 var = 245 var = 246 var = 247 var = 248 var = 249 var = 250 var = 251 var = 252 var = 253 var = 254 var = 255 var = 256 var = 257 var = 258 var = 259 return var test.__code__ = ConcreteBytecode.from_code( test.__code__, extended_arg=True ).to_code() self.assertEqual(test.__code__.co_stacksize, 1) self.assertEqual(test(), 259)
def test_fail_extended_arg_jump(self): def test(): var = None for _ in range(0, 1): var = 0 var = 1 var = 2 var = 3 var = 4 var = 5 var = 6 var = 7 var = 8 var = 9 var = 10 var = 11 var = 12 var = 13 var = 14 var = 15 var = 16 var = 17 var = 18 var = 19 var = 20 var = 21 var = 22 var = 23 var = 24 var = 25 var = 26 var = 27 var = 28 var = 29 var = 30 var = 31 var = 32 var = 33 var = 34 var = 35 var = 36 var = 37 var = 38 var = 39 var = 40 var = 41 var = 42 var = 43 var = 44 var = 45 var = 46 var = 47 var = 48 var = 49 var = 50 var = 51 var = 52 var = 53 var = 54 var = 55 var = 56 var = 57 var = 58 var = 59 var = 60 var = 61 var = 62 var = 63 var = 64 var = 65 var = 66 var = 67 var = 68 var = 69 var = 70 return var # Generate the bytecode with extended arguments bytecode = ConcreteBytecode.from_code(test.__code__, extended_arg=True) bytecode.to_code()
def test_fail_extended_arg_jump(self): def test(): var = None for _ in range(0, 1): var = 0 var = 1 var = 2 var = 3 var = 4 var = 5 var = 6 var = 7 var = 8 var = 9 var = 10 var = 11 var = 12 var = 13 var = 14 var = 15 var = 16 var = 17 var = 18 var = 19 var = 20 var = 21 var = 22 var = 23 var = 24 var = 25 var = 26 var = 27 var = 28 var = 29 var = 30 var = 31 var = 32 var = 33 var = 34 var = 35 var = 36 var = 37 var = 38 var = 39 var = 40 var = 41 var = 42 var = 43 var = 44 var = 45 var = 46 var = 47 var = 48 var = 49 var = 50 var = 51 var = 52 var = 53 var = 54 var = 55 var = 56 var = 57 var = 58 var = 59 var = 60 var = 61 var = 62 var = 63 var = 64 var = 65 var = 66 var = 67 var = 68 var = 69 var = 70 return var # Generate the bytecode with extended arguments bytecode = ConcreteBytecode.from_code(test.__code__, extended_arg=True) # This is where computation fails # It seems like it is caused by the split of blocks and a wrong start size # for one block. bytecode.to_code()