]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-146056: Fix list.__repr__() for lists containing NULLs (GH-146129)
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 19 Mar 2026 07:59:48 +0000 (09:59 +0200)
committerGitHub <noreply@github.com>
Thu, 19 Mar 2026 07:59:48 +0000 (09:59 +0200)
Co-authored-by: Victor Stinner <vstinner@python.org>
Doc/c-api/file.rst
Doc/c-api/object.rst
Doc/c-api/unicode.rst
Lib/test/test_capi/test_list.py
Lib/test/test_capi/test_tuple.py
Lib/test/test_capi/test_unicode.py
Misc/NEWS.d/next/C_API/2026-03-18-20-18-59.gh-issue-146056.nnZIgp.rst [new file with mode: 0644]
Misc/NEWS.d/next/Core_and_Builtins/2026-03-18-18-52-00.gh-issue-146056.r1tVSo.rst [new file with mode: 0644]
Modules/_testcapi/unicode.c
Objects/listobject.c
Objects/unicode_writer.c

index 0580e4c8f79db082aae57b785d8609b885683227..d89072ab24e241d9eebc40d2f8096e1f61bd33f0 100644 (file)
@@ -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 ``"<NULL>"``.
 
+   Return ``0`` on success or ``-1`` on failure; the
+   appropriate exception will be set.
 
 .. c:function:: int PyFile_WriteString(const char *s, PyObject *p)
 
index 15a4b55eab82f08550d81eea827b8bd57d0457ae..eedeb180c6b76061fcd547f729e2fdc73d243cda 100644 (file)
@@ -363,6 +363,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 ``'<NULL>'``.
+
    .. versionchanged:: 3.4
       This function now includes a debug assertion to help ensure that it
       does not silently discard an active exception.
@@ -377,6 +379,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 ``'<NULL>'``.
+
    .. index:: string; PyObject_Str (C function)
 
 
@@ -387,6 +391,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 ``'<NULL>'``.
+
    .. versionchanged:: 3.4
       This function now includes a debug assertion to help ensure that it
       does not silently discard an active exception.
@@ -402,6 +408,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'<NULL>'``.
+
 
 .. c:function:: int PyObject_IsSubclass(PyObject *derived, PyObject *cls)
 
index 4845e0f300278db4f736b395b3d91b46e0594baf..7e9346ea9f7c6d50444043f3003fed7c70bb77d5 100644 (file)
@@ -1887,6 +1887,8 @@ object.
 
    Call :c:func:`PyObject_Repr` on *obj* and write the output into *writer*.
 
+   If *obj* is ``NULL``, write the string ``"<NULL>"`` into *writer*.
+
    On success, return ``0``.
    On error, set an exception, leave the writer unchanged, and return ``-1``.
 
index 67ed5d0b4f8722be61ab0bb55dae23968738c708..b95b8ba960bd8b27a95706c7eba8062faefd0948 100644 (file)
@@ -350,6 +350,10 @@ class CAPITest(unittest.TestCase):
         # CRASHES list_extend(NULL, [])
         # CRASHES list_extend([], NULL)
 
+    def test_uninitialized_list_repr(self):
+        lst = _testlimitedcapi.list_new(3)
+        self.assertEqual(repr(lst), '[<NULL>, <NULL>, <NULL>]')
+
 
 if __name__ == "__main__":
     unittest.main()
index 0c27e81168ff770858f94808d95b0b042e7e11cc..d497ba30b001ac556892287758974540284ffc18 100644 (file)
@@ -73,6 +73,7 @@ class CAPITest(unittest.TestCase):
         self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
         self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX)
 
+
     def test_tuple_fromarray(self):
         # Test PyTuple_FromArray()
         tuple_fromarray = _testcapi.tuple_fromarray
@@ -357,6 +358,10 @@ class CAPITest(unittest.TestCase):
         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), '(<NULL>, <NULL>, <NULL>)')
+
 
 if __name__ == "__main__":
     unittest.main()
index 6a9c60f3a6d75ecc0a5c8c57fc65cfd51462dd59..55120448a8a3ec0105d97692f0bb9530bff6eda8 100644 (file)
@@ -1779,6 +1779,13 @@ class PyUnicodeWriterTest(unittest.TestCase):
         self.assertEqual(writer.finish(),
                          "var=long value 'repr'")
 
+    def test_repr_null(self):
+        writer = self.create_writer(0)
+        writer.write_utf8(b'var=', -1)
+        writer.write_repr(NULL)
+        self.assertEqual(writer.finish(),
+                         "var=<NULL>")
+
     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 (file)
index 0000000..7c5fc7a
--- /dev/null
@@ -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 (file)
index 0000000..6750265
--- /dev/null
@@ -0,0 +1 @@
+Fix :meth:`!list.__repr__` for lists containing ``NULL``\ s.
index 203282dd53dd0a0e2015c548d8ead43c0c2a2de3..668adc5085b4fed9870ee633154df34e7479d331 100644 (file)
@@ -449,6 +449,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;
index 1cc62764e2fd8c3fdfe87311e5e724aef52d77f1..654b8130e70840f7b0e2c9f36625219789340796 100644 (file)
@@ -601,7 +601,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) {
index 2b944bf1ea8cdee5f33358555c11532fb2dd01c6..cd2688e32dfaf8d096b4d678f2f238c1a9495181 100644 (file)
@@ -383,6 +383,10 @@ PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj)
 int
 PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj)
 {
+    if (obj == NULL) {
+        return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, "<NULL>", 6);
+    }
+
     if (Py_TYPE(obj) == &PyLong_Type) {
         return _PyLong_FormatWriter((_PyUnicodeWriter*)writer, obj, 10, 0);
     }