Bug description:
array.array('Zd', ...).byteswap() produces incorrect data for arrays
with more than one element. The 16-byte-item loop in array_array_byteswap_impl
(Modules/arraymodule.c) advances the buffer pointer by only 8 bytes per
iteration even though each iteration manually swaps a full 16-byte item
(two independent 8-byte halves, for the real/imaginary double components).
This causes every item after the first to overlap the previous iteration's
byte window and come out scrambled.
Reproducer:
import array
a = array.array('Zd', [1+2j, 3+4j])
raw_before = a.tobytes()
a.byteswap()
raw_after = a.tobytes()
expected = bytearray()
for i in range(0, len(raw_before), 8):
expected += raw_before[i:i+8][::-1]
print(bytes(expected) == raw_after) # False -- should be True
A single byteswap() call gives the wrong bytes for the second item
onward. Calling byteswap() twice happens to round-trip back to the
original value (the corruption is self-canceling under double
application), which is why the existing test suite (test_byteswap in
Lib/test/test_array.py, which only checks a double-call round-trip)
doesn't catch it.
The sibling cases in the same switch statement (case 4:, case 8:)
correctly advance the pointer by the item's own size (4, 8 bytes); the
case 16: block is the only one where the increment doesn't match the
16 bytes actually touched in the loop body.
CPython versions tested on:
CPython main branch (3.16.0a0)
Operating systems tested on:
macOS (arm64)
Linked PRs
Bug description:
array.array('Zd', ...).byteswap()produces incorrect data for arrayswith more than one element. The 16-byte-item loop in
array_array_byteswap_impl(
Modules/arraymodule.c) advances the buffer pointer by only 8 bytes periteration even though each iteration manually swaps a full 16-byte item
(two independent 8-byte halves, for the real/imaginary
doublecomponents).This causes every item after the first to overlap the previous iteration's
byte window and come out scrambled.
Reproducer:
A single
byteswap()call gives the wrong bytes for the second itemonward. Calling
byteswap()twice happens to round-trip back to theoriginal value (the corruption is self-canceling under double
application), which is why the existing test suite (
test_byteswapinLib/test/test_array.py, which only checks a double-call round-trip)doesn't catch it.
The sibling cases in the same switch statement (
case 4:,case 8:)correctly advance the pointer by the item's own size (4, 8 bytes); the
case 16:block is the only one where the increment doesn't match the16 bytes actually touched in the loop body.
CPython versions tested on:
CPython main branch (3.16.0a0)
Operating systems tested on:
macOS (arm64)
Linked PRs