diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst index dbf35611ecc..c15cecd41b8 100644 --- a/Doc/c-api/list.rst +++ b/Doc/c-api/list.rst @@ -86,6 +86,10 @@ List Objects Macro form of :c:func:`PyList_SetItem` without error checking. This is normally only used to fill in new lists where there is no previous content. + Bounds checking is performed as an assertion if Python is built in + :ref:`debug mode ` or :option:`with assertions + <--with-assertions>`. + .. note:: This macro "steals" a reference to *item*, and, unlike diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index ac62058676e..3fe1062aa85 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -89,6 +89,9 @@ Tuple Objects Like :c:func:`PyTuple_SetItem`, but does no error checking, and should *only* be used to fill in brand new tuples. + Bounds checking is performed as an assertion if Python is built in + :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. + .. note:: This function "steals" a reference to *o*, and, unlike @@ -194,12 +197,17 @@ type. .. c:function:: PyObject* PyStructSequence_GetItem(PyObject *p, Py_ssize_t pos) Return the object at position *pos* in the struct sequence pointed to by *p*. - No bounds checking is performed. + + Bounds checking is performed as an assertion if Python is built in + :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. .. c:function:: PyObject* PyStructSequence_GET_ITEM(PyObject *p, Py_ssize_t pos) - Macro equivalent of :c:func:`PyStructSequence_GetItem`. + Alias to :c:func:`PyStructSequence_GetItem`. + + .. versionchanged:: 3.13 + Now implemented as an alias to :c:func:`PyStructSequence_GetItem`. .. c:function:: void PyStructSequence_SetItem(PyObject *p, Py_ssize_t pos, PyObject *o) @@ -208,6 +216,9 @@ type. :c:func:`PyTuple_SET_ITEM`, this should only be used to fill in brand new instances. + Bounds checking is performed as an assertion if Python is built in + :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. + .. note:: This function "steals" a reference to *o*. @@ -215,9 +226,7 @@ type. .. c:function:: void PyStructSequence_SET_ITEM(PyObject *p, Py_ssize_t *pos, PyObject *o) - Similar to :c:func:`PyStructSequence_SetItem`, but implemented as a static - inlined function. + Alias to :c:func:`PyStructSequence_SetItem`. - .. note:: - - This function "steals" a reference to *o*. + .. versionchanged:: 3.13 + Now implemented as an alias to :c:func:`PyStructSequence_SetItem`. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index f3460beeb16..c0e9e924c8e 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -441,6 +441,12 @@ New Features ``NULL`` if the referent is no longer live. (Contributed by Victor Stinner in :gh:`105927`.) +* If Python is built in :ref:`debug mode ` or :option:`with + assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and + :c:func:`PyList_SET_ITEM` now check the index argument with an assertion. + If the assertion fails, make sure that the size is set before. + (Contributed by Victor Stinner in :gh:`106168`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/cpython/listobject.h b/Include/cpython/listobject.h index 8fa82122d8d..b3b23985de7 100644 --- a/Include/cpython/listobject.h +++ b/Include/cpython/listobject.h @@ -41,6 +41,8 @@ static inline Py_ssize_t PyList_GET_SIZE(PyObject *op) { static inline void PyList_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { PyListObject *list = _PyList_CAST(op); + assert(0 <= index); + assert(index < Py_SIZE(list)); list->ob_item[index] = value; } #define PyList_SET_ITEM(op, index, value) \ diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h index f6a1f076e03..370da1612a6 100644 --- a/Include/cpython/tupleobject.h +++ b/Include/cpython/tupleobject.h @@ -31,6 +31,8 @@ static inline Py_ssize_t PyTuple_GET_SIZE(PyObject *op) { static inline void PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { PyTupleObject *tuple = _PyTuple_CAST(op); + assert(0 <= index); + assert(index < Py_SIZE(tuple)); tuple->ob_item[index] = value; } #define PyTuple_SET_ITEM(op, index, value) \ diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 2fcbe12cd65..b2e503c8754 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -49,8 +49,8 @@ _PyList_AppendTakeRef(PyListObject *self, PyObject *newitem) Py_ssize_t allocated = self->allocated; assert((size_t)len + 1 < PY_SSIZE_T_MAX); if (allocated > len) { - PyList_SET_ITEM(self, len, newitem); Py_SET_SIZE(self, len + 1); + PyList_SET_ITEM(self, len, newitem); return 0; } return _PyList_AppendTakeRefListResize(self, newitem); diff --git a/Include/structseq.h b/Include/structseq.h index 96871155611..29e24fee54e 100644 --- a/Include/structseq.h +++ b/Include/structseq.h @@ -31,18 +31,15 @@ PyAPI_FUNC(PyTypeObject*) PyStructSequence_NewType(PyStructSequence_Desc *desc); PyAPI_FUNC(PyObject *) PyStructSequence_New(PyTypeObject* type); -#ifndef Py_LIMITED_API -typedef PyTupleObject PyStructSequence; - -/* Macro, *only* to be used to fill in brand new objects */ -#define PyStructSequence_SET_ITEM(op, i, v) PyTuple_SET_ITEM((op), (i), (v)) - -#define PyStructSequence_GET_ITEM(op, i) PyTuple_GET_ITEM((op), (i)) -#endif - PyAPI_FUNC(void) PyStructSequence_SetItem(PyObject*, Py_ssize_t, PyObject*); PyAPI_FUNC(PyObject*) PyStructSequence_GetItem(PyObject*, Py_ssize_t); +#ifndef Py_LIMITED_API +typedef PyTupleObject PyStructSequence; +#define PyStructSequence_SET_ITEM PyStructSequence_SetItem +#define PyStructSequence_GET_ITEM PyStructSequence_GetItem +#endif + #ifdef __cplusplus } #endif diff --git a/Misc/NEWS.d/next/C API/2023-06-28-02-30-50.gh-issue-106168.NFOZPv.rst b/Misc/NEWS.d/next/C API/2023-06-28-02-30-50.gh-issue-106168.NFOZPv.rst new file mode 100644 index 00000000000..741d709bf82 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-06-28-02-30-50.gh-issue-106168.NFOZPv.rst @@ -0,0 +1,5 @@ +If Python is built in :ref:`debug mode ` or :option:`with +assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and +:c:func:`PyList_SET_ITEM` now check the index argument with an assertion. If +the assertion fails, make sure that the size is set before. Patch by Victor +Stinner. diff --git a/Objects/listobject.c b/Objects/listobject.c index f1edfb3a9a0..f1f324f7439 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -953,8 +953,9 @@ list_extend(PyListObject *self, PyObject *iterable) } if (Py_SIZE(self) < self->allocated) { /* steals ref */ - PyList_SET_ITEM(self, Py_SIZE(self), item); - Py_SET_SIZE(self, Py_SIZE(self) + 1); + Py_ssize_t len = Py_SIZE(self); + Py_SET_SIZE(self, len + 1); + PyList_SET_ITEM(self, len, item); } else { if (_PyList_AppendTakeRef(self, item) < 0) diff --git a/Objects/structseq.c b/Objects/structseq.c index 8b189595710..49011139b66 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -74,15 +74,28 @@ PyStructSequence_New(PyTypeObject *type) } void -PyStructSequence_SetItem(PyObject* op, Py_ssize_t i, PyObject* v) +PyStructSequence_SetItem(PyObject *op, Py_ssize_t index, PyObject *value) { - PyStructSequence_SET_ITEM(op, i, v); + PyTupleObject *tuple = _PyTuple_CAST(op); + assert(0 <= index); +#ifndef NDEBUG + Py_ssize_t n_fields = REAL_SIZE(op); + assert(n_fields >= 0); + assert(index < n_fields); +#endif + tuple->ob_item[index] = value; } PyObject* -PyStructSequence_GetItem(PyObject* op, Py_ssize_t i) +PyStructSequence_GetItem(PyObject *op, Py_ssize_t index) { - return PyStructSequence_GET_ITEM(op, i); + assert(0 <= index); +#ifndef NDEBUG + Py_ssize_t n_fields = REAL_SIZE(op); + assert(n_fields >= 0); + assert(index < n_fields); +#endif + return PyTuple_GET_ITEM(op, index); } @@ -287,7 +300,7 @@ structseq_repr(PyStructSequence *obj) goto error; } - PyObject *value = PyStructSequence_GET_ITEM(obj, i); + PyObject *value = PyStructSequence_GetItem((PyObject*)obj, i); assert(value != NULL); PyObject *repr = PyObject_Repr(value); if (repr == NULL) {