Skip to content

Commit 7b1b994

Browse files
committed
gh-154566: Fix array.byteswap() corrupting 'Zd' arrays with more than one element
The 16-byte-item loop in array_array_byteswap_impl() advanced the buffer pointer by only 8 bytes per iteration, even though each iteration swaps a full 16-byte item (two independent 8-byte halves for the real/imaginary double components). This caused every item after the first to overlap the previous iteration's byte window and come out scrambled. A single byteswap() call gave wrong bytes for the second item onward. Calling byteswap() twice happened to round-trip back to the original value (the corruption is self-canceling under double application), which is why the existing test suite -- which only checked a double-call round-trip -- didn't catch it. Added a regression test that checks the actual byte-level result of a single call.
1 parent 66e313f commit 7b1b994

3 files changed

Lines changed: 21 additions & 1 deletion

File tree

Lib/test/test_array.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,20 @@ class ComplexDoubleTest(CFPTest, unittest.TestCase):
16111611
typecode = 'Zd'
16121612
minitemsize = 16
16131613

1614+
def test_byteswap_single_call_result(self):
1615+
# A single byteswap() must swap each 16-byte item's two 8-byte
1616+
# halves (real, imag) independently. The pre-existing test only
1617+
# checked that byteswap() twice round-trips to the original,
1618+
# which passes even if a single call scrambles multi-item arrays.
1619+
a = array.array(self.typecode, [1+2j, 3+4j, 5+6j])
1620+
original = a.tobytes()
1621+
a.byteswap()
1622+
expected = bytearray()
1623+
for i in range(0, len(original), 16):
1624+
item = original[i:i + 16]
1625+
expected += item[7::-1] + item[15:7:-1]
1626+
self.assertEqual(a.tobytes(), bytes(expected))
1627+
16141628

16151629
class LargeArrayTest(unittest.TestCase):
16161630
typecode = 'b'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix :meth:`array.array.byteswap` corrupting data for ``'Zd'`` (complex
2+
double) arrays with more than one element: the 16-byte item loop advanced
3+
the buffer pointer by only 8 bytes per iteration, causing items after the
4+
first to be scrambled. A single :meth:`!byteswap` call produced incorrect
5+
results while a double call happened to round-trip back to the original
6+
value, which is why the existing test suite did not catch it.

Modules/arraymodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1617,7 +1617,7 @@ array_array_byteswap_impl(arrayobject *self)
16171617
break;
16181618
case 16:
16191619
assert(strcmp(self->ob_descr->typecode, "Zd") == 0);
1620-
for (p = self->ob_item, i = Py_SIZE(self); --i >= 0; p += 8) {
1620+
for (p = self->ob_item, i = Py_SIZE(self); --i >= 0; p += 16) {
16211621
char t0 = p[0];
16221622
char t1 = p[1];
16231623
char t2 = p[2];

0 commit comments

Comments
 (0)