-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
PyFrame_LocalsToFast
misbehaving when there is a comprehension with colliding local variable
#130809
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
Comments
Hi there! You've discovered the unfortunate truth about foo = None
[foo for foo in [0]]
import inspect, ctypes
frame = inspect.currentframe()
frame.f_locals['a'] = 1
print(frame.f_locals['a'])
ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame), ctypes.c_int(0))
frame.f_locals['b'] = 2
print(frame.f_locals['b']) # KeyError :( There's probably some way to fix it, but keep in mind that 3.12's final bugfix release is coming up in about a month. I'm not sure we can get this fixed by then, or if it's worth doing at all. ( |
…n there is a name collision
|
Also, the original bug in debugpy is obviously a bug for their debugger. |
Ah! There's a reason we got rid of it :) |
Thank you both for the context and quick response! @gaogaotiantian, I still believe the issue should be considered a bug in Python and not simply misuse by debugpy. Here is a repro that does not utilize the C APIs: # Setup: in global scope, use a comprehension with colliding variable name
foo = 123
[foo for foo in [0]]
# Import * merges locals to fast internally, triggering the bug. The module here is arbitrary.
from abc import *
# Now modifications to locals (e.g. via exec) do not persist.
exec("b = 1")
print(b) # NameError: name 'b' is not defined Please consider merging #130816 to fix the issue. I've updated it with a test and blurb as well. |
No, you can't expect |
I'll take a closer look at the fundamental cause for this issue. |
Thank you. Another effect is that the comprehension variable ends up storing a reference to the outer variable, which can lead to unexpected ref counts / problems with garbage collection: # Setup: in global scope, use a comprehension with colliding variable name
foo = {}
[foo for foo in [0]]
# Import * merges locals to fast internally, triggering the bug. The module here is arbitrary.
from abc import *
# The hidden `foo` variable now unexpectedly holds a reference to the same object as the outer `foo` variable.
import sys
print(sys.getrefcount(foo)) # 3 in Python 3.12, 2 elsewhere
del foo
# Even after deletion there is still a reference via the hidden variable, so the object can't be cleaned up |
But it does work in 3.13, only gives an error in 3.12, right? |
That's because 3.13 has PEP 667 which solves everything. We can't backport a PEP to 3.12. Also that won't work in function scope either in 3.13. Overall that's a very bad thing to do. |
Okay I spent some time investigating this. I think it is a bug, but the fundamental issue is complicated.
Therefore, if you get the The second issue, which is what the linked PR fixed, made it worse. With a closer look at the problem, I believe that is an issue, but not the issue. The PR fixed an issue where This system is just beyond repairing, but the good news is we don't have to - we already replaced it in 3.13. The linked PR actually seemed okay to me. It does not solve the big issue, but does make the current situation better. I think it's pretty safe and explanable. I'll get a second opinion about whether to merge it, but I'm +1 on it. |
I'm removing the pending label, not because I'm +1, but whether we decide to close as planned/not planned or not, we had an argument in favor. I don't have enough insight on that matter to say +1. I would however say: is there some possibility to actually trigger the issue in a production code? while there are issues with poorly written code, is it possible to 1) avoid |
I believe the debugpy PR linked is something out there that's broken - that's why I'm +1 on this. If it's just hypothetical I would probably be +0. |
I see. If that helps then it's probably better to have that fix. Note that I'm leaving for 10 days so I won't be able to interact fast with this issue in the upcoming days. |
I think I got the blessing from Carl so the fix is good. I'll help @kycutler to finish the PR. |
…130816) * gh-130809: Fix `PyFrame_LocalsToFast` copying the wrong value * Skip hidden locals * test, blurb * Update Misc/NEWS.d/next/Core_and_Builtins/2025-03-04-12-52-21.gh-issue-130809.fSXq60.rst Co-authored-by: Tian Gao <[email protected]> * Update test * PR feedback * formatting * comment --------- Co-authored-by: Tian Gao <[email protected]>
Thank you for the PR! |
Bug report
Bug description:
The following fails in Python 3.12 (but not 3.11 or 3.13):
This impacts debuggers' ability to assign and evaluate local variables -- see microsoft/debugpy#1849 and microsoft/debugpy#1636.
Possibly related changes:
CPython versions tested on:
3.12
Operating systems tested on:
Windows
Linked PRs
PyFrame_LocalsToFast
copying the wrong value #130816The text was updated successfully, but these errors were encountered: