Skip to content

Commit a4f28a5

Browse files
johngvstinner
andauthored
gh-158316: Avoid second buffer fetch on bytearray_decode() (#158485)
Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent ab01d18 commit a4f28a5

3 files changed

Lines changed: 40 additions & 1 deletion

File tree

‎Lib/test/test_bytes.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import textwrap
1919
import threading
2020
import unittest
21+
from _codecs import _unregister_error as _codecs_unregister_error
2122

2223
import test.support
2324
from test import support
@@ -2195,6 +2196,30 @@ def delslice():
21952196
self.assertRaises(BufferError, delslice)
21962197
self.assertEqual(b, orig)
21972198

2199+
def test_decode_resize_forbidden(self):
2200+
# The storage is pinned while it is decoded, so an error handler
2201+
# cannot resize the bytearray.
2202+
b = bytearray(b'ab\xffcd')
2203+
errors = 'test.bytearray_decode_resize'
2204+
def handler(exc):
2205+
self.assertRaises(BufferError, b.clear)
2206+
self.assertRaises(BufferError, b.append, 0)
2207+
return ('?', exc.end)
2208+
self.addCleanup(_codecs_unregister_error, errors)
2209+
codecs.register_error(errors, handler)
2210+
for encoding in 'utf-8', 'utf-8-sig':
2211+
with self.subTest(encoding=encoding):
2212+
self.assertEqual(b.decode(encoding, errors), 'ab?cd')
2213+
self.assertEqual(b, b'ab\xffcd')
2214+
2215+
def test_decode_subclass_buffer(self):
2216+
# decode() decodes the buffer that the object exports.
2217+
class B(bytearray):
2218+
def __buffer__(self, flags):
2219+
return memoryview(b'other')
2220+
self.assertEqual(B(b'mine').decode(), 'other')
2221+
self.assertEqual(B(b'mine').decode('latin-1'), 'other')
2222+
21982223
@test.support.cpython_only
21992224
def test_obsolete_write_lock(self):
22002225
_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+
On Free Threading, speed up :meth:`bytearray.decode` by decoding the underlying
2+
storage directly 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)