Skip to content

Commit 920cc2b

Browse files
committed
Avoid second buffer fetch on bytearray_decode
1 parent 77c0675 commit 920cc2b

3 files changed

Lines changed: 39 additions & 1 deletion

File tree

‎Lib/test/test_bytes.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,6 +2195,30 @@ def delslice():
21952195
self.assertRaises(BufferError, delslice)
21962196
self.assertEqual(b, orig)
21972197

2198+
def test_decode_resize_forbidden(self):
2199+
# The storage is pinned while it is decoded, so an error handler
2200+
# cannot resize the bytearray.
2201+
b = bytearray(b'ab\xffcd')
2202+
def handler(exc):
2203+
self.assertRaises(BufferError, b.clear)
2204+
self.assertRaises(BufferError, b.append, 0)
2205+
return ('?', exc.end)
2206+
codecs.register_error('test.bytearray_decode_resize', handler)
2207+
for encoding in 'utf-8', 'utf-8-sig':
2208+
with self.subTest(encoding=encoding):
2209+
self.assertEqual(
2210+
b.decode(encoding, 'test.bytearray_decode_resize'),
2211+
'ab?cd')
2212+
self.assertEqual(b, b'ab\xffcd')
2213+
2214+
def test_decode_subclass_buffer(self):
2215+
# decode() decodes the buffer that the object exports.
2216+
class B(bytearray):
2217+
def __buffer__(self, flags):
2218+
return memoryview(b'other')
2219+
self.assertEqual(B(b'mine').decode(), 'other')
2220+
self.assertEqual(B(b'mine').decode('latin-1'), 'other')
2221+
21982222
@test.support.cpython_only
21992223
def test_obsolete_write_lock(self):
22002224
_testcapi = import_helper.import_module('_testcapi')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up :meth:`bytearray.decode` by decoding the underlying storage directly
2+
instead of going through the buffer protocol.

‎Objects/bytearrayobject.c‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2599,7 +2599,19 @@ bytearray_decode_impl(PyByteArrayObject *self, const char *encoding,
25992599
{
26002600
if (encoding == NULL)
26012601
encoding = PyUnicode_GetDefaultEncoding();
2602-
return PyUnicode_FromEncodedObject((PyObject*)self, encoding, errors);
2602+
if (Py_TYPE(self)->tp_as_buffer->bf_getbuffer != bytearray_getbuffer) {
2603+
/* A subclass may export a different buffer. */
2604+
return PyUnicode_FromEncodedObject((PyObject*)self, encoding, errors);
2605+
}
2606+
2607+
/* Decode the storage directly instead of exporting a buffer, which
2608+
would re-acquire the critical section we already hold. Increase
2609+
exports to prevent the storage from changing during the decode. */
2610+
self->ob_exports++;
2611+
PyObject *res = PyUnicode_Decode(PyByteArray_AS_STRING(self),
2612+
Py_SIZE(self), encoding, errors);
2613+
self->ob_exports--;
2614+
return res;
26032615
}
26042616

26052617
PyDoc_STRVAR(alloc_doc,

0 commit comments

Comments
 (0)