Skip to content

Commit 5c8b54e

Browse files
committed
Issue 6507: accept source strings directly in dis.dis(). Original patch by Daniel Urban
1 parent 9bf2b3a commit 5c8b54e

5 files changed

Lines changed: 79 additions & 6 deletions

File tree

Doc/library/dis.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ The :mod:`dis` module defines the following functions and constants:
3333
.. function:: dis(x=None)
3434

3535
Disassemble the *x* object. *x* can denote either a module, a
36-
class, a method, a function, or a code object. For a module, it disassembles
37-
all functions. For a class, it disassembles all methods. For a single code
38-
sequence, it prints one line per bytecode instruction. If no object is
39-
provided, it disassembles the last traceback.
36+
class, a method, a function, a code object, a string of source code or a
37+
byte sequence of raw bytecode. For a module, it disassembles all
38+
functions. For a class, it disassembles all methods. For a code object
39+
or sequence of raw bytecode, it prints one line per bytecode instruction.
40+
Strings are first compiled to code objects with the :func:`compile`
41+
built-in function before being disassembled. If no object is provided,
42+
this function disassembles the last traceback.
4043

4144

4245
.. function:: distb(tb=None)

Lib/dis.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@
1212

1313
_have_code = (types.MethodType, types.FunctionType, types.CodeType, type)
1414

15+
def _try_compile(source, name):
16+
"""Attempts to compile the given source, first as an expression and
17+
then as a statement if the first approach fails.
18+
19+
Utility function to accept strings in functions that otherwise
20+
expect code objects
21+
"""
22+
# ncoghlan: currently only used by dis(), but plan to add an
23+
# equivalent for show_code() as well (but one that returns a
24+
# string rather than printing directly to the console)
25+
try:
26+
c = compile(source, name, 'eval')
27+
except SyntaxError:
28+
c = compile(source, name, 'exec')
29+
return c
30+
1531
def dis(x=None):
1632
"""Disassemble classes, methods, functions, or code.
1733
@@ -38,7 +54,9 @@ def dis(x=None):
3854
elif hasattr(x, 'co_code'):
3955
disassemble(x)
4056
elif isinstance(x, (bytes, bytearray)):
41-
disassemble_string(x)
57+
_disassemble_bytes(x)
58+
elif isinstance(x, str):
59+
_disassemble_str(x)
4260
else:
4361
raise TypeError("don't know how to disassemble %s objects" %
4462
type(x).__name__)
@@ -157,7 +175,7 @@ def disassemble(co, lasti=-1):
157175
print('(' + free[oparg] + ')', end=' ')
158176
print()
159177

160-
def disassemble_string(code, lasti=-1, varnames=None, names=None,
178+
def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
161179
constants=None):
162180
labels = findlabels(code)
163181
n = len(code)
@@ -196,6 +214,10 @@ def disassemble_string(code, lasti=-1, varnames=None, names=None,
196214
print('(' + cmp_op[oparg] + ')', end=' ')
197215
print()
198216

217+
def _disassemble_str(source):
218+
"""Compile the source string, then disassemble the code object."""
219+
disassemble(_try_compile(source, '<dis>'))
220+
199221
disco = disassemble # XXX For backwards compatibility
200222

201223
def findlabels(code):

Lib/test/test_dis.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,46 @@ def bug1333982(x=[]):
9696
9797
"""
9898

99+
expr_str = "x + 1"
100+
101+
dis_expr_str = """\
102+
1 0 LOAD_NAME 0 (x)
103+
3 LOAD_CONST 0 (1)
104+
6 BINARY_ADD
105+
7 RETURN_VALUE
106+
"""
107+
108+
simple_stmt_str = "x = x + 1"
109+
110+
dis_simple_stmt_str = """\
111+
1 0 LOAD_NAME 0 (x)
112+
3 LOAD_CONST 0 (1)
113+
6 BINARY_ADD
114+
7 STORE_NAME 0 (x)
115+
10 LOAD_CONST 1 (None)
116+
13 RETURN_VALUE
117+
"""
118+
119+
compound_stmt_str = """\
120+
x = 0
121+
while 1:
122+
x += 1"""
123+
# Trailing newline has been deliberately omitted
124+
125+
dis_compound_stmt_str = """\
126+
1 0 LOAD_CONST 0 (0)
127+
3 STORE_NAME 0 (x)
128+
129+
2 6 SETUP_LOOP 13 (to 22)
130+
131+
3 >> 9 LOAD_NAME 0 (x)
132+
12 LOAD_CONST 1 (1)
133+
15 INPLACE_ADD
134+
16 STORE_NAME 0 (x)
135+
19 JUMP_ABSOLUTE 9
136+
>> 22 LOAD_CONST 2 (None)
137+
25 RETURN_VALUE
138+
"""
99139

100140
class DisTests(unittest.TestCase):
101141
def do_disassembly_test(self, func, expected):
@@ -166,6 +206,11 @@ def test_big_linenos(self):
166206
from test import dis_module
167207
self.do_disassembly_test(dis_module, dis_module_expected_results)
168208

209+
def test_disassemble_str(self):
210+
self.do_disassembly_test(expr_str, dis_expr_str)
211+
self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str)
212+
self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str)
213+
169214
def test_main():
170215
run_unittest(DisTests)
171216

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,7 @@ Doobee R. Tzeck
797797
Eren Türkay
798798
Lionel Ulmer
799799
Roger Upole
800+
Daniel Urban
800801
Michael Urman
801802
Hector Urtubia
802803
Andi Vajda

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,8 @@ C-API
468468
Library
469469
-------
470470

471+
- Issue #6507: Accept source strings in dis.dis()
472+
471473
- Issue #7829: Clearly document that the dis module is exposing an
472474
implementation detail that is not stable between Python VMs or releases.
473475

0 commit comments

Comments
 (0)