8000 gh-107932: Fix dis module for bytecode that does not have an associat… · python/cpython@24aa249 · GitHub
[go: up one dir, main page]

Skip to content

Commit 24aa249

Browse files
authored
gh-107932: Fix dis module for bytecode that does not have an associated source line (GH-107988)
1 parent 8d40520 commit 24aa249

File tree

5 files changed

+468
-405
lines changed

5 files changed

+468
-405
lines changed

Doc/library/dis.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ operation is being performed, so the intermediate analysis object isn't useful:
297297
The :pep:`626` ``co_lines`` method is used instead of the ``co_firstlineno``
298298
and ``co_lnotab`` attributes of the code object.
299299

300+
.. versionchanged:: 3.13
301+
Line numbers can be ``None`` for bytecode that does not map to source lines.
302+
300303

301304
.. function:: findlabels(code)
302305

@@ -402,7 +405,12 @@ details of bytecode instructions as :class:`Instruction` instances:
402405

403406
.. data:: starts_line
404407

405-
line started by this opcode (if any), otherwise ``None``
408+
``True`` if this opcode starts a source line, otherwise ``False``
409+
410+
411+
.. data:: line_number
412+
413+
source line number associated with this opcode (if any), otherwise ``None``
406414

407415

408416
.. data:: is_jump_target
@@ -429,8 +437,11 @@ details of bytecode instructions as :class:`Instruction` instances:
429437

430438
.. versionchanged:: 3.13
431439

440+
Changed field ``starts_line``.
441+
432442
Added fields ``start_offset``, ``cache_offset``, ``end_offset``,
433-
``baseopname``, ``baseopcode``, ``jump_target`` and ``oparg``.
443+
``baseopname``, ``baseopcode``, ``jump_target``, ``oparg``, and
444+
``line_number``.
434445

435446

436447
.. class:: Positions

Lib/dis.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ def show_code(co, *, file=None):
262262
'offset',
263263
'start_offset',
264264
'starts_line',
265+
'line_number',
265266
'is_jump_target',
266267
'positions'
267268
],
@@ -278,7 +279,8 @@ def show_code(co, *, file=None):
278279
"Start index of operation within bytecode sequence, including extended args if present; "
279280
"otherwise equal to Instruction.offset"
280281
)
281-
_Instruction.starts_line.__doc__ = "Line started by this opcode (if any), otherwise None"
282+
_Instruction.starts_line.__doc__ = "True if this opcode starts a source line, otherwise False"
283+
_Instruction.line_number.__doc__ = "source line number associated with this opcode (if any), otherwise None"
282284
_Instruction.is_jump_target.__doc__ = "True if other code jumps to here, otherwise False"
283285
_Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction"
284286

@@ -321,7 +323,8 @@ class Instruction(_Instruction):
321323
offset - start index of operation within bytecode sequence
322324
start_offset - start index of operation within bytecode sequence including extended args if present;
323325
otherwise equal to Instruction.offset
324-
starts_line - line started by this opcode (if any), otherwise None
326+
starts_line - True if this opcode starts a source line, otherwise False
327+
line_number - source line number associated with this opcode (if any), otherwise None
325328
is_jump_target - True if other code jumps to here, otherwise False
326329
positions - Optional dis.Positions object holding the span of source code
327330
covered by this instruction
@@ -376,9 +379,10 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=4):
376379< 6D47 code class="diff-text syntax-highlighted-line">
fields = []
377380
# Column: Source code line number
378381
if lineno_width:
379-
if self.starts_line is not None:
380-
lineno_fmt = "%%%dd" % lineno_width
381-
fields.append(lineno_fmt % self.starts_line)
382+
if self.starts_line:
383+
lineno_fmt = "%%%dd" if self.line_number is not None else "%%%ds"
384+
lineno_fmt = lineno_fmt % lineno_width
385+
fields.append(lineno_fmt % self.line_number)
382386
else:
383387
fields.append(' ' * lineno_width)
384388
# Column: Current instruction indicator
@@ -527,12 +531,18 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
527531
for start, end, target, _, _ in exception_entries:
528532
for i in range( 9E81 start, end):
529533
labels.add(target)
530-
starts_line = None
534+
starts_line = False
535+
local_line_number = None
536+
line_number = None
531537
for offset, start_offset, op, arg in _unpack_opargs(original_code):
532538
if linestarts is not None:
533-
starts_line = linestarts.get(offset, None)
534-
if starts_line is not None:
535-
starts_line += line_offset
539+
starts_line = offset in linestarts
540+
if starts_line:
541+
local_line_number = linestarts[offset]
542+
if local_line_number is not None:
543+
line_number = local_line_number + line_offset
544+
else:
545+
line_number = None
536546
is_jump_target = offset in labels
537547
argval = None
538548
argrepr = ''
@@ -599,7 +609,8 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
599609
argrepr = _intrinsic_2_descs[arg]
600610
yield Instruction(_all_opname[op], op,
601611
arg, argval, argrepr,
602-
offset, start_offset, starts_line, is_jump_target, positions)
612+
offset, start_offset, starts_line, line_number,
613+
is_jump_target, positions)
603614
if not caches:
604615
continue
605616
if not show_caches:
@@ -618,7 +629,7 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
618629
else:
619630
argrepr = ""
620631
yield Instruction(
621-
"CACHE", CACHE, 0, None, argrepr, offset, offset, None, False,
632+
"CACHE", CACHE, 0, None, argrepr, offset, offset, False, None, False,
622633
Positions(*next(co_positions, ()))
623634
)
624635

@@ -651,13 +662,21 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
651662
*, file=None, line_offset=0, exception_entries=(),
652663
co_positions=None, show_caches=False, original_code=None):
653664
# Omit the line number column entirely if we have no line number info
654-
show_lineno = bool(linestarts)
665+
if bool(linestarts):
666+
linestarts_ints = [line for line in linestarts.values() if line is not None]
667+
show_lineno = len(linestarts_ints) > 0
668+
else:
669+
show_lineno = False
670+
655671
if show_lineno:
656-
maxlineno = max(linestarts.values()) + line_offset
672+
maxlineno = max(linestarts_ints) + line_offset
657673
if maxlineno >= 1000:
658674
lineno_width = len(str(maxlineno))
659675
else:
660676
lineno_width = 3
677+
678+
if lineno_width < len(str(None)) and None in linestarts.values():
679+
lineno_width = len(str(None))
661680
else:
662681
lineno_width = 0
663682
maxoffset = len(code) - 2
@@ -673,7 +692,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None,
673692
show_caches=show_caches,
674693
original_code=original_code):
675694
new_source_line = (show_lineno and
676-
instr.starts_line is not None and
695+
instr.starts_line and
677696
instr.offset > 0)
678697
if new_source_line:
679698
print(file=file)
@@ -755,10 +774,12 @@ def findlinestarts(code):
755774
"""Find the offsets in a byte code which are start of lines in the source.
756775
757776
Generate pairs (offset, lineno)
777+
lineno will be an integer or None the offset does not have a source line.
758778
"""
759-
lastline = None
779+
780+
lastline = False # None is a valid line number
760781
for start, end, line in code.co_lines():
761-
if line is not None and line != lastline:
782+
if line is not lastline:
762783
lastline = line
763784
yield start, line
764785
return

0 commit comments

Comments
 (0)
0