From e0f81d474847178b14e938941b2cee73163172e5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 29 Sep 2026 11:31:54 +0200 Subject: [PATCH] gh-158219: Fix bytearray constructor to not use hashed object (#158329) Add _PyBytes_GET_CACHED_HASH() static inline function. Co-authored-by: Cody Maloney (cherry picked from commit 969af80daf09fcd2ce8f358e5f9d54e75f8ef330) --- Include/internal/pycore_bytesobject.h | 16 ++++++++++ Lib/test/test_bytes.py | 30 +++++++++++++++++-- ...-09-28-11-52-49.gh-issue-158219.tNjBVV.rst | 2 ++ Objects/bytearrayobject.c | 3 +- Objects/bytesobject.c | 13 +------- 5 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-11-52-49.gh-issue-158219.tNjBVV.rst diff --git a/Include/internal/pycore_bytesobject.h b/Include/internal/pycore_bytesobject.h index 177e6d10134adbe..be85d9debd8e969 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 c714fc2e76e83c5..c2163a7908f7538 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 @@ -1645,6 +1646,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 000000000000000..691a588e03c2b2f --- /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 e5024b1c74682da..e3ec862939a2412 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -989,7 +989,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 1f0b03beed43b10..2931a1402908dd5 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -62,18 +62,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) /*