From 8d46f961c30b795357df9a8cde479742562d8fc5 Mon Sep 17 00:00:00 2001 From: Hauke D Date: Thu, 25 Dec 2025 12:34:44 +0100 Subject: [PATCH 1/4] gh-143103: Added pad parameter to base64.z85encode() (GH-143106) This makes it analogous to a85encode() and b85encode() and allows the user to more easily meet the Z85 specification, which requires input lengths to be a multiple of 4. --- Doc/library/base64.rst | 8 +++++++- Lib/base64.py | 4 ++-- Lib/test/test_base64.py | 16 ++++++++++++++++ Misc/ACKS | 1 + ...025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst | 1 + 5 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst diff --git a/Doc/library/base64.rst b/Doc/library/base64.rst index 529a7242443820..2d901824335145 100644 --- a/Doc/library/base64.rst +++ b/Doc/library/base64.rst @@ -267,14 +267,20 @@ Refer to the documentation of the individual functions for more information. .. versionadded:: 3.4 -.. function:: z85encode(s) +.. function:: z85encode(s, pad=False) Encode the :term:`bytes-like object` *s* using Z85 (as used in ZeroMQ) and return the encoded :class:`bytes`. See `Z85 specification `_ for more information. + If *pad* is true, the input is padded with ``b'\0'`` so its length is a + multiple of 4 bytes before encoding. + .. versionadded:: 3.13 + .. versionchanged:: next + The *pad* parameter was added. + .. function:: z85decode(s) diff --git a/Lib/base64.py b/Lib/base64.py index 341bf8eaf1891e..c2fdee8eab9690 100644 --- a/Lib/base64.py +++ b/Lib/base64.py @@ -508,9 +508,9 @@ def b85decode(b): ) _z85_encode_translation = bytes.maketrans(_b85alphabet, _z85alphabet) -def z85encode(s): +def z85encode(s, pad=False): """Encode bytes-like object b in z85 format and return a bytes object.""" - return b85encode(s).translate(_z85_encode_translation) + return b85encode(s, pad).translate(_z85_encode_translation) def z85decode(s): """Decode the z85-encoded bytes-like object or ASCII string b diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py index ac3f09405457df..288caf663e8321 100644 --- a/Lib/test/test_base64.py +++ b/Lib/test/test_base64.py @@ -665,6 +665,7 @@ def test_z85encode(self): tests = { b'': b'', + b'\x86\x4F\xD2\x6F\xB5\x59\xF7\x5B': b'HelloWorld', b'www.python.org': b'CxXl-AcVLsz/dgCA+t', bytes(range(255)): b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x""" b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD""" @@ -840,6 +841,21 @@ def test_b85_padding(self): eq(base64.b85decode(b'czAet'), b"xxxx") eq(base64.b85decode(b'czAetcmMzZ'), b"xxxxx\x00\x00\x00") + def test_z85_padding(self): + eq = self.assertEqual + + eq(base64.z85encode(b"x", pad=True), b'CMmZz') + eq(base64.z85encode(b"xx", pad=True), b'CZ6h*') + eq(base64.z85encode(b"xxx", pad=True), b'CZaDk') + eq(base64.z85encode(b"xxxx", pad=True), b'CZaET') + eq(base64.z85encode(b"xxxxx", pad=True), b'CZaETCMmZz') + + eq(base64.z85decode(b'CMmZz'), b"x\x00\x00\x00") + eq(base64.z85decode(b'CZ6h*'), b"xx\x00\x00") + eq(base64.z85decode(b'CZaDk'), b"xxx\x00") + eq(base64.z85decode(b'CZaET'), b"xxxx") + eq(base64.z85decode(b'CZaETCMmZz'), b"xxxxx\x00\x00\x00") + def test_a85decode_errors(self): illegal = (set(range(32)) | set(range(118, 256))) - set(b' \t\n\r\v') for c in illegal: diff --git a/Misc/ACKS b/Misc/ACKS index a14089a39cce82..bb6b6bde822a4e 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -418,6 +418,7 @@ Lisandro Dalcin Darren Dale Andrew Dalke Lars Damerow +Hauke Dämpfling Evan Dandrea Eric Daniel Scott David Daniels diff --git a/Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst b/Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst new file mode 100644 index 00000000000000..b00c03707ca352 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-23-17-07-22.gh-issue-143103.LRjXEW.rst @@ -0,0 +1 @@ +Add padding support to :func:`base64.z85encode` via the ``pad`` parameter. From 579c5b496b467a2b175cb30caa4f6873cb13c9a1 Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Thu, 25 Dec 2025 22:24:25 +0800 Subject: [PATCH 2/4] gh-143145: Fix possible reference leak in ctypes _build_result() (GH-143131) The result tuple was leaked if __ctypes_from_outparam__() failed for any item. Signed-off-by: Yongtao Huang --- .../next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst | 1 + Modules/_ctypes/_ctypes.c | 1 + 2 files changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst diff --git a/Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst b/Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst new file mode 100644 index 00000000000000..2aff1090b1812f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-24-14-18-52.gh-issue-143145.eXLw8D.rst @@ -0,0 +1 @@ +Fixed a possible reference leak in ctypes when constructing results with multiple output parameters on error. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 774ac71ce9ec56..563e95a762599b 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -4557,6 +4557,7 @@ _build_result(PyObject *result, PyObject *callargs, v = PyTuple_GET_ITEM(callargs, i); v = PyObject_CallMethodNoArgs(v, &_Py_ID(__ctypes_from_outparam__)); if (v == NULL || numretvals == 1) { + Py_XDECREF(tup); Py_DECREF(callargs); return v; } From 8611f74e089d9ac9de84dd42be9d251db27889aa Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 25 Dec 2025 11:31:41 -0500 Subject: [PATCH 3/4] gh-142975: During GC, mark frozen objects with a merged zero refcount for destruction (GH-143156) --- Lib/test/test_free_threading/test_gc.py | 32 +++++++++++++++++++ ...-12-24-13-44-24.gh-issue-142975.8C4vIP.rst | 2 ++ Python/gc_free_threading.c | 6 +++- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst diff --git a/Lib/test/test_free_threading/test_gc.py b/Lib/test/test_free_threading/test_gc.py index 3b83e0431efa6b..8b45b6e2150c28 100644 --- a/Lib/test/test_free_threading/test_gc.py +++ b/Lib/test/test_free_threading/test_gc.py @@ -62,6 +62,38 @@ def mutator_thread(): with threading_helper.start_threads(gcs + mutators): pass + def test_freeze_object_in_brc_queue(self): + # GH-142975: Freezing objects in the BRC queue could result in some + # objects having a zero refcount without being deallocated. + + class Weird: + # We need a destructor to trigger the check for object resurrection + def __del__(self): + pass + + # This is owned by the main thread, so the subthread will have to increment + # this object's reference count. + weird = Weird() + + def evil(): + gc.freeze() + + # Decrement the reference count from this thread, which will trigger the + # slow path during resurrection and add our weird object to the BRC queue. + nonlocal weird + del weird + + # Collection will merge the object's reference count and make it zero. + gc.collect() + + # Unfreeze the object, making it visible to the GC. + gc.unfreeze() + gc.collect() + + thread = Thread(target=evil) + thread.start() + thread.join() + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst new file mode 100644 index 00000000000000..9d7f57ee60aa47 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-24-13-44-24.gh-issue-142975.8C4vIP.rst @@ -0,0 +1,2 @@ +Fix crash after unfreezing all objects tracked by the garbage collector on +the :term:`free threaded ` build. diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 04b9b8f3f85603..51261cea0cfe2c 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -906,7 +906,11 @@ gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, gc_mark_args_t *ar static void queue_untracked_obj_decref(PyObject *op, struct collection_state *state) { - if (!_PyObject_GC_IS_TRACKED(op)) { + assert(Py_REFCNT(op) == 0); + // gh-142975: We have to treat frozen objects as untracked in this function + // or else they might be picked up in a future collection, which breaks the + // assumption that all incoming objects have a non-zero reference count. + if (!_PyObject_GC_IS_TRACKED(op) || gc_is_frozen(op)) { // GC objects with zero refcount are handled subsequently by the // GC as if they were cyclic trash, but we have to handle dead // non-GC objects here. Add one to the refcount so that we can From b9a48064306229287d7211e9510f578065e457fc Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Fri, 26 Dec 2025 01:08:43 +0800 Subject: [PATCH 4/4] gh-143164: Fix incorrect error message for ctypes bitfield overflow (GH-143165) Signed-off-by: Yongtao Huang --- Lib/test/test_ctypes/test_struct_fields.py | 15 +++++++++++++++ ...2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst | 1 + Modules/_ctypes/cfield.c | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst diff --git a/Lib/test/test_ctypes/test_struct_fields.py b/Lib/test/test_ctypes/test_struct_fields.py index b50bbcbb65c423..dc26e26d8a9fb1 100644 --- a/Lib/test/test_ctypes/test_struct_fields.py +++ b/Lib/test/test_ctypes/test_struct_fields.py @@ -130,6 +130,21 @@ class S(Structure): self.check_struct(S) self.assertEqual(S.largeField.bit_size, size * 8) + def test_bitfield_overflow_error_message(self): + with self.assertRaisesRegex( + ValueError, + r"bit field 'x' overflows its type \(2 \+ 7 > 8\)", + ): + CField( + name="x", + type=c_byte, + byte_size=1, + byte_offset=0, + index=0, + _internal_use=True, + bit_size=7, + bit_offset=2, + ) # __set__ and __get__ should raise a TypeError in case their self # argument is not a ctype instance. diff --git a/Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst b/Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst new file mode 100644 index 00000000000000..e75270b9e94c03 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-25-08-58-55.gh-issue-142164.XrFztf.rst @@ -0,0 +1 @@ +Fix the ctypes bitfield overflow error message to report the correct offset and size calculation. diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index 547e2471a1cbc0..4ebca0e0b3db0a 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -160,8 +160,8 @@ PyCField_new_impl(PyTypeObject *type, PyObject *name, PyObject *proto, if ((bitfield_size + bit_offset) > byte_size * 8) { PyErr_Format( PyExc_ValueError, - "bit field %R overflows its type (%zd + %zd >= %zd)", - name, bit_offset, byte_size*8); + "bit field %R overflows its type (%zd + %zd > %zd)", + name, bit_offset, bitfield_size, byte_size * 8); goto error; } }