Skip to content

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

Closed
@vstinner

Description

@vstinner

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*.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions