https://github.com/python/cpython/commit/b079bef92ca8b99db08243705b2756503beb5ac6
commit: b079bef92ca8b99db08243705b2756503beb5ac6
branch: 3.15
author: Victor Stinner <[email protected]>
committer: encukou <[email protected]>
date: 2026-10-12T14:08:56+02:00
summary:

[3.15] gh-158219: Fix bytearray constructor to not use hashed object (#158329) 
(GH-158397)

Add _PyBytes_GET_CACHED_HASH() static inline function.


(cherry picked from commit 969af80daf09fcd2ce8f358e5f9d54e75f8ef330)

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

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-11-52-49.gh-issue-158219.tNjBVV.rst
M Include/internal/pycore_bytesobject.h
M Lib/test/test_bytes.py
M Objects/bytearrayobject.c
M Objects/bytesobject.c

diff --git a/Include/internal/pycore_bytesobject.h 
b/Include/internal/pycore_bytesobject.h
index 177e6d10134adb..be85d9debd8e96 100644
--- a/Include/internal/pycore_bytesobject.h
+++ b/Include/internal/pycore_bytesobject.h
@@ -73,6 +73,22 @@ _PyBytes_Repeat(char* dest, Py_ssize_t len_dest,
 */
 #define _PyBytesObject_SIZE (offsetof(PyBytesObject, ob_sval) + 1)
 
+
+// Return the cached hash value, or -1 if not cached yet.
+static inline Py_hash_t
+_PyBytes_GET_CACHED_HASH(PyBytesObject *self)
+{
+_Py_COMP_DIAG_PUSH
+_Py_COMP_DIAG_IGNORE_DEPR_DECLS
+#ifdef Py_GIL_DISABLED
+    return _Py_atomic_load_ssize_relaxed(&self->ob_shash);
+#else
+    return self->ob_shash;
+#endif
+_Py_COMP_DIAG_POP
+}
+
+
 /* --- PyBytesWriter ------------------------------------------------------ */
 
 struct PyBytesWriter {
diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py
index c602382ac424d7..1f82d04db3ea5c 100644
--- a/Lib/test/test_bytes.py
+++ b/Lib/test/test_bytes.py
@@ -5,13 +5,14 @@
 """
 
 import array
+import codecs
+import copy
+import functools
 import operator
 import os
+import pickle
 import re
 import sys
-import copy
-import functools
-import pickle
 import tempfile
 import textwrap
 import threading
@@ -1654,6 +1655,29 @@ def test_take_bytes_optimization(self):
         bytes_header_size = sys.getsizeof(b'')
         self.assertEqual(ba.__alloc__(), 499 + bytes_header_size)
 
+    def test_take_bytes_hash(self):
+        # gh-158219: bytearray constructor must not use a bytes object
+        # if its hash value is already cached.
+
+        def encode(string, errors='strict'):
+            encoded = string.encode('utf-8')
+            hash(encoded)   # a codec may hash its own output
+            return encoded, len(string)
+
+        def hashing_codec(name):
+            if name != 'test_take_bytes_hash':
+                return None
+            return codecs.CodecInfo(encode, None, name=name)
+
+        codecs.register(hashing_codec)
+        self.addCleanup(codecs.unregister, hashing_codec)
+
+        ba = bytearray('hello', 'test_take_bytes_hash')
+        ba[0] = ord('H')
+        taken = ba.take_bytes()
+        self.assertEqual(taken, b'Hello')
+        self.assertEqual(hash(taken), hash(b'Hello'))
+
     def test_take_bytes_reentrant_resize(self):
         # gh-153570: n.__index__() can resize the bytearray, so take_bytes()
         # must re-read the size afterwards.  It cached the size before the
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-11-52-49.gh-issue-158219.tNjBVV.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-11-52-49.gh-issue-158219.tNjBVV.rst
new file mode 100644
index 00000000000000..691a588e03c2b2
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-11-52-49.gh-issue-158219.tNjBVV.rst
@@ -0,0 +1,2 @@
+Fix :class:`bytearray` constructor: do not use a bytes object if its hash
+value is already computed. Patch by Cody Maloney and Victor Stinner.
diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c
index 934b091b912730..241d53787aad86 100644
--- a/Objects/bytearrayobject.c
+++ b/Objects/bytearrayobject.c
@@ -992,7 +992,8 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject 
*arg,
 
         /* Most encodes return a new unique bytes, just use it as buffer. */
         if (_PyObject_IsUniquelyReferenced(encoded)
-            && PyBytes_CheckExact(encoded))
+            && PyBytes_CheckExact(encoded)
+            && _PyBytes_GET_CACHED_HASH((PyBytesObject*)encoded) == -1)
         {
             Py_ssize_t size = Py_SIZE(encoded);
             self->ob_bytes_object = encoded;
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 0464baf064ae30..0a8b63ea1dd104 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -63,18 +63,7 @@ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
 _Py_COMP_DIAG_POP
 }
 
-static inline Py_hash_t
-get_ob_shash(PyBytesObject *a)
-{
-_Py_COMP_DIAG_PUSH
-_Py_COMP_DIAG_IGNORE_DEPR_DECLS
-#ifdef Py_GIL_DISABLED
-    return _Py_atomic_load_ssize_relaxed(&a->ob_shash);
-#else
-    return a->ob_shash;
-#endif
-_Py_COMP_DIAG_POP
-}
+#define get_ob_shash(op) _PyBytes_GET_CACHED_HASH(op)
 
 
 /*

_______________________________________________
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