mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
bpo-42246: Make sure that line number is correct after a return, as required by PEP 626 (GH-23495)
Make sure that line number is correct after a return, as defined by PEP 626.
This commit is contained in:
parent
4e7a69bdb6
commit
5977a7989d
7 changed files with 4973 additions and 4824 deletions
|
@ -156,7 +156,7 @@ if 1:
|
||||||
s256 = "".join(["\n"] * 256 + ["spam"])
|
s256 = "".join(["\n"] * 256 + ["spam"])
|
||||||
co = compile(s256, 'fn', 'exec')
|
co = compile(s256, 'fn', 'exec')
|
||||||
self.assertEqual(co.co_firstlineno, 1)
|
self.assertEqual(co.co_firstlineno, 1)
|
||||||
self.assertEqual(list(co.co_lines()), [(0, 4, 257), (4, 8, None)])
|
self.assertEqual(list(co.co_lines()), [(0, 8, 257)])
|
||||||
|
|
||||||
def test_literals_with_leading_zeroes(self):
|
def test_literals_with_leading_zeroes(self):
|
||||||
for arg in ["077787", "0xj", "0x.", "0e", "090000000000000",
|
for arg in ["077787", "0xj", "0x.", "0e", "090000000000000",
|
||||||
|
@ -775,6 +775,39 @@ if 1:
|
||||||
self.assertIn('LOAD_', opcodes[0].opname)
|
self.assertIn('LOAD_', opcodes[0].opname)
|
||||||
self.assertEqual('RETURN_VALUE', opcodes[1].opname)
|
self.assertEqual('RETURN_VALUE', opcodes[1].opname)
|
||||||
|
|
||||||
|
def test_lineno_after_implicit_return(self):
|
||||||
|
TRUE = True
|
||||||
|
# Don't use constant True or False, as compiler will remove test
|
||||||
|
def if1(x):
|
||||||
|
x()
|
||||||
|
if TRUE:
|
||||||
|
pass
|
||||||
|
def if2(x):
|
||||||
|
x()
|
||||||
|
if TRUE:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
def if3(x):
|
||||||
|
x()
|
||||||
|
if TRUE:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
def if4(x):
|
||||||
|
x()
|
||||||
|
if not TRUE:
|
||||||
|
pass
|
||||||
|
funcs = [ if1, if2, if3, if4]
|
||||||
|
lastlines = [ 3, 3, 3, 2]
|
||||||
|
frame = None
|
||||||
|
def save_caller_frame():
|
||||||
|
nonlocal frame
|
||||||
|
frame = sys._getframe(1)
|
||||||
|
for func, lastline in zip(funcs, lastlines, strict=True):
|
||||||
|
with self.subTest(func=func):
|
||||||
|
func(save_caller_frame)
|
||||||
|
self.assertEqual(frame.f_lineno-frame.f_code.co_firstlineno, lastline)
|
||||||
|
|
||||||
def test_big_dict_literal(self):
|
def test_big_dict_literal(self):
|
||||||
# The compiler has a flushing point in "compiler_dict" that calls compiles
|
# The compiler has a flushing point in "compiler_dict" that calls compiles
|
||||||
|
|
|
@ -131,12 +131,14 @@ dis_bug708901 = """\
|
||||||
12 STORE_FAST 0 (res)
|
12 STORE_FAST 0 (res)
|
||||||
|
|
||||||
%3d 14 JUMP_ABSOLUTE 10
|
%3d 14 JUMP_ABSOLUTE 10
|
||||||
>> 16 LOAD_CONST 0 (None)
|
|
||||||
|
%3d >> 16 LOAD_CONST 0 (None)
|
||||||
18 RETURN_VALUE
|
18 RETURN_VALUE
|
||||||
""" % (bug708901.__code__.co_firstlineno + 1,
|
""" % (bug708901.__code__.co_firstlineno + 1,
|
||||||
bug708901.__code__.co_firstlineno + 2,
|
bug708901.__code__.co_firstlineno + 2,
|
||||||
bug708901.__code__.co_firstlineno + 1,
|
bug708901.__code__.co_firstlineno + 1,
|
||||||
bug708901.__code__.co_firstlineno + 3)
|
bug708901.__code__.co_firstlineno + 3,
|
||||||
|
bug708901.__code__.co_firstlineno + 1)
|
||||||
|
|
||||||
|
|
||||||
def bug1333982(x=[]):
|
def bug1333982(x=[]):
|
||||||
|
@ -295,13 +297,15 @@ dis_traceback = """\
|
||||||
52 STORE_FAST 0 (e)
|
52 STORE_FAST 0 (e)
|
||||||
54 DELETE_FAST 0 (e)
|
54 DELETE_FAST 0 (e)
|
||||||
56 RERAISE
|
56 RERAISE
|
||||||
>> 58 RERAISE
|
|
||||||
|
%3d >> 58 RERAISE
|
||||||
""" % (TRACEBACK_CODE.co_firstlineno + 1,
|
""" % (TRACEBACK_CODE.co_firstlineno + 1,
|
||||||
TRACEBACK_CODE.co_firstlineno + 2,
|
TRACEBACK_CODE.co_firstlineno + 2,
|
||||||
TRACEBACK_CODE.co_firstlineno + 5,
|
TRACEBACK_CODE.co_firstlineno + 5,
|
||||||
TRACEBACK_CODE.co_firstlineno + 3,
|
TRACEBACK_CODE.co_firstlineno + 3,
|
||||||
TRACEBACK_CODE.co_firstlineno + 4,
|
TRACEBACK_CODE.co_firstlineno + 4,
|
||||||
TRACEBACK_CODE.co_firstlineno + 5)
|
TRACEBACK_CODE.co_firstlineno + 5,
|
||||||
|
TRACEBACK_CODE.co_firstlineno + 3)
|
||||||
|
|
||||||
def _fstring(a, b, c, d):
|
def _fstring(a, b, c, d):
|
||||||
return f'{a} {b:4} {c!r} {d!r:4}'
|
return f'{a} {b:4} {c!r} {d!r:4}'
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
PEP 626: After a return, the f_lineno attribute of a frame is always the
|
||||||
|
last line executed.
|
137
Python/compile.c
137
Python/compile.c
|
@ -812,6 +812,28 @@ compiler_use_next_block(struct compiler *c, basicblock *block)
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static basicblock *
|
||||||
|
compiler_copy_block(struct compiler *c, basicblock *block)
|
||||||
|
{
|
||||||
|
/* Cannot copy a block if it has a fallthrough, since
|
||||||
|
* a block can only have one fallthrough predecessor.
|
||||||
|
*/
|
||||||
|
assert(block->b_nofallthrough);
|
||||||
|
basicblock *result = compiler_next_block(c);
|
||||||
|
if (result == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < block->b_iused; i++) {
|
||||||
|
int n = compiler_next_instr(result);
|
||||||
|
if (n < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
result->b_instr[n] = block->b_instr[i];
|
||||||
|
}
|
||||||
|
result->b_exit = block->b_exit;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns the offset of the next instruction in the current block's
|
/* Returns the offset of the next instruction in the current block's
|
||||||
b_instr array. Resizes the b_instr as necessary.
|
b_instr array. Resizes the b_instr as necessary.
|
||||||
Returns -1 on failure.
|
Returns -1 on failure.
|
||||||
|
@ -2732,12 +2754,13 @@ compiler_if(struct compiler *c, stmt_ty s)
|
||||||
static int
|
static int
|
||||||
compiler_for(struct compiler *c, stmt_ty s)
|
compiler_for(struct compiler *c, stmt_ty s)
|
||||||
{
|
{
|
||||||
basicblock *start, *cleanup, *end;
|
basicblock *start, *body, *cleanup, *end;
|
||||||
|
|
||||||
start = compiler_new_block(c);
|
start = compiler_new_block(c);
|
||||||
|
body = compiler_new_block(c);
|
||||||
cleanup = compiler_new_block(c);
|
cleanup = compiler_new_block(c);
|
||||||
end = compiler_new_block(c);
|
end = compiler_new_block(c);
|
||||||
if (start == NULL || end == NULL || cleanup == NULL) {
|
if (start == NULL || body == NULL || end == NULL || cleanup == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!compiler_push_fblock(c, FOR_LOOP, start, end, NULL)) {
|
if (!compiler_push_fblock(c, FOR_LOOP, start, end, NULL)) {
|
||||||
|
@ -2747,6 +2770,7 @@ compiler_for(struct compiler *c, stmt_ty s)
|
||||||
ADDOP(c, GET_ITER);
|
ADDOP(c, GET_ITER);
|
||||||
compiler_use_next_block(c, start);
|
compiler_use_next_block(c, start);
|
||||||
ADDOP_JUMP(c, FOR_ITER, cleanup);
|
ADDOP_JUMP(c, FOR_ITER, cleanup);
|
||||||
|
compiler_use_next_block(c, body);
|
||||||
VISIT(c, expr, s->v.For.target);
|
VISIT(c, expr, s->v.For.target);
|
||||||
VISIT_SEQ(c, stmt, s->v.For.body);
|
VISIT_SEQ(c, stmt, s->v.For.body);
|
||||||
ADDOP_JUMP(c, JUMP_ABSOLUTE, start);
|
ADDOP_JUMP(c, JUMP_ABSOLUTE, start);
|
||||||
|
@ -5929,9 +5953,16 @@ dump_basicblock(const basicblock *b)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
normalize_basic_block(basicblock *bb);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
optimize_cfg(struct assembler *a, PyObject *consts);
|
optimize_cfg(struct assembler *a, PyObject *consts);
|
||||||
|
|
||||||
|
static int
|
||||||
|
ensure_exits_have_lineno(struct compiler *c);
|
||||||
|
|
||||||
static PyCodeObject *
|
static PyCodeObject *
|
||||||
assemble(struct compiler *c, int addNone)
|
assemble(struct compiler *c, int addNone)
|
||||||
{
|
{
|
||||||
|
@ -5952,6 +5983,16 @@ assemble(struct compiler *c, int addNone)
|
||||||
ADDOP(c, RETURN_VALUE);
|
ADDOP(c, RETURN_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (basicblock *b = c->u->u_blocks; b != NULL; b = b->b_list) {
|
||||||
|
if (normalize_basic_block(b)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ensure_exits_have_lineno(c)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
nblocks = 0;
|
nblocks = 0;
|
||||||
entryblock = NULL;
|
entryblock = NULL;
|
||||||
for (b = c->u->u_blocks; b != NULL; b = b->b_list) {
|
for (b = c->u->u_blocks; b != NULL; b = b->b_list) {
|
||||||
|
@ -5966,6 +6007,7 @@ assemble(struct compiler *c, int addNone)
|
||||||
else
|
else
|
||||||
c->u->u_firstlineno = 1;
|
c->u->u_firstlineno = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!assemble_init(&a, nblocks, c->u->u_firstlineno))
|
if (!assemble_init(&a, nblocks, c->u->u_firstlineno))
|
||||||
goto error;
|
goto error;
|
||||||
a.a_entry = entryblock;
|
a.a_entry = entryblock;
|
||||||
|
@ -6338,7 +6380,6 @@ clean_basic_block(basicblock *bb) {
|
||||||
bb->b_iused = dest;
|
bb->b_iused = dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
normalize_basic_block(basicblock *bb) {
|
normalize_basic_block(basicblock *bb) {
|
||||||
/* Mark blocks as exit and/or nofallthrough.
|
/* Mark blocks as exit and/or nofallthrough.
|
||||||
|
@ -6349,7 +6390,8 @@ normalize_basic_block(basicblock *bb) {
|
||||||
case RAISE_VARARGS:
|
case RAISE_VARARGS:
|
||||||
case RERAISE:
|
case RERAISE:
|
||||||
bb->b_exit = 1;
|
bb->b_exit = 1;
|
||||||
/* fall through */
|
bb->b_nofallthrough = 1;
|
||||||
|
break;
|
||||||
case JUMP_ABSOLUTE:
|
case JUMP_ABSOLUTE:
|
||||||
case JUMP_FORWARD:
|
case JUMP_FORWARD:
|
||||||
bb->b_nofallthrough = 1;
|
bb->b_nofallthrough = 1;
|
||||||
|
@ -6358,16 +6400,21 @@ normalize_basic_block(basicblock *bb) {
|
||||||
case POP_JUMP_IF_TRUE:
|
case POP_JUMP_IF_TRUE:
|
||||||
case JUMP_IF_FALSE_OR_POP:
|
case JUMP_IF_FALSE_OR_POP:
|
||||||
case JUMP_IF_TRUE_OR_POP:
|
case JUMP_IF_TRUE_OR_POP:
|
||||||
|
case FOR_ITER:
|
||||||
if (i != bb->b_iused-1) {
|
if (i != bb->b_iused-1) {
|
||||||
PyErr_SetString(PyExc_SystemError, "malformed control flow graph.");
|
PyErr_SetString(PyExc_SystemError, "malformed control flow graph.");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
/* Skip over empty basic blocks. */
|
||||||
|
while (bb->b_instr[i].i_target->b_iused == 0) {
|
||||||
|
bb->b_instr[i].i_target = bb->b_instr[i].i_target->b_next;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
mark_reachable(struct assembler *a) {
|
mark_reachable(struct assembler *a) {
|
||||||
basicblock **stack, **sp;
|
basicblock **stack, **sp;
|
||||||
|
@ -6398,8 +6445,27 @@ mark_reachable(struct assembler *a) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If an instruction has no line number, but it's predecessor in the BB does,
|
||||||
|
* then copy the line number. This reduces the size of the line number table,
|
||||||
|
* but has no impact on the generated line number events.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
minimize_lineno_table(struct assembler *a) {
|
||||||
|
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
||||||
|
int prev_lineno = -1;
|
||||||
|
for (int i = 0; i < b->b_iused; i++) {
|
||||||
|
if (b->b_instr[i].i_lineno < 0) {
|
||||||
|
b->b_instr[i].i_lineno = prev_lineno;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prev_lineno = b->b_instr[i].i_lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Perform basic peephole optimizations on a control flow graph.
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform optimizations on a control flow graph.
|
||||||
The consts object should still be in list form to allow new constants
|
The consts object should still be in list form to allow new constants
|
||||||
to be appended.
|
to be appended.
|
||||||
|
|
||||||
|
@ -6411,11 +6477,6 @@ mark_reachable(struct assembler *a) {
|
||||||
static int
|
static int
|
||||||
optimize_cfg(struct assembler *a, PyObject *consts)
|
optimize_cfg(struct assembler *a, PyObject *consts)
|
||||||
{
|
{
|
||||||
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
|
||||||
if (normalize_basic_block(b)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
||||||
if (optimize_basic_block(b, consts)) {
|
if (optimize_basic_block(b, consts)) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -6432,9 +6493,63 @@ optimize_cfg(struct assembler *a, PyObject *consts)
|
||||||
b->b_iused = 0;
|
b->b_iused = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
minimize_lineno_table(a);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
is_exit_without_lineno(basicblock *b) {
|
||||||
|
return b->b_exit && b->b_instr[0].i_lineno < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PEP 626 mandates that the f_lineno of a frame is correct
|
||||||
|
* after a frame terminates. It would be prohibitively expensive
|
||||||
|
* to continuously update the f_lineno field at runtime,
|
||||||
|
* so we make sure that all exiting instruction (raises and returns)
|
||||||
|
* have a valid line number, allowing us to compute f_lineno lazily.
|
||||||
|
* We can do this by duplicating the exit blocks without line number
|
||||||
|
* so that none have more than one predecessor. We can then safely
|
||||||
|
* copy the line number from the sole predecessor block.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
ensure_exits_have_lineno(struct compiler *c)
|
||||||
|
{
|
||||||
|
/* Copy all exit blocks without line number that are targets of a jump.
|
||||||
|
*/
|
||||||
|
for (basicblock *b = c->u->u_blocks; b != NULL; b = b->b_list) {
|
||||||
|
if (b->b_iused > 0 && is_jump(&b->b_instr[b->b_iused-1])) {
|
||||||
|
switch (b->b_instr[b->b_iused-1].i_opcode) {
|
||||||
|
/* Note: Only actual jumps, not exception handlers */
|
||||||
|
case SETUP_ASYNC_WITH:
|
||||||
|
case SETUP_WITH:
|
||||||
|
case SETUP_FINALLY:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
basicblock *target = b->b_instr[b->b_iused-1].i_target;
|
||||||
|
if (is_exit_without_lineno(target)) {
|
||||||
|
basicblock *new_target = compiler_copy_block(c, target);
|
||||||
|
if (new_target == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
new_target->b_instr[0].i_lineno = b->b_instr[b->b_iused-1].i_lineno;
|
||||||
|
b->b_instr[b->b_iused-1].i_target = new_target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 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) {
|
||||||
|
if (!b->b_nofallthrough && b->b_next && b->b_iused > 0) {
|
||||||
|
if (is_exit_without_lineno(b->b_next)) {
|
||||||
|
assert(b->b_next->b_iused > 0);
|
||||||
|
b->b_next->b_instr[0].i_lineno = b->b_instr[b->b_iused-1].i_lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Retained for API compatibility.
|
/* Retained for API compatibility.
|
||||||
* Optimization is now done in optimize_cfg */
|
* Optimization is now done in optimize_cfg */
|
||||||
|
|
||||||
|
|
2875
Python/importlib.h
generated
2875
Python/importlib.h
generated
File diff suppressed because it is too large
Load diff
5014
Python/importlib_external.h
generated
5014
Python/importlib_external.h
generated
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue