-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
Macro Py_CLEAR references argument two times. #98724
Comments
Here's an example of an expression in cpython/Modules/selectmodule.c Lines 108 to 116 in 365852a
We also have: static int
template_clear(TemplateObject *self)
{
Py_CLEAR(self->literal);
for (Py_ssize_t i = 0, n = Py_SIZE(self); i < n; i++) {
Py_CLEAR(self->items[i].literal);
}
return 0;
} And for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
Py_CLEAR(values->values[i]);
} All cases are element access, but this is the most complex examples I am able to find. |
I am not sure that I made the point totally clear. Here is an example program for clarification, using some dummy type for
This code will give a buffer overflow. Changing the last line to |
I'm not sure we've ever promised that macros won't evaluate their args more than once. @vstinner ? |
"Duplication of side effects" is one catch of macros that PEP 670 tries to avoid: https://peps.python.org/pep-0670/#rationale Sadly, Py_CLEAR() cannot be converted to a function (as part of PEP 670) since it magically gets a reference to a pointer thanks to magic macro preprocessor. An hypothetical Py_Clear() function would take a pointer to a Python object, so IMO using
I agree with you. Moreover, correctness matters more than performance for Py_CLEAR() API. |
Maybe a Somehow related discussions: |
I am against extending ABI without need. Py_CLEAR is a part of API, but not in ABI. Incref/decref is anough, Py_CLEAR is just an API sugar. Calling |
How about this macro?
Replace the free line with the commented out line for real Python, this is for my earlier example program. |
This issue looks an hypothetical bug, but I'm not convinced that it's possible to write an expression which a side effect and which makes sense to set to NULL. For example, in your example, what's the point of writing To be clear, GCC fails to build the following C code:
|
I created PR #99100 to fix this issue. I'm not convinced that we should fix it, but a full PR might help to make a decision. In the past, I saw surprising bug like https://bugs.python.org/issue43181 about passing a C++ expression to a C macro. So well, maybe in case of doubt, it's better to fix the issue to be extra safe. Py_CLEAR() pretends to be safer than using directly the Py_DECREF() macro ;-) |
Ah wait, I read again #98724 (comment) and now I got the issue :-) I updated the unit test in my PR #99100. |
Perfect! The main problem I have with macros that pretend to be functions is that they might evaluate their arguments several times, and this is totally unexpected. I hope the improved version works for all use-cases :) |
I am not sure that it is not just a documentation issue. We can just document that the argument of Py_CLEAR (and the first argument of Py_SETREF) should not have side effect. We can make it working even for arguments with a side effect, but should it be considered a bug fix or a new feature? |
My PR fix the macro so it behaves correctly with arguments with side effects. For me it's a bugfix and should be backported to stable branches. If you are scared of the new implementation (using a pointer to a pointer), I'm fine with only changing the macro in Python 3.12 for now, and only backport if there is a strong pressure from many users to backport the fix. |
I am fine with the new implementation. I think all modern compilers can get handle it without adding overhead if it is in a macro. I afraid that users will start to rely on this feature and then their code will work incoretly when compiled with older Python, depending on the bugfix number. |
I think this should be extended to all macros, so that they only reference their arguments once. If that can be done it could be made public that from now on macros are safe to use even with arguments that have side effects. |
I completed my PR to fix also Py_SETREF() and Py_XSETREF() macros. |
The Py_CLEAR(), Py_SETREF() and Py_XSETREF() macros now only evaluate their argument once. If an argument has side effects, these side effects are no longer duplicated. Add test_py_clear() and test_py_setref() unit tests to _testcapi.
The Py_CLEAR(), Py_SETREF() and Py_XSETREF() macros now only evaluate their argument once. If an argument has side effects, these side effects are no longer duplicated. Add test_py_clear() and test_py_setref() unit tests to _testcapi. (cherry picked from commit c03e05c)
…ythonGH-99288) The Py_CLEAR(), Py_SETREF() and Py_XSETREF() macros now only evaluate their argument once. If an argument has side effects, these side effects are no longer duplicated. Add test_py_clear() and test_py_setref() unit tests to _testcapi. (cherry picked from commit 1082890) Co-authored-by: Victor Stinner <vstinner@python.org> (cherry picked from commit c03e05c)
Oh wow, I didn't notice that my commit c03e05c can introduce a type punning issue and miscompile Python using strict aliasing (which is the default behavior of C compilers). That's really bad. I reverted my change to fix #99701. Miscompiling Python is really bad. Articles about these problems:
I wrote PR #99739 to fix type punning by using I don't like the idea of only fixing the macro if the used C compiler provides So I think that the best we can do here is to document the issue and explain how to work around it: the macro does duplicate side effects, so just avoid expression with side effects. For example, Replace |
I am not entirely sure if reverting the change is the way to go. As I see it there is a more fundamental type punning problem here where this macro update only triggered a different bug that is still also here. No idea how to fix that, I usually use |
The Py_CLEAR(), Py_SETREF() and Py_XSETREF() macros now only evaluate their arguments once. If an argument has side effects, these side effects are no longer duplicated. Use temporary variables to avoid duplicating side effects of macro arguments. If available, use _Py_TYPEOF() to avoid type punning. Otherwise, use memcpy() for the assignment to prevent a miscompilation with strict aliasing caused by type punning. Add _Py_TYPEOF() macro: __typeof__() on GCC and clang. Add test_py_clear() and test_py_setref() unit tests to _testcapi.
I fixed again Py_CLEAR(), Py_SETREF() and Py_XSETREF() macros with commit b11a384. This time, the fix should avoid the type punning issue which caused miscompilation of Python. The fix uses
I expect that memcpy() is never called as a function: C compilers usually use their own "built-in" flavor which is more efficient. I expect that memcpy() is implemented with a single MOV instruction on x86-64. Example with _testcapi.test_py_setref():
x86-64 assembly compiled by GCC 12.2.1 with -O3, implementation using
The obj variable is not even allocated in the stack: it only exists in the rax register. Py_SETREF() doesn't introduce inefficient memory copies. The If I disable
GCC emits the same machine code for both implementation, GCC is smart and works as expected ;-) |
I am sad to say this: but looks like this feature broke one more thing :( Since b11a384 we have this failure of @vstinner tried to fixed it in cd67c1b but it did not work. And buildbots are still failing with this problem:
Is it really related? Or just a coincedence? 🤔 |
I fixed the issue with commit cd67c1b.
Tests on recursive functions are fragile, unless they use On Windows, MSVC doesn't inline "static inline functions" in debug mode: |
Capturing a few comments I made on Discord: I think it's a mistake to change the behaviour of |
I reopen the issue. The limited C API of Python 3.12 is still broken on compilers which don't provide Either the change is reverted to keep the bug on purpose, or the limited C API must be fixed. |
Minimum extension module code reproducing the build error: #define Py_LIMITED_API 0x030b0000
#include "Python.h"
static int
xx_modexec(PyObject *m)
{
PyObject *o = PyLong_FromLong(1);
Py_CLEAR(o);
return 0;
}
static PyModuleDef_Slot xx_slots[] = {
{Py_mod_exec, xx_modexec},
{0, NULL}
};
static struct PyModuleDef xxmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "xxlimited",
.m_size = 0,
.m_slots = xx_slots,
};
PyMODINIT_FUNC
PyInit_xxlimited(void)
{
return PyModuleDef_Init(&xxmodule);
} Python is built with
By the way, in LLVM 16, clang now treats this warning as an error by default:
The problem of implicit function declaration is also becoming a major problem to migrate the C ecosystem to C99 and newer, see: https://fedoraproject.org/wiki/Changes/PortingToModernC |
I created a thread on discuss about this issue: https://discuss.python.org/t/c-api-design-goal-hide-implementation-details-convert-macros-to-regular-functions-avoid-memset-or-errno-in-header-files-etc/25137 |
Maybe I missed it, but has anyone proposed yet to mix a macro (that takes the address of the argument and passes it into an inline function) with an inline function (that receives the pointer and does all the rest)? |
I proposed multiple times to add functions rather than macros. Examples:
@scoder's idea is a little bit different: the added function doesn't have to be part of the public C API. @scoder: Do you think that such function should be public? Do you see advantages of a function rather than using a macro? I have my own rationale, but as you saw, I failed to convinced other people :-) Accepted PEP 670 suggests converting macros to functions to they can be used in programs which cannot use macros. |
I'm very late to this unfortunately, but the fallback version of There's no guarantee that It's probably not good practice to call I think it'd be good to drop the guarantee of single evaluation from the documentation, because it can't be implemented portably. (Consider also that You could use a template function when compiling as C++ (or |
I disagree with converting Py_CLEAR to regular function at first place. It was designed as a macro, and worked as a macro all these years. Your cure is worse than the (imaginary) disease. |
I also think that we're trying to fix a non-problem here. |
I'm not sure you mean my suggestion of using a template function, but the template function would be defined in the header and always inlined when optimizing, so it shouldn't affect other optimizations or warnings (which was your concern in an earlier comment). And of course But thinking about it again, if single evaluation can't be guaranteed anyway in the spec (and I think it shouldn't be) then it would probably be best to just revert to the old implementation. |
Bug report
The macro
Py_CLEAR(op)
references the argumentop
two times. If the macro is called with an expression it will be evaluated two times, for examplePy_CLEAR(p++)
.Your environment
x86_64
Python 3.7m
Debian Stable
I suggest a fix similar to this (old version commented out with #if 0):
I am not sure if this has happened anywhere, but I see a possible problem here. I think the compiler will optimize out the additional temporary variable in most cases.
The text was updated successfully, but these errors were encountered: