Skip to content

Commit 5a78fd5

Browse files
committed
Report a missing line number instead of reconstructing one
Per review, do not try to recover a line number for a frame stopped on an instruction that carries no line: just skip the source-context lookup that assumed lineno was an int, and let getframeinfo() report lineno, code_context and index as None. The previous approach did not work anyway. _lasti2lineno() walked dis.findlinestarts(), which since 3.11 yields None line numbers of its own, so it returned None for exactly the frames it was meant to fix. Add a regression test that reproduces the original TypeError.
1 parent f33497b commit 5a78fd5

3 files changed

Lines changed: 41 additions & 30 deletions

File tree

Lib/inspect.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,10 +1609,10 @@ def getframeinfo(frame, context=1):
16091609
to return, which are centered around the current line."""
16101610
if istraceback(frame):
16111611
positions = _get_code_position_from_tb(frame)
1612-
lineno = _tblineno(frame)
1612+
lineno = frame.tb_lineno
16131613
frame = frame.tb_frame
16141614
else:
1615-
lineno = getlineno(frame)
1615+
lineno = frame.f_lineno
16161616
positions = _get_code_position(frame.f_code, frame.f_lasti)
16171617

16181618
if positions[0] is None:
@@ -1626,7 +1626,7 @@ def getframeinfo(frame, context=1):
16261626
raise TypeError('{!r} is not a frame or traceback object'.format(frame))
16271627

16281628
filename = getsourcefile(frame) or getfile(frame)
1629-
if context > 0:
1629+
if context > 0 and lineno is not None:
16301630
start = lineno - 1 - context//2
16311631
try:
16321632
lines, lnum = findsource(frame)
@@ -1642,34 +1642,9 @@ def getframeinfo(frame, context=1):
16421642
return Traceback(filename, lineno, frame.f_code.co_name, lines,
16431643
index, positions=dis.Positions(*positions))
16441644

1645-
1646-
def _tblineno(tb):
1647-
tb_lineno = tb.tb_lineno
1648-
if tb_lineno is not None:
1649-
return tb_lineno
1650-
1651-
return _lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)
1652-
1653-
1654-
def _lasti2lineno(code, lasti):
1655-
prev_line = code.co_firstlineno
1656-
1657-
for start, next_line in dis.findlinestarts(code):
1658-
if lasti < start:
1659-
return prev_line
1660-
prev_line = next_line
1661-
1662-
return prev_line
1663-
16641645
def getlineno(frame):
16651646
"""Get the line number from a frame object, allowing for optimization."""
1666-
# FrameType.f_lineno is now a descriptor that often grovels co_lnotab
1667-
f_lineno = frame.f_lineno
1668-
if f_lineno is not None:
1669-
return f_lineno
1670-
1671-
return _lasti2lineno(frame.f_code, frame.f_lasti)
1672-
1647+
return frame.f_lineno
16731648

16741649
_FrameInfo = namedtuple('_FrameInfo', ('frame',) + Traceback._fields)
16751650
class FrameInfo(_FrameInfo):

Lib/test/test_inspect/test_inspect.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,39 @@ def test_getframeinfo_get_first_line(self):
846846
self.assertEqual(frame_info.code_context[0], "# line 1\n")
847847
self.assertEqual(frame_info.code_context[1], "'A module docstring.'\n")
848848

849+
def test_getframeinfo_no_lineno(self):
850+
# gh-89726: some instructions carry no line number, so a frame
851+
# stopped on one has f_lineno set to None. getframeinfo() must
852+
# report that rather than failing.
853+
def f():
854+
try:
855+
raise ValueError
856+
except ValueError:
857+
pass
858+
859+
infos = []
860+
861+
def trace(frame, event, arg):
862+
if frame.f_code is f.__code__ and frame.f_lineno is None:
863+
infos.append(inspect.getframeinfo(frame))
864+
frame.f_trace_opcodes = True
865+
return trace
866+
867+
old_trace = sys.gettrace()
868+
sys.settrace(trace)
869+
try:
870+
f()
871+
finally:
872+
sys.settrace(old_trace)
873+
874+
self.assertTrue(infos, 'no frame with f_lineno set to None')
875+
for info in infos:
876+
with self.subTest(info=info):
877+
self.assertIsNone(info.lineno)
878+
self.assertIsNone(info.code_context)
879+
self.assertIsNone(info.index)
880+
self.assertEqual(info.function, 'f')
881+
849882
def test_getsource(self):
850883
self.assertSourceEqual(git.abuse, 29, 39)
851884
self.assertSourceEqual(mod.StupidGit, 21, 51)
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
handle frame.f_lineno is None in inspect.getframeinfo
1+
:func:`inspect.getframeinfo` no longer raises :exc:`TypeError` for a frame
2+
stopped on an instruction that carries no line number, such as while tracing
3+
opcodes. ``lineno``, ``code_context`` and ``index`` are reported as ``None``
4+
instead.

0 commit comments

Comments
 (0)