Skip to content

Commit 05956df

Browse files
miss-islingtonStanFromIrelandpicnixz
authored
[3.13] gh-148390: fix undefined behavior of memoryview(...).cast("?") (GH-148454) (GH-149299)
(cherry picked from commit 69e0a78) (cherry picked from commit 454401c) Co-authored-by: Stan Ulbrych <stan@python.org> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 412b001 commit 05956df

4 files changed

Lines changed: 34 additions & 3 deletions

File tree

Lib/test/test_memoryview.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,28 @@ def test_array_assign(self):
520520
m[:] = new_a
521521
self.assertEqual(a, new_a)
522522

523+
def test_boolean_format(self):
524+
# Test '?' format (keep all the checks below for UBSan)
525+
# See github.com/python/cpython/issues/148390.
526+
527+
# m1a and m1b are equivalent to [False, True, False]
528+
m1a = memoryview(b'\0\2\0').cast('?')
529+
self.assertEqual(m1a.tolist(), [False, True, False])
530+
m1b = memoryview(b'\0\4\0').cast('?')
531+
self.assertEqual(m1b.tolist(), [False, True, False])
532+
self.assertEqual(m1a, m1b)
533+
534+
# m2a and m2b are equivalent to [True, True, True]
535+
m2a = memoryview(b'\1\3\5').cast('?')
536+
self.assertEqual(m2a.tolist(), [True, True, True])
537+
m2b = memoryview(b'\2\4\6').cast('?')
538+
self.assertEqual(m2b.tolist(), [True, True, True])
539+
self.assertEqual(m2a, m2b)
540+
541+
allbytes = bytes(range(256))
542+
allbytes = memoryview(allbytes).cast('?')
543+
self.assertEqual(allbytes.tolist(), [False] + [True] * 255)
544+
523545

524546
class BytesMemorySliceTest(unittest.TestCase,
525547
BaseMemorySliceTests, BaseBytesMemoryTests):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix an undefined behavior in :class:`memoryview` when using the native
2+
boolean format (``?``) in :meth:`~memoryview.cast`. Previously, on some
3+
common platforms, calling ``memoryview(b).cast("?").tolist()`` incorrectly
4+
returned ``[False]`` instead of ``[True]`` for any even byte *b*.
5+
Patch by Bénédikt Tran.

Objects/memoryobject.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,10 @@ fix_error_int(const char *fmt)
16531653
return -1;
16541654
}
16551655

1656+
// UNPACK_TO_BOOL: Return 0 if PTR represents "false", and 1 otherwise.
1657+
static const _Bool bool_false = 0;
1658+
#define UNPACK_TO_BOOL(PTR) (memcmp((PTR), &bool_false, sizeof(_Bool)) != 0)
1659+
16561660
/* Accept integer objects or objects with an __index__() method. */
16571661
static long
16581662
pylong_as_ld(PyObject *item)
@@ -1788,7 +1792,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
17881792
case 'l': UNPACK_SINGLE(ld, ptr, long); goto convert_ld;
17891793

17901794
/* boolean */
1791-
case '?': UNPACK_SINGLE(ld, ptr, _Bool); goto convert_bool;
1795+
case '?': ld = UNPACK_TO_BOOL(ptr); goto convert_bool;
17921796

17931797
/* unsigned integers */
17941798
case 'H': UNPACK_SINGLE(lu, ptr, unsigned short); goto convert_lu;
@@ -2835,7 +2839,7 @@ unpack_cmp(const char *p, const char *q, char fmt,
28352839
case 'l': CMP_SINGLE(p, q, long); return equal;
28362840

28372841
/* boolean */
2838-
case '?': CMP_SINGLE(p, q, _Bool); return equal;
2842+
case '?': return UNPACK_TO_BOOL(p) == UNPACK_TO_BOOL(q);
28392843

28402844
/* unsigned integers */
28412845
case 'H': CMP_SINGLE(p, q, unsigned short); return equal;

Tools/c-analyzer/cpython/globals-to-fix.tsv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ Modules/_testclinic.c - TestClass -
349349
##################################
350350
## global non-objects to fix in builtin modules
351351

352-
# <none>
352+
Objects/memoryobject.c - bool_false -
353353

354354

355355
##################################

0 commit comments

Comments
 (0)