-
-
Notifications
You must be signed in to change notification settings - Fork 31.5k
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
Cannot perform frame jump after handled exception #92228
Comments
@dkrystki Do you have a full reproducer? |
For the presented example I performed jump using PyCharm. Edit: For my library purposes I am setting f_lineno manually and it is causing the same issue |
I managed to make a full reproducer:
Traceback:
|
diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py
index b1c8f6f80a..34126464bc 100644
--- a/Lib/test/test_sys_settrace.py
+++ b/Lib/test/test_sys_settrace.py
@@ -2217,6 +2217,16 @@ def test_jump_backwards_into_try_except_block(output):
raise
output.append(6)
+ @jump_test(7, 1, [1, 3, 6, 1, 3, 6])
+ def test_jump_backwards_over_try_except_block(output):
+ output.append(1)
+ try:
+ output.append(3)
+ 1/0
+ except ZeroDivisionError:
+ output.append(6)
+ x = 7
+
# 'except' with a variable creates an implicit finally block
@jump_test(5, 7, [4], (ValueError, 'within'))
def test_no_jump_between_except_blocks_2(output): |
All works if add anything above or below the assignment. |
For what it's worth, this is the problematic result from the reproducer. Bytecode:
Result from
Here, for (i = 0; i < len; i++) {
int64_t next_stack = stacks[i];
if (next_stack == UNINITIALIZED) {
continue;
}
// ...
} After BTW, bisected to adcd220 |
I'm making this a deferred blocker. |
We need to use the exception handling table to fill in the stacks for exception handlers. |
Bumping this to release-blocker |
The issue here is actually not jumping from within an exception handler to code outside of the handler (I'm not sure we want to allow that). Rather, it's jumping between two points that are outside of the handler. If it wasn't for the inline-small-exit-blocks optimisation, there would be a jump over the except block that
Thinking.. |
This confirms what I wrote above - the problem goes away if we make this change:
|
Indeed - then the size of the block exceeds the threshold for the optimisation. |
This optimisation is very old, and I wanted to check that it is still worth having. I ran pyperformance a few times on my Mac with and without it, and my results are that removing it actually makes python a little faster. This is not a shock if it's true - adding more code to save one jump per function is not an obvious improvement. If this is repeatable then the fix for this issue is obvious. Another option is to leave the optimisation for blocks without line numbers (the implicit 'return None's) but disable it for small blocks that do have line numbers. I want to get numbers from our benchmarking machine (which is not working for me at the moment for some reason) before we make a decision. |
Since the optimization does not have a noticeable negative impact and in fact improves performance on your system, then it is reasonable to remove it and get rid of one more release blocker. In worst case we can reconsider the optimization for next beta or 3.12. |
The PR is ready now. I went for the smaller change of disabling the optimisation only for blocks that have a line number. |
@iritkatriel : Just to double check, is this the optimization introduced in: https://bugs.python.org/issue44626 From what I can tell this came with Python 3.10, or am I mistaken? |
…tion for blocks that have a line number (GH-94592) Inlining of code that corresponds to source code lines, can make it hard to distinguish later between code which is only reachable from except handlers, and that which is reachable in normal control flow. This caused problems with the debugger's jump feature. This PR turns off the inlining optimisation for code which has line numbers. We still inline things like the implicit "return None".
…ing' optimization for blocks that have a line number (pythonGH-94592) Inlining of code that corresponds to source code lines, can make it hard to distinguish later between code which is only reachable from except handlers, and that which is reachable in normal control flow. This caused problems with the debugger's jump feature. This PR turns off the inlining optimisation for code which has line numbers. We still inline things like the implicit "return None".. (cherry picked from commit bde06e1) Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
…timization for blocks that have a line number (pythonGH-94592) Inlining of code that corresponds to source code lines, can make it hard to distinguish later between code which is only reachable from except handlers, and that which is reachable in normal control flow. This caused problems with the debugger's jump feature. This PR turns off the inlining optimisation for code which has line numbers. We still inline things like the implicit "return None".
…ptimization for blocks that have a line number (GH-94592) (GH-94643) Inlining of code that corresponds to source code lines, can make it hard to distinguish later between code which is only reachable from except handlers, and that which is reachable in normal control flow. This caused problems with the debugger's jump feature. This PR turns off the inlining optimisation for code which has line numbers. We still inline things like the implicit "return None".. (cherry picked from commit bde06e1) Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
@iritkatriel : Thank you for answering my question. I have a followup questions about this. I will post the question here, but do let me know if this is not the appropriate place. I am asking, because I only discovered this bytecode optimization being triggered on Python 3.10. Maybe you can shed some light as to why I hadn't seen this before, e.g. on 3.9. Allow me to illustrate: Using the following example:
Using https://godbolt.org/ I get the following for 3.9
and then for 3.10:
From what I can tell, the |
Possibly it was added in 3.10. You can dig in the code if you want. The compiler has changed quite a lot between 3.9 and now. |
Yes, I am aware of the changes to the compiler in 3.10. Thank you for your answer, my guess is that the code has been there for some time, but due to the refactoring in 3.10 it actually started triggering. |
Unfortunately #94592 doesn't fix this. @jump_test(7, 1, [1, 6, 1, 6])
def test_jump_over_try_except_early_return(output):
output.append(1)
try:
1 / 0
return
except ZeroDivisionError as e:
output.append(6)
pass # This block is dominated by the except handler Code like this is much less likely than the original example, but I think we should fix this properly by marking the stacks for exception handlers in |
That we are not tracking the exception handler is a separate (and known) issue. The problem here was that we could not tell where an exception handler ends. |
The issue of exception handlers is tracked here: #94739 |
The jump from the example I give is from the |
I see, will check. |
This fails too:
|
That should fail. |
You mean, the compiler can tell that it's unreachable. If you allow the debugger to jump around you can do all kinds of impossible things (like get to the pass in your example without an exception being raised). I'm not sure what the semantics of these debugger jumps are, really. |
No one is. In my mind, they are:
|
We understand the issue, and it's the subject of #94739 so I'll close this again (the original problem reported here was resolved). |
For given code:
Jumping from
current location
tojump location
yieldsValueError: can't jump from within an exception handler
Your environment
Works as expected for python < 3.11
The text was updated successfully, but these errors were encountered: