Skip to content

Commit ae13f1b

Browse files
gpsheadjunnplus
andauthored
[3.15] gh-88336: Fix UnicodeDecodeError with multibyte utf8 characters in ShareableList (GH-26328) (#161116)
* 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 6d18fc9 commit ae13f1b

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
@@ -4810,7 +4810,7 @@ def test_shared_memory_SharedMemoryManager_basics(self):
48104810

48114811
def test_shared_memory_ShareableList_basics(self):
48124812
sl = shared_memory.ShareableList(
4813-
['howdy', b'HoWdY', -273.154, 100, None, True, 42]
4813+
['howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥']
48144814
)
48154815
self.addCleanup(sl.shm.unlink)
48164816

@@ -4820,22 +4820,22 @@ def test_shared_memory_ShareableList_basics(self):
48204820

48214821
# Index Out of Range (get)
48224822
with self.assertRaises(IndexError):
4823-
sl[7]
4823+
sl[8]
48244824

48254825
# Index Out of Range (set)
48264826
with self.assertRaises(IndexError):
4827-
sl[7] = 2
4827+
sl[8] = 2
48284828

48294829
# Assign value without format change (str -> str)
48304830
current_format = sl._get_packing_format(0)
48314831
sl[0] = 'howdy'
48324832
self.assertEqual(current_format, sl._get_packing_format(0))
48334833

48344834
# Verify attributes are readable.
4835-
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q')
4835+
self.assertEqual(sl.format, '8s8sdqxxxxxx?xxxxxxxx?q16s')
48364836

48374837
# Exercise len().
4838-
self.assertEqual(len(sl), 7)
4838+
self.assertEqual(len(sl), 8)
48394839

48404840
# Exercise index().
48414841
with warnings.catch_warnings():
@@ -4847,30 +4847,31 @@ def test_shared_memory_ShareableList_basics(self):
48474847

48484848
# Exercise retrieving individual values.
48494849
self.assertEqual(sl[0], 'howdy')
4850-
self.assertEqual(sl[-2], True)
4850+
self.assertEqual(sl[-3], True)
4851+
self.assertEqual(sl[-1], '💥 💥')
48514852

48524853
# Exercise iterability.
48534854
self.assertEqual(
48544855
tuple(sl),
4855-
('howdy', b'HoWdY', -273.154, 100, None, True, 42)
4856+
('howdy', b'HoWdY', -273.154, 100, None, True, 42, '💥 💥')
48564857
)
48574858

48584859
# Exercise modifying individual values.
48594860
sl[3] = 42
48604861
self.assertEqual(sl[3], 42)
48614862
sl[4] = 'some' # Change type at a given position.
48624863
self.assertEqual(sl[4], 'some')
4863-
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q')
4864+
self.assertEqual(sl.format, '8s8sdq8sxxxxxxx?q16s')
48644865
with self.assertRaisesRegex(ValueError,
48654866
"exceeds available storage"):
48664867
sl[4] = 'far too many'
48674868
self.assertEqual(sl[4], 'some')
4868-
sl[0] = 'encodés' # Exactly 8 bytes of UTF-8 data
4869-
self.assertEqual(sl[0], 'encodés')
4869+
sl[0] = 'éncodé' # Exactly 8 bytes of UTF-8 data
4870+
self.assertEqual(sl[0], 'éncodé')
48704871
self.assertEqual(sl[1], b'HoWdY') # no spillage
48714872
with self.assertRaisesRegex(ValueError,
48724873
"exceeds available storage"):
4873-
sl[0] = 'encodées' # Exactly 9 bytes of UTF-8 data
4874+
sl[0] = 'éncodés' # Exactly 9 bytes of UTF-8 data
48744875
self.assertEqual(sl[1], b'HoWdY')
48754876
with self.assertRaisesRegex(ValueError,
48764877
"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)