Skip to content

Commit 5f92e38

Browse files
gpsheadjunnplus
andauthored
[3.14] gh-88336: Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList (GH-26328) (#161117)
* bpo-44170: Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList * Round up the allocated size instead of rejecting the exact fit Allocating one extra alignment block for a value whose encoded size is a multiple of the alignment made a copy of the list wider than the original. Round the size up instead, so that it is the same for the same value, and keep allowing a value which fills the slot exactly: rejecting it broke setting an 8-byte string in a slot of an integer. --------- (cherry picked from commit 9348e13) Co-authored-by: Jun <junnplus@gmail.com>
1 parent 9fc07bf commit 5f92e38

3 files changed

Lines changed: 23 additions & 15 deletions

File tree

‎Lib/multiprocessing/shared_memory.py‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,21 @@ def _extract_recreation_code(value):
305305
else:
306306
return 3 # NoneType
307307

308+
@staticmethod
309+
def _encode_value(value):
310+
if not isinstance(value, str):
311+
return value
312+
else:
313+
return value.encode(_encoding)
314+
308315
def __init__(self, sequence=None, *, name=None):
309316
if name is None or sequence is not None:
310317
sequence = sequence or ()
311318
_formats = [
312319
self._types_mapping[type(item)]
313320
if not isinstance(item, (str, bytes))
314321
else self._types_mapping[type(item)] % (
315-
self._alignment * (len(item) // self._alignment + 1),
322+
self._alignment * ((len(self._encode_value(item)) - 1) // self._alignment + 1),
316323
)
317324
for item in sequence
318325
]
@@ -353,7 +360,7 @@ def __init__(self, sequence=None, *, name=None):
353360
"".join(_formats),
354361
self.shm.buf,
355362
self._offset_data_start,
356-
*(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
363+
*(self._encode_value(v) for v in sequence)
357364
)
358365
struct.pack_into(
359366
self._format_packing_metainfo,
@@ -463,8 +470,7 @@ def __setitem__(self, position, value):
463470
else:
464471
allocated_length = self._allocated_offsets[position + 1] - item_offset
465472

466-
encoded_value = (value.encode(_encoding)
467-
if isinstance(value, str) else value)
473+
encoded_value = self._encode_value(value)
468474
if len(encoded_value) > allocated_length:
469475
raise ValueError("bytes/str item exceeds available storage")
470476
if current_format[-1] == "s":

‎Lib/test/_test_multiprocessing.py‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4688,7 +4688,7 @@ def test_shared_memory_SharedMemoryManager_basics(self):
46884688

46894689
def test_shared_memory_ShareableList_basics(self):
46904690
sl = shared_memory.ShareableList(
4691-
['howdy', b'HoWdY', -273.154, 100, None, True, 42]
4691+
['howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥']
46924692
)
46934693
self.addCleanup(sl.shm.unlink)
46944694

@@ -4698,22 +4698,22 @@ def test_shared_memory_ShareableList_basics(self):
46984698

46994699
# Index Out of Range (get)
47004700
with self.assertRaises(IndexError):
4701-
sl[7]
4701+
sl[8]
47024702

47034703
# Index Out of Range (set)
47044704
with self.assertRaises(IndexError):
4705-
sl[7] = 2
4705+
sl[8] = 2
47064706

47074707
# Assign value without format change (str -> str)
47084708
current_format = sl._get_packing_format(0)
47094709
sl[0] = 'howdy'
47104710
self.assertEqual(current_format, sl._get_packing_format(0))
47114711

47124712
# Verify attributes are readable.
4713-
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q')
4713+
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q16s')
47144714

47154715
# Exercise len().
4716-
self.assertEqual(len(sl), 7)
4716+
self.assertEqual(len(sl), 8)
47174717

47184718
# Exercise index().
47194719
with warnings.catch_warnings():
@@ -4725,30 +4725,31 @@ def test_shared_memory_ShareableList_basics(self):
47254725

47264726
# Exercise retrieving individual values.
47274727
self.assertEqual(sl[0], 'howdy')
4728-
self.assertEqual(sl[-2], True)
4728+
self.assertEqual(sl[-3], True)
4729+
self.assertEqual(sl[-1], '💥 💥')
47294730

47304731
# Exercise iterability.
47314732
self.assertEqual(
47324733
tuple(sl),
4733-
('howdy', b'HoWdY', -273.154, 100, None, True, 42)
4734+
('howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥')
47344735
)
47354736

47364737
# Exercise modifying individual values.
47374738
sl[3] = 42
47384739
self.assertEqual(sl[3], 42)
47394740
sl[4] = 'some' # Change type at a given position.
47404741
self.assertEqual(sl[4], 'some')
4741-
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q')
4742+
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q16s')
47424743
with self.assertRaisesRegex(ValueError,
47434744
"exceeds available storage"):
47444745
sl[4] = 'far too many'
47454746
self.assertEqual(sl[4], 'some')
4746-
sl[0] = 'encodés' # Exactly 8 bytes of UTF-8 data
4747-
self.assertEqual(sl[0], 'encodés')
4747+
sl[0] = 'éncodé' # Exactly 8 bytes of UTF-8 data
4748+
self.assertEqual(sl[0], 'éncodé')
47484749
self.assertEqual(sl[1], b'HoWdY') # no spillage
47494750
with self.assertRaisesRegex(ValueError,
47504751
"exceeds available storage"):
4751-
sl[0] = 'encodées' # Exactly 9 bytes of UTF-8 data
4752+
sl[0] = 'éncodés' # Exactly 9 bytes of UTF-8 data
47524753
self.assertEqual(sl[1], b'HoWdY')
47534754
with self.assertRaisesRegex(ValueError,
47544755
"exceeds available storage"):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList.

0 commit comments

Comments
 (0)