https://github.com/python/cpython/commit/1d6a2e648130cd834f7ed4fd210ef7dd4d4fc797
commit: 1d6a2e648130cd834f7ed4fd210ef7dd4d4fc797
branch: main
author: Bénédikt Tran <10796600+picn...@users.noreply.github.com>
committer: encukou <encu...@gmail.com>
date: 2025-03-24T17:15:32+01:00
summary:

gh-111178: fix UBSan for example code in `extending/newtypes_tutorial` docs 
(GH-131606)


Co-authored-by: Petr Viktorin <encu...@gmail.com>

files:
M Doc/extending/newtypes_tutorial.rst
M Doc/includes/newtypes/custom2.c
M Doc/includes/newtypes/custom3.c
M Doc/includes/newtypes/custom4.c
M Doc/includes/newtypes/sublist.c

diff --git a/Doc/extending/newtypes_tutorial.rst 
b/Doc/extending/newtypes_tutorial.rst
index b2ae18d75c3831..3fc91841416d71 100644
--- a/Doc/extending/newtypes_tutorial.rst
+++ b/Doc/extending/newtypes_tutorial.rst
@@ -250,16 +250,17 @@ Because we now have data to manage, we have to be more 
careful about object
 allocation and deallocation.  At a minimum, we need a deallocation method::
 
    static void
-   Custom_dealloc(CustomObject *self)
+   Custom_dealloc(PyObject *op)
    {
+       CustomObject *self = (CustomObject *) op;
        Py_XDECREF(self->first);
        Py_XDECREF(self->last);
-       Py_TYPE(self)->tp_free((PyObject *) self);
+       Py_TYPE(self)->tp_free(self);
    }
 
 which is assigned to the :c:member:`~PyTypeObject.tp_dealloc` member::
 
-   .tp_dealloc = (destructor) Custom_dealloc,
+   .tp_dealloc = Custom_dealloc,
 
 This method first clears the reference counts of the two Python attributes.
 :c:func:`Py_XDECREF` correctly handles the case where its argument is
@@ -270,11 +271,31 @@ the object's type might not be :class:`!CustomType`, 
because the object may
 be an instance of a subclass.
 
 .. note::
-   The explicit cast to ``destructor`` above is needed because we defined
-   ``Custom_dealloc`` to take a ``CustomObject *`` argument, but the 
``tp_dealloc``
-   function pointer expects to receive a ``PyObject *`` argument.  Otherwise,
-   the compiler will emit a warning.  This is object-oriented polymorphism,
-   in C!
+
+   The explicit cast to ``CustomObject *`` above is needed because we defined
+   ``Custom_dealloc`` to take a ``PyObject *`` argument, as the ``tp_dealloc``
+   function pointer expects to receive a ``PyObject *`` argument.
+   By assigning to the the ``tp_dealloc`` slot of a type, we declare
+   that it can only be called with instances of our ``CustomObject``
+   class, so the cast to ``(CustomObject *)`` is safe.
+   This is object-oriented polymorphism, in C!
+
+   In existing code, or in previous versions of this tutorial,
+   you might see similar functions take a pointer to the subtype
+   object structure (``CustomObject*``) directly, like this::
+
+      Custom_dealloc(CustomObject *self)
+      {
+          Py_XDECREF(self->first);
+          Py_XDECREF(self->last);
+          Py_TYPE(self)->tp_free((PyObject *) self);
+      }
+      ...
+      .tp_dealloc = (destructor) Custom_dealloc,
+
+   This does the same thing on all architectures that CPython
+   supports, but according to the C standard, it invokes
+   undefined behavior.
 
 We want to make sure that the first and last names are initialized to empty
 strings, so we provide a ``tp_new`` implementation::
@@ -352,8 +373,9 @@ We also define an initialization function which accepts 
arguments to provide
 initial values for our instance::
 
    static int
-   Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
+   Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
+       CustomObject *self = (CustomObject *) op;
        static char *kwlist[] = {"first", "last", "number", NULL};
        PyObject *first = NULL, *last = NULL, *tmp;
 
@@ -379,7 +401,7 @@ initial values for our instance::
 
 by filling the :c:member:`~PyTypeObject.tp_init` slot. ::
 
-   .tp_init = (initproc) Custom_init,
+   .tp_init = Custom_init,
 
 The :c:member:`~PyTypeObject.tp_init` slot is exposed in Python as the
 :meth:`~object.__init__` method.  It is used to initialize an object after it's
@@ -451,8 +473,9 @@ We define a single method, :meth:`!Custom.name`, that 
outputs the objects name a
 concatenation of the first and last names. ::
 
    static PyObject *
-   Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
+   Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
    {
+       CustomObject *self = (CustomObject *) op;
        if (self->first == NULL) {
            PyErr_SetString(PyExc_AttributeError, "first");
            return NULL;
@@ -486,7 +509,7 @@ Now that we've defined the method, we need to create an 
array of method
 definitions::
 
    static PyMethodDef Custom_methods[] = {
-       {"name", (PyCFunction) Custom_name, METH_NOARGS,
+       {"name", Custom_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
@@ -543,15 +566,17 @@ we'll use custom getter and setter functions.  Here are 
the functions for
 getting and setting the :attr:`!first` attribute::
 
    static PyObject *
-   Custom_getfirst(CustomObject *self, void *closure)
+   Custom_getfirst(PyObject *op, void *closure)
    {
+       CustomObject *self = (CustomObject *) op;
        Py_INCREF(self->first);
        return self->first;
    }
 
    static int
-   Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
+   Custom_setfirst(PyObject *op, PyObject *value, void *closure)
    {
+       CustomObject *self = (CustomObject *) op;
        PyObject *tmp;
        if (value == NULL) {
            PyErr_SetString(PyExc_TypeError, "Cannot delete the first 
attribute");
@@ -583,9 +608,9 @@ new value is not a string.
 We create an array of :c:type:`PyGetSetDef` structures::
 
    static PyGetSetDef Custom_getsetters[] = {
-       {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
+       {"first", Custom_getfirst, Custom_setfirst,
         "first name", NULL},
-       {"last", (getter) Custom_getlast, (setter) Custom_setlast,
+       {"last", Custom_getlast, Custom_setlast,
         "last name", NULL},
        {NULL}  /* Sentinel */
    };
@@ -609,8 +634,9 @@ We also need to update the 
:c:member:`~PyTypeObject.tp_init` handler to only
 allow strings [#]_ to be passed::
 
    static int
-   Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
+   Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
+       CustomObject *self = (CustomObject *) op;
        static char *kwlist[] = {"first", "last", "number", NULL};
        PyObject *first = NULL, *last = NULL, *tmp;
 
@@ -689,8 +715,9 @@ First, the traversal method lets the cyclic GC know about 
subobjects that could
 participate in cycles::
 
    static int
-   Custom_traverse(CustomObject *self, visitproc visit, void *arg)
+   Custom_traverse(PyObject *op, visitproc visit, void *arg)
    {
+       CustomObject *self = (CustomObject *) op;
        int vret;
        if (self->first) {
            vret = visit(self->first, arg);
@@ -716,8 +743,9 @@ functions.  With :c:func:`Py_VISIT`, we can minimize the 
amount of boilerplate
 in ``Custom_traverse``::
 
    static int
-   Custom_traverse(CustomObject *self, visitproc visit, void *arg)
+   Custom_traverse(PyObject *op, visitproc visit, void *arg)
    {
+       CustomObject *self = (CustomObject *) op;
        Py_VISIT(self->first);
        Py_VISIT(self->last);
        return 0;
@@ -731,8 +759,9 @@ Second, we need to provide a method for clearing any 
subobjects that can
 participate in cycles::
 
    static int
-   Custom_clear(CustomObject *self)
+   Custom_clear(PyObject *op)
    {
+       CustomObject *self = (CustomObject *) op;
        Py_CLEAR(self->first);
        Py_CLEAR(self->last);
        return 0;
@@ -765,11 +794,11 @@ Here is our reimplemented deallocator using 
:c:func:`PyObject_GC_UnTrack`
 and ``Custom_clear``::
 
    static void
-   Custom_dealloc(CustomObject *self)
+   Custom_dealloc(PyObject *op)
    {
-       PyObject_GC_UnTrack(self);
-       Custom_clear(self);
-       Py_TYPE(self)->tp_free((PyObject *) self);
+       PyObject_GC_UnTrack(op);
+       (void)Custom_clear(op);
+       Py_TYPE(op)->tp_free(op);
    }
 
 Finally, we add the :c:macro:`Py_TPFLAGS_HAVE_GC` flag to the class flags::
@@ -825,9 +854,10 @@ When a Python object is a :class:`!SubList` instance, its 
``PyObject *`` pointer
 can be safely cast to both ``PyListObject *`` and ``SubListObject *``::
 
    static int
-   SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
+   SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
-       if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
+       SubListObject *self = (SubListObject *) op;
+       if (PyList_Type.tp_init(op, args, kwds) < 0)
            return -1;
        self->state = 0;
        return 0;
diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c
index 768ce29fab9ff0..a87917583ca495 100644
--- a/Doc/includes/newtypes/custom2.c
+++ b/Doc/includes/newtypes/custom2.c
@@ -10,11 +10,12 @@ typedef struct {
 } CustomObject;
 
 static void
-Custom_dealloc(CustomObject *self)
+Custom_dealloc(PyObject *op)
 {
+    CustomObject *self = (CustomObject *) op;
     Py_XDECREF(self->first);
     Py_XDECREF(self->last);
-    Py_TYPE(self)->tp_free((PyObject *) self);
+    Py_TYPE(self)->tp_free(self);
 }
 
 static PyObject *
@@ -39,8 +40,9 @@ Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 }
 
 static int
-Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
+Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
 {
+    CustomObject *self = (CustomObject *) op;
     static char *kwlist[] = {"first", "last", "number", NULL};
     PyObject *first = NULL, *last = NULL;
 
@@ -69,8 +71,9 @@ static PyMemberDef Custom_members[] = {
 };
 
 static PyObject *
-Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
+Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
 {
+    CustomObject *self = (CustomObject *) op;
     if (self->first == NULL) {
         PyErr_SetString(PyExc_AttributeError, "first");
         return NULL;
@@ -83,7 +86,7 @@ Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
 }
 
 static PyMethodDef Custom_methods[] = {
-    {"name", (PyCFunction) Custom_name, METH_NOARGS,
+    {"name", Custom_name, METH_NOARGS,
      "Return the name, combining the first and last name"
     },
     {NULL}  /* Sentinel */
@@ -97,8 +100,8 @@ static PyTypeObject CustomType = {
     .tp_itemsize = 0,
     .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
     .tp_new = Custom_new,
-    .tp_init = (initproc) Custom_init,
-    .tp_dealloc = (destructor) Custom_dealloc,
+    .tp_init = Custom_init,
+    .tp_dealloc = Custom_dealloc,
     .tp_members = Custom_members,
     .tp_methods = Custom_methods,
 };
diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c
index 7d969adfa7c9cc..854034d4066d20 100644
--- a/Doc/includes/newtypes/custom3.c
+++ b/Doc/includes/newtypes/custom3.c
@@ -10,11 +10,12 @@ typedef struct {
 } CustomObject;
 
 static void
-Custom_dealloc(CustomObject *self)
+Custom_dealloc(PyObject *op)
 {
+    CustomObject *self = (CustomObject *) op;
     Py_XDECREF(self->first);
     Py_XDECREF(self->last);
-    Py_TYPE(self)->tp_free((PyObject *) self);
+    Py_TYPE(self)->tp_free(self);
 }
 
 static PyObject *
@@ -39,8 +40,9 @@ Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 }
 
 static int
-Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
+Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
 {
+    CustomObject *self = (CustomObject *) op;
     static char *kwlist[] = {"first", "last", "number", NULL};
     PyObject *first = NULL, *last = NULL;
 
@@ -65,14 +67,16 @@ static PyMemberDef Custom_members[] = {
 };
 
 static PyObject *
-Custom_getfirst(CustomObject *self, void *closure)
+Custom_getfirst(PyObject *op, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     return Py_NewRef(self->first);
 }
 
 static int
-Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
+Custom_setfirst(PyObject *op, PyObject *value, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     if (value == NULL) {
         PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
         return -1;
@@ -87,14 +91,16 @@ Custom_setfirst(CustomObject *self, PyObject *value, void 
*closure)
 }
 
 static PyObject *
-Custom_getlast(CustomObject *self, void *closure)
+Custom_getlast(PyObject *op, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     return Py_NewRef(self->last);
 }
 
 static int
-Custom_setlast(CustomObject *self, PyObject *value, void *closure)
+Custom_setlast(PyObject *op, PyObject *value, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     if (value == NULL) {
         PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
         return -1;
@@ -109,21 +115,22 @@ Custom_setlast(CustomObject *self, PyObject *value, void 
*closure)
 }
 
 static PyGetSetDef Custom_getsetters[] = {
-    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
+    {"first", Custom_getfirst, Custom_setfirst,
      "first name", NULL},
-    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
+    {"last", Custom_getlast, Custom_setlast,
      "last name", NULL},
     {NULL}  /* Sentinel */
 };
 
 static PyObject *
-Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
+Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
 {
+    CustomObject *self = (CustomObject *) op;
     return PyUnicode_FromFormat("%S %S", self->first, self->last);
 }
 
 static PyMethodDef Custom_methods[] = {
-    {"name", (PyCFunction) Custom_name, METH_NOARGS,
+    {"name", Custom_name, METH_NOARGS,
      "Return the name, combining the first and last name"
     },
     {NULL}  /* Sentinel */
@@ -137,8 +144,8 @@ static PyTypeObject CustomType = {
     .tp_itemsize = 0,
     .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
     .tp_new = Custom_new,
-    .tp_init = (initproc) Custom_init,
-    .tp_dealloc = (destructor) Custom_dealloc,
+    .tp_init = Custom_init,
+    .tp_dealloc = Custom_dealloc,
     .tp_members = Custom_members,
     .tp_methods = Custom_methods,
     .tp_getset = Custom_getsetters,
diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c
index a7b8de44a57c90..a0a1eeb289190b 100644
--- a/Doc/includes/newtypes/custom4.c
+++ b/Doc/includes/newtypes/custom4.c
@@ -10,27 +10,29 @@ typedef struct {
 } CustomObject;
 
 static int
-Custom_traverse(CustomObject *self, visitproc visit, void *arg)
+Custom_traverse(PyObject *op, visitproc visit, void *arg)
 {
+    CustomObject *self = (CustomObject *) op;
     Py_VISIT(self->first);
     Py_VISIT(self->last);
     return 0;
 }
 
 static int
-Custom_clear(CustomObject *self)
+Custom_clear(PyObject *op)
 {
+    CustomObject *self = (CustomObject *) op;
     Py_CLEAR(self->first);
     Py_CLEAR(self->last);
     return 0;
 }
 
 static void
-Custom_dealloc(CustomObject *self)
+Custom_dealloc(PyObject *op)
 {
-    PyObject_GC_UnTrack(self);
-    Custom_clear(self);
-    Py_TYPE(self)->tp_free((PyObject *) self);
+    PyObject_GC_UnTrack(op);
+    (void)Custom_clear(op);
+    Py_TYPE(op)->tp_free(op);
 }
 
 static PyObject *
@@ -55,8 +57,9 @@ Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 }
 
 static int
-Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
+Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
 {
+    CustomObject *self = (CustomObject *) op;
     static char *kwlist[] = {"first", "last", "number", NULL};
     PyObject *first = NULL, *last = NULL;
 
@@ -81,14 +84,16 @@ static PyMemberDef Custom_members[] = {
 };
 
 static PyObject *
-Custom_getfirst(CustomObject *self, void *closure)
+Custom_getfirst(PyObject *op, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     return Py_NewRef(self->first);
 }
 
 static int
-Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
+Custom_setfirst(PyObject *op, PyObject *value, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     if (value == NULL) {
         PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
         return -1;
@@ -103,14 +108,16 @@ Custom_setfirst(CustomObject *self, PyObject *value, void 
*closure)
 }
 
 static PyObject *
-Custom_getlast(CustomObject *self, void *closure)
+Custom_getlast(PyObject *op, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     return Py_NewRef(self->last);
 }
 
 static int
-Custom_setlast(CustomObject *self, PyObject *value, void *closure)
+Custom_setlast(PyObject *op, PyObject *value, void *closure)
 {
+    CustomObject *self = (CustomObject *) op;
     if (value == NULL) {
         PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
         return -1;
@@ -125,21 +132,22 @@ Custom_setlast(CustomObject *self, PyObject *value, void 
*closure)
 }
 
 static PyGetSetDef Custom_getsetters[] = {
-    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
+    {"first", Custom_getfirst, Custom_setfirst,
      "first name", NULL},
-    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
+    {"last", Custom_getlast, Custom_setlast,
      "last name", NULL},
     {NULL}  /* Sentinel */
 };
 
 static PyObject *
-Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
+Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
 {
+    CustomObject *self = (CustomObject *) op;
     return PyUnicode_FromFormat("%S %S", self->first, self->last);
 }
 
 static PyMethodDef Custom_methods[] = {
-    {"name", (PyCFunction) Custom_name, METH_NOARGS,
+    {"name", Custom_name, METH_NOARGS,
      "Return the name, combining the first and last name"
     },
     {NULL}  /* Sentinel */
@@ -153,10 +161,10 @@ static PyTypeObject CustomType = {
     .tp_itemsize = 0,
     .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
     .tp_new = Custom_new,
-    .tp_init = (initproc) Custom_init,
-    .tp_dealloc = (destructor) Custom_dealloc,
-    .tp_traverse = (traverseproc) Custom_traverse,
-    .tp_clear = (inquiry) Custom_clear,
+    .tp_init = Custom_init,
+    .tp_dealloc = Custom_dealloc,
+    .tp_traverse = Custom_traverse,
+    .tp_clear = Custom_clear,
     .tp_members = Custom_members,
     .tp_methods = Custom_methods,
     .tp_getset = Custom_getsetters,
diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c
index d8aba463f30ba2..00664f3454156f 100644
--- a/Doc/includes/newtypes/sublist.c
+++ b/Doc/includes/newtypes/sublist.c
@@ -7,22 +7,24 @@ typedef struct {
 } SubListObject;
 
 static PyObject *
-SubList_increment(SubListObject *self, PyObject *unused)
+SubList_increment(PyObject *op, PyObject *Py_UNUSED(dummy))
 {
+    SubListObject *self = (SubListObject *) op;
     self->state++;
     return PyLong_FromLong(self->state);
 }
 
 static PyMethodDef SubList_methods[] = {
-    {"increment", (PyCFunction) SubList_increment, METH_NOARGS,
+    {"increment", SubList_increment, METH_NOARGS,
      PyDoc_STR("increment state counter")},
     {NULL},
 };
 
 static int
-SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
+SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
 {
-    if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
+    SubListObject *self = (SubListObject *) op;
+    if (PyList_Type.tp_init(op, args, kwds) < 0)
         return -1;
     self->state = 0;
     return 0;
@@ -35,7 +37,7 @@ static PyTypeObject SubListType = {
     .tp_basicsize = sizeof(SubListObject),
     .tp_itemsize = 0,
     .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-    .tp_init = (initproc) SubList_init,
+    .tp_init = SubList_init,
     .tp_methods = SubList_methods,
 };
 

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to