Skip to content
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] Avoid "const PyObject*" type #91768

Closed
vstinner opened this issue Apr 20, 2022 · 3 comments
Closed

[C API] Avoid "const PyObject*" type #91768

vstinner opened this issue Apr 20, 2022 · 3 comments
Labels
type-bug An unexpected behavior, bug, or error

Comments

@vstinner
Copy link
Member

When I converted Py_REFCNT(), Py_TYPE() and Py_SIZE() macros to static inline functions (issue #83754), I chose const PyObject* for their argument because these macros don't modify any member of the PyObject structure. When the Py_IS_TYPE() function was added, it followed the trend (commit 8767ce9). The _PyObject_CAST_CONST() macro was added to easily convert any pointer to const PyObject* for these macros expecting const PyObject*.

The problem is that passing PyObject* to one of these functions emits a compiler warning using gcc -Wcast-qual. The _PyObject_CAST_CONST() doesn't make the warning quiet.

Example explaining the issue:

static inline int value(int *p) { return *p; }
#define value(p) value((int*)(p))

int main()
{
    int x = 1;
    const int *p = &x;
    return value(p);
}

Output:

$ gcc x.c -Wcast-qual -o x && ./x; echo $?
x.c: In function 'main':
x.c:2:24: warning: cast discards 'const' qualifier from pointer target type [-Wcast-qual]
    2 | #define value(p) value((int*)(p))
      |                        ^
x.c:8:12: note: in expansion of macro 'value'
    8 |     return value(p);
      |            ^~~~~
1

In practice, the problem was that building a C extension (which includes <Python.h>) with gcc -Wcast-qual -Werror on Python 3.10 failed with an error on Py_IS_TYPE() implemented as:

static inline int Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) {
    return Py_TYPE(ob) == type;
}
#define Py_IS_TYPE(ob, type) Py_IS_TYPE(_PyObject_CAST_CONST(ob), type)

See the issue #88544 for the compiler error. I removed the Py_TYPE() call in Py_IS_TYPE() to work around the issue:

static inline int Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) {
    // bpo-44378: Don't use Py_TYPE() since Py_TYPE() requires a non-const
    // object.
    return ob->ob_type == type;
}
#define Py_IS_TYPE(ob, type) Py_IS_TYPE(_PyObject_CAST_CONST(ob), type)

But this problem strikes back when I try to convert unicodeobject.h macros to static inline functions for PEP 670 which removes the cast to PyObject* in the limited C API version 3.11: see PR #91696 and PR #91705. For example, _Py_strhex_with_sep() and _Py_strhex_bytes_with_sep() function get a const PyObject* argument and use PyUnicode C functions like PyUnicode_READY(), PyUnicode_KIND() and PyUnicode_READ_CHAR(), but these PyUnicode functions expect PyObject*: the const qualifier is lost. If Python/pystrhex.c is built with gcc -Wcast-qual, gcc emits warnings on PyUnicode_KIND() and PyUnicode_READ_CHAR() calls. That's just an example of problem which can happen in C extensions as well.

To avoid these problems, I propose to avoid const PyObject* in Python: only use PyObject*.

@vstinner vstinner added the type-bug An unexpected behavior, bug, or error label Apr 20, 2022
@vstinner
Copy link
Member Author

Using const in C is appealing to help the C compiler to optimize the code. Problem: it's useless.

The C language is weird because it allows to cast const int* to int* (for example). So the compiler cannot rely on const of a parameter or variable declaration to assume that the code will really not modify these parameters and variables. This is well explained in the article: Why const Doesn't Make C Code Faster.

See also this question of the C FAQ: Why can't I pass a char ** to a function which expects a const char **?.

@vstinner
Copy link
Member Author

This problem will become worse with PEP 670 (convert macros to functions) which removes the implicit cast of function arguments to PyObject* (and other pointer types like void*) in the limited C API version 3.11 and newer. See my PR #91697.

vstinner added a commit that referenced this issue Apr 21, 2022
Py_REFCNT(), Py_TYPE(), Py_SIZE() and Py_IS_TYPE() functions argument
type is now "PyObject*", rather than "const PyObject*".

* Replace also "const PyObject*" with "PyObject*" in functions:

  * _Py_strhex_impl()
  * _Py_strhex_with_sep()
  * _Py_strhex_bytes_with_sep()

* Remove _PyObject_CAST_CONST() and _PyVarObject_CAST_CONST() macros.
* Py_IS_TYPE() can now use Py_TYPE() in its implementation.
@vstinner
Copy link
Member Author

Fixed by #91769

jamadden added a commit to python-greenlet/greenlet that referenced this issue Jan 27, 2023
See also python/cpython#91768

Fixes #302

Also stops building the mac wheels we ship with Ofast and debugging assertions. That's not meant for production use.
jamadden added a commit to python-greenlet/greenlet that referenced this issue Jan 27, 2023
See also python/cpython#91768

Fixes #302

Also stops building the mac wheels we ship with Ofast and debugging assertions. That's not meant for production use.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

1 participant