Skip to content

Commit a9a1423

Browse files
committed
Revert bytearray changes
1 parent 306f132 commit a9a1423

3 files changed

Lines changed: 37 additions & 134 deletions

File tree

Lib/test/test_bytes.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"""
66

77
import array
8-
import contextlib
98
import operator
109
import os
1110
import re
@@ -49,19 +48,6 @@ def __index__(self):
4948
return self.value
5049

5150

52-
@contextlib.contextmanager
53-
def inject_memory_error(testcase, start):
54-
# Raise SkipTest if _testcapi extension module is missing
55-
_testcapi = import_helper.import_module('_testcapi')
56-
57-
with testcase.assertRaises(MemoryError):
58-
try:
59-
_testcapi.set_nomemory(start)
60-
yield
61-
finally:
62-
_testcapi.remove_mem_hooks()
63-
64-
6551
class BaseBytesTest:
6652

6753
def assertTypedEqual(self, actual, expected):
@@ -1569,35 +1555,6 @@ def test_resize(self):
15691555
self.assertRaises(MemoryError, bytearray().resize, sys.maxsize)
15701556
self.assertRaises(MemoryError, bytearray(1000).resize, sys.maxsize)
15711557

1572-
def test_resize_error(self):
1573-
# gh-157242: If bytearray.resize() fails (MemoryError),
1574-
# the bytearray must be left unchanged.
1575-
1576-
offset = 3
1577-
for logical_offset in (False, True):
1578-
with self.subTest(logical_offset=logical_offset):
1579-
# grow bytearray
1580-
ba = bytearray(b'0123456789')
1581-
if logical_offset:
1582-
expected = ba[offset:]
1583-
del ba[:offset]
1584-
else:
1585-
expected = ba.copy()
1586-
with inject_memory_error(self, 0):
1587-
ba.resize(1024)
1588-
self.assertEqual(ba, expected)
1589-
1590-
# shrink bytearray
1591-
ba = bytearray(b'0123456789')
1592-
if logical_offset:
1593-
expected = ba[offset:]
1594-
del ba[:offset]
1595-
else:
1596-
expected = ba.copy()
1597-
with inject_memory_error(self, 0):
1598-
ba.resize(1)
1599-
self.assertEqual(ba, expected)
1600-
16011558
def test_take_bytes(self):
16021559
ba = bytearray(b'ab')
16031560
self.assertEqual(ba.take_bytes(), b'ab')
@@ -1662,28 +1619,6 @@ def test_take_bytes(self):
16621619
self.assertEqual(ba, bytearray(b'A'))
16631620
self.assertEqual(ord(b'c'), ord('c'))
16641621

1665-
def test_take_bytes_error(self):
1666-
# gh-157242: If bytearray.take_bytes() fails (MemoryError),
1667-
# the bytearray must be left unchanged.
1668-
1669-
for logical_offset, to_take, mem_errors in (
1670-
(True, 5, (0, 1)),
1671-
(False, 5, (0, 1)),
1672-
(True, None, (0,)),
1673-
):
1674-
for mem_error in mem_errors:
1675-
with self.subTest(logical_offset=logical_offset,
1676-
to_take=to_take, mem_error=mem_error):
1677-
ba = bytearray(b'0123456789')
1678-
if logical_offset:
1679-
expected = ba[3:]
1680-
del ba[:3]
1681-
else:
1682-
expected = ba.copy()
1683-
with inject_memory_error(self, mem_error):
1684-
ba.take_bytes(to_take)
1685-
self.assertEqual(ba, expected)
1686-
16871622
@support.cpython_only # tests an implementation detail
16881623
def test_take_bytes_optimization(self):
16891624
# Validate optimization around taking lots of little chunks out of a

Misc/NEWS.d/next/Core_and_Builtins/2026-09-10-02-50-14.gh-issue-157242.LsqOUJ.rst

Lines changed: 0 additions & 3 deletions
This file was deleted.

Objects/bytearrayobject.c

Lines changed: 37 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,12 @@ _getbytevalue(PyObject* arg, int *value)
4343
return 1;
4444
}
4545

46-
static inline void
47-
bytearray_write_trailing_null_byte(PyByteArrayObject *self)
48-
{
49-
char *data = PyByteArray_AS_STRING(self);
50-
Py_ssize_t size = PyByteArray_GET_SIZE(self);
51-
data[size] = '\0';
52-
}
53-
54-
5546
static void
56-
bytearray_reinit_from_bytes(PyByteArrayObject *self, Py_ssize_t size)
47+
bytearray_reinit_from_bytes(PyByteArrayObject *self, Py_ssize_t size,
48+
Py_ssize_t alloc)
5749
{
58-
Py_ssize_t alloc = PyBytes_GET_SIZE(self->ob_bytes_object);
59-
assert(0 <= size && size <= alloc);
60-
6150
/* Only the empty bytes may be immortal. */
6251
assert((alloc == 0) == _Py_IsImmortal(self->ob_bytes_object));
63-
6452
self->ob_bytes = self->ob_start = PyBytes_AS_STRING(self->ob_bytes_object);
6553
Py_SET_SIZE(self, size);
6654
FT_ATOMIC_STORE_SSIZE_RELAXED(self->ob_alloc, alloc);
@@ -197,7 +185,7 @@ PyByteArray_FromStringAndSize(const char *bytes, Py_ssize_t size)
197185
Py_DECREF(new);
198186
return NULL;
199187
}
200-
bytearray_reinit_from_bytes(new, size);
188+
bytearray_reinit_from_bytes(new, size, size);
201189
if (bytes != NULL && size > 0) {
202190
memcpy(new->ob_bytes, bytes, size);
203191
}
@@ -223,43 +211,6 @@ PyByteArray_AsString(PyObject *self)
223211
return PyByteArray_AS_STRING(self);
224212
}
225213

226-
227-
static int
228-
bytearray_resize_storage(PyByteArrayObject *self,
229-
Py_ssize_t new_size, Py_ssize_t alloc)
230-
{
231-
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
232-
assert(1 <= new_size && new_size <= alloc);
233-
234-
Py_ssize_t size = Py_SIZE(self);
235-
236-
/* Re-align data to the start of the allocation. */
237-
char *old_start = self->ob_start;
238-
if (self->ob_start != self->ob_bytes) {
239-
/* optimization tradeoff: This is faster than a new allocation when
240-
the number of bytes being removed in a resize is small; for
241-
large size changes it may be better to just make a new bytes
242-
object as _PyBytes_Resize will do a malloc + memcpy internally.
243-
*/
244-
Py_ssize_t move = Py_MIN(new_size, size);
245-
memmove(self->ob_bytes, self->ob_start, move);
246-
self->ob_start = self->ob_bytes;
247-
}
248-
249-
if (_PyBytes_ResizeKeepOnError(&self->ob_bytes_object, alloc) < 0) {
250-
if (old_start != self->ob_bytes && new_size < size) {
251-
// Move remaining bytes
252-
Py_ssize_t moved = new_size;
253-
Py_ssize_t remaining = size - moved;
254-
memmove(self->ob_bytes + moved, old_start + moved, remaining);
255-
}
256-
bytearray_write_trailing_null_byte(self);
257-
return -1;
258-
}
259-
return 0;
260-
}
261-
262-
263214
static int
264215
bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size)
265216
{
@@ -295,7 +246,7 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size)
295246
if (requested_size == 0) {
296247
Py_SETREF(obj->ob_bytes_object,
297248
Py_GetConstant(Py_CONSTANT_EMPTY_BYTES));
298-
bytearray_reinit_from_bytes(obj, 0);
249+
bytearray_reinit_from_bytes(obj, 0, 0);
299250
return 0;
300251
}
301252

@@ -310,7 +261,7 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size)
310261
/* Minor downsize; quick exit */
311262
Py_SET_SIZE(self, size);
312263
/* Add mid-buffer null; end provided by bytes. */
313-
bytearray_write_trailing_null_byte(_PyByteArray_CAST(self));
264+
PyByteArray_AS_STRING(self)[size] = '\0'; /* Trailing null */
314265
return 0;
315266
}
316267
}
@@ -330,16 +281,28 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size)
330281
return -1;
331282
}
332283

333-
if (bytearray_resize_storage(obj, requested_size, (Py_ssize_t)alloc) < 0) {
334-
return -1;
284+
/* Re-align data to the start of the allocation. */
285+
if (logical_offset > 0) {
286+
/* optimization tradeoff: This is faster than a new allocation when
287+
the number of bytes being removed in a resize is small; for large
288+
size changes it may be better to just make a new bytes object as
289+
_PyBytes_Resize will do a malloc + memcpy internally. */
290+
memmove(obj->ob_bytes, obj->ob_start,
291+
Py_MIN(requested_size, Py_SIZE(self)));
335292
}
336293

337-
bytearray_reinit_from_bytes(obj, size);
294+
int ret = _PyBytes_Resize(&obj->ob_bytes_object, alloc);
295+
if (ret == -1) {
296+
obj->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
297+
size = alloc = 0;
298+
}
299+
bytearray_reinit_from_bytes(obj, size, alloc);
338300
if (alloc != size) {
339301
/* Add mid-buffer null; end provided by bytes. */
340-
bytearray_write_trailing_null_byte(obj);
302+
obj->ob_bytes[size] = '\0';
341303
}
342-
return 0;
304+
305+
return ret;
343306
}
344307

345308
int
@@ -965,7 +928,7 @@ bytearray_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
965928
}
966929
PyByteArrayObject *self = _PyByteArray_CAST(op);
967930
self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
968-
bytearray_reinit_from_bytes(self, 0);
931+
bytearray_reinit_from_bytes(self, 0, 0);
969932
self->ob_exports = 0;
970933
return op;
971934
}
@@ -1031,9 +994,9 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg,
1031994
if (_PyObject_IsUniquelyReferenced(encoded)
1032995
&& PyBytes_CheckExact(encoded))
1033996
{
1034-
Py_ssize_t size = PyBytes_GET_SIZE(encoded);
997+
Py_ssize_t size = Py_SIZE(encoded);
1035998
self->ob_bytes_object = encoded;
1036-
bytearray_reinit_from_bytes(self, size);
999+
bytearray_reinit_from_bytes(self, size, size);
10371000
return 0;
10381001
}
10391002
new = bytearray_iconcat((PyObject*)self, encoded);
@@ -1157,7 +1120,7 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg,
11571120
/* Append the byte */
11581121
if (Py_SIZE(self) + 1 < self->ob_alloc) {
11591122
Py_SET_SIZE(self, Py_SIZE(self) + 1);
1160-
bytearray_write_trailing_null_byte(self);
1123+
PyByteArray_AS_STRING(self)[Py_SIZE(self)] = '\0';
11611124
}
11621125
else if (PyByteArray_Resize((PyObject *)self, Py_SIZE(self)+1) < 0)
11631126
goto error;
@@ -1647,7 +1610,6 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n)
16471610
}
16481611

16491612
Py_ssize_t remaining_length = size - to_take;
1650-
16511613
// optimization: If taking less than leaving, just copy the small to_take
16521614
// portion out and move ob_start.
16531615
if (to_take < remaining_length) {
@@ -1669,15 +1631,24 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n)
16691631
memcpy(PyBytes_AS_STRING(remaining), self->ob_start + to_take,
16701632
remaining_length);
16711633

1672-
if (bytearray_resize_storage(self, to_take, to_take) < 0) {
1634+
// If the bytes are offset inside the buffer must first align.
1635+
if (self->ob_start != self->ob_bytes) {
1636+
memmove(self->ob_bytes, self->ob_start, to_take);
1637+
self->ob_start = self->ob_bytes;
1638+
}
1639+
1640+
if (_PyBytes_Resize(&self->ob_bytes_object, to_take) == -1) {
1641+
assert(self->ob_bytes_object == NULL);
1642+
self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
1643+
bytearray_reinit_from_bytes(self, 0, 0);
16731644
Py_DECREF(remaining);
16741645
return NULL;
16751646
}
16761647

16771648
// Point the bytearray towards the buffer with the remaining data.
16781649
PyObject *result = self->ob_bytes_object;
16791650
self->ob_bytes_object = remaining;
1680-
bytearray_reinit_from_bytes(self, remaining_length);
1651+
bytearray_reinit_from_bytes(self, remaining_length, remaining_length);
16811652
return result;
16821653
}
16831654

0 commit comments

Comments
 (0)