From 0f28d27b11e9e01126bb820bf90dbc81cbc3e4ee Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 2 Nov 2026 17:53:28 +0100 Subject: [PATCH] WIP: [C API] Add PyTupleWriter API --- Include/cpython/tupleobject.h | 33 ++- Include/internal/pycore_tuple.h | 71 ++++++ Modules/_tkinter.c | 21 +- Modules/itertoolsmodule.c | 20 +- Objects/genericaliasobject.c | 63 ++--- Objects/structseq.c | 25 +- Objects/tupleobject.c | 416 +++++++++++++++++++++++++++----- 7 files changed, 521 insertions(+), 128 deletions(-) diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h index 888baaf3358267..b9e20ebd792ba4 100644 --- a/Include/cpython/tupleobject.h +++ b/Include/cpython/tupleobject.h @@ -30,7 +30,12 @@ static inline Py_ssize_t PyTuple_GET_SIZE(PyObject *op) { /* Function *only* to be used to fill in brand new tuples */ static inline void -PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { +PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) +{ + // For backward compatibility, accept mutating a tuple even if + // _PyObject_IsUniquelyReferenced(op) is false and/or tuple->ob_hash != -1. + // See for example _PyTuple_Recycle(). Accept also setting an item which + // was already set: the caller must Py_DECREF() the old value in this case. PyTupleObject *tuple = _PyTuple_CAST(op); assert(0 <= index); assert(index < Py_SIZE(tuple)); @@ -42,3 +47,29 @@ PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { PyAPI_FUNC(PyObject*) PyTuple_FromArray( PyObject *const *array, Py_ssize_t size); + + +/* --- PyTupleWriter API -------------------------------------------------- */ + +struct PyTupleWriter; +typedef struct PyTupleWriter PyTupleWriter; + +PyAPI_FUNC(PyTupleWriter*) PyTupleWriter_Create(Py_ssize_t size); +PyAPI_FUNC(PyObject*) PyTupleWriter_Finish(PyTupleWriter *writer); +PyAPI_FUNC(PyObject*) PyTupleWriter_FinishWithSize( + PyTupleWriter *writer, + Py_ssize_t final_size); +PyAPI_FUNC(void) PyTupleWriter_Discard(PyTupleWriter *writer); +PyAPI_FUNC(Py_ssize_t) PyTupleWriter_GetSize(PyTupleWriter *writer); +PyAPI_FUNC(int) PyTupleWriter_SetItem( + PyTupleWriter *writer, + Py_ssize_t index, + PyObject *item); +PyAPI_FUNC(int) PyTupleWriter_SetItems( + PyTupleWriter *writer, + Py_ssize_t index, + PyObject *const *items, + Py_ssize_t nitem); +PyAPI_FUNC(int) PyTupleWriter_Resize( + PyTupleWriter **writer, + Py_ssize_t new_size); diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index e294b16b3df60f..771b0def43e4c5 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -76,6 +76,77 @@ _PyTuple_Recycle(PyObject *op) #endif #define _PyTuple_HASH_EMPTY (_PyTuple_HASH_XXPRIME_5 + (_PyTuple_HASH_XXPRIME_5 ^ 3527539UL)) + +#ifndef NDEBUG +// Check that a tuple can be mutated in-place. +// Usage: assert(_PyTuple_CanMutate(v)); +static int +_PyTuple_CanMutate(PyTupleObject *tuple) +{ + assert(PyTuple_Check(tuple)); + + // Do not check _PyObject_IsUniquelyReferenced(): the function is called + // by _PyTupleWriter_CheckConsistency() and a tuple writer can be used by + // multiple threads using external locking. + assert(Py_REFCNT(tuple) == 1); + + // The empty tuple must not be modified + assert(1 <= Py_SIZE(tuple)); + assert(tuple != &_Py_SINGLETON(tuple_empty)); + + // The hash value must not be cached yet. Otherwise, it means that the + // tuple object was already used in Python somehow (ex: as a dictionary + // key). + assert(tuple->ob_hash == -1); + + return 1; +} +#endif + + +/* --- PyTupleWriter API -------------------------------------------------- */ + +#ifndef NDEBUG +static inline int +_PyTupleWriter_CheckConsistency(PyTupleWriter *writer) +{ + assert(writer != NULL); + assert(PyTuple_CheckExact(_PyObject_CAST(writer))); + PyTupleObject *tuple = (PyTupleObject*)writer; + // Do not use _PyObject_IsUniquelyReferenced(): the caller can have + // its own lock to prevent a writer from being used by two threads + // at the same time. + assert(Py_REFCNT(tuple) == 1); + assert(0 <= Py_SIZE(tuple)); + return 1; +} +#endif + + +static inline void +_PyTupleWriter_SET_ITEM_STEAL(PyTupleWriter *writer, + Py_ssize_t index, PyObject *item) +{ + assert(_PyTupleWriter_CheckConsistency(writer)); + PyTupleObject *tuple = _PyTuple_CAST(writer); + + assert(_PyTuple_CanMutate(tuple)); + assert(0 <= index && index < Py_SIZE(tuple)); + assert(tuple->ob_item[index] == NULL); + assert(item != NULL); + + tuple->ob_item[index] = item; +} + + +static inline void +_PyTupleWriter_SET_ITEM(PyTupleWriter *writer, + Py_ssize_t index, PyObject *item) +{ + assert(item != NULL); + return _PyTupleWriter_SET_ITEM_STEAL(writer, index, Py_NewRef(item)); +} + #ifdef __cplusplus } #endif diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index d0a03953a80d92..8ee9c3c511d9e2 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -31,6 +31,7 @@ Copyright (C) 1994 Steen Lumholt. #endif #include "pycore_long.h" // _PyLong_IsNegative() +#include "pycore_tuple.h" // _PyTupleWriter_SET_ITEM() #include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8String #include // SIGINT @@ -3401,7 +3402,7 @@ Tkapp_Dealloc(PyObject *op) /**** Tkinter Module ****/ typedef struct { - PyObject* tuple; + PyTupleWriter *writer; Py_ssize_t size; /* current size */ Py_ssize_t maxsize; /* allocated size */ } FlattenContext; @@ -3419,7 +3420,7 @@ _bump(FlattenContext* context, Py_ssize_t size) context->maxsize = maxsize; - return _PyTuple_Resize(&context->tuple, maxsize) >= 0; + return PyTupleWriter_Resize(&context->writer, maxsize) >= 0; } static int @@ -3449,8 +3450,7 @@ _flatten1(FlattenContext* context, PyObject* item, int depth) if (context->size + 1 > context->maxsize && !_bump(context, 1)) return 0; - PyTuple_SET_ITEM(context->tuple, - context->size++, Py_NewRef(o)); + _PyTupleWriter_SET_ITEM(context->writer, context->size++, o); } } } else { @@ -3480,21 +3480,18 @@ _tkinter__flatten(PyObject *module, PyObject *item) if (context.maxsize == 0) return PyTuple_New(0); - context.tuple = PyTuple_New(context.maxsize); - if (!context.tuple) + context.writer = PyTupleWriter_Create(context.maxsize); + if (!context.writer) { return NULL; - + } context.size = 0; if (!_flatten1(&context, item, 0)) { - Py_XDECREF(context.tuple); + PyTupleWriter_Discard(context.writer); return NULL; } - if (_PyTuple_Resize(&context.tuple, context.size)) - return NULL; - - return context.tuple; + return PyTupleWriter_FinishWithSize(context.writer, context.size); } /*[clinic input] diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 636b28fa06ef2e..c20bd6343da8e2 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -207,25 +207,24 @@ batched_next(PyObject *op) Py_ssize_t n = FT_ATOMIC_LOAD_SSIZE_RELAXED(bo->batch_size); PyObject *it = bo->it; PyObject *item; - PyObject *result; if (n < 0) { return NULL; } - result = PyTuple_New(n); - if (result == NULL) { + + PyTupleWriter *writer = PyTupleWriter_Create(n); + if (writer == NULL) { return NULL; } iternextfunc iternext = *Py_TYPE(it)->tp_iternext; - PyObject **items = _PyTuple_ITEMS(result); for (i=0 ; i < n ; i++) { item = iternext(it); if (item == NULL) { goto null_item; } - items[i] = item; + _PyTupleWriter_SET_ITEM_STEAL(writer, i, item); } - return result; + return PyTupleWriter_Finish(writer); null_item: if (PyErr_Occurred()) { @@ -235,7 +234,7 @@ batched_next(PyObject *op) #ifndef Py_GIL_DISABLED Py_CLEAR(bo->it); #endif - Py_DECREF(result); + PyTupleWriter_Discard(writer); return NULL; } PyErr_Clear(); @@ -245,7 +244,7 @@ batched_next(PyObject *op) #ifndef Py_GIL_DISABLED Py_CLEAR(bo->it); #endif - Py_DECREF(result); + PyTupleWriter_Discard(writer); return NULL; } if (bo->strict) { @@ -253,12 +252,11 @@ batched_next(PyObject *op) #ifndef Py_GIL_DISABLED Py_CLEAR(bo->it); #endif - Py_DECREF(result); + PyTupleWriter_Discard(writer); PyErr_SetString(PyExc_ValueError, "batched(): incomplete batch"); return NULL; } - _PyTuple_Resize(&result, i); - return result; + return PyTupleWriter_FinishWithSize(writer, i); } static PyType_Slot batched_slots[] = { diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 2633b9af81c5b9..dc0ab017c17163 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -5,6 +5,7 @@ #include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_object.h" +#include "pycore_tuple.h" // _PyTupleWriter_SET_ITEM() #include "pycore_typevarobject.h" // _Py_typing_type_repr #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString() #include "pycore_unionobject.h" // _Py_union_type_or, _PyGenericAlias_Check @@ -168,17 +169,17 @@ tuple_add(PyObject *self, Py_ssize_t len, PyObject *item) } static Py_ssize_t -tuple_extend(PyObject **dst, Py_ssize_t dstindex, +tuple_extend(PyTupleWriter **dst, Py_ssize_t dstindex, PyObject **src, Py_ssize_t count) { assert(count >= 0); - if (_PyTuple_Resize(dst, PyTuple_GET_SIZE(*dst) + count - 1) != 0) { + if (PyTupleWriter_Resize(dst, PyTupleWriter_GetSize(*dst) + count - 1) < 0) { return -1; } - assert(dstindex + count <= PyTuple_GET_SIZE(*dst)); - for (Py_ssize_t i = 0; i < count; ++i) { - PyObject *item = src[i]; - PyTuple_SET_ITEM(*dst, dstindex + i, Py_NewRef(item)); + PyTupleWriter *writer = *dst; + assert(dstindex + count <= PyTupleWriter_GetSize(writer)); + if (PyTupleWriter_SetItems(writer, dstindex, src, count) < 0) { + return -1; } return dstindex + count; } @@ -285,7 +286,7 @@ subs_tvars(PyObject *obj, PyObject *params, if (subparams && PyTuple_Check(subparams) && PyTuple_GET_SIZE(subparams)) { Py_ssize_t nparams = PyTuple_GET_SIZE(params); Py_ssize_t nsubargs = PyTuple_GET_SIZE(subparams); - PyObject *subargs = PyTuple_New(nsubargs); + PyTupleWriter *subargs = PyTupleWriter_Create(nsubargs); if (subargs == NULL) { Py_DECREF(subparams); return NULL; @@ -299,24 +300,29 @@ subs_tvars(PyObject *obj, PyObject *params, arg = argitems[iparam]; if (Py_TYPE(param)->tp_iter && PyTuple_Check(arg)) { // TypeVarTuple j = tuple_extend(&subargs, j, - &PyTuple_GET_ITEM(arg, 0), - PyTuple_GET_SIZE(arg)); + &PyTuple_GET_ITEM(arg, 0), + PyTuple_GET_SIZE(arg)); if (j < 0) { Py_DECREF(subparams); - assert(subargs == NULL); + PyTupleWriter_Discard(subargs); return NULL; } continue; } } - PyTuple_SET_ITEM(subargs, j, Py_NewRef(arg)); + _PyTupleWriter_SET_ITEM(subargs, j, arg); j++; } - assert(j == PyTuple_GET_SIZE(subargs)); + assert(j == PyTupleWriter_GetSize(subargs)); - obj = PyObject_GetItem(obj, subargs); + PyObject *subargs_tuple = PyTupleWriter_Finish(subargs); + if (subargs_tuple == NULL) { + Py_DECREF(subparams); + return NULL; + } - Py_DECREF(subargs); + obj = PyObject_GetItem(obj, subargs_tuple); + Py_DECREF(subargs_tuple); } else { Py_INCREF(obj); @@ -468,7 +474,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje } } Py_ssize_t nargs = PyTuple_GET_SIZE(args); - PyObject *newargs = PyTuple_New(nargs); + PyTupleWriter *newargs = PyTupleWriter_Create(nargs); if (newargs == NULL) { Py_DECREF(item); Py_XDECREF(tuple_args); @@ -477,7 +483,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje for (Py_ssize_t iarg = 0, jarg = 0; iarg < nargs; iarg++) { PyObject *arg = PyTuple_GET_ITEM(args, iarg); if (PyType_Check(arg)) { - PyTuple_SET_ITEM(newargs, jarg, Py_NewRef(arg)); + _PyTupleWriter_SET_ITEM(newargs, jarg, arg); jarg++; continue; } @@ -485,13 +491,13 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje if (PyTuple_Check(arg) || PyList_Check(arg)) { PyObject *subargs = _Py_subs_parameters(self, arg, parameters, item); if (subargs == NULL) { - Py_DECREF(newargs); + PyTupleWriter_Discard(newargs); Py_DECREF(item); Py_XDECREF(tuple_args); return NULL; } if (PyTuple_Check(arg)) { - PyTuple_SET_ITEM(newargs, jarg, subargs); + _PyTupleWriter_SET_ITEM_STEAL(newargs, jarg, subargs); } else { // _Py_subs_parameters returns a tuple. If the original arg was a list, @@ -499,26 +505,26 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje PyObject *subargs_list = PySequence_List(subargs); Py_DECREF(subargs); if (subargs_list == NULL) { - Py_DECREF(newargs); + PyTupleWriter_Discard(newargs); Py_DECREF(item); Py_XDECREF(tuple_args); return NULL; } - PyTuple_SET_ITEM(newargs, jarg, subargs_list); + _PyTupleWriter_SET_ITEM_STEAL(newargs, jarg, subargs_list); } jarg++; continue; } int unpack = _is_unpacked_typevartuple(arg); if (unpack < 0) { - Py_DECREF(newargs); + PyTupleWriter_Discard(newargs); Py_DECREF(item); Py_XDECREF(tuple_args); return NULL; } PyObject *subst; if (PyObject_GetOptionalAttr(arg, &_Py_ID(__typing_subst__), &subst) < 0) { - Py_DECREF(newargs); + PyTupleWriter_Discard(newargs); Py_DECREF(item); Py_XDECREF(tuple_args); return NULL; @@ -543,7 +549,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje arg = subs_tvars(arg, parameters, argitems, nitems); } if (arg == NULL) { - Py_DECREF(newargs); + PyTupleWriter_Discard(newargs); Py_DECREF(item); Py_XDECREF(tuple_args); return NULL; @@ -554,31 +560,32 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje PyErr_Format(PyExc_TypeError, "expected __typing_subst__ of %T objects to return a tuple, not %T", original, arg); - Py_DECREF(newargs); + PyTupleWriter_Discard(newargs); Py_DECREF(item); Py_XDECREF(tuple_args); Py_DECREF(arg); return NULL; } jarg = tuple_extend(&newargs, jarg, - &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg)); + &PyTuple_GET_ITEM(arg, 0), + PyTuple_GET_SIZE(arg)); Py_DECREF(arg); if (jarg < 0) { Py_DECREF(item); Py_XDECREF(tuple_args); - assert(newargs == NULL); + PyTupleWriter_Discard(newargs); return NULL; } } else { - PyTuple_SET_ITEM(newargs, jarg, arg); + _PyTupleWriter_SET_ITEM_STEAL(newargs, jarg, arg); jarg++; } } Py_DECREF(item); Py_XDECREF(tuple_args); - return newargs; + return PyTupleWriter_Finish(newargs); } PyDoc_STRVAR(genericalias__doc__, diff --git a/Objects/structseq.c b/Objects/structseq.c index 949790e6f416e9..8010e7fe38de1f 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -474,12 +474,11 @@ count_members(PyStructSequence_Desc *desc, Py_ssize_t *n_unnamed_members) { static int initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, - Py_ssize_t n_members, Py_ssize_t n_unnamed_members) { - PyObject *v; - + Py_ssize_t n_members, Py_ssize_t n_unnamed_members) +{ #define SET_DICT_FROM_SIZE(key, value) \ do { \ - v = PyLong_FromSsize_t(value); \ + PyObject *v = PyLong_FromSsize_t(value); \ if (v == NULL) { \ return -1; \ } \ @@ -496,7 +495,7 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, // Prepare and set __match_args__ Py_ssize_t i, k; - PyObject* keys = PyTuple_New(desc->n_in_sequence); + PyTupleWriter *keys = PyTupleWriter_Create(desc->n_in_sequence); if (keys == NULL) { return -1; } @@ -509,24 +508,26 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, if (new_member == NULL) { goto error; } - PyTuple_SET_ITEM(keys, k, new_member); + _PyTupleWriter_SET_ITEM_STEAL(keys, k, new_member); k++; } - if (_PyTuple_Resize(&keys, k) == -1) { - assert(keys == NULL); - return -1; + PyObject *keys_obj = PyTupleWriter_FinishWithSize(keys, k); + keys = NULL; + if (keys_obj == NULL) { + goto error; } - if (PyDict_SetItemString(dict, match_args_key, keys) < 0) { + if (PyDict_SetItemString(dict, match_args_key, keys_obj) < 0) { + Py_DECREF(keys_obj); goto error; } + Py_DECREF(keys_obj); - Py_DECREF(keys); return 0; error: - Py_DECREF(keys); + PyTupleWriter_Discard(keys); return -1; } diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 992699756ac770..520614622786b1 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -130,6 +130,9 @@ PyTuple_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) "tuple assignment index out of range"); return -1; } + // For backward compatibility, accept mutating a tuple even + // if tuple->ob_hash != -1. See for example _PyTuple_Recycle(). + p = ((PyTupleObject *)op) -> ob_item + i; Py_XSETREF(*p, newitem); return 0; @@ -858,14 +861,14 @@ tuple_subtype_new(PyTypeObject *type, PyObject *iterable) Py_DECREF(tmp); return NULL; } + _PyTuple_RESET_HASH_CACHE(newobj); + for (i = 0; i < n; i++) { item = PyTuple_GET_ITEM(tmp, i); PyTuple_SET_ITEM(newobj, i, Py_NewRef(item)); } Py_DECREF(tmp); - _PyTuple_RESET_HASH_CACHE(newobj); - // Don't track if a subclass tp_alloc is PyType_GenericAlloc() if (!_PyObject_GC_IS_TRACKED(newobj)) { _PyObject_GC_TRACK(newobj); @@ -1033,6 +1036,123 @@ PyTypeObject PyTuple_Type = { ._tp_iteritem = tuple_iteritem, }; + +static int +tuple_resize_inplace(PyTupleObject **pv, Py_ssize_t newsize, int gc_track) +{ + PyTupleObject *v = *pv; + Py_ssize_t oldsize = Py_SIZE(v); + + assert(1 <= oldsize); + assert(oldsize < newsize); + assert(_PyTuple_CanMutate(v)); + + /* Check for overflow */ + if ((size_t)newsize > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) - + sizeof(PyObject *))) / sizeof(PyObject *)) + { + PyErr_NoMemory(); + return -1; + } + + if (gc_track) { + if (_PyObject_GC_IS_TRACKED(v)) { + _PyObject_GC_UNTRACK(v); + } + } + else { + assert(!_PyObject_GC_IS_TRACKED(v)); + } +#ifdef Py_TRACE_REFS + _Py_ForgetReference((PyObject *) v); +#endif + _PyReftracerTrack((PyObject *)v, PyRefTracer_DESTROY); + + PyTupleObject *sv = PyObject_GC_Resize(PyTupleObject, v, newsize); + if (sv == NULL) { + assert(PyErr_Occurred()); +#ifdef Py_TRACE_REFS + _Py_AddToAllObjects(v); +#endif + _PyReftracerTrack((PyObject *)v, PyRefTracer_CREATE); + return -1; + } + + _Py_NewReferenceNoTotal((PyObject *) sv); + /* Zero out items added by growing */ + memset(&sv->ob_item[oldsize], 0, + sizeof(sv->ob_item[0]) * (newsize - oldsize)); + *pv = sv; + if (gc_track) { + _PyObject_GC_TRACK(sv); + } + return 0; +} + + +static int +_PyTuple_ResizeKeepOnError(PyTupleObject **pv, Py_ssize_t newsize, int gc_track) +{ + PyTupleObject *v = *pv; + assert(v != NULL); + assert(PyTuple_CheckExact(v)); + assert(0 <= newsize); + assert(Py_SIZE(v) == 0 || Py_REFCNT(v) == 1); + assert(Py_SIZE(v) == 0 || v->ob_hash == -1); + + Py_ssize_t oldsize = Py_SIZE(v); + if (oldsize == newsize) { + return 0; + } + + if (newsize == 0) { + Py_DECREF(v); + *pv = (PyTupleObject*)tuple_get_empty(); // cannot fail + return 0; + } + + // If the tuple is shrinked, copy the tuple to leave items unchanged + // on error + if (oldsize == 0 + || newsize < oldsize + || v->ob_hash != -1) + { + /* The empty tuple is statically allocated so we never + resize it in-place. */ + if (oldsize == 0) { + assert(_PyObject_CAST(v) == tuple_get_empty()); + } + + PyTupleObject *new_tuple = (PyTupleObject*)tuple_alloc(newsize); + if (new_tuple == NULL) { + return -1; + } + Py_ssize_t steal = Py_MIN(oldsize, newsize); + if (steal) { + // Steal items + memcpy(new_tuple->ob_item, v->ob_item, + steal * sizeof(v->ob_item[0])); + memset(v->ob_item, 0, + steal * sizeof(v->ob_item[0])); + } + if (newsize > oldsize) { + // Set new items to NULL + memset(&new_tuple->ob_item[oldsize], 0, + (newsize - oldsize) * sizeof(v->ob_item[0])); + } + Py_DECREF(v); + if (gc_track) { + _PyObject_GC_TRACK(new_tuple); + } + + *pv = new_tuple; + return 0; + } + + return tuple_resize_inplace(pv, newsize, gc_track); +} + + /* The following function breaks the notion that tuples are immutable: it changes the size of a tuple. We get away with this only if there is only one module referencing the object. You can also think of it @@ -1043,12 +1163,7 @@ PyTypeObject PyTuple_Type = { int _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) { - PyTupleObject *v; - PyTupleObject *sv; - Py_ssize_t i; - Py_ssize_t oldsize; - - v = (PyTupleObject *) *pv; + PyTupleObject *v = (PyTupleObject *) *pv; if (v == NULL || !Py_IS_TYPE(v, &PyTuple_Type) || newsize < 0 || (Py_SIZE(v) != 0 && !_PyObject_IsUniquelyReferenced(*pv)) // gh-161226: Disallow resizing a tuple if the hash value is cached @@ -1060,66 +1175,12 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) return -1; } - oldsize = Py_SIZE(v); - if (oldsize == newsize) { - return 0; - } - if (newsize == 0) { - Py_DECREF(v); - *pv = tuple_get_empty(); - return 0; - } - if (oldsize == 0) { -#ifdef Py_DEBUG - assert(v == &_Py_SINGLETON(tuple_empty)); -#endif - /* The empty tuple is statically allocated so we never - resize it in-place. */ - Py_DECREF(v); - *pv = PyTuple_New(newsize); - return *pv == NULL ? -1 : 0; - } - /* Check for overflow */ - if ((size_t)newsize > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) - - sizeof(PyObject *))) / sizeof(PyObject *)) { + if (_PyTuple_ResizeKeepOnError((PyTupleObject **)pv, newsize, 1) < 0) { + PyObject *v = *pv; *pv = NULL; Py_DECREF(v); - PyErr_NoMemory(); return -1; } - - if (_PyObject_GC_IS_TRACKED(v)) { - _PyObject_GC_UNTRACK(v); - } -#ifdef Py_TRACE_REFS - _Py_ForgetReference((PyObject *) v); -#endif - /* DECREF items deleted by shrinkage */ - for (i = newsize; i < oldsize; i++) { - Py_CLEAR(v->ob_item[i]); - } - _PyReftracerTrack((PyObject *)v, PyRefTracer_DESTROY); - sv = PyObject_GC_Resize(PyTupleObject, v, newsize); - if (sv == NULL) { - *pv = NULL; -#ifdef Py_REF_DEBUG - _Py_DecRefTotal(_PyThreadState_GET()); -#endif - /* The failed reallocation leaves the original items owned by v. */ - i = oldsize; - while (--i >= 0) { - Py_XDECREF(v->ob_item[i]); - } - PyObject_GC_Del(v); - return -1; - } - _Py_NewReferenceNoTotal((PyObject *) sv); - /* Zero out items added by growing */ - if (newsize > oldsize) - memset(&sv->ob_item[oldsize], 0, - sizeof(*sv->ob_item) * (newsize - oldsize)); - *pv = (PyObject *) sv; - _PyObject_GC_TRACK(sv); return 0; } @@ -1324,3 +1385,230 @@ _PyTuple_DebugMallocStats(FILE *out) _PyType_PreHeaderSize(&PyTuple_Type) + _PyObject_VAR_SIZE(&PyTuple_Type, len)); } } + + +// --- PyTupleWriter API ----------------------------------------------------- + +#ifdef Py_DEBUG +# define PyTupleWriter_INIT_ITEMS +#endif + +#define _PyTupleWriter_CAST(obj) _Py_CAST(PyTupleWriter*, (obj)) + + +PyTupleWriter* +PyTupleWriter_Create(Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be >= 0"); + return NULL; + } + + PyTupleObject *tuple; + if (size == 0) { + tuple = (PyTupleObject*)tuple_get_empty(); // cannot fail + } + else { + tuple = tuple_alloc(size); + if (tuple == NULL) { + return NULL; + } + +#ifdef PyTupleWriter_INIT_ITEMS + // Initialize items to NULL for tuplewriter_check_final() + memset(tuple->ob_item, 0, size * sizeof(tuple->ob_item[0])); +#endif + assert(!_PyObject_GC_IS_TRACKED(tuple)); + } + + return _PyTupleWriter_CAST(tuple); // can be NULL +} + + +int +PyTupleWriter_Resize(PyTupleWriter **writer, Py_ssize_t new_size) +{ + assert(writer != NULL); + assert(*writer != NULL); + PyObject *tuple = _PyObject_CAST(*writer); + + if (_PyTuple_Resize(&tuple, new_size) < 0) { + assert(tuple == NULL); + return -1; + } + *writer = _PyTupleWriter_CAST(tuple); + return 0; +} + + +#ifdef PyTupleWriter_INIT_ITEMS +// Make sure that all items are set +static void +tuplewriter_check_final(PyTupleObject *tuple) +{ + Py_ssize_t size = Py_SIZE(tuple); + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *item = tuple->ob_item[i]; + assert(item != NULL); + } +} +#endif + + +PyObject* +PyTupleWriter_FinishWithSize(PyTupleWriter *writer, Py_ssize_t final_size) +{ + assert(_PyTupleWriter_CheckConsistency(writer)); + PyTupleObject *tuple = _PyTuple_CAST(writer); + + if (final_size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be positive"); + return NULL; + } + if (final_size > Py_SIZE(tuple)) { + PyErr_SetString(PyExc_ValueError, "size larger than allocated size"); + return NULL; + } + + if (final_size == 0) { + Py_XDECREF(tuple); + tuple = (PyTupleObject*)tuple_get_empty(); // cannot fail + } + else { + Py_ssize_t size = PyTuple_GET_SIZE(tuple); + if (final_size != size) { + assert(final_size < size); + if (_PyTuple_ResizeKeepOnError(&tuple, final_size, 0) < 0) { + Py_DECREF(tuple); + return NULL; + } + } + + _PyObject_GC_TRACK(tuple); + } + +#ifdef PyTupleWriter_INIT_ITEMS + tuplewriter_check_final(tuple); +#endif + + return _PyObject_CAST(tuple); +} + + +PyObject* +PyTupleWriter_Finish(PyTupleWriter *writer) +{ + assert(_PyTupleWriter_CheckConsistency(writer)); + PyTupleObject *result = _PyTuple_CAST(writer); +#ifdef PyTupleWriter_INIT_ITEMS + tuplewriter_check_final(result); +#endif + + if (PyTuple_GET_SIZE(result) == 0) { + assert(_PyObject_CAST(result) == tuple_get_empty()); + } + else { + _PyObject_GC_TRACK(result); + } + return _PyObject_CAST(result); +} + + +void +PyTupleWriter_Discard(PyTupleWriter *writer) +{ + assert(_PyTupleWriter_CheckConsistency(writer)); + PyObject *tuple = _PyObject_CAST(writer); // can be NULL + Py_XDECREF(tuple); +} + + +Py_ssize_t +PyTupleWriter_GetSize(PyTupleWriter *writer) +{ + assert(_PyTupleWriter_CheckConsistency(writer)); + PyTupleObject *tuple = _PyTuple_CAST(writer); + return Py_SIZE(tuple); +} + + +int +PyTupleWriter_SetItem(PyTupleWriter *writer, Py_ssize_t index, PyObject *item) +{ + assert(_PyTupleWriter_CheckConsistency(writer)); + PyTupleObject *tuple = _PyTuple_CAST(writer); + assert(_PyTuple_CanMutate(tuple)); + + if (!(0 <= index && index < Py_SIZE(tuple))) { + PyErr_SetString(PyExc_ValueError, "invalid index"); + return -1; + } + +#ifdef Py_DEBUG + if (item == NULL) { + PyErr_SetString(PyExc_SystemError, "item is NULL"); + return -1; + } +#endif + +#ifdef PyTupleWriter_INIT_ITEMS + if (tuple->ob_item[index] != NULL) { + PyErr_Format(PyExc_SystemError, "tuple item %zd already set", index); + return -1; + } +#endif + + tuple->ob_item[index] = Py_NewRef(item); + return 0; +} + + +int +PyTupleWriter_SetItems(PyTupleWriter *writer, Py_ssize_t start, + PyObject *const *items, Py_ssize_t nitem) +{ + assert(_PyTupleWriter_CheckConsistency(writer)); + PyTupleObject *tuple = _PyTuple_CAST(writer); + assert(_PyTuple_CanMutate(tuple)); + + Py_ssize_t size = Py_SIZE(tuple); + if (!(0 <= start && start < size)) { + PyErr_SetString(PyExc_ValueError, "invalid start"); + return -1; + } + + if (nitem < 0) { + PyErr_SetString(PyExc_ValueError, "nitem must be positive"); + return -1; + } + // nitem can be 0 + +#ifdef Py_DEBUG + for (Py_ssize_t index = 0; index < nitem; index++) { + if (items[index] == NULL) { + PyErr_Format(PyExc_SystemError, "item #%zd is NULL", index); + return -1; + } + } +#endif + + if (start + nitem > size) { + PyErr_SetString(PyExc_ValueError, "too many items"); + return -1; + } + +#ifdef PyTupleWriter_INIT_ITEMS + for (Py_ssize_t index = 0; index < nitem; index++) { + if (tuple->ob_item[start + index] != NULL) { + PyErr_Format(PyExc_SystemError, + "tuple item %zd already set", start + index); + return -1; + } + } +#endif + + for (Py_ssize_t index = 0; index < nitem; index++) { + tuple->ob_item[start + index] = Py_NewRef(items[index]); + } + return 0; +}