From 1fb5d8a01f69dad9e525b2db576f079f3a2dfcc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:06:58 +0100 Subject: [PATCH 1/5] fix UBSan for `custom.c` examples --- Doc/includes/newtypes/custom2.c | 17 +++++++------ Doc/includes/newtypes/custom3.c | 33 +++++++++++++++---------- Doc/includes/newtypes/custom4.c | 43 ++++++++++++++++++++------------- Doc/includes/newtypes/sublist.c | 10 +++++--- 4 files changed, 62 insertions(+), 41 deletions(-) diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index 768ce29fab9ff0..43734040fee4fa 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..edb9599feb253d 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..79a4fc9e601559 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -10,27 +10,30 @@ 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) { + CustomObject *self = (CustomObject *)op; PyObject_GC_UnTrack(self); Custom_clear(self); - Py_TYPE(self)->tp_free((PyObject *) self); + Py_TYPE(self)->tp_free(self); } static PyObject * @@ -55,8 +58,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 +85,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 +109,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 +133,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 +162,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..00696ff081d217 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -7,21 +7,23 @@ 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) { + SubListObject *self = (SubListObject *)op; if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0) return -1; self->state = 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, }; From 811b8eb9b5e7e2b2051e7d3dc872a783f2dfbff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:47:34 +0100 Subject: [PATCH 2/5] sync docs --- Doc/extending/newtypes_tutorial.rst | 61 +++++++++++++++++------------ Doc/includes/newtypes/custom2.c | 6 +-- Doc/includes/newtypes/custom3.c | 14 +++---- Doc/includes/newtypes/custom4.c | 23 ++++++----- Doc/includes/newtypes/sublist.c | 6 +-- 5 files changed, 59 insertions(+), 51 deletions(-) diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index bcf938f117d148..404d1ab8ac609c 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(op); } 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,10 @@ 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. Otherwise, + this would result in an undefined behaviour at runtime! 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 +352,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 +380,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 +452,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 +488,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 +545,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 +587,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 +613,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 +694,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 +722,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 +738,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 +773,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 +833,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 43734040fee4fa..a87917583ca495 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -12,7 +12,7 @@ typedef struct { static void Custom_dealloc(PyObject *op) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; Py_XDECREF(self->first); Py_XDECREF(self->last); Py_TYPE(self)->tp_free(self); @@ -42,7 +42,7 @@ Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static int Custom_init(PyObject *op, PyObject *args, PyObject *kwds) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; static char *kwlist[] = {"first", "last", "number", NULL}; PyObject *first = NULL, *last = NULL; @@ -73,7 +73,7 @@ static PyMemberDef Custom_members[] = { static PyObject * Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy)) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; if (self->first == NULL) { PyErr_SetString(PyExc_AttributeError, "first"); return NULL; diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index edb9599feb253d..854034d4066d20 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -12,7 +12,7 @@ typedef struct { static void Custom_dealloc(PyObject *op) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; Py_XDECREF(self->first); Py_XDECREF(self->last); Py_TYPE(self)->tp_free(self); @@ -42,7 +42,7 @@ Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static int Custom_init(PyObject *op, PyObject *args, PyObject *kwds) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; static char *kwlist[] = {"first", "last", "number", NULL}; PyObject *first = NULL, *last = NULL; @@ -69,14 +69,14 @@ static PyMemberDef Custom_members[] = { static PyObject * Custom_getfirst(PyObject *op, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; return Py_NewRef(self->first); } static int Custom_setfirst(PyObject *op, PyObject *value, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute"); return -1; @@ -93,14 +93,14 @@ Custom_setfirst(PyObject *op, PyObject *value, void *closure) static PyObject * Custom_getlast(PyObject *op, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; return Py_NewRef(self->last); } static int Custom_setlast(PyObject *op, PyObject *value, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute"); return -1; @@ -125,7 +125,7 @@ static PyGetSetDef Custom_getsetters[] = { static PyObject * Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy)) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; return PyUnicode_FromFormat("%S %S", self->first, self->last); } diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index 79a4fc9e601559..a0a1eeb289190b 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -12,7 +12,7 @@ typedef struct { static int Custom_traverse(PyObject *op, visitproc visit, void *arg) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; Py_VISIT(self->first); Py_VISIT(self->last); return 0; @@ -21,7 +21,7 @@ Custom_traverse(PyObject *op, visitproc visit, void *arg) static int Custom_clear(PyObject *op) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; Py_CLEAR(self->first); Py_CLEAR(self->last); return 0; @@ -30,10 +30,9 @@ Custom_clear(PyObject *op) static void Custom_dealloc(PyObject *op) { - CustomObject *self = (CustomObject *)op; - PyObject_GC_UnTrack(self); - Custom_clear(self); - Py_TYPE(self)->tp_free(self); + PyObject_GC_UnTrack(op); + (void)Custom_clear(op); + Py_TYPE(op)->tp_free(op); } static PyObject * @@ -60,7 +59,7 @@ Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static int Custom_init(PyObject *op, PyObject *args, PyObject *kwds) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; static char *kwlist[] = {"first", "last", "number", NULL}; PyObject *first = NULL, *last = NULL; @@ -87,14 +86,14 @@ static PyMemberDef Custom_members[] = { static PyObject * Custom_getfirst(PyObject *op, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; return Py_NewRef(self->first); } static int Custom_setfirst(PyObject *op, PyObject *value, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute"); return -1; @@ -111,14 +110,14 @@ Custom_setfirst(PyObject *op, PyObject *value, void *closure) static PyObject * Custom_getlast(PyObject *op, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; return Py_NewRef(self->last); } static int Custom_setlast(PyObject *op, PyObject *value, void *closure) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute"); return -1; @@ -143,7 +142,7 @@ static PyGetSetDef Custom_getsetters[] = { static PyObject * Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy)) { - CustomObject *self = (CustomObject *)op; + CustomObject *self = (CustomObject *) op; return PyUnicode_FromFormat("%S %S", self->first, self->last); } diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index 00696ff081d217..00664f3454156f 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -9,7 +9,7 @@ typedef struct { static PyObject * SubList_increment(PyObject *op, PyObject *Py_UNUSED(dummy)) { - SubListObject *self = (SubListObject *)op; + SubListObject *self = (SubListObject *) op; self->state++; return PyLong_FromLong(self->state); } @@ -23,8 +23,8 @@ static PyMethodDef SubList_methods[] = { static int SubList_init(PyObject *op, PyObject *args, PyObject *kwds) { - SubListObject *self = (SubListObject *)op; - 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; From 816c0138d8ccfe0f53bfe131fd50140208d64baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:49:15 +0100 Subject: [PATCH 3/5] fixup --- Doc/extending/newtypes_tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index 404d1ab8ac609c..06e3c90ac71b25 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -255,7 +255,7 @@ allocation and deallocation. At a minimum, we need a deallocation method:: CustomObject *self = (CustomObject *) op; Py_XDECREF(self->first); Py_XDECREF(self->last); - Py_TYPE(self)->tp_free(op); + Py_TYPE(self)->tp_free(self); } which is assigned to the :c:member:`~PyTypeObject.tp_dealloc` member:: From d3b5c48d31a79afec2574d350bd0933e7f75c6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:10:03 +0100 Subject: [PATCH 4/5] Update Doc/extending/newtypes_tutorial.rst Co-authored-by: Petr Viktorin --- Doc/extending/newtypes_tutorial.rst | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index 06e3c90ac71b25..20fff177d27231 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -273,8 +273,28 @@ be an instance of a subclass. .. note:: 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. Otherwise, - this would result in an undefined behaviour at runtime! + 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:: From 4ef74c060e2fc329892f5bf72d085fe88b0d5379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:37:23 +0100 Subject: [PATCH 5/5] Update Doc/extending/newtypes_tutorial.rst --- Doc/extending/newtypes_tutorial.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index 20fff177d27231..b4a09d20108ab9 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -271,6 +271,7 @@ the object's type might not be :class:`!CustomType`, because the object may be an instance of a subclass. .. note:: + 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.