From d4d6f2402d3b0f91236593903b73b9758b787763 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 10 Apr 2024 18:30:23 +0100 Subject: [PATCH 01/17] More robust and maybe faster tuples --- Include/cpython/tupleobject.h | 1 + Include/internal/pycore_tuple.h | 2 +- Modules/_testbuffer.c | 4 +-- Modules/itertoolsmodule.c | 10 +++---- Objects/abstract.c | 46 ++++++++----------------------- Objects/tupleobject.c | 19 ++++--------- Objects/weakrefobject.c | 2 +- Python/bytecodes.c | 3 +- Python/ceval.c | 2 +- Python/executor_cases.c.h | 3 +- Python/generated_cases.c.h | 3 +- Python/optimizer.c | 2 +- Tools/cases_generator/analyzer.py | 2 +- 13 files changed, 35 insertions(+), 64 deletions(-) diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h index e530c8beda44ab..75c5ef89b1acc7 100644 --- a/Include/cpython/tupleobject.h +++ b/Include/cpython/tupleobject.h @@ -30,6 +30,7 @@ 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(value != NULL); assert(0 <= index); assert(index < Py_SIZE(tuple)); tuple->ob_item[index] = value; diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index 14a9e42c3a324c..d1d3123332286f 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -21,7 +21,7 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); #define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item) extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t); -PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); +PyAPI_FUNC(PyObject *)_PyTuple_FromNonEmptyArraySteal(PyObject *const *, Py_ssize_t); typedef struct { PyObject_HEAD diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c index cad21bdb4d85ed..3bcf8dce029d0d 100644 --- a/Modules/_testbuffer.c +++ b/Modules/_testbuffer.c @@ -333,10 +333,10 @@ pack_from_list(PyObject *obj, PyObject *items, PyObject *format, offset = NULL; for (i = 0; i < nitems; i++) { - /* Loop invariant: args[j] are borrowed references or NULL. */ + /* Loop invariant: args[j] are borrowed references or None. */ PyTuple_SET_ITEM(args, 0, obj); for (j = 1; j < 2+nmemb; j++) - PyTuple_SET_ITEM(args, j, NULL); + PyTuple_SET_ITEM(args, j, Py_None); Py_XDECREF(offset); offset = PyLong_FromSsize_t(i*itemsize); diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 44b92f8dcffe4d..c7ba92c34294c0 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -4473,7 +4473,7 @@ zip_longest_next(ziplongestobject *lz) Py_INCREF(result); for (i=0 ; i < tuplesize ; i++) { it = PyTuple_GET_ITEM(lz->ittuple, i); - if (it == NULL) { + if (it == Py_None) { item = Py_NewRef(lz->fillvalue); } else { item = PyIter_Next(it); @@ -4485,7 +4485,7 @@ zip_longest_next(ziplongestobject *lz) return NULL; } else { item = Py_NewRef(lz->fillvalue); - PyTuple_SET_ITEM(lz->ittuple, i, NULL); + PyTuple_SET_ITEM(lz->ittuple, i, Py_None); Py_DECREF(it); } } @@ -4505,7 +4505,7 @@ zip_longest_next(ziplongestobject *lz) return NULL; for (i=0 ; i < tuplesize ; i++) { it = PyTuple_GET_ITEM(lz->ittuple, i); - if (it == NULL) { + if (it == Py_None) { item = Py_NewRef(lz->fillvalue); } else { item = PyIter_Next(it); @@ -4517,7 +4517,7 @@ zip_longest_next(ziplongestobject *lz) return NULL; } else { item = Py_NewRef(lz->fillvalue); - PyTuple_SET_ITEM(lz->ittuple, i, NULL); + PyTuple_SET_ITEM(lz->ittuple, i, Py_None); Py_DECREF(it); } } @@ -4542,7 +4542,7 @@ zip_longest_reduce(ziplongestobject *lz, PyObject *Py_UNUSED(ignored)) return NULL; for (i=0; iittuple); i++) { PyObject *elem = PyTuple_GET_ITEM(lz->ittuple, i); - if (elem == NULL) { + if (elem == Py_None) { elem = PyTuple_New(0); if (elem == NULL) { Py_DECREF(args); diff --git a/Objects/abstract.c b/Objects/abstract.c index 8357175aa5591e..a6ae00df65da3f 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2040,8 +2040,6 @@ PySequence_Tuple(PyObject *v) { PyObject *it; /* iter(v) */ Py_ssize_t n; /* guess for result tuple size */ - PyObject *result = NULL; - Py_ssize_t j; if (v == NULL) { return null_error(); @@ -2067,53 +2065,31 @@ PySequence_Tuple(PyObject *v) n = PyObject_LengthHint(v, 10); if (n == -1) goto Fail; - result = PyTuple_New(n); - if (result == NULL) + PyObject *temp = PyList_New(0); + if (temp == NULL) goto Fail; /* Fill the tuple. */ - for (j = 0; ; ++j) { + for (;;) { PyObject *item = PyIter_Next(it); if (item == NULL) { - if (PyErr_Occurred()) + if (PyErr_Occurred()) { + Py_DECREF(temp); goto Fail; + } break; } - if (j >= n) { - size_t newn = (size_t)n; - /* The over-allocation strategy can grow a bit faster - than for lists because unlike lists the - over-allocation isn't permanent -- we reclaim - the excess before the end of this routine. - So, grow by ten and then add 25%. - */ - newn += 10u; - newn += newn >> 2; - if (newn > PY_SSIZE_T_MAX) { - /* Check for overflow */ - PyErr_NoMemory(); - Py_DECREF(item); - goto Fail; - } - n = (Py_ssize_t)newn; - if (_PyTuple_Resize(&result, n) != 0) { - Py_DECREF(item); - goto Fail; - } + if (_PyList_AppendTakeRef((PyListObject *)temp, item)) { + Py_DECREF(temp); + goto Fail; } - PyTuple_SET_ITEM(result, j, item); } - - /* Cut tuple back if guess was too large. */ - if (j < n && - _PyTuple_Resize(&result, j) != 0) - goto Fail; - Py_DECREF(it); + PyObject *result = PyList_AsTuple(temp); + Py_DECREF(temp); return result; Fail: - Py_XDECREF(result); Py_DECREF(it); return NULL; } diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index d9dc00da368a84..b9836b94052460 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -34,14 +34,7 @@ static inline int maybe_freelist_push(PyTupleObject *); static PyTupleObject * tuple_alloc(Py_ssize_t size) { - if (size < 0) { - PyErr_BadInternalCall(); - return NULL; - } -#ifdef Py_DEBUG - assert(size != 0); // The empty tuple is statically allocated. -#endif - + assert(size > 0); PyTupleObject *op = maybe_freelist_pop(size); if (op == NULL) { /* Check for overflow */ @@ -78,7 +71,7 @@ PyTuple_New(Py_ssize_t size) return NULL; } for (Py_ssize_t i = 0; i < size; i++) { - op->ob_item[i] = NULL; + op->ob_item[i] = Py_None; } _PyObject_GC_TRACK(op); return (PyObject *) op; @@ -205,7 +198,8 @@ tupledealloc(PyTupleObject *op) Py_ssize_t i = Py_SIZE(op); while (--i >= 0) { - Py_XDECREF(op->ob_item[i]); + assert(op->ob_item[i] != NULL); + Py_DECREF(op->ob_item[i]); } // This will abort on the empty singleton (if there is one). if (!maybe_freelist_push(op)) { @@ -391,11 +385,8 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n) } PyObject * -_PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n) +_PyTuple_FromNonEmptyArraySteal(PyObject *const *src, Py_ssize_t n) { - if (n == 0) { - return tuple_get_empty(); - } PyTupleObject *tuple = tuple_alloc(n); if (tuple == NULL) { for (Py_ssize_t i = 0; i < n; i++) { diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index d8dd6aea3aff02..6a944950208545 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -1002,7 +1002,7 @@ PyObject_ClearWeakRefs(PyObject *object) PyObject *callback = PyTuple_GET_ITEM(tuple, i * 2 + 1); /* The tuple may have slots left to NULL */ - if (callback != NULL) { + if (callback != Py_None) { PyObject *item = PyTuple_GET_ITEM(tuple, i * 2); handle_callback((PyWeakReference *)item, callback); } diff --git a/Python/bytecodes.c b/Python/bytecodes.c index d6fb66a7be34ac..b6c771a773ceb5 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1587,7 +1587,8 @@ dummy_func( } inst(BUILD_TUPLE, (values[oparg] -- tup)) { - tup = _PyTuple_FromArraySteal(values, oparg); + assert(oparg != 0); + tup = _PyTuple_FromNonEmptyArraySteal(values, oparg); ERROR_IF(tup == NULL, error); } diff --git a/Python/ceval.c b/Python/ceval.c index f718a77fb029cb..c94ba55fb9ebf3 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1458,7 +1458,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func, } else { assert(args != NULL); - u = _PyTuple_FromArraySteal(args + n, argcount - n); + u = _PyTuple_FromNonEmptyArraySteal(args + n, argcount - n); } if (u == NULL) { goto fail_post_positional; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index a3447da00477ca..a60fc5b4cea249 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1588,7 +1588,8 @@ PyObject *tup; oparg = CURRENT_OPARG(); values = &stack_pointer[-oparg]; - tup = _PyTuple_FromArraySteal(values, oparg); + assert(oparg != 0); + tup = _PyTuple_FromNonEmptyArraySteal(values, oparg); if (tup == NULL) JUMP_TO_ERROR(); stack_pointer[-oparg] = tup; stack_pointer += 1 - oparg; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a7764b0ec12e10..33c370b6beb3f7 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -725,7 +725,8 @@ PyObject **values; PyObject *tup; values = &stack_pointer[-oparg]; - tup = _PyTuple_FromArraySteal(values, oparg); + assert(oparg != 0); + tup = _PyTuple_FromNonEmptyArraySteal(values, oparg); if (tup == NULL) { stack_pointer += -oparg; goto error; } stack_pointer[-oparg] = tup; stack_pointer += 1 - oparg; diff --git a/Python/optimizer.c b/Python/optimizer.c index 5c69d9d5de92eb..2cf06283cce028 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -369,7 +369,7 @@ uop_item(_PyExecutorObject *self, Py_ssize_t index) return NULL; } PyObject *args[4] = { oname, oparg, target, operand }; - return _PyTuple_FromArraySteal(args, 4); + return _PyTuple_FromNonEmptyArraySteal(args, 4); } PySequenceMethods uop_as_sequence = { diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index e38ab3c9047039..3480962713c62f 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -396,7 +396,7 @@ def has_error_without_pop(op: parser.InstDef) -> bool: "_PyType_HasFeature", "PyUnicode_Concat", "_PyList_FromArraySteal", - "_PyTuple_FromArraySteal", + "_PyTuple_FromNonEmptyArraySteal", "PySlice_New", "_Py_LeaveRecursiveCallPy", "CALL_STAT_INC", From 6a1f806c8eeba0ad280657f6e7c7352068d176ef Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 10 Apr 2024 18:54:54 +0100 Subject: [PATCH 02/17] Add test --- Lib/test/test_tuple.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py index 9ce80c5e8ea009..8dd6edc0c8f13a 100644 --- a/Lib/test/test_tuple.py +++ b/Lib/test/test_tuple.py @@ -416,6 +416,28 @@ def test_lexicographic_ordering(self): self.assertLess(a, b) self.assertLess(b, c) + def test_bug_59313(self): + # Until 3.13, the C-API function PySequence_Tuple + # would create incomplete tuples which were visible + # to the cycle GC + TAG = object() + + def monitor(): + lst = [x for x in gc.get_referrers(TAG) + if isinstance(x, tuple)] + t = lst[0] # this *is* the result tuple + print(t) # full of nulls ! + return t # Keep it alive for some time + + def my_iter(): + yield TAG # 'tag' gets stored in the result tuple + t = monitor() + for x in range(10): + yield x # SystemError when the tuple needs to be resized + + with self.assertRaises(IndexError): + tuple(my_iter()) + # Notes on testing hash codes. The primary thing is that Python doesn't # care about "random" hash codes. To the contrary, we like them to be # very regular when possible, so that the low-order bits are as evenly From 40a49d61324e1acec2d6f93be42a392d1eb4b67c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 09:54:59 +0100 Subject: [PATCH 03/17] Add news --- .../2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst new file mode 100644 index 00000000000000..69fbce1ac1beb0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst @@ -0,0 +1,3 @@ +Make sure that all tuple elements are valid and never NULL. Ensuring that +tuples are always valid reduces the risk of cycle GC bugs and prevents +``gc.get_referrers`` from returning invalid tuples. From 0a06553947e4c693218be05fdf1b58f82025ee89 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 10:33:34 +0100 Subject: [PATCH 04/17] Don't leak references --- Include/internal/pycore_list.h | 1 + Objects/abstract.c | 4 ++-- Objects/listobject.c | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 2a82912e41d557..5b814c3e4683c4 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -55,6 +55,7 @@ typedef struct { } _PyListIterObject; PyAPI_FUNC(PyObject *)_PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n); +PyObject *_PyList_AsTupleStealItems(PyObject *); #ifdef __cplusplus } diff --git a/Objects/abstract.c b/Objects/abstract.c index a6ae00df65da3f..cd033c736ea50e 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2069,7 +2069,7 @@ PySequence_Tuple(PyObject *v) if (temp == NULL) goto Fail; - /* Fill the tuple. */ + /* Fill the temporary list. */ for (;;) { PyObject *item = PyIter_Next(it); if (item == NULL) { @@ -2085,7 +2085,7 @@ PySequence_Tuple(PyObject *v) } } Py_DECREF(it); - PyObject *result = PyList_AsTuple(temp); + PyObject *result = _PyList_AsTupleStealItems(temp); Py_DECREF(temp); return result; diff --git a/Objects/listobject.c b/Objects/listobject.c index 472c471d9968a4..42386c5022bda2 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3174,6 +3174,25 @@ PyList_AsTuple(PyObject *v) return ret; } +PyObject * +_PyList_AsTupleStealItems(PyObject *v) +{ + if (v == NULL || !PyList_Check(v)) { + PyErr_BadInternalCall(); + return NULL; + } + PyObject *ret; + PyListObject *self = (PyListObject *)v; + if (Py_SIZE(v) == 0) { + return PyTuple_New(0); + } + Py_BEGIN_CRITICAL_SECTION(self); + ret = _PyTuple_FromNonEmptyArraySteal(self->ob_item, Py_SIZE(v)); + Py_SET_SIZE(v, 0); + Py_END_CRITICAL_SECTION(); + return ret; +} + PyObject * _PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n) { From 207005cdd282b625dfa51894ac521740c713fb41 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 12:10:02 +0100 Subject: [PATCH 05/17] Revert changes to _PyTuple_FromArraySteal --- Include/internal/pycore_tuple.h | 2 +- Objects/listobject.c | 2 +- Objects/tupleobject.c | 5 ++++- Python/bytecodes.c | 3 +-- Python/ceval.c | 2 +- Python/executor_cases.c.h | 3 +-- Python/generated_cases.c.h | 3 +-- Python/optimizer.c | 2 +- Tools/cases_generator/analyzer.py | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index d1d3123332286f..14a9e42c3a324c 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -21,7 +21,7 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); #define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item) extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t); -PyAPI_FUNC(PyObject *)_PyTuple_FromNonEmptyArraySteal(PyObject *const *, Py_ssize_t); +PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); typedef struct { PyObject_HEAD diff --git a/Objects/listobject.c b/Objects/listobject.c index 42386c5022bda2..7f273f48a19808 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3187,7 +3187,7 @@ _PyList_AsTupleStealItems(PyObject *v) return PyTuple_New(0); } Py_BEGIN_CRITICAL_SECTION(self); - ret = _PyTuple_FromNonEmptyArraySteal(self->ob_item, Py_SIZE(v)); + ret = _PyTuple_FromArraySteal(self->ob_item, Py_SIZE(v)); Py_SET_SIZE(v, 0); Py_END_CRITICAL_SECTION(); return ret; diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 0cb2436255ab54..893264b175f0ae 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -385,8 +385,11 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n) } PyObject * -_PyTuple_FromNonEmptyArraySteal(PyObject *const *src, Py_ssize_t n) +_PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n) { + if (n == 0) { + return tuple_get_empty(); + } PyTupleObject *tuple = tuple_alloc(n); if (tuple == NULL) { for (Py_ssize_t i = 0; i < n; i++) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index b6c771a773ceb5..d6fb66a7be34ac 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1587,8 +1587,7 @@ dummy_func( } inst(BUILD_TUPLE, (values[oparg] -- tup)) { - assert(oparg != 0); - tup = _PyTuple_FromNonEmptyArraySteal(values, oparg); + tup = _PyTuple_FromArraySteal(values, oparg); ERROR_IF(tup == NULL, error); } diff --git a/Python/ceval.c b/Python/ceval.c index c94ba55fb9ebf3..f718a77fb029cb 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1458,7 +1458,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func, } else { assert(args != NULL); - u = _PyTuple_FromNonEmptyArraySteal(args + n, argcount - n); + u = _PyTuple_FromArraySteal(args + n, argcount - n); } if (u == NULL) { goto fail_post_positional; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index a60fc5b4cea249..a3447da00477ca 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1588,8 +1588,7 @@ PyObject *tup; oparg = CURRENT_OPARG(); values = &stack_pointer[-oparg]; - assert(oparg != 0); - tup = _PyTuple_FromNonEmptyArraySteal(values, oparg); + tup = _PyTuple_FromArraySteal(values, oparg); if (tup == NULL) JUMP_TO_ERROR(); stack_pointer[-oparg] = tup; stack_pointer += 1 - oparg; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 33c370b6beb3f7..a7764b0ec12e10 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -725,8 +725,7 @@ PyObject **values; PyObject *tup; values = &stack_pointer[-oparg]; - assert(oparg != 0); - tup = _PyTuple_FromNonEmptyArraySteal(values, oparg); + tup = _PyTuple_FromArraySteal(values, oparg); if (tup == NULL) { stack_pointer += -oparg; goto error; } stack_pointer[-oparg] = tup; stack_pointer += 1 - oparg; diff --git a/Python/optimizer.c b/Python/optimizer.c index 2cf06283cce028..5c69d9d5de92eb 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -369,7 +369,7 @@ uop_item(_PyExecutorObject *self, Py_ssize_t index) return NULL; } PyObject *args[4] = { oname, oparg, target, operand }; - return _PyTuple_FromNonEmptyArraySteal(args, 4); + return _PyTuple_FromArraySteal(args, 4); } PySequenceMethods uop_as_sequence = { diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 3480962713c62f..e38ab3c9047039 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -396,7 +396,7 @@ def has_error_without_pop(op: parser.InstDef) -> bool: "_PyType_HasFeature", "PyUnicode_Concat", "_PyList_FromArraySteal", - "_PyTuple_FromNonEmptyArraySteal", + "_PyTuple_FromArraySteal", "PySlice_New", "_Py_LeaveRecursiveCallPy", "CALL_STAT_INC", From 03643cf7df095279772673f159a07be7978d0970 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 12:22:03 +0100 Subject: [PATCH 06/17] Mark list as empty before transfering ownership of items --- Include/internal/pycore_list.h | 3 ++- Objects/abstract.c | 6 +----- Objects/listobject.c | 5 +++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 5b814c3e4683c4..8d4e5ed19e33c2 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -55,7 +55,8 @@ typedef struct { } _PyListIterObject; PyAPI_FUNC(PyObject *)_PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n); -PyObject *_PyList_AsTupleStealItems(PyObject *); +/* Creates tuple from the list, leaving the list empty */ +PyObject *_PyList_AsTupleTakeItems(PyObject *); #ifdef __cplusplus } diff --git a/Objects/abstract.c b/Objects/abstract.c index cd033c736ea50e..1b57aa1c7251e4 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2061,10 +2061,6 @@ PySequence_Tuple(PyObject *v) if (it == NULL) return NULL; - /* Guess result size and allocate space. */ - n = PyObject_LengthHint(v, 10); - if (n == -1) - goto Fail; PyObject *temp = PyList_New(0); if (temp == NULL) goto Fail; @@ -2085,7 +2081,7 @@ PySequence_Tuple(PyObject *v) } } Py_DECREF(it); - PyObject *result = _PyList_AsTupleStealItems(temp); + PyObject *result = _PyList_AsTupleTakeItems(temp); Py_DECREF(temp); return result; diff --git a/Objects/listobject.c b/Objects/listobject.c index 7f273f48a19808..a2001cc870ddab 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3175,7 +3175,7 @@ PyList_AsTuple(PyObject *v) } PyObject * -_PyList_AsTupleStealItems(PyObject *v) +_PyList_AsTupleTakeItems(PyObject *v) { if (v == NULL || !PyList_Check(v)) { PyErr_BadInternalCall(); @@ -3187,8 +3187,9 @@ _PyList_AsTupleStealItems(PyObject *v) return PyTuple_New(0); } Py_BEGIN_CRITICAL_SECTION(self); - ret = _PyTuple_FromArraySteal(self->ob_item, Py_SIZE(v)); + Py_ssize_t size = Py_SIZE(v); Py_SET_SIZE(v, 0); + ret = _PyTuple_FromArraySteal(self->ob_item, size); Py_END_CRITICAL_SECTION(); return ret; } From 0d8974ecfdeba8f84f610bb0944f026dcadffa8e Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 12:33:33 +0100 Subject: [PATCH 07/17] Document change to PyTuple_SET_ITEM --- Doc/whatsnew/3.13.rst | 4 ++++ Include/internal/pycore_list.h | 1 + 2 files changed, 5 insertions(+) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index d394fbe3b0c357..0a1a6b4a9244c0 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1872,6 +1872,10 @@ Porting to Python 3.13 platforms, the ``HAVE_STDDEF_H`` macro is only defined on Windows. (Contributed by Victor Stinner in :gh:`108765`.) +* The :c:func`PyTuple_SET_ITEM` inline function may not be passed ``NULL``. + This has always been the documented behavior, but was not enforced for + the value until now. + Deprecated ---------- diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 8d4e5ed19e33c2..c37162f14f7f81 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -55,6 +55,7 @@ typedef struct { } _PyListIterObject; PyAPI_FUNC(PyObject *)_PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n); + /* Creates tuple from the list, leaving the list empty */ PyObject *_PyList_AsTupleTakeItems(PyObject *); From 8fad5a43552b04fa43ca99d53d7cc89343940a36 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 12:44:37 +0100 Subject: [PATCH 08/17] Redo test --- Lib/test/test_tuple.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py index 8dd6edc0c8f13a..7003845df0115a 100644 --- a/Lib/test/test_tuple.py +++ b/Lib/test/test_tuple.py @@ -421,22 +421,23 @@ def test_bug_59313(self): # would create incomplete tuples which were visible # to the cycle GC TAG = object() + tuples = [] - def monitor(): - lst = [x for x in gc.get_referrers(TAG) + def referrer_tuples(): + return [x for x in gc.get_referrers(TAG) if isinstance(x, tuple)] - t = lst[0] # this *is* the result tuple - print(t) # full of nulls ! - return t # Keep it alive for some time def my_iter(): + nonlocal tuples yield TAG # 'tag' gets stored in the result tuple - t = monitor() + tuples += referrer_tuples() for x in range(10): - yield x # SystemError when the tuple needs to be resized + tuples += referrer_tuples() + # Prior to 3.13 would raise a SystemError when the tuple needs to be resized + yield x - with self.assertRaises(IndexError): - tuple(my_iter()) + self.assertEqual(tuple(my_iter()), (TAG, *range(10))) + self.assertEqual(tuples, []) # Notes on testing hash codes. The primary thing is that Python doesn't # care about "random" hash codes. To the contrary, we like them to be From e4b4f3965e0b5c61b3f19607f77a17bfb75697c6 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 12:49:14 +0100 Subject: [PATCH 09/17] Recommend using None --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 0a1a6b4a9244c0..41d48959ab5058 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1874,7 +1874,7 @@ Porting to Python 3.13 * The :c:func`PyTuple_SET_ITEM` inline function may not be passed ``NULL``. This has always been the documented behavior, but was not enforced for - the value until now. + the value until now. :c:var:``Py_None` should be used instead of ``NULL``. Deprecated ---------- From f4c0a292aa10d3bdeca2c2cdb05653026aafb25e Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 12:53:56 +0100 Subject: [PATCH 10/17] Fix formatting --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 41d48959ab5058..782d854d18e0b4 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1872,7 +1872,7 @@ Porting to Python 3.13 platforms, the ``HAVE_STDDEF_H`` macro is only defined on Windows. (Contributed by Victor Stinner in :gh:`108765`.) -* The :c:func`PyTuple_SET_ITEM` inline function may not be passed ``NULL``. +* The :c:func:`PyTuple_SET_ITEM` inline function may not be passed ``NULL``. This has always been the documented behavior, but was not enforced for the value until now. :c:var:``Py_None` should be used instead of ``NULL``. From bf69fc5d5d8d1788e08ca3a8c852c9dcaef52d98 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 15:33:17 +0100 Subject: [PATCH 11/17] Improve thread safety of _PyList_AsTupleTakeItems --- Objects/abstract.c | 5 +---- Objects/listobject.c | 16 ++++++++++------ Objects/tupleobject.c | 1 + 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index 1b57aa1c7251e4..48c46d187c982d 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2038,9 +2038,6 @@ PySequence_DelSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2) PyObject * PySequence_Tuple(PyObject *v) { - PyObject *it; /* iter(v) */ - Py_ssize_t n; /* guess for result tuple size */ - if (v == NULL) { return null_error(); } @@ -2057,7 +2054,7 @@ PySequence_Tuple(PyObject *v) return PyList_AsTuple(v); /* Get iterator. */ - it = PyObject_GetIter(v); + PyObject *it = PyObject_GetIter(v); if (it == NULL) return NULL; diff --git a/Objects/listobject.c b/Objects/listobject.c index a2001cc870ddab..29c8dd4af10a61 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3177,20 +3177,24 @@ PyList_AsTuple(PyObject *v) PyObject * _PyList_AsTupleTakeItems(PyObject *v) { - if (v == NULL || !PyList_Check(v)) { - PyErr_BadInternalCall(); - return NULL; - } + assert(PyList_Check(v)); PyObject *ret; PyListObject *self = (PyListObject *)v; if (Py_SIZE(v) == 0) { return PyTuple_New(0); } + Py_ssize_t size; + PyObject **items; Py_BEGIN_CRITICAL_SECTION(self); - Py_ssize_t size = Py_SIZE(v); + size = Py_SIZE(v); + items = self->ob_item; Py_SET_SIZE(v, 0); - ret = _PyTuple_FromArraySteal(self->ob_item, size); + self->ob_item = NULL; Py_END_CRITICAL_SECTION(); + ret = _PyTuple_FromArraySteal(items, size); + if (size) { + free_list_items(items, false); + } return ret; } diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 893264b175f0ae..bdf3ea329b03a6 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -105,6 +105,7 @@ PyTuple_GetItem(PyObject *op, Py_ssize_t i) int PyTuple_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) { + assert(newitem != NULL); PyObject **p; if (!PyTuple_Check(op) || Py_REFCNT(op) != 1) { Py_XDECREF(newitem); From 4d0ad9e9c635f2d09afe6f9d6363bc4d69905409 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 16:12:55 +0100 Subject: [PATCH 12/17] Minor tweaks --- Objects/abstract.c | 9 +++++---- Objects/listobject.c | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index 48c46d187c982d..56c660f5667700 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2059,21 +2059,21 @@ PySequence_Tuple(PyObject *v) return NULL; PyObject *temp = PyList_New(0); - if (temp == NULL) - goto Fail; + if (temp == NULL) { + Py_DECREF(it); + return NULL; + } /* Fill the temporary list. */ for (;;) { PyObject *item = PyIter_Next(it); if (item == NULL) { if (PyErr_Occurred()) { - Py_DECREF(temp); goto Fail; } break; } if (_PyList_AppendTakeRef((PyListObject *)temp, item)) { - Py_DECREF(temp); goto Fail; } } @@ -2083,6 +2083,7 @@ PySequence_Tuple(PyObject *v) return result; Fail: + Py_DECREF(temp); Py_DECREF(it); return NULL; } diff --git a/Objects/listobject.c b/Objects/listobject.c index 29c8dd4af10a61..ed8ee4d70c75da 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3186,7 +3186,7 @@ _PyList_AsTupleTakeItems(PyObject *v) Py_ssize_t size; PyObject **items; Py_BEGIN_CRITICAL_SECTION(self); - size = Py_SIZE(v); + size = PyList_GET_SIZE(v); items = self->ob_item; Py_SET_SIZE(v, 0); self->ob_item = NULL; From 269832a30cb06a01b3400e8fee2d31b8bef25e50 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 16:17:23 +0100 Subject: [PATCH 13/17] Doc that the value cannot be NULL. --- Doc/c-api/tuple.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index b3710560ebe7ac..7ad9ad1865d44c 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -87,7 +87,7 @@ Tuple Objects .. c:function:: void PyTuple_SET_ITEM(PyObject *p, Py_ssize_t pos, PyObject *o) Like :c:func:`PyTuple_SetItem`, but does no error checking, and should *only* be - used to fill in brand new tuples. + used to fill in brand new tuples. Both ``p`` and ``o`` must be non-``NULL``. Bounds checking is performed as an assertion if Python is built in :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. From 3a766d842b7942af2f89c43c0e6f9c926a8d4bfa Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 11 Apr 2024 16:36:38 +0100 Subject: [PATCH 14/17] Handle the mathematically challenged function _PyTuple_Resize --- Objects/tupleobject.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index bdf3ea329b03a6..a5df7593f311fc 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -895,7 +895,6 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) { PyTupleObject *v; PyTupleObject *sv; - Py_ssize_t i; Py_ssize_t oldsize; v = (PyTupleObject *) *pv; @@ -934,7 +933,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) _Py_ForgetReference((PyObject *) v); #endif /* DECREF items deleted by shrinkage */ - for (i = newsize; i < oldsize; i++) { + for (Py_ssize_t i = newsize; i < oldsize; i++) { Py_CLEAR(v->ob_item[i]); } sv = PyObject_GC_Resize(PyTupleObject, v, newsize); @@ -947,10 +946,10 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) 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)); + /* Set items added by growing to None */ + for (Py_ssize_t i = oldsize; i < newsize; i++) { + sv->ob_item[i] = Py_None; + } *pv = (PyObject *) sv; _PyObject_GC_TRACK(sv); return 0; From a1558020c5c1779242705f2262aa0bc91a8eb389 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 8 Nov 2024 14:08:40 +0000 Subject: [PATCH 15/17] Fix handling of zero and negative sizes --- Lib/test/test_capi/test_tuple.py | 22 ++++++----------- Modules/_testbuffer.c | 2 +- Modules/_testcapi/tuple.c | 1 - Objects/tupleobject.c | 41 ++++++++++++++++++++------------ 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index e6b49caeb51f32..0e7f334889d87c 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -57,7 +57,7 @@ def test_tuple_new(self): self.assertIs(type(tup2), tuple) self.assertEqual(size(tup2), 1) self.assertIsNot(tup2, tup1) - self.assertTrue(checknull(tup2, 0)) + self.assertIs(tup2[0], None) self.assertRaises(SystemError, tuple_new, -1) self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) @@ -71,8 +71,8 @@ def test_tuple_pack(self): self.assertEqual(pack(1, [1]), ([1],)) self.assertEqual(pack(2, [1], [2]), ([1], [2])) - self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN) - self.assertRaises(SystemError, pack, -1) + self.assertRaises(ValueError, pack, PY_SSIZE_T_MIN) + self.assertRaises(ValueError, pack, -1) self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX) # CRASHES pack(1, NULL) @@ -183,9 +183,6 @@ def test_tuple_setitem(self): self.assertEqual(setitem(tup, 0, []), ([], [2])) self.assertEqual(setitem(tup, 1, []), ([1], [])) - tup2 = setitem(tup, 1, NULL) - self.assertTrue(checknull(tup2, 1)) - tup2 = TupleSubclass(([1], [2])) self.assertRaises(SystemError, setitem, tup2, 0, []) @@ -196,8 +193,6 @@ def test_tuple_setitem(self): self.assertRaises(SystemError, setitem, [1], 0, []) self.assertRaises(SystemError, setitem, 42, 0, []) - # CRASHES setitem(NULL, 0, []) - def test_tuple_set_item(self): # Test PyTuple_SET_ITEM() set_item = _testcapi.tuple_set_item @@ -207,9 +202,6 @@ def test_tuple_set_item(self): self.assertEqual(set_item(tup, 0, []), ([], [2])) self.assertEqual(set_item(tup, 1, []), ([1], [])) - tup2 = set_item(tup, 1, NULL) - self.assertTrue(checknull(tup2, 1)) - tup2 = TupleSubclass(([1], [2])) self.assertIs(set_item(tup2, 0, []), tup2) self.assertEqual(tup2, ([], [2])) @@ -231,8 +223,8 @@ def test__tuple_resize(self): b = resize(a, 2, False) self.assertEqual(len(a), 0) self.assertEqual(len(b), 2) - self.assertTrue(checknull(b, 0)) - self.assertTrue(checknull(b, 1)) + self.assertIs(b[0], None) + self.assertIs(b[1], None) a = ([1], [2], [3]) b = resize(a, 3) @@ -242,8 +234,8 @@ def test__tuple_resize(self): b = resize(a, 5) self.assertEqual(len(b), 5) self.assertEqual(b[:3], a) - self.assertTrue(checknull(b, 3)) - self.assertTrue(checknull(b, 4)) + self.assertIs(b[3], None) + self.assertIs(b[4], None) a = () self.assertRaises(MemoryError, resize, a, PY_SSIZE_T_MAX) diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c index 89e2f89d41459f..5ccd0561743a29 100644 --- a/Modules/_testbuffer.c +++ b/Modules/_testbuffer.c @@ -335,7 +335,7 @@ pack_from_list(PyObject *obj, PyObject *items, PyObject *format, offset = NULL; for (i = 0; i < nitems; i++) { - /* Loop invariant: args[j] are borrowed references or None. */ + /* Loop invariant: args[j] are borrowed references. */ PyTuple_SET_ITEM(args, 0, obj); for (j = 1; j < 2+nmemb; j++) PyTuple_SET_ITEM(args, j, Py_None); diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index d9c02ba0ff04fe..d463698dc30e52 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -43,7 +43,6 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) { return NULL; } - NULLABLE(value); if (PyTuple_CheckExact(obj)) { newtuple = tuple_copy(obj); if (!newtuple) { diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index dd5205b1c9a3c4..37e1db6c3a3047 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -35,18 +35,19 @@ static PyTupleObject * tuple_alloc(Py_ssize_t size) { assert(size > 0); - PyTupleObject *op = maybe_freelist_pop(size); - if (op == NULL) { - /* Check for overflow */ - if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) - - sizeof(PyObject *))) / sizeof(PyObject *)) { - return (PyTupleObject *)PyErr_NoMemory(); + Py_ssize_t index = size - 1; + if (index < PyTuple_MAXSAVESIZE) { + PyTupleObject *op = _Py_FREELIST_POP(PyTupleObject, tuples[index]); + if (op != NULL) { + return op; } - op = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size); - if (op == NULL) - return NULL; } - return op; + /* Check for overflow */ + if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - (sizeof(PyTupleObject) - + sizeof(PyObject *))) / sizeof(PyObject *)) { + return (PyTupleObject *)PyErr_NoMemory(); + } + return PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size); } // The empty tuple singleton is not tracked by the GC. @@ -63,7 +64,11 @@ PyObject * PyTuple_New(Py_ssize_t size) { PyTupleObject *op; - if (size == 0) { + if (size <= 0) { + if (size < 0) { + PyErr_BadInternalCall(); + return NULL; + } return tuple_get_empty(); } op = tuple_alloc(size); @@ -153,10 +158,15 @@ PyTuple_Pack(Py_ssize_t n, ...) PyObject **items; va_list vargs; - if (n == 0) { - return tuple_get_empty(); + if (n <= 0) { + if (n == 0) { + return tuple_get_empty(); + } + if (n < 0) { + PyErr_SetString(PyExc_ValueError, "negative size"); + return NULL; + } } - va_start(vargs, n); PyTupleObject *result = tuple_alloc(n); if (result == NULL) { @@ -924,7 +934,8 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) v = (PyTupleObject *) *pv; if (v == NULL || !Py_IS_TYPE(v, &PyTuple_Type) || - (Py_SIZE(v) != 0 && Py_REFCNT(v) != 1)) { + (Py_SIZE(v) != 0 && Py_REFCNT(v) != 1) || + newsize < 0) { *pv = 0; Py_XDECREF(v); PyErr_BadInternalCall(); From ae437f12d55d14b848621f235d7f8dc982e356e3 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 8 Nov 2024 14:23:18 +0000 Subject: [PATCH 16/17] Fix up docs --- Doc/c-api/tuple.rst | 2 +- Doc/whatsnew/3.13.rst | 4 ---- Doc/whatsnew/3.14.rst | 4 ++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index c88e0ec9da7bcd..5f9edec00f8578 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -86,7 +86,7 @@ Tuple Objects Insert a reference to object *o* at position *pos* of the tuple pointed to by *p*. Return ``0`` on success. If *pos* is out of bounds, return ``-1`` - and set an :exc:`IndexError` exception. + and set an :exc:`IndexError` exception. Both ``p`` and ``o`` must be non-``NULL``. .. note:: diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index dacae020316a7a..de4c7fd4c0486b 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -2661,10 +2661,6 @@ Changes in the C API Note that ``Py_TRASHCAN_BEGIN`` has a second argument which should be the deallocation function it is in. The new macros were -* The :c:func:`PyTuple_SET_ITEM` inline function may not be passed ``NULL``. - This has always been the documented behavior, but was not enforced for - the value until now. :c:var:``Py_None` should be used instead of ``NULL``. - added in Python 3.8 and the old macros were deprecated in Python 3.11. (Contributed by Irit Katriel in :gh:`105111`.) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b9d2c27eb9a321..1db1b945cc01cf 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -827,6 +827,10 @@ Porting to Python 3.14 implementation details. (Contributed by Victor Stinner in :gh:`120600` and :gh:`124127`.) +* The :c:func:`PyTuple_SET_ITEM` inline function may not be passed ``NULL``. + This has always been the documented behavior, but was not enforced for + the value until now. :c:var:``Py_None` should be used instead of ``NULL``. + Deprecated ---------- From 03ce1216d033ddc5c259b8fc23e8171840edabbb Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 8 Nov 2024 14:25:04 +0000 Subject: [PATCH 17/17] Move news --- .../2024-11-08-14-24-51.gh-issue-59313.ByDzOy.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core and Builtins/2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst => Core_and_Builtins/2024-11-08-14-24-51.gh-issue-59313.ByDzOy.rst} (100%) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-08-14-24-51.gh-issue-59313.ByDzOy.rst similarity index 100% rename from Misc/NEWS.d/next/Core and Builtins/2024-04-11-09-54-49.gh-issue-59313.ByDzOy.rst rename to Misc/NEWS.d/next/Core_and_Builtins/2024-11-08-14-24-51.gh-issue-59313.ByDzOy.rst