Skip to content

Commit f51cba5

Browse files
committed
Fix take_bytes()
1 parent 4d581fa commit f51cba5

2 files changed

Lines changed: 43 additions & 8 deletions

File tree

Lib/test/test_bytes.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,25 @@ def test_take_bytes(self):
16591659
self.assertEqual(ba, bytearray(b'A'))
16601660
self.assertEqual(ord(b'c'), ord('c'))
16611661

1662+
def test_take_bytes_error(self):
1663+
# gh-157242: If bytearray.take_bytes() fails (memory allocation
1664+
# failure), the bytearray must be left unchanged.
1665+
_testcapi = import_helper.import_module('_testcapi')
1666+
1667+
for mem_error in (0, 1):
1668+
for to_take in (5, None):
1669+
with self.subTest(mem_error=mem_error, to_take=to_take):
1670+
ba = bytearray(b'0123456789')
1671+
expected = ba[3:]
1672+
del ba[:3]
1673+
with self.assertRaises(MemoryError):
1674+
try:
1675+
_testcapi.set_nomemory(mem_error)
1676+
ba.take_bytes(5)
1677+
finally:
1678+
_testcapi.remove_mem_hooks()
1679+
self.assertEqual(ba, expected)
1680+
16621681
@support.cpython_only # tests an implementation detail
16631682
def test_take_bytes_optimization(self):
16641683
# Validate optimization around taking lots of little chunks out of a

Objects/bytearrayobject.c

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,7 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n)
16211621
}
16221622

16231623
Py_ssize_t remaining_length = size - to_take;
1624+
16241625
// optimization: If taking less than leaving, just copy the small to_take
16251626
// portion out and move ob_start.
16261627
if (to_take < remaining_length) {
@@ -1642,15 +1643,30 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n)
16421643
memcpy(PyBytes_AS_STRING(remaining), self->ob_start + to_take,
16431644
remaining_length);
16441645

1645-
// If the bytes are offset inside the buffer must first align.
1646-
if (self->ob_start != self->ob_bytes) {
1647-
memmove(self->ob_bytes, self->ob_start, to_take);
1648-
self->ob_start = self->ob_bytes;
1649-
}
1646+
size_t logical_offset = (size_t) (self->ob_start - self->ob_bytes);
1647+
if (logical_offset == 0 || remaining_length == 0) {
1648+
// If the bytes are offset inside the buffer must first align.
1649+
if (logical_offset != 0) {
1650+
memmove(self->ob_bytes, self->ob_start, to_take);
1651+
self->ob_start = self->ob_bytes;
1652+
}
16501653

1651-
if (_PyBytes_ResizeKeepOnError(&self->ob_bytes_object, to_take) == -1) {
1652-
Py_DECREF(remaining);
1653-
return NULL;
1654+
if (_PyBytes_ResizeKeepOnError(&self->ob_bytes_object, to_take) == -1) {
1655+
Py_DECREF(remaining);
1656+
return NULL;
1657+
}
1658+
}
1659+
else {
1660+
// Using memmove() would be unsafe, since _PyBytes_ResizeKeepOnError()
1661+
// failure code path would be unable to restore the bytearray to its
1662+
// previous state.
1663+
PyObject *resized = PyBytes_FromStringAndSize(NULL, to_take);
1664+
if (resized == NULL) {
1665+
Py_DECREF(remaining);
1666+
return NULL;
1667+
}
1668+
memcpy(PyBytes_AS_STRING(resized), self->ob_start, to_take);
1669+
Py_SETREF(self->ob_bytes_object, resized);
16541670
}
16551671

16561672
// Point the bytearray towards the buffer with the remaining data.

0 commit comments

Comments
 (0)