mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
gh-158219: Fix bytearray constructor to not use hashed object (#158329)
* Add _PyBytes_GET_CACHED_HASH() static inline function. * _PyBytes_IsMutable() makes sure that the hash value is not cached yet. * bytearray_reinit_from_bytes() checks that the bytes object is mutable. Co-authored-by: Cody Maloney <cmaloney@theoreticalchaos.com>
This commit is contained in:
1 parent
cac016736e
commit
969af80daf
5 files changed
+62
-19
No files matched your search
@@ -88,6 +88,22 @@ extern void _PyBytes_CheckOverflow(
|
||||
const char *type_name);
|
||||
#endif
|
||||
|
||||
|
||||
// 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 {
|
||||
|
||||
+28
-4
@@ -5,14 +5,15 @@ the latter should be modernized).
|
||||
"""
|
||||
|
||||
import array
|
||||
import codecs
|
||||
import contextlib
|
||||
import operator
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import copy
|
||||
import functools
|
||||
import operator
|
||||
import os
|
||||
import pickle
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import threading
|
||||
@@ -1706,6 +1707,29 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase):
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
@@ -58,8 +58,13 @@ bytearray_reinit_from_bytes(PyByteArrayObject *self, Py_ssize_t size)
|
||||
Py_ssize_t alloc = PyBytes_GET_SIZE(self->ob_bytes_object);
|
||||
assert(0 <= size && size <= alloc);
|
||||
|
||||
/* Only the empty bytes may be immortal. */
|
||||
assert((alloc == 0) == _Py_IsImmortal(self->ob_bytes_object));
|
||||
if (alloc != 0) {
|
||||
assert(_PyBytes_IsMutable(self->ob_bytes_object));
|
||||
}
|
||||
else {
|
||||
// Use the empty bytes string singleton for an empty bytearray
|
||||
assert(_Py_IsImmortal(self->ob_bytes_object));
|
||||
}
|
||||
|
||||
self->ob_bytes = self->ob_start = PyBytes_AS_STRING(self->ob_bytes_object);
|
||||
Py_SET_SIZE(self, size);
|
||||
@@ -1030,7 +1035,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 = PyBytes_GET_SIZE(encoded);
|
||||
self->ob_bytes_object = encoded;
|
||||
|
||||
+7
-12
@@ -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)
|
||||
|
||||
|
||||
/*
|
||||
@@ -3346,6 +3335,12 @@ _PyBytes_IsMutable(PyObject *self)
|
||||
unsigned char ch = PyBytes_AS_STRING(self)[0];
|
||||
assert(self != (PyObject*)CHARACTER(ch));
|
||||
}
|
||||
|
||||
// gh-158219: The hash value must not be cached yet. Otherwise, it means
|
||||
// that the bytes object was already used in Python somehow (ex: as a
|
||||
// dictionary key).
|
||||
assert(get_ob_shash((PyBytesObject *)self) == -1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in new issue
Block a user