You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
LOAD_GLOBAL used to load None under certain circumstances #60823
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=Noneclosed_at=<Date2012-12-06.22:41:12.773>created_at=<Date2012-12-05.19:28:31.623>labels= ['interpreter-core', 'type-bug']
title='LOAD_GLOBAL used to load `None` under certain circumstances'updated_at=<Date2012-12-14.16:39:12.068>user='https://bugs.python.org/brunodupuis'
To me, the whole issue is at line ~ 480 in peehole.c in the LOAD_NAME/LOAD_GLOBAL switch case.
This explains the difference between return and return None as the former is actually compiled to LOAD_CONST. OTOH, return None has to pass the optim pass to be changed in LOAD_CONST. The real bug is sometime it doesn't.
brunodupuis commented on Dec 5, 2012
brunodupuismannequin
MannequinAuthor
More actions
line 426 in peehole.c :
if (codestr[codelen-1] != RETURN_VALUE)
goto exitUnchanged;
before the optim. I'm quite sure it's here. i patch and see...
brunodupuis commented on Dec 5, 2012
brunodupuismannequin
MannequinAuthor
More actions
This first patch spots the issue, but doesn't solve it if the bytecode of the function is > 32700 (see PyCode_Optimize comment). However with this patch, we get the LOAD_CONST anytime for None, True and False.
There is two questions :
1- is it safe to strip all the code after RETURN_VALUE as the patch does?
2- to correct this bug, we will need a deep refactoring of PyCode_Optimize (so that it accepts any code length).
The other way, is not to rely on PyCode_Optimize to compile return None/True/False, but do modifictations in the compiler itself. This must be the right way to do this, but it's far beyond my C skills and python core knowledge.
Stripping truly dead code after return is really tricky in general because
a) it might be in a conditional block, and
b) unreachable yield and assignment can affect compilation.
Assignments that make names local are detected on a first pass, but I do not know about yield. Anyway, I believe the policy has been to not do it.
The final patch will need a cpython-only test based on g(x), with dead code.
Possibly intersecting issues are proposals to change where optimization is done and, for testing, to add a generator to dis so its output can be directly captured and analyzed instead of having to redirect, capture, and parse stdout.
We definitely need to put the code that loads constants with LOAD_CONST out of the optimization code. It's not optim, it's a language feature: None *is* a 'singleton' constant.
I'm trying to figure out how to change compile.c to achieve this, as it's my first dive into the compiler code, it's not that easy.
Another approch is to strip unreachable nodes in AST, but
a) it's quite complex, as Terry said
b) it solves only this particular bug, not the general assertion "None, True and False are reserved words bound to constants. It can not ever be loaded with LOAD_NAME or LOAD_GLOBAL"
First reaction is +1 for finally switching to real constant nodes in the AST for 3.4+. This is an inherited behaviour from 2.x where these were ordinary names rather than true keywords, so we weren't able to completely disallow overwriting them.
As a smaller impact change for earlier versions, we should be able to do something in compiler_nameop [1] that picks up the 3 singleton names and allows only LOAD_CONST, erroring out otherwise.
The difference in the errors below is the reason the systematic fix in Benjamin's patch simply wasn't practical in 2.x (as it would have required a complex deprecation dance to turn None, True and False into real keywords):
Python 2.7.3 (default, Jul 24 2012, 10:05:38)
[GCC 4.7.0 20120507 (Red Hat 4.7.0-5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> classC: pass
...
>>> C.None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: class C has no attribute 'None'
>>>
Python 3.2.3 (default, Jun 8 2012, 05:36:09)
[GCC 4.7.0 20120507 (Red Hat 4.7.0-5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> classC: pass
...
>>> C.None
File "<stdin>", line 1
C.None
^
SyntaxError: invalid syntax
loewis commented on Dec 6, 2012
loewismannequin
Mannequin
More actions
I claim that this is not a bug in the existing versions. It is documented as "illegal" to assign to None:
So what exactly happens if you manage to bypass the existing checks is implementation-specific (or, in the C sense, "undefined behavior"). Therefore, the reaction of Python 2.7 (e.g.) is perfectly fine.
Activity
brunodupuis commentedon Dec 5, 2012
We found some strange behaviour of the compiler in this discussion on python-list: http://mail.python.org/pipermail/python-list/2012-December/636104.html
The fact is,
returnandreturn Noneresult in inconsistent bytecode depending on the context.Consider :
3 4 LOAD_GLOBAL 1 (print)
7 LOAD_FAST 0 (x)
10 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
13 POP_TOP
return Nonestatement results in LOAD_GLOBAL 0 if there is some unreachable code after it. I first saw that as an optimization issue, but Ian Kelly's message http://mail.python.org/pipermail/python-list/2012-December/636117.html gives an extensive analysis and some examples:"""
I think this should even be considered a bug, not just a missing
optimization. Consider:
>>> globals()['None'] = 42 >>> def f(x): ... return None ... print(x) ... >>> f('test') 42The use of the LOAD_GLOBAL allows None to effectively be reassigned.
"""
Ian also points out in this message that
returnandreturn Nonedon't result in the same bytecode when followed by trash code.mrabarnett commentedon Dec 5, 2012
The same problem occurs with both
FalseandTrue.brunodupuis commentedon Dec 5, 2012
To me, the whole issue is at line ~ 480 in peehole.c in the LOAD_NAME/LOAD_GLOBAL switch case.
This explains the difference between
returnandreturn Noneas the former is actually compiled to LOAD_CONST. OTOH,return Nonehas to pass the optim pass to be changed in LOAD_CONST. The real bug is sometime it doesn't.brunodupuis commentedon Dec 5, 2012
line 426 in peehole.c :
if (codestr[codelen-1] != RETURN_VALUE)
goto exitUnchanged;
before the optim. I'm quite sure it's here. i patch and see...
brunodupuis commentedon Dec 5, 2012
This first patch spots the issue, but doesn't solve it if the bytecode of the function is > 32700 (see PyCode_Optimize comment). However with this patch, we get the LOAD_CONST anytime for None, True and False.
There is two questions :
1- is it safe to strip all the code after RETURN_VALUE as the patch does?
2- to correct this bug, we will need a deep refactoring of PyCode_Optimize (so that it accepts any code length).
The other way, is not to rely on PyCode_Optimize to compile return None/True/False, but do modifictations in the compiler itself. This must be the right way to do this, but it's far beyond my C skills and python core knowledge.
terryjreedy commentedon Dec 5, 2012
(2.6 is security fix only)
Stripping truly dead code after return is really tricky in general because
a) it might be in a conditional block, and
b) unreachable yield and assignment can affect compilation.
Assignments that make names local are detected on a first pass, but I do not know about yield. Anyway, I believe the policy has been to not do it.
The final patch will need a cpython-only test based on g(x), with dead code.
Possibly intersecting issues are proposals to change where optimization is done and, for testing, to add a generator to dis so its output can be directly captured and analyzed instead of having to redirect, capture, and parse stdout.
brunodupuis commentedon Dec 6, 2012
We definitely need to put the code that loads constants with LOAD_CONST out of the optimization code. It's not optim, it's a language feature: None *is* a 'singleton' constant.
I'm trying to figure out how to change compile.c to achieve this, as it's my first dive into the compiler code, it's not that easy.
Another approch is to strip unreachable nodes in AST, but
a) it's quite complex, as Terry said
b) it solves only this particular bug, not the general assertion "None, True and False are reserved words bound to constants. It can not ever be loaded with LOAD_NAME or LOAD_GLOBAL"
benjaminp commentedon Dec 6, 2012
Here's what I think we should do for 3.4. Nick, care to commment?
ncoghlan commentedon Dec 6, 2012
First reaction is +1 for finally switching to real constant nodes in the AST for 3.4+. This is an inherited behaviour from 2.x where these were ordinary names rather than true keywords, so we weren't able to completely disallow overwriting them.
As a smaller impact change for earlier versions, we should be able to do something in compiler_nameop [1] that picks up the 3 singleton names and allows only LOAD_CONST, erroring out otherwise.
http://hg.python.org/cpython/file/default/Python/compile.c#l2625
alex commentedon Dec 6, 2012
Nick, None was a constant even in 2k
ncoghlan commentedon Dec 6, 2012
True and False weren't, though (and even None wasn't a proper keyword)
ncoghlan commentedon Dec 6, 2012
The difference in the errors below is the reason the systematic fix in Benjamin's patch simply wasn't practical in 2.x (as it would have required a complex deprecation dance to turn None, True and False into real keywords):
loewis commentedon Dec 6, 2012
I claim that this is not a bug in the existing versions. It is documented as "illegal" to assign to None:
http://docs.python.org/2.7/library/constants.html?highlight=none#None
So what exactly happens if you manage to bypass the existing checks is implementation-specific (or, in the C sense, "undefined behavior"). Therefore, the reaction of Python 2.7 (e.g.) is perfectly fine.
python-dev commentedon Dec 6, 2012
New changeset 03f92a9f0875 by Benjamin Peterson in branch 'default':
create NameConstant AST class for None, True, and False literals (closes bpo-16619)
http://hg.python.org/cpython/rev/03f92a9f0875