-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
[C API] Add private "CAST" macros to clean up casts in C code #91320
Comments
Last years, I started to add "CAST" macros like: #define _PyObject_CAST(op) ((PyObject*)(op))
#define _PyType_CAST(op) (assert(PyType_Check(op)), (PyTypeObject*)(op)) Example of usage: #define PyObject_TypeCheck(ob, type) PyObject_TypeCheck(_PyObject_CAST(ob), type) These macros avoids parenthesis. Example without CAST macro: #define PyCFunction_GET_FLAGS(func) \
(((PyCFunctionObject *)func) -> m_ml -> ml_flags) Currently, inline cast requires adding parenthesis: ((PyCFunctionObject *)func) IMO it's more readable with a CAST macro: #define _PyCFunction_CAST(func) ((PyCFunctionObject *)func)
#define PyCFunction_GET_FLAGS(func) \
(_PyCFunction_CAST(func)->m_ml->ml_flags) I propose to add more CAST macros. By the way, the current Python C API is not fully compatible with C++. "(type)expr" C syntax is seen as "old-style cast" in C++ which recommends static_cast<type>(expr), reinterpret_cast<type>(expr), or another kind of cast. But I prefer to discuss C++ in a separated issue ;-) IMO without considering C++, adding CAST macros is worth it for readability. I am preparing pull requests for add CAST macros and use existing CAST macros. |
I think that adding macros makes readability worse. The macro is only more readable if you already know what it does. In general, I find the excessive use of macros and tiny inline function obscures the meaning of code, and makes it hard to reason about what the code is doing. A few well chosen macros (like Py_INCREF(), etc) can definitely help readability, but if there are too many then anyone reading the code ends up having to lookup loads of macro definitions to understand the code. Without the macro, the reader needs to parse the cast, which is admittedly a |
The problem in the example you give is the need for the cast in the first place. If Making the casts explicit serves as a reminder that a type check is needed.
is dangerous.
is safe. |
Sadly I have to agree with Mark for that specific case. I've had to debug a segfault before only because the inline function implicitly cast its arguments, and it was accessing a non-existent member. If it were a macro it would access the struct member directly, and the compiler would be able to catch that and warn me before runtime. However, for the case of _PyCFunctionObject_CAST, I'm not against it. The assert(PyCFunction_Check) pattern has genuinely helped me catch problems in the case of similar macros. |
See the attached PRs, I can remove multiple layers of parenthesis in macros by using CAST macros.
Right. PEP-670 proposes to remove the cast, but only in the limited C API version 3.11 or newer. Sadly, I don't think that we can remove the cast in the general Python C API, it would simply add too many compiler warnings in existing C extensions which previously built without warnings. (See also PEP-670 discussions on python-dev). Currently, PyCFunction_GET_FLAGS() doesn't check its argument. The macro documentation is quite explicit about it:
My #76371 PR adds a check in debug mode. So using PyCFunction_GET_FLAGS() in debug mode is safer with this PR. |
I created bpo-47165 "[C API] Test that the Python C API is compatible with C++". |
Hum, there are two things and maybe we are not talking about the same thing.
I created this issue for (A). I propose to do (B), but this part is optional. Mark, Ken: are you fine with (A)? Is your concern about (B)? But modifying macros to remove the cast of their argument is out of the scope of this issue. As I wrote, it would add compiler warnings, something that I would like to avoid. See: |
#76373 "Add _PyCFunction_CAST() macro" is special. It's used to define functions in PyTypeObject.tp_methods or PyModuleDef.m_methods. Casting a function pointer can cause issues with Control Flow Integrity (CFI) implemented in LLVM. The _thread module has an undefined behavior fixed by the commit 9eea6ea of bpo-33015. Previously, a cast ignored that the callback has no return value, whereas pthread_create() requires a function which as a void* return value. To fix compiler warnings, the (PyCFunction)func cast was replaced with the (PyCFunction)(void(*)(void))func cast to fix bpo-bpo-33012 GCC 8 compiler warning:
|
I'm not against (A) or any of the PRs you proposed. They look fine to me. My concern was with certain static inline functions (there are none here :). The _CAST macros have genuinely helped me debug code before. |
This is part of Python C API legacy and as I wrote, it's not going to change soon. The minor enhancement that we can do is to inject an assertion when Python is built in debug mode. |
After reading Mark's comments, I reworked my #76371 PR to only use the CAST macros in other macros, not in the C code. The CAST macros are not used in such code pattern:
In fact, this change doesn't bring any value. |
Concrete problem of METH_NOARG undefined behavior on a function with a single parameter, whereas the ABI requires 2 parameters (pass NULL as the second parameter): https://blog.pyodide.org/posts/function-pointer-cast-handling/ It's also an issue for CFI. |
Fix C++ compiler warnings about "old-style cast" (g++ -Wold-style-cast) in the Python C API. Use C++ reinterpret_cast<> and static_cast<> casts when the Python C API is used in C++. Example of fixed warning: Include/object.h:107:43: error: use of old-style cast to ‘PyObject*’ {aka ‘struct _object*’} [-Werror=old-style-cast] #define _PyObject_CAST(op) ((PyObject*)(op)) Add _Py_reinterpret_cast() and _Py_static_cast() macros.
Replace "(PyCFunction)(void(*)(void))func" cast with _PyCFunction_CAST(func).
Use _Py_CAST(), _Py_STATIC_CAST() and _PyASCIIObject_CAST() in static inline functions to fix C++ compiler warnings: "use of old-style cast" (clang -Wold-style-cast). test_cppext now builds the C++ test extension with -Wold-style-cast.
Replace "(PyCFunction)(void(*)(void))func" cast with _PyCFunction_CAST(func). Change generated by the command: sed -i -e \ 's!(PyCFunction)(void(\*)(void)) *\([A-Za-z0-9_]\+\)!_PyCFunction_CAST(\1)!g' \ $(find -name "*.c")
Hum, the issue was not fully fixed (especially, related to C++: see issue #91321). I pushed a few more changes:
|
Use _PyObject_CAST() in the public C API to fix C++ compiler warnings: "use of old-style cast" (clang -Wold-style-cast).
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:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: