From fec7f555945e345c68d6bd36ce35524283980c23 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 19 Mar 2026 09:59:48 +0200 Subject: [PATCH 1/2] [3.14] gh-146056: Fix list.__repr__() for lists containing NULLs (GH-146129) (cherry picked from commit 0f2246b1553f401da5ade47e0fd1c80ad7a8dfa5) Co-authored-by: Serhiy Storchaka Co-authored-by: Victor Stinner --- Doc/c-api/file.rst | 7 +++++-- Doc/c-api/object.rst | 8 ++++++++ Doc/c-api/unicode.rst | 2 ++ Include/cpython/unicodeobject.h | 8 ++++++++ Lib/test/test_capi/test_list.py | 4 ++++ Lib/test/test_capi/test_tuple.py | 4 ++++ Lib/test/test_capi/test_unicode.py | 8 ++++++++ ...-03-18-20-18-59.gh-issue-146056.nnZIgp.rst | 1 + ...-03-18-18-52-00.gh-issue-146056.r1tVSo.rst | 1 + Modules/_testcapi/unicode.c | 19 +++++++++++++++++++ Objects/listobject.c | 2 +- Objects/unicodeobject.c | 10 ++++++++++ 12 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-18-18-52-00.gh-issue-146056.r1tVSo.rst diff --git a/Doc/c-api/file.rst b/Doc/c-api/file.rst index 0580e4c8f79db0..d89072ab24e241 100644 --- a/Doc/c-api/file.rst +++ b/Doc/c-api/file.rst @@ -123,9 +123,12 @@ the :mod:`io` APIs instead. Write object *obj* to file object *p*. The only supported flag for *flags* is :c:macro:`Py_PRINT_RAW`; if given, the :func:`str` of the object is written - instead of the :func:`repr`. Return ``0`` on success or ``-1`` on failure; the - appropriate exception will be set. + instead of the :func:`repr`. + + If *obj* is ``NULL``, write the string ``""``. + Return ``0`` on success or ``-1`` on failure; the + appropriate exception will be set. .. c:function:: int PyFile_WriteString(const char *s, PyObject *p) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 228b3c8aa30844..44e1220d109d05 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -319,6 +319,8 @@ Object Protocol representation on success, ``NULL`` on failure. This is the equivalent of the Python expression ``repr(o)``. Called by the :func:`repr` built-in function. + If argument is ``NULL``, return the string ``''``. + .. versionchanged:: 3.4 This function now includes a debug assertion to help ensure that it does not silently discard an active exception. @@ -333,6 +335,8 @@ Object Protocol a string similar to that returned by :c:func:`PyObject_Repr` in Python 2. Called by the :func:`ascii` built-in function. + If argument is ``NULL``, return the string ``''``. + .. index:: string; PyObject_Str (C function) @@ -343,6 +347,8 @@ Object Protocol Python expression ``str(o)``. Called by the :func:`str` built-in function and, therefore, by the :func:`print` function. + If argument is ``NULL``, return the string ``''``. + .. versionchanged:: 3.4 This function now includes a debug assertion to help ensure that it does not silently discard an active exception. @@ -358,6 +364,8 @@ Object Protocol a TypeError is raised when *o* is an integer instead of a zero-initialized bytes object. + If argument is ``NULL``, return the :class:`bytes` object ``b''``. + .. c:function:: int PyObject_IsSubclass(PyObject *derived, PyObject *cls) diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index b7d3aaf3227bf0..f12f62a59419b7 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -1874,6 +1874,8 @@ object. Call :c:func:`PyObject_Repr` on *obj* and write the output into *writer*. + *obj* should not be ``NULL``. + On success, return ``0``. On error, set an exception, leave the writer unchanged, and return ``-1``. diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 3d0414f5291fe4..94605a3e26fef7 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -497,6 +497,14 @@ PyAPI_FUNC(int) PyUnicodeWriter_WriteStr( PyAPI_FUNC(int) PyUnicodeWriter_WriteRepr( PyUnicodeWriter *writer, PyObject *obj); + +PyAPI_FUNC(int) _PyUnicodeWriter_WriteReprTrue( + PyUnicodeWriter *writer, + PyObject *obj); +#if defined(Py_BUILD_CORE) || defined(Py_BUILD_CORE_MODULE) +#define PyUnicodeWriter_WriteRepr _PyUnicodeWriter_WriteReprTrue +#endif + PyAPI_FUNC(int) PyUnicodeWriter_WriteSubstring( PyUnicodeWriter *writer, PyObject *str, diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py index 67ed5d0b4f8722..b95b8ba960bd8b 100644 --- a/Lib/test/test_capi/test_list.py +++ b/Lib/test/test_capi/test_list.py @@ -350,6 +350,10 @@ def test_list_extend(self): # CRASHES list_extend(NULL, []) # CRASHES list_extend([], NULL) + def test_uninitialized_list_repr(self): + lst = _testlimitedcapi.list_new(3) + self.assertEqual(repr(lst), '[, , ]') + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index be6c9155831206..3683c008dbcaaf 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -292,6 +292,10 @@ def my_iter(): self.assertEqual(tuple(my_iter()), (TAG, *range(10))) self.assertEqual(tuples, []) + def test_uninitialized_tuple_repr(self): + tup = _testlimitedcapi.tuple_new(3) + self.assertEqual(repr(tup), '(, , )') + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index c8be4f3faa9483..65eb8119aab710 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -1765,6 +1765,14 @@ def test_basic(self): self.assertEqual(writer.finish(), "var=long value 'repr'") + def test_repr_null(self): + writer = self.create_writer(0) + writer.write_utf8(b'var=', -1) + # CRASHES writer.write_repr(NULL) + writer.write_repr_true(NULL) + self.assertEqual(writer.finish(), + "var=") + def test_utf8(self): writer = self.create_writer(0) writer.write_utf8(b"ascii", -1) diff --git a/Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst b/Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst new file mode 100644 index 00000000000000..7c5fc7a0538e21 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst @@ -0,0 +1 @@ +:c:func:`PyUnicodeWriter_WriteRepr` now supports ``NULL`` argument. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-18-18-52-00.gh-issue-146056.r1tVSo.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-18-18-52-00.gh-issue-146056.r1tVSo.rst new file mode 100644 index 00000000000000..67502657047dee --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-18-18-52-00.gh-issue-146056.r1tVSo.rst @@ -0,0 +1 @@ +Fix :meth:`!list.__repr__` for lists containing ``NULL``\ s. diff --git a/Modules/_testcapi/unicode.c b/Modules/_testcapi/unicode.c index e70f5c68bc3b69..22bc4711e022fb 100644 --- a/Modules/_testcapi/unicode.c +++ b/Modules/_testcapi/unicode.c @@ -443,6 +443,7 @@ writer_write_repr(PyObject *self_raw, PyObject *args) if (!PyArg_ParseTuple(args, "O", &obj)) { return NULL; } + NULLABLE(obj); if (PyUnicodeWriter_WriteRepr(self->writer, obj) < 0) { return NULL; @@ -451,6 +452,23 @@ writer_write_repr(PyObject *self_raw, PyObject *args) } +static PyObject* +writer_write_repr_true(PyObject *self_raw, PyObject *obj) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + NULLABLE(obj); + + if (_PyUnicodeWriter_WriteReprTrue(self->writer, obj) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + static PyObject* writer_write_substring(PyObject *self_raw, PyObject *args) { @@ -539,6 +557,7 @@ static PyMethodDef writer_methods[] = { {"write_ucs4", _PyCFunction_CAST(writer_write_ucs4), METH_VARARGS}, {"write_str", _PyCFunction_CAST(writer_write_str), METH_VARARGS}, {"write_repr", _PyCFunction_CAST(writer_write_repr), METH_VARARGS}, + {"write_repr_true", _PyCFunction_CAST(writer_write_repr_true), METH_O}, {"write_substring", _PyCFunction_CAST(writer_write_substring), METH_VARARGS}, {"decodeutf8stateful", _PyCFunction_CAST(writer_decodeutf8stateful), METH_VARARGS}, {"get_pointer", _PyCFunction_CAST(writer_get_pointer), METH_VARARGS}, diff --git a/Objects/listobject.c b/Objects/listobject.c index 98c90665be5721..6407d0ac5833fe 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -595,7 +595,7 @@ list_repr_impl(PyListObject *v) so must refetch the list size on each iteration. */ for (Py_ssize_t i = 0; i < Py_SIZE(v); ++i) { /* Hold a strong reference since repr(item) can mutate the list */ - item = Py_NewRef(v->ob_item[i]); + item = Py_XNewRef(v->ob_item[i]); if (i > 0) { if (PyUnicodeWriter_WriteChar(writer, ',') < 0) { diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 53f219eb185d77..b51815e9215ff5 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -13972,6 +13972,16 @@ PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) return res; } +#undef PyUnicodeWriter_WriteRepr + +int +_PyUnicodeWriter_WriteReprTrue(PyUnicodeWriter *writer, PyObject *obj) +{ + if (obj == NULL) { + return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, "", 6); + } + return PyUnicodeWriter_WriteRepr(writer, obj); +} int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) From 5e3172c3dab5f25f02c3c44eaf9b9f899d18e78d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 20 Mar 2026 14:13:45 +0200 Subject: [PATCH 2/2] Delete Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst --- .../next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst diff --git a/Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst b/Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst deleted file mode 100644 index 7c5fc7a0538e21..00000000000000 --- a/Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst +++ /dev/null @@ -1 +0,0 @@ -:c:func:`PyUnicodeWriter_WriteRepr` now supports ``NULL`` argument.