diff -r 61463ff7dc68 Lib/dis.py --- a/Lib/dis.py Wed Oct 23 22:03:45 2013 +0200 +++ b/Lib/dis.py Thu Oct 24 20:54:07 2013 +0300 @@ -8,7 +8,7 @@ from opcode import __all__ as _opcodes_all __all__ = ["code_info", "dis", "disassemble", "distb", "disco", - "findlinestarts", "findlabels", "show_code", + "findlinestarts", "findlabels", "show_code", "TracebackBytecode", "get_instructions", "Instruction", "Bytecode"] + _opcodes_all del _opcodes_all @@ -422,7 +422,7 @@ """Print the information about the code object as returned by info().""" print(self.info(), file=file) - def display_code(self, *, file=None): + def display_code(self, *, file=None, current_offset=None): """Print a formatted view of the bytecode operations. """ co = self.codeobj @@ -430,9 +430,46 @@ names=co.co_names, constants=co.co_consts, cells=self.cell_names, linestarts=self.linestarts, - file=file + file=file, + lasti=current_offset ) +class TracebackBytecode: + """The bytecode operations of a piece of traceback + + Instantiate this with a traceback. + Iterating over this yields tuples, where the first item is + bytecode operations as Instruction instance and the second argument + shows if this instruction is the instruction where the error occurred. + """ + + def __init__(self, tb): + while tb.tb_next: + tb = tb.tb_next + + self.original_object = tb + self.bytecode = Bytecode(tb.tb_frame.f_code) + self.last_offset = tb.tb_lasti + + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, + self.original_object) + + def __iter__(self): + co = self.bytecode.codeobj + for instr in _get_instructions_bytes(co.co_code, co.co_varnames, + co.co_names, co.co_consts, + self.bytecode.cell_names, + self.bytecode.linestarts, + self.bytecode.line_offset): + yield instr, instr.offset == self.last_offset + + def display_code(self, *, file=None): + """Print a formatted view of the bytecode operations. + """ + return self.bytecode.display_code(file=file, + current_offset=self.last_offset) + def _test(): """Simple test program to disassemble a file.""" diff -r 61463ff7dc68 Lib/test/test_dis.py --- a/Lib/test/test_dis.py Wed Oct 23 22:03:45 2013 +0200 +++ b/Lib/test/test_dis.py Thu Oct 24 20:54:07 2013 +0300 @@ -13,6 +13,21 @@ def __init__(self, x): self.x = x == 1 +def get_tb(): + def _error(): + try: + 1 / 0 + except Exception as e: + tb = e.__traceback__ + return tb + + tb = _error() + while tb.tb_next: + tb = tb.tb_next + return tb + +TRACEBACK_CODE = get_tb().tb_frame.f_code + dis_c_instance_method = """\ %-4d 0 LOAD_FAST 1 (x) 3 LOAD_CONST 1 (1) @@ -173,6 +188,46 @@ 25 RETURN_VALUE """ +dis_traceback = """\ + %-4d 0 SETUP_EXCEPT 12 (to 15) + + %-4d 3 LOAD_CONST 1 (1) + 6 LOAD_CONST 2 (0) + --> 9 BINARY_TRUE_DIVIDE + 10 POP_TOP + 11 POP_BLOCK + 12 JUMP_FORWARD 46 (to 61) + + %-4d >> 15 DUP_TOP + 16 LOAD_GLOBAL 0 (Exception) + 19 COMPARE_OP 10 (exception match) + 22 POP_JUMP_IF_FALSE 60 + 25 POP_TOP + 26 STORE_FAST 0 (e) + 29 POP_TOP + 30 SETUP_FINALLY 14 (to 47) + + %-4d 33 LOAD_FAST 0 (e) + 36 LOAD_ATTR 1 (__traceback__) + 39 STORE_FAST 1 (tb) + 42 POP_BLOCK + 43 POP_EXCEPT + 44 LOAD_CONST 0 (None) + >> 47 LOAD_CONST 0 (None) + 50 STORE_FAST 0 (e) + 53 DELETE_FAST 0 (e) + 56 END_FINALLY + 57 JUMP_FORWARD 1 (to 61) + >> 60 END_FINALLY + + %-4d >> 61 LOAD_FAST 1 (tb) + 64 RETURN_VALUE +""" % (TRACEBACK_CODE.co_firstlineno + 1, + TRACEBACK_CODE.co_firstlineno + 2, + TRACEBACK_CODE.co_firstlineno + 3, + TRACEBACK_CODE.co_firstlineno + 4, + TRACEBACK_CODE.co_firstlineno + 5) + class DisTests(unittest.TestCase): def get_disassembly(self, func, lasti=-1, wrapper=True): @@ -721,9 +776,41 @@ result = [line.rstrip() for line in output.getvalue().splitlines()] self.assertEqual(result, dis_f.splitlines()) +class TracebackBytecodeTests(unittest.TestCase): + + def setUp(self): + self.tb = get_tb() + self.bytecode = dis.TracebackBytecode(self.tb) + + def test_iteration(self): + for instr, current in self.bytecode: + self.assertIsInstance(instr, dis.Instruction) + self.assertIsInstance(current, bool) + + if instr.opname == 'BINARY_TRUE_DIVIDE': + self.assertTrue(current) + else: + self.assertFalse(current) + + def test_instantiation(self): + tb = self.tb + while tb.tb_next: tb = tb.tb_next + + self.assertIsInstance(self.bytecode.bytecode, dis.Bytecode) + self.assertEqual(self.bytecode.original_object, tb) + self.assertEqual(self.bytecode.last_offset, tb.tb_lasti) + + def test_display_code(self): + output = io.StringIO() + self.bytecode.display_code(file=output) + result = [line.rstrip() for line in output.getvalue().splitlines()] + self.assertEqual(result, dis_traceback.splitlines()) + def test_main(): - run_unittest(DisTests, CodeInfoTests, InstructionTests, BytecodeTests) + run_unittest(DisTests, CodeInfoTests, + InstructionTests, BytecodeTests, + TracebackBytecodeTests) if __name__ == "__main__": test_main()