Skip to content

Commit b079bef

Browse files
vstinnercmaloney
andauthored
[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 969af80) Co-authored-by: Cody Maloney <cmaloney@theoreticalchaos.com>
1 parent ad0a18a commit b079bef

5 files changed

Lines changed: 48 additions & 16 deletions

File tree

‎Include/internal/pycore_bytesobject.h‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ _PyBytes_Repeat(char* dest, Py_ssize_t len_dest,
7373
*/
7474
#define _PyBytesObject_SIZE (offsetof(PyBytesObject, ob_sval) + 1)
7575

76+
77+
// Return the cached hash value, or -1 if not cached yet.
78+
static inline Py_hash_t
79+
_PyBytes_GET_CACHED_HASH(PyBytesObject *self)
80+
{
81+
_Py_COMP_DIAG_PUSH
82+
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
83+
#ifdef Py_GIL_DISABLED
84+
return _Py_atomic_load_ssize_relaxed(&self->ob_shash);
85+
#else
86+
return self->ob_shash;
87+
#endif
88+
_Py_COMP_DIAG_POP
89+
}
90+
91+
7692
/* --- PyBytesWriter ------------------------------------------------------ */
7793

7894
struct PyBytesWriter {

‎Lib/test/test_bytes.py‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
"""
66

77
import array
8+
import codecs
9+
import copy
10+
import functools
811
import operator
912
import os
13+
import pickle
1014
import re
1115
import sys
12-
import copy
13-
import functools
14-
import pickle
1516
import tempfile
1617
import textwrap
1718
import threading
@@ -1654,6 +1655,29 @@ def test_take_bytes_optimization(self):
16541655
bytes_header_size = sys.getsizeof(b'')
16551656
self.assertEqual(ba.__alloc__(), 499 + bytes_header_size)
16561657

1658+
def test_take_bytes_hash(self):
1659+
# gh-158219: bytearray constructor must not use a bytes object
1660+
# if its hash value is already cached.
1661+
1662+
def encode(string, errors='strict'):
1663+
encoded = string.encode('utf-8')
1664+
hash(encoded) # a codec may hash its own output
1665+
return encoded, len(string)
1666+
1667+
def hashing_codec(name):
1668+
if name != 'test_take_bytes_hash':
1669+
return None
1670+
return codecs.CodecInfo(encode, None, name=name)
1671+
1672+
codecs.register(hashing_codec)
1673+
self.addCleanup(codecs.unregister, hashing_codec)
1674+
1675+
ba = bytearray('hello', 'test_take_bytes_hash')
1676+
ba[0] = ord('H')
1677+
taken = ba.take_bytes()
1678+
self.assertEqual(taken, b'Hello')
1679+
self.assertEqual(hash(taken), hash(b'Hello'))
1680+
16571681
def test_take_bytes_reentrant_resize(self):
16581682
# gh-153570: n.__index__() can resize the bytearray, so take_bytes()
16591683
# must re-read the size afterwards. It cached the size before the
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :class:`bytearray` constructor: do not use a bytes object if its hash
2+
value is already computed. Patch by Cody Maloney and Victor Stinner.

‎Objects/bytearrayobject.c‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,8 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg,
992992

993993
/* Most encodes return a new unique bytes, just use it as buffer. */
994994
if (_PyObject_IsUniquelyReferenced(encoded)
995-
&& PyBytes_CheckExact(encoded))
995+
&& PyBytes_CheckExact(encoded)
996+
&& _PyBytes_GET_CACHED_HASH((PyBytesObject*)encoded) == -1)
996997
{
997998
Py_ssize_t size = Py_SIZE(encoded);
998999
self->ob_bytes_object = encoded;

‎Objects/bytesobject.c‎

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,7 @@ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
6363
_Py_COMP_DIAG_POP
6464
}
6565

66-
static inline Py_hash_t
67-
get_ob_shash(PyBytesObject *a)
68-
{
69-
_Py_COMP_DIAG_PUSH
70-
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
71-
#ifdef Py_GIL_DISABLED
72-
return _Py_atomic_load_ssize_relaxed(&a->ob_shash);
73-
#else
74-
return a->ob_shash;
75-
#endif
76-
_Py_COMP_DIAG_POP
77-
}
66+
#define get_ob_shash(op) _PyBytes_GET_CACHED_HASH(op)
7867

7968

8069
/*

0 commit comments

Comments
 (0)