Skip to content

Commit c289854

Browse files
committed
bpo-44170: Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList
1 parent 3ad101b commit c289854

2 files changed

Lines changed: 23 additions & 17 deletions

File tree

Lib/multiprocessing/shared_memory.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,21 @@ def _extract_recreation_code(value):
293293
else:
294294
return 3 # NoneType
295295

296+
@staticmethod
297+
def _encode_value(value):
298+
if not isinstance(value, str):
299+
return value
300+
else:
301+
return value.encode(_encoding)
302+
296303
def __init__(self, sequence=None, *, name=None):
297304
if name is None or sequence is not None:
298305
sequence = sequence or ()
299306
_formats = [
300307
self._types_mapping[type(item)]
301308
if not isinstance(item, (str, bytes))
302309
else self._types_mapping[type(item)] % (
303-
self._alignment * (len(item) // self._alignment + 1),
310+
self._alignment * (len(self._encode_value(item)) // self._alignment + 1),
304311
)
305312
for item in sequence
306313
]
@@ -341,7 +348,7 @@ def __init__(self, sequence=None, *, name=None):
341348
"".join(_formats),
342349
self.shm.buf,
343350
self._offset_data_start,
344-
*(v.encode(_enc) if isinstance(v, str) else v for v in sequence)
351+
*(self._encode_value(v) for v in sequence)
345352
)
346353
struct.pack_into(
347354
self._format_packing_metainfo,
@@ -451,9 +458,8 @@ def __setitem__(self, position, value):
451458
else:
452459
allocated_length = self._allocated_offsets[position + 1] - item_offset
453460

454-
encoded_value = (value.encode(_encoding)
455-
if isinstance(value, str) else value)
456-
if len(encoded_value) > allocated_length:
461+
encoded_value = self._encode_value(value)
462+
if len(encoded_value) >= allocated_length:
457463
raise ValueError("bytes/str item exceeds available storage")
458464
if current_format[-1] == "s":
459465
new_format = current_format

Lib/test/_test_multiprocessing.py

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

39983998
def test_shared_memory_ShareableList_basics(self):
39993999
sl = shared_memory.ShareableList(
4000-
['howdy', b'HoWdY', -273.154, 100, None, True, 42]
4000+
['howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥']
40014001
)
40024002
self.addCleanup(sl.shm.unlink)
40034003

@@ -4007,22 +4007,22 @@ def test_shared_memory_ShareableList_basics(self):
40074007

40084008
# Index Out of Range (get)
40094009
with self.assertRaises(IndexError):
4010-
sl[7]
4010+
sl[8]
40114011

40124012
# Index Out of Range (set)
40134013
with self.assertRaises(IndexError):
4014-
sl[7] = 2
4014+
sl[8] = 2
40154015

40164016
# Assign value without format change (str -> str)
40174017
current_format = sl._get_packing_format(0)
40184018
sl[0] = 'howdy'
40194019
self.assertEqual(current_format, sl._get_packing_format(0))
40204020

40214021
# Verify attributes are readable.
4022-
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q')
4022+
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q16s')
40234023

40244024
# Exercise len().
4025-
self.assertEqual(len(sl), 7)
4025+
self.assertEqual(len(sl), 8)
40264026

40274027
# Exercise index().
40284028
with warnings.catch_warnings():
@@ -4034,30 +4034,30 @@ def test_shared_memory_ShareableList_basics(self):
40344034

40354035
# Exercise retrieving individual values.
40364036
self.assertEqual(sl[0], 'howdy')
4037-
self.assertEqual(sl[-2], True)
4037+
self.assertEqual(sl[-3], True)
4038+
self.assertEqual(sl[-1], '💥 💥')
40384039

40394040
# Exercise iterability.
40404041
self.assertEqual(
40414042
tuple(sl),
4042-
('howdy', b'HoWdY', -273.154, 100, None, True, 42)
4043+
('howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥')
40434044
)
40444045

40454046
# Exercise modifying individual values.
40464047
sl[3] = 42
40474048
self.assertEqual(sl[3], 42)
40484049
sl[4] = 'some' # Change type at a given position.
40494050
self.assertEqual(sl[4], 'some')
4050-
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q')
4051+
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q16s')
40514052
with self.assertRaisesRegex(ValueError,
40524053
"exceeds available storage"):
40534054
sl[4] = 'far too many'
40544055
self.assertEqual(sl[4], 'some')
4055-
sl[0] = 'encodés' # Exactly 8 bytes of UTF-8 data
4056-
self.assertEqual(sl[0], 'encodés')
4057-
self.assertEqual(sl[1], b'HoWdY') # no spillage
4056+
sl[0] = 'encodé'
4057+
self.assertEqual(sl[0], 'encodé') # no spillage
40584058
with self.assertRaisesRegex(ValueError,
40594059
"exceeds available storage"):
4060-
sl[0] = 'encodées' # Exactly 9 bytes of UTF-8 data
4060+
sl[0] = 'encodés' # Exactly 8 bytes of UTF-8 data
40614061
self.assertEqual(sl[1], b'HoWdY')
40624062
with self.assertRaisesRegex(ValueError,
40634063
"exceeds available storage"):

0 commit comments

Comments
 (0)