8000 bpo-22352: Adjust widths in the output of dis.dis() for large line nu… · python/cpython@d90045f · GitHub
[go: up one dir, main page]

Skip to content

Commit d90045f

Browse files
bpo-22352: Adjust widths in the output of dis.dis() for large line numbers and (#1153)
instruction offsets. Add tests for widths of opcode names.
1 parent bf623ae commit d90045f

File tree

3 files changed

+77
-8
lines changed

3 files changed

+77
-8
lines changed

Lib/dis.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ def show_code(co, *, file=None):
175175
_Instruction.starts_line.__doc__ = "Line started by this opcode (if any), otherwise None"
176176
_Instruction.is_jump_target.__doc__ = "True if other code jumps to here, otherwise False"
177177

178+
_OPNAME_WIDTH = 20
179+
_OPARG_WIDTH = 5
180+
178181
class Instruction(_Instruction):
179182
"""Details for a bytecode operation
180183
@@ -189,11 +192,12 @@ class Instruction(_Instruction):
189192
is_jump_target - True if other code jumps to here, otherwise False
190193
"""
191194

192-
def _disassemble(self, lineno_width=3, mark_as_current=False):
195+
def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
193196
"""Format instruction details for inclusion in disassembly output
194197
195198
*lineno_width* sets the width of the line number field (0 omits it)
196199
*mark_as_current* inserts a '-->' marker arrow as part of the line
200+
*offset_width* sets the width of the instruction offset field
197201
"""
198202
fields = []
199203
# Column: Source code line number
@@ -214,12 +218,12 @@ def _disassemble(self, lineno_width=3, mark_as_current=False):
214218
else:
215219
fields.append(' ')
216220
# Column: Instruction offset from start of code sequence
217-
fields.append(repr(self.offset).rjust(4))
221+
fields.append(repr(self.offset).rjust(offset_width))
218222
# Column: Opcode name
219-
fields.append(self.opname.ljust(20))
223+
fields.append(self.opname.ljust(_OPNAME_WIDTH))
220224
# Column: Opcode argument
221225
if self.arg is not None:
222-
fields.append(repr(self.arg).rjust(5))
226+
fields.append(repr(self.arg).rjust(_OPARG_WIDTH))
223227
# Column: Opcode argument details
224228
if self.argrepr:
225229
fields.append('(' + self.argrepr + ')')
@@ -339,8 +343,19 @@ def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
339343
*, file=None, line_offset=0):
340344
# Omit the line number column entirely if we have no line number info
341345
show_lineno = linestarts is not None
342-
# TODO?: Adjust width upwards if max(linestarts.values()) >= 1000?
343-
lineno_width = 3 if show_lineno else 0
346+
if show_lineno:
347+
maxlineno = max(linestarts.values()) + line_offset
348+
if maxlineno >= 1000:
349+
lineno_width = len(str(maxlineno))
350+
else:
351+
lineno_width = 3
352+
else:
353+
lineno_width = 0
354+
maxoffset = len(code) - 2
355+
if maxoffset >= 10000:
356+
offset_width = len(str(maxoffset))
357+
else:
358+
offset_width = 4
344359
for instr in _get_instructions_bytes(code, varnames, names,
345360
constants, cells, linestarts,
346361
line_offset=line_offset):
@@ -350,7 +365,8 @@ def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
350365
if new_source_line:
351366
print(file=file)
352367
is_current_instr = instr.offset == lasti
353-
print(instr._disassemble(lineno_width, is_current_instr), file=file)
368+
print(instr._disassemble(lineno_width, is_current_instr, offset_width),
369+
file=file)
354370

355371
def _disassemble_str(source, *, file=None):
356372
"""Compile the source string, then disassemble the code object."""

Lib/test/test_dis.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ def bug1333982(x=[]):
175175
6 RETURN_VALUE
176176
"""
177177

178+
_BIG_LINENO_FORMAT2 = """\
179+
%4d 0 LOAD_GLOBAL 0 (spam)
180+
2 POP_TOP
181+
4 LOAD_CONST 0 (None)
182+
6 RETURN_VALUE
183+
"""
184+
178185
dis_module_expected_results = """\
179186
Disassembly of f:
180187
4 0 LOAD_CONST 0 (None)
@@ -360,6 +367,17 @@ def test_boundaries(self):
360367
self.assertEqual(dis.opmap["EXTENDED_ARG"], dis.EXTENDED_ARG)
361368
self.assertEqual(dis.opmap["STORE_NAME"], dis.HAVE_ARGUMENT)
362369

370+
def test_widths(self):
371+
for opcode, opname in enumerate(dis.opname):
372+
if opname in ('BUILD_MAP_UNPACK_WITH_CALL',
373+
'BUILD_TUPLE_UNPACK_WITH_CALL'):
374+
continue
375+
with self.subTest(opname=opname):
376+
width = dis._OPNAME_WIDTH
377+
if opcode < dis.HAVE_ARGUMENT:
378+
width += 1 + dis._OPARG_WIDTH
379+
self.assertLessEqual(len(opname), width)
380+
363381
def test_dis(self):
364382
self.do_disassembly_test(_f, dis_f)
365383

@@ -387,13 +405,45 @@ def func(count):
387405
self.do_disassembly_test(func(i), expected)
388406

389407
# Test some larger ranges too
390-
for i in range(300, 5000, 10):
408+
for i in range(300, 1000, 10):
391409
expected = _BIG_LINENO_FORMAT % (i + 2)
392410
self.do_disassembly_test(func(i), expected)
393411

412+
for i in range(1000, 5000, 10):
413+
expected = _BIG_LINENO_FORMAT2 % (i + 2)
414+
self.do_disassembly_test(func(i), expected)
415+
394416
from test import dis_module
395417
self.do_disassembly_test(dis_module, dis_module_expected_results)
396418

419+
def test_big_offsets(self):
420+
def func(count):
421+
namespace = {}
422+
func = "def foo(x):\n " + ";".join(["x = x + 1"] * count) + "\n return x"
423+
exec(func, namespace)
424+
return namespace['foo']
425+
426+
def expected(count, w):
427+
s = ['''\
428+
%*d LOAD_FAST 0 (x)
429+
%*d LOAD_CONST 1 (1)
430+
%*d BINARY_ADD
431+
%*d STORE_FAST 0 (x)
432+
''' % (w, 8*i, w, 8*i + 2, w, 8*i + 4, w, 8*i + 6)
433+
for i in range(count)]
434+
s += ['''\
435+
436+
3 %*d LOAD_FAST 0 (x)
437+
%*d RETURN_VALUE
438+
''' % (w, 8*count, w, 8*count + 2)]
439+
s[0] = ' 2' + s[0][3:]
440+
return ''.join(s)
441+
442+
for i in range(1, 5):
443+
self.do_disassembly_test(func(i), expected(i, 4))
444+
self.do_disassembly_test(func(1249), expected(1249, 4))
445+
self.do_disassembly_test(func(1250), expected(1250, 5))
446+
397447
def test_disassemble_str(self):
398448
self.do_disassembly_test(expr_str, dis_expr_str)
399449
self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ Extension Modules
313313
Library
314314
-------
315315

316+
- bpo-22352: Column widths in the output of dis.dis() are now adjusted for
317+
large line numbers and instruction offsets.
318+
316319
- bpo-30061: Fixed crashes in IOBase methods __next__() and readlines() when
317320
readline() or __next__() respectively return non-sizeable object.
318321
Fixed possible other errors caused by not checking results of PyObject_Size(),

0 commit comments

Comments
 (0)
0