Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 3.8 changes how returns through finally clauses are traced #78886

Closed
nedbat opened this issue Sep 16, 2018 · 9 comments
Closed

Python 3.8 changes how returns through finally clauses are traced #78886

nedbat opened this issue Sep 16, 2018 · 9 comments
Labels
3.8 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@nedbat
Copy link
Member

nedbat commented Sep 16, 2018

BPO 34705
Nosy @nedbat, @markshannon, @serhiy-storchaka

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = <Date 2021-01-13.16:20:06.038>
created_at = <Date 2018-09-16.13:15:56.029>
labels = ['interpreter-core', 'type-bug', '3.8']
title = 'Python 3.8 changes how returns through finally clauses are traced'
updated_at = <Date 2021-01-13.16:20:06.037>
user = 'https://github.com/nedbat'

bugs.python.org fields:

activity = <Date 2021-01-13.16:20:06.037>
actor = 'Mark.Shannon'
assignee = 'none'
closed = True
closed_date = <Date 2021-01-13.16:20:06.038>
closer = 'Mark.Shannon'
components = ['Interpreter Core']
creation = <Date 2018-09-16.13:15:56.029>
creator = 'nedbat'
dependencies = []
files = []
hgrepos = []
issue_num = 34705
keywords = []
message_count = 9.0
messages = ['325484', '325485', '325489', '325490', '326180', '326185', '326215', '326216', '385047']
nosy_count = 3.0
nosy_names = ['nedbat', 'Mark.Shannon', 'serhiy.storchaka']
pr_nums = []
priority = 'normal'
resolution = 'fixed'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue34705'
versions = ['Python 3.8']

@nedbat
Copy link
Member Author

nedbat commented Sep 16, 2018

When a return statement also executes a finally clause, the sequence of lines reported to the trace function is different in 3.8 than it has been before 3.8:

$ cat finally_trace.py
def return_from_finally():
    try:
        print("returning")
        return 17
    finally:
        print("finally")
def trace(frame, event, arg):
    print(frame.f_lineno, event)
    return trace

import sys
sys.settrace(trace)
return_from_finally()
$ python3.7 finally_trace.py
1 call
2 line
3 line
returning
4 line
6 line
finally
6 return

$ python3.8 finally_trace.py
1 call
2 line
3 line
returning
4 line
6 line
finally
4 line
4 return

Is this intentional? Is it a bug? Will it change back before 3.8 is shipped?

@nedbat nedbat added 3.8 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error labels Sep 16, 2018
@nedbat
Copy link
Member Author

nedbat commented Sep 16, 2018

This affects coverage.py, as reported in this bug: nedbat/coveragepy#707

@serhiy-storchaka
Copy link
Member

This is a side effect of specific optimization. If the return value is constant, it is pushed on the stack after executing the finally code (see LOAD_CONST at offset 14 below). But other opcodes at this line (POP_BLOCK and CALL_FINALLY) are executed after executing the finally code. Thus it looks like the line 4 is executed twice, but actually different opcodes marked with the same line are executed before and after executing the finally code.

Disassembly of <code object return_from_finally at 0x7feff78897c0, file "<stdin>", line 1>:
2 0 SETUP_FINALLY 16 (to 18)

3 2 LOAD_GLOBAL 0 (print)
4 LOAD_CONST 1 ('returning')
6 CALL_FUNCTION 1
8 POP_TOP

4 10 POP_BLOCK
12 CALL_FINALLY 4 (to 18)
14 LOAD_CONST 2 (17)
16 RETURN_VALUE

6 >> 18 LOAD_GLOBAL 0 (print)
20 LOAD_CONST 3 ('finally')
22 CALL_FUNCTION 1
24 POP_TOP
26 END_FINALLY
28 LOAD_CONST 0 (None)
30 RETURN_VALUE

The benefit of this optimization is that it can make the stack smaller. This decreases the memory consumption of the Python function frame by one pointer and speeds up the Python function frame creation time (one pointer assignment less). It is tiny, but I think it is worth to keep it.

I don't know what is the right solution here.

@serhiy-storchaka
Copy link
Member

Humm, the optimization is not related here. Even if it is not involved (replace 17 with []), the line 4 is reported twice, because RETURN_VALUE is executed after CALL_FINALLY.

4 10 BUILD_LIST 0
12 POP_BLOCK
14 CALL_FINALLY 2 (to 18)
16 RETURN_VALUE

In 3.7 RETURN_VALUE was the opcode executed at line 4. The stack of blocks was unwinded at interpreted loop implicitly when execute RETURN_VALUE. But in 3.8 calling the finally code is explicit.

@nedbat
Copy link
Member Author

nedbat commented Sep 23, 2018

I can't tell if you think this is something that should be fixed, or not? (Also, I'm not getting email notifications from bpo, sorry for the delay).

@serhiy-storchaka
Copy link
Member

I think that this can be fixed. But this is not easy.

@markshannon
Copy link
Member

The new behaviour looks the more correct to me.

Arguably the sequence should not include the second "4 line", but is otherwise correct.

@markshannon
Copy link
Member

When I get a chance I'll see what happens with #6641

@markshannon
Copy link
Member

In master, the sequence of events is:

1 call
2 line
3 line
returning
4 line
6 line
finally
6 return

which is the same as 3.7.
I now believe this is the correct trace, as the language spec states:

When a return, break or continue statement is executed in the try suite of a try…finally statement, the finally clause is also executed ‘on the way out.’

So line 6 should be last line traced.

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.8 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants