Skip to content

Commit

Permalink
bpo-42823: Fix frame lineno when frame.f_trace is set (pythonGH-24099)
Browse files Browse the repository at this point in the history
* Add test for frame.f_lineno with/without tracing.

* Make sure that frame.f_lineno is correct regardless of whether frame.f_trace is set.

* Update importlib

* Add NEWS
  • Loading branch information
markshannon authored Jan 5, 2021
1 parent e40e2a2 commit ee9f98d
Show file tree
Hide file tree
Showing 8 changed files with 1,181 additions and 1,158 deletions.
7 changes: 1 addition & 6 deletions Include/cpython/frameobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,7 @@ struct _frame {
PyObject *f_gen;

int f_lasti; /* Last instruction if called */
/* Call PyFrame_GetLineNumber() instead of reading this field
directly. As of 2.3 f_lineno is only valid when tracing is
active (i.e. when f_trace is set). At other times we use
PyCode_Addr2Line to calculate the line from the current
bytecode index. */
int f_lineno; /* Current line number */
int f_lineno; /* Current line number. Only valid if non-zero */
int f_iblock; /* index in f_blockstack */
PyFrameState f_state; /* What state the frame is in */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_frame.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import sys
import types
import unittest
import weakref
Expand Down Expand Up @@ -94,6 +95,26 @@ def g():
f.clear()
self.assertTrue(endly)

def test_lineno_with_tracing(self):
def record_line():
f = sys._getframe(1)
lines.append(f.f_lineno-f.f_code.co_firstlineno)

def test(trace):
record_line()
if trace:
sys._getframe(0).f_trace = True
record_line()
record_line()

expected_lines = [1, 4, 5]
lines = []
test(False)
self.assertEqual(lines, expected_lines)
lines = []
test(True)
self.assertEqual(lines, expected_lines)

@support.cpython_only
def test_clear_refcycles(self):
# .clear() doesn't leave any refcycle behind
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
frame.f_lineno is correct even if frame.f_trace is set to True
3 changes: 2 additions & 1 deletion Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,8 @@ PyLineTable_InitAddressRange(char *linetable, int firstlineno, PyCodeAddressRang
range->lo_next = linetable;
range->ar_start = -1;
range->ar_end = 0;
range->ar_computed_line = range->ar_line = firstlineno;
range->ar_computed_line = firstlineno;
range->ar_line = -1;
}

int
Expand Down
12 changes: 5 additions & 7 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ int
PyFrame_GetLineNumber(PyFrameObject *f)
{
assert(f != NULL);
if (f->f_trace) {
if (f->f_lineno != 0) {
return f->f_lineno;
}
else {
Expand Down Expand Up @@ -476,8 +476,8 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
start_block_stack = pop_block(start_block_stack);
}

/* Finally set the new f_lineno and f_lasti and return OK. */
f->f_lineno = new_lineno;
/* Finally set the new f_lasti and return OK. */
f->f_lineno = 0;
f->f_lasti = best_addr;
return 0;
}
Expand All @@ -498,11 +498,9 @@ frame_gettrace(PyFrameObject *f, void *closure)
static int
frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
{
/* We rely on f_lineno being accurate when f_trace is set. */
f->f_lineno = PyFrame_GetLineNumber(f);

if (v == Py_None)
if (v == Py_None) {
v = NULL;
}
Py_XINCREF(v);
Py_XSETREF(f->f_trace, v);

Expand Down
21 changes: 11 additions & 10 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -4993,27 +4993,28 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
PyCodeAddressRange *bounds, int *instr_prev)
{
int result = 0;
int line = frame->f_lineno;

/* If the last instruction executed isn't in the current
instruction window, reset the window.
*/
line = _PyCode_CheckLineNumber(frame->f_lasti, bounds);
/* If the last instruction falls at the start of a line or if it
represents a jump backwards, update the frame's line number and
then call the trace function if we're tracing source lines.
*/
if ((line != frame->f_lineno || frame->f_lasti < *instr_prev)) {
if (line != -1) {
int lastline = bounds->ar_line;
int line = _PyCode_CheckLineNumber(frame->f_lasti, bounds);
if (line != -1 && frame->f_trace_lines) {
/* Trace backward edges or first instruction of a new line */
if (frame->f_lasti < *instr_prev ||
(line != lastline && frame->f_lasti == bounds->ar_start))
{
frame->f_lineno = line;
if (frame->f_trace_lines) {
result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None);
}
result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None);
frame->f_lineno = 0;
}
}
/* Always emit an opcode event if we're tracing all opcodes. */
if (frame->f_trace_opcodes) {
frame->f_lineno = _PyCode_CheckLineNumber(frame->f_lasti, bounds);
result = call_trace(func, obj, tstate, frame, PyTrace_OPCODE, Py_None);
frame->f_lineno = 0;
}
*instr_prev = frame->f_lasti;
return result;
Expand Down
6 changes: 6 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6558,6 +6558,12 @@ ensure_exits_have_lineno(struct compiler *c)
if (is_exit_without_lineno(entry)) {
entry->b_instr[0].i_lineno = c->u->u_firstlineno;
}
/* Eliminate empty blocks */
for (basicblock *b = c->u->u_blocks; b != NULL; b = b->b_list) {
while (b->b_next && b->b_next->b_iused == 0) {
b->b_next = b->b_next->b_next;
}
}
/* Any remaining reachable exit blocks without line number can only be reached by
* fall through, and thus can only have a single predecessor */
for (basicblock *b = c->u->u_blocks; b != NULL; b = b->b_list) {
Expand Down
Loading

0 comments on commit ee9f98d

Please sign in to comment.