From c8a9982f740e9864212ef388dcca710afff05e40 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Sat, 15 Aug 2026 21:50:17 +0800 Subject: [PATCH 1/2] gh-155850: Fix array unpickling of odd-length 'w' arrays across endianness mformat_descriptors stored a wrong item size for the UTF-16 (4, should be 2) and UTF-32 (8, should be 4) machine formats. The field gates the cross-endian unpickling length check, so an odd-length 'w' array pickled on an opposite- endian machine was rejected with ValueError. --- Lib/test/test_array.py | 22 ++++++++++++------- ...-08-15-15-00-00.gh-issue-155850.ArrUtf.rst | 3 +++ Modules/arraymodule.c | 8 +++---- 3 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-15-15-00-00.gh-issue-155850.ArrUtf.rst diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index c931be6df5fdaa..1740fb45f43d73 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -254,20 +254,26 @@ def test_float16_endianness(self): self.assertEqual(b_be.tolist(), [1.5]) def test_unicode(self): - teststr = "Bonne Journ\xe9e \U0002030a\U00020347" testcases = ( (UTF16_LE, "UTF-16-LE"), (UTF16_BE, "UTF-16-BE"), (UTF32_LE, "UTF-32-LE"), (UTF32_BE, "UTF-32-BE") ) - for testcase in testcases: - mformat_code, encoding = testcase - a = array.array('w', teststr) - b = array_reconstructor( - array.array, 'w', mformat_code, teststr.encode(encoding)) - self.assertEqual(a, b, - msg="{0!r} != {1!r}; testcase={2!r}".format(a, b, testcase)) + # An even and an odd number of code points: the reconstructor's + # slow-path length check used a wrong item size for UTF-16/UTF-32, + # which rejected odd-length input. + for teststr in ("Bonne Journ\xe9e \U0002030a\U00020347", + "Bonne Journ\xe9e \U0002030a\U00020347!"): + for testcase in testcases: + mformat_code, encoding = testcase + a = array.array('w', teststr) + b = array_reconstructor( + array.array, 'w', mformat_code, teststr.encode(encoding)) + self.assertEqual( + a, b, + msg="{0!r} != {1!r}; testcase={2!r}".format( + a, b, testcase)) class BaseTest: diff --git a/Misc/NEWS.d/next/Library/2026-08-15-15-00-00.gh-issue-155850.ArrUtf.rst b/Misc/NEWS.d/next/Library/2026-08-15-15-00-00.gh-issue-155850.ArrUtf.rst new file mode 100644 index 00000000000000..9da84b3f1ccfe4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-15-15-00-00.gh-issue-155850.ArrUtf.rst @@ -0,0 +1,3 @@ +Fix :mod:`array` unpickling of an odd-length ``'w'`` array pickled on a +machine of the opposite endianness; a wrong item size for the UTF-16 and +UTF-32 machine formats made it raise :exc:`ValueError`. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index a0181c083a6036..ebc8bfd667c2a8 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -2050,10 +2050,10 @@ static const struct mformatdescr { {4, 0, 1}, /* 15: IEEE_754_FLOAT_BE */ {8, 0, 0}, /* 16: IEEE_754_DOUBLE_LE */ {8, 0, 1}, /* 17: IEEE_754_DOUBLE_BE */ - {4, 0, 0}, /* 18: UTF16_LE */ - {4, 0, 1}, /* 19: UTF16_BE */ - {8, 0, 0}, /* 20: UTF32_LE */ - {8, 0, 1}, /* 21: UTF32_BE */ + {2, 0, 0}, /* 18: UTF16_LE */ + {2, 0, 1}, /* 19: UTF16_BE */ + {4, 0, 0}, /* 20: UTF32_LE */ + {4, 0, 1}, /* 21: UTF32_BE */ {8, 0, 0}, /* 22: IEEE_754_FLOAT_COMPLEX_LE */ {8, 0, 1}, /* 23: IEEE_754_FLOAT_COMPLEX_BE */ {16, 0, 0}, /* 24: IEEE_754_DOUBLE_COMPLEX_LE */ From 73d3aa29da157af41e4c6f9e21601b12029d2029 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Sat, 15 Aug 2026 22:29:40 +0800 Subject: [PATCH 2/2] Trim the regression test comment --- Lib/test/test_array.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index 1740fb45f43d73..71f4f586b97489 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -260,9 +260,7 @@ def test_unicode(self): (UTF32_LE, "UTF-32-LE"), (UTF32_BE, "UTF-32-BE") ) - # An even and an odd number of code points: the reconstructor's - # slow-path length check used a wrong item size for UTF-16/UTF-32, - # which rejected odd-length input. + # An even and an odd length; odd lengths were wrongly rejected. for teststr in ("Bonne Journ\xe9e \U0002030a\U00020347", "Bonne Journ\xe9e \U0002030a\U00020347!"): for testcase in testcases: