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:
Mark Shannon 2020-12-02 13:31:40 +00:00 committed by GitHub
parent 4e7a69bdb6
commit 5977a7989d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 4973 additions and 4824 deletions

View File

@ -156,7 +156,7 @@ if 1:
s256 = "".join(["\n"] * 256 + ["spam"])
co = compile(s256, 'fn', 'exec')
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):
for arg in ["077787", "0xj", "0x.", "0e", "090000000000000",
@ -775,6 +775,39 @@ if 1:
self.assertIn('LOAD_', opcodes[0].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):
# The compiler has a flushing point in "compiler_dict" that calls compiles

View File

@ -131,12 +131,14 @@ dis_bug708901 = """\
12 STORE_FAST 0 (res)
%3d 14 JUMP_ABSOLUTE 10
>> 16 LOAD_CONST 0 (None)
%3d >> 16 LOAD_CONST 0 (None)
18 RETURN_VALUE
""" % (bug708901.__code__.co_firstlineno + 1,
bug708901.__code__.co_firstlineno + 2,
bug708901.__code__.co_firstlineno + 1,
bug708901.__code__.co_firstlineno + 3)
bug708901.__code__.co_firstlineno + 3,
bug708901.__code__.co_firstlineno + 1)
def bug1333982(x=[]):
@ -295,13 +297,15 @@ dis_traceback = """\
52 STORE_FAST 0 (e)
54 DELETE_FAST 0 (e)
56 RERAISE
>> 58 RERAISE
%3d >> 58 RERAISE
""" % (TRACEBACK_CODE.co_firstlineno + 1,
TRACEBACK_CODE.co_firstlineno + 2,
TRACEBACK_CODE.co_firstlineno + 5,
TRACEBACK_CODE.co_firstlineno + 3,
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):
return f'{a} {b:4} {c!r} {d!r:4}'

View File

@ -0,0 +1,2 @@
PEP 626: After a return, the f_lineno attribute of a frame is always the
last line executed.

View File

@ -812,6 +812,28 @@ compiler_use_next_block(struct compiler *c, basicblock *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
b_instr array. Resizes the b_instr as necessary.
Returns -1 on failure.
@ -2732,12 +2754,13 @@ compiler_if(struct compiler *c, stmt_ty s)
static int
compiler_for(struct compiler *c, stmt_ty s)
{
basicblock *start, *cleanup, *end;
basicblock *start, *body, *cleanup, *end;
start = compiler_new_block(c);
body = compiler_new_block(c);
cleanup = 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;
}
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);
compiler_use_next_block(c, start);
ADDOP_JUMP(c, FOR_ITER, cleanup);
compiler_use_next_block(c, body);
VISIT(c, expr, s->v.For.target);
VISIT_SEQ(c, stmt, s->v.For.body);
ADDOP_JUMP(c, JUMP_ABSOLUTE, start);
@ -5929,9 +5953,16 @@ dump_basicblock(const basicblock *b)
}
#endif
static int
normalize_basic_block(basicblock *bb);
static int
optimize_cfg(struct assembler *a, PyObject *consts);
static int
ensure_exits_have_lineno(struct compiler *c);
static PyCodeObject *
assemble(struct compiler *c, int addNone)
{
@ -5952,6 +5983,16 @@ assemble(struct compiler *c, int addNone)
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;
entryblock = NULL;
for (b = c->u->u_blocks; b != NULL; b = b->b_list) {
@ -5966,6 +6007,7 @@ assemble(struct compiler *c, int addNone)
else
c->u->u_firstlineno = 1;
}
if (!assemble_init(&a, nblocks, c->u->u_firstlineno))
goto error;
a.a_entry = entryblock;
@ -6338,7 +6380,6 @@ clean_basic_block(basicblock *bb) {
bb->b_iused = dest;
}
static int
normalize_basic_block(basicblock *bb) {
/* Mark blocks as exit and/or nofallthrough.
@ -6349,7 +6390,8 @@ normalize_basic_block(basicblock *bb) {
case RAISE_VARARGS:
case RERAISE:
bb->b_exit = 1;
/* fall through */
bb->b_nofallthrough = 1;
break;
case JUMP_ABSOLUTE:
case JUMP_FORWARD:
bb->b_nofallthrough = 1;
@ -6358,16 +6400,21 @@ normalize_basic_block(basicblock *bb) {
case POP_JUMP_IF_TRUE:
case JUMP_IF_FALSE_OR_POP:
case JUMP_IF_TRUE_OR_POP:
case FOR_ITER:
if (i != bb->b_iused-1) {
PyErr_SetString(PyExc_SystemError, "malformed control flow graph.");
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;
}
static int
mark_reachable(struct assembler *a) {
basicblock **stack, **sp;
@ -6398,8 +6445,27 @@ mark_reachable(struct assembler *a) {
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
to be appended.
@ -6411,11 +6477,6 @@ mark_reachable(struct assembler *a) {
static int
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) {
if (optimize_basic_block(b, consts)) {
return -1;
@ -6432,9 +6493,63 @@ optimize_cfg(struct assembler *a, PyObject *consts)
b->b_iused = 0;
}
}
minimize_lineno_table(a);
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.
* Optimization is now done in optimize_cfg */

2875
Python/importlib.h generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff