Close #17916: dis.Bytecode based replacement for distb

- Bytecode.from_traceback() alternate constructor
- current_offset parameter and attribute

Patch by Claudiu Popa
This commit is contained in:
Nick Coghlan 2013-11-23 00:57:00 +10:00
parent 8fbdb097cf
commit 50c48b89e3
5 changed files with 98 additions and 4 deletions

View File

@ -44,7 +44,7 @@ The bytecode analysis API allows pieces of Python code to be wrapped in a
:class:`Bytecode` object that provides easy access to details of the :class:`Bytecode` object that provides easy access to details of the
compiled code. compiled code.
.. class:: Bytecode(x, *, first_line=None) .. class:: Bytecode(x, *, first_line=None, current_offset=None)
Analyse the bytecode corresponding to a function, method, string of Analyse the bytecode corresponding to a function, method, string of
source code, or a code object (as returned by :func:`compile`). source code, or a code object (as returned by :func:`compile`).
@ -59,6 +59,16 @@ compiled code.
Otherwise, the source line information (if any) is taken directly from Otherwise, the source line information (if any) is taken directly from
the disassembled code object. the disassembled code object.
If *current_offset* is not None, it refers to an instruction offset
in the disassembled code. Setting this means :meth:`dis` will display
a "current instruction" marker against the specified opcode.
.. classmethod:: from_traceback(tb)
Construct a :class:`Bytecode` instance from the given traceback,
setting *current_offset* to the instruction responsible for the
exception.
.. data:: codeobj .. data:: codeobj
The compiled code object. The compiled code object.

View File

@ -385,7 +385,8 @@ The new :class:`dis.Bytecode` class provides an object-oriented API for
inspecting bytecode, both in human-readable form and for iterating over inspecting bytecode, both in human-readable form and for iterating over
instructions. instructions.
(Contributed by Nick Coghlan, Ryan Kelly and Thomas Kluyver in :issue:`11816`) (Contributed by Nick Coghlan, Ryan Kelly and Thomas Kluyver in :issue:`11816`
and Claudiu Popa in :issue:`17916`)
doctest doctest

View File

@ -406,7 +406,7 @@ class Bytecode:
Iterating over this yields the bytecode operations as Instruction instances. Iterating over this yields the bytecode operations as Instruction instances.
""" """
def __init__(self, x, *, first_line=None): def __init__(self, x, *, first_line=None, current_offset=None):
self.codeobj = co = _get_code_object(x) self.codeobj = co = _get_code_object(x)
if first_line is None: if first_line is None:
self.first_line = co.co_firstlineno self.first_line = co.co_firstlineno
@ -417,6 +417,7 @@ class Bytecode:
self._cell_names = co.co_cellvars + co.co_freevars self._cell_names = co.co_cellvars + co.co_freevars
self._linestarts = dict(findlinestarts(co)) self._linestarts = dict(findlinestarts(co))
self._original_object = x self._original_object = x
self.current_offset = current_offset
def __iter__(self): def __iter__(self):
co = self.codeobj co = self.codeobj
@ -429,6 +430,13 @@ class Bytecode:
return "{}({!r})".format(self.__class__.__name__, return "{}({!r})".format(self.__class__.__name__,
self._original_object) self._original_object)
@classmethod
def from_traceback(cls, tb):
""" Construct a Bytecode from the given traceback """
while tb.tb_next:
tb = tb.tb_next
return cls(tb.tb_frame.f_code, current_offset=tb.tb_lasti)
def info(self): def info(self):
"""Return formatted information about the code object.""" """Return formatted information about the code object."""
return _format_code_info(self.codeobj) return _format_code_info(self.codeobj)
@ -436,13 +444,18 @@ class Bytecode:
def dis(self): def dis(self):
"""Return a formatted view of the bytecode operations.""" """Return a formatted view of the bytecode operations."""
co = self.codeobj co = self.codeobj
if self.current_offset is not None:
offset = self.current_offset
else:
offset = -1
with io.StringIO() as output: with io.StringIO() as output:
_disassemble_bytes(co.co_code, varnames=co.co_varnames, _disassemble_bytes(co.co_code, varnames=co.co_varnames,
names=co.co_names, constants=co.co_consts, names=co.co_names, constants=co.co_consts,
cells=self._cell_names, cells=self._cell_names,
linestarts=self._linestarts, linestarts=self._linestarts,
line_offset=self._line_offset, line_offset=self._line_offset,
file=output) file=output,
lasti=offset)
return output.getvalue() return output.getvalue()

View File

@ -10,6 +10,21 @@ import io
import types import types
import contextlib import contextlib
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
class _C: class _C:
def __init__(self, x): def __init__(self, x):
self.x = x == 1 self.x = x == 1
@ -174,6 +189,46 @@ dis_compound_stmt_str = """\
25 RETURN_VALUE 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): class DisTests(unittest.TestCase):
def get_disassembly(self, func, lasti=-1, wrapper=True): def get_disassembly(self, func, lasti=-1, wrapper=True):
@ -758,6 +813,17 @@ class BytecodeTests(unittest.TestCase):
actual = dis.Bytecode(_f).dis() actual = dis.Bytecode(_f).dis()
self.assertEqual(actual, dis_f) self.assertEqual(actual, dis_f)
def test_from_traceback(self):
tb = get_tb()
b = dis.Bytecode.from_traceback(tb)
while tb.tb_next: tb = tb.tb_next
self.assertEqual(b.current_offset, tb.tb_lasti)
def test_from_traceback_dis(self):
tb = get_tb()
b = dis.Bytecode.from_traceback(tb)
self.assertEqual(b.dis(), dis_traceback)
def test_main(): def test_main():
run_unittest(DisTests, DisWithFileTests, CodeInfoTests, run_unittest(DisTests, DisWithFileTests, CodeInfoTests,

View File

@ -65,6 +65,10 @@ Core and Builtins
Library Library
------- -------
- Issue #17916: Added dis.Bytecode.from_traceback() and
dis.Bytecode.current_offset to easily display "current instruction"
markers in the new disassembly API (Patch by Claudiu Popa).
- Issue #19552: venv now supports bootstrapping pip into virtual environments - Issue #19552: venv now supports bootstrapping pip into virtual environments
- Issue #17134: Finalize interface to Windows' certificate store. Cert and - Issue #17134: Finalize interface to Windows' certificate store. Cert and