https://github.com/python/cpython/commit/a728f1cffac2925099adace65ee2fa600c542f75
commit: a728f1cffac2925099adace65ee2fa600c542f75
branch: 3.13
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-13T20:18:34+02:00
summary:

[3.13] gh-156995: Fix _PyBytes_Resize() in Free Threading (#156996) (#157373) 
(#157436)

[3.14] gh-156995: Fix _PyBytes_Resize() in Free Threading (#156996) (#157373)

_PyBytes_Resize(&obj, 1) no longer returns a single byte singleton if
the reference count is greater than 1.

(cherry picked from commit fd569ea94120815b01baf8bcbcf21e8e09102db6)

Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: Cody Maloney <[email protected]>

files:
A Misc/NEWS.d/next/C_API/2026-09-12-21-00-42.gh-issue-156995.UVjqQe.rst
M Lib/test/test_capi/test_bytes.py
M Objects/bytesobject.c

diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py
index 35b725ae34a1edc..b67948a644e682b 100644
--- a/Lib/test/test_capi/test_bytes.py
+++ b/Lib/test/test_capi/test_bytes.py
@@ -1,3 +1,4 @@
+import sys
 import unittest
 from test.support import import_helper
 
@@ -231,26 +232,43 @@ def test_decodeescape(self):
 
     def test_resize(self):
         """Test _PyBytes_Resize()"""
-        resize = _testcapi.bytes_resize
+        _resize = _testcapi.bytes_resize
+        empty_singleton = bytes()
+
+        def resize(obj, size, new):
+            result = _resize(obj, size, new)
+            if 1 <= len(result):
+                if new or size != len(obj):
+                    # gh-156995: Make sure that the result is a fresh object.
+                    # Previously, _PyBytes_Resize(&obj, 1) returned a singleton
+                    # if the reference count is higher than 1.
+                    # Expect 2 references since sys.getrefcount() call adds
+                    # a reference.
+                    self.assertEqual(sys.getrefcount(result), 2)
+            else:
+                # check that the result is the empty bytes string singleton
+                self.assertIs(result, empty_singleton)
+            return result
 
         for new in True, False:
-            self.assertEqual(resize(b'abc', 0, new), b'')
-            self.assertEqual(resize(b'abc', 1, new), b'a')
-            self.assertEqual(resize(b'abc', 2, new), b'ab')
-            self.assertEqual(resize(b'abc', 3, new), b'abc')
-            b = resize(b'abc', 4, new)
-            self.assertEqual(len(b), 4)
-            self.assertEqual(b[:3], b'abc')
-
-            self.assertEqual(resize(b'a', 0, new), b'')
-            self.assertEqual(resize(b'a', 1, new), b'a')
-            b = resize(b'a', 2, new)
-            self.assertEqual(len(b), 2)
-            self.assertEqual(b[:1], b'a')
-
-            self.assertEqual(resize(b'', 0, new), b'')
-            self.assertEqual(len(resize(b'', 1, new)), 1)
-            self.assertEqual(len(resize(b'', 2, new)), 2)
+            with self.subTest(new=new):
+                self.assertEqual(resize(b'abc', 0, new), b'')
+                self.assertEqual(resize(b'abc', 1, new), b'a')
+                self.assertEqual(resize(b'abc', 2, new), b'ab')
+                self.assertEqual(resize(b'abc', 3, new), b'abc')
+                b = resize(b'abc', 4, new)
+                self.assertEqual(len(b), 4)
+                self.assertEqual(b[:3], b'abc')
+
+                self.assertEqual(resize(b'a', 0, new), b'')
+                self.assertEqual(resize(b'a', 1, new), b'a')
+                b = resize(b'a', 2, new)
+                self.assertEqual(len(b), 2)
+                self.assertEqual(b[:1], b'a')
+
+                self.assertEqual(resize(b'', 0, new), b'')
+                self.assertEqual(len(resize(b'', 1, new)), 1)
+                self.assertEqual(len(resize(b'', 2, new)), 2)
 
         self.assertRaises(SystemError, resize, b'abc', -1, False)
         self.assertRaises(SystemError, resize, bytearray(b'abc'), 3, False)
diff --git 
a/Misc/NEWS.d/next/C_API/2026-09-12-21-00-42.gh-issue-156995.UVjqQe.rst 
b/Misc/NEWS.d/next/C_API/2026-09-12-21-00-42.gh-issue-156995.UVjqQe.rst
new file mode 100644
index 000000000000000..11629eca29bdcf2
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-09-12-21-00-42.gh-issue-156995.UVjqQe.rst
@@ -0,0 +1,2 @@
+``_PyBytes_Resize(&obj, 1)`` no longer returns a single byte singleton if the
+reference count is greater than ``1``. Patch by Stan Ulbrych.
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index af6ca7270b007ba..fb5526370a54ab8 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -3192,14 +3192,11 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
         return 0;
     }
     if (Py_REFCNT(v) != 1) {
-        if (oldsize < newsize) {
-            *pv = _PyBytes_FromSize(newsize, 0);
-            if (*pv) {
-                memcpy(PyBytes_AS_STRING(*pv), PyBytes_AS_STRING(v), oldsize);
-            }
-        }
-        else {
-            *pv = PyBytes_FromStringAndSize(PyBytes_AS_STRING(v), newsize);
+        // Allocate and then copy so we don't get a one-character singleton!
+        *pv = _PyBytes_FromSize(newsize, 0);
+        if (*pv) {
+            memcpy(PyBytes_AS_STRING(*pv), PyBytes_AS_STRING(v),
+                   Py_MIN(oldsize, newsize));
         }
         Py_DECREF(v);
         return (*pv == NULL) ? -1 : 0;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to