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
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
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
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
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
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

View File

@ -406,7 +406,7 @@ class Bytecode:
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)
if first_line is None:
self.first_line = co.co_firstlineno
@ -417,6 +417,7 @@ class Bytecode:
self._cell_names = co.co_cellvars + co.co_freevars
self._linestarts = dict(findlinestarts(co))
self._original_object = x
self.current_offset = current_offset
def __iter__(self):
co = self.codeobj
@ -429,6 +430,13 @@ class Bytecode:
return "{}({!r})".format(self.__class__.__name__,
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):
"""Return formatted information about the code object."""
return _format_code_info(self.codeobj)
@ -436,13 +444,18 @@ class Bytecode:
def dis(self):
"""Return a formatted view of the bytecode operations."""
co = self.codeobj
if self.current_offset is not None:
offset = self.current_offset
else:
offset = -1
with io.StringIO() as output:
_disassemble_bytes(co.co_code, varnames=co.co_varnames,
names=co.co_names, constants=co.co_consts,
cells=self._cell_names,
linestarts=self._linestarts,
line_offset=self._line_offset,
file=output)
file=output,
lasti=offset)
return output.getvalue()

View File

@ -10,6 +10,21 @@ import io
import types
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:
def __init__(self, x):
self.x = x == 1
@ -174,6 +189,46 @@ dis_compound_stmt_str = """\
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):
@ -758,6 +813,17 @@ class BytecodeTests(unittest.TestCase):
actual = dis.Bytecode(_f).dis()
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():
run_unittest(DisTests, DisWithFileTests, CodeInfoTests,

View File

@ -65,6 +65,10 @@ Core and Builtins
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 #17134: Finalize interface to Windows' certificate store. Cert and