Skip to content

Commit 4d581fa

Browse files
committed
Fix resize() when shrinking the bytearray
1 parent ba27fe0 commit 4d581fa

2 files changed

Lines changed: 46 additions & 22 deletions

File tree

Lib/test/test_bytes.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,24 +1563,36 @@ def test_resize_error(self):
15631563
# Simple bytearray
15641564
data = b'some data'
15651565
ba = bytearray(data)
1566-
try:
1567-
with self.assertRaises(MemoryError):
1566+
with self.assertRaises(MemoryError):
1567+
try:
15681568
_testcapi.set_nomemory(0)
15691569
ba.resize(1024)
1570-
finally:
1571-
_testcapi.remove_mem_hooks()
1570+
finally:
1571+
_testcapi.remove_mem_hooks()
15721572
self.assertEqual(ba, bytearray(data))
15731573

1574-
# bytearray with non-zero logical start
1574+
# growing bytearray with non-zero logical start
15751575
ba = bytearray(b'0123456789')
15761576
expected = ba[3:]
15771577
del ba[:3]
1578-
try:
1579-
with self.assertRaises(MemoryError):
1578+
with self.assertRaises(MemoryError):
1579+
try:
15801580
_testcapi.set_nomemory(0)
15811581
ba.resize(1024)
1582-
finally:
1583-
_testcapi.remove_mem_hooks()
1582+
finally:
1583+
_testcapi.remove_mem_hooks()
1584+
self.assertEqual(ba, expected)
1585+
1586+
# shrink bytearray with non-zero logical start
1587+
ba = bytearray(b'0123456789')
1588+
expected = ba[3:]
1589+
del ba[:3]
1590+
with self.assertRaises(MemoryError):
1591+
try:
1592+
_testcapi.set_nomemory(0)
1593+
ba.resize(1)
1594+
finally:
1595+
_testcapi.remove_mem_hooks()
15841596
self.assertEqual(ba, expected)
15851597

15861598
def test_take_bytes(self):

Objects/bytearrayobject.c

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -281,19 +281,31 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size)
281281
return -1;
282282
}
283283

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)));
292-
obj->ob_start = obj->ob_bytes;
293-
}
294-
295-
if (_PyBytes_ResizeKeepOnError(&obj->ob_bytes_object, alloc) < 0) {
296-
return -1;
284+
if (logical_offset == 0 || requested_size >= Py_SIZE(self)) {
285+
/* Re-align data to the start of the allocation. */
286+
if (logical_offset > 0) {
287+
/* optimization tradeoff: This is faster than a new allocation when
288+
the number of bytes being removed in a resize is small; for large
289+
size changes it may be better to just make a new bytes object as
290+
_PyBytes_Resize will do a malloc + memcpy internally. */
291+
memmove(obj->ob_bytes, obj->ob_start, Py_SIZE(self));
292+
obj->ob_start = obj->ob_bytes;
293+
}
294+
295+
if (_PyBytes_ResizeKeepOnError(&obj->ob_bytes_object, alloc) < 0) {
296+
return -1;
297+
}
298+
}
299+
else {
300+
// Using memmove() would be unsafe, since _PyBytes_ResizeKeepOnError()
301+
// failure code path would be unable to restore the bytearray to its
302+
// previous state.
303+
PyObject *resized = PyBytes_FromStringAndSize(NULL, requested_size);
304+
if (resized == NULL) {
305+
return -1;
306+
}
307+
memcpy(PyBytes_AS_STRING(resized), obj->ob_start, requested_size);
308+
Py_SETREF(obj->ob_bytes_object, resized);
297309
}
298310

299311
bytearray_reinit_from_bytes(obj, size, alloc);

0 commit comments

Comments
 (0)