Skip to content

Commit 8d117b7

Browse files
committed
gh-148390: fix undefined behavior of memoryview(...).cast("?")
1 parent 639f218 commit 8d117b7

File tree

3 files changed

+22
-3
lines changed

3 files changed

+22
-3
lines changed

Lib/test/test_memoryview.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,9 +620,21 @@ def check_equal(view, is_equal):
620620
m = memoryview(a.tobytes()).cast('n')
621621
check_equal(m, True)
622622

623-
# Test '?' format
623+
# Test '?' format (keep all the checks below for UBSan)
624624
m = memoryview(b'\0\1\2').cast('?')
625625
check_equal(m, True)
626+
# m1a and m1b are equivalent to [False, True, False]
627+
m1a = memoryview(b'\0\2\0').cast('?')
628+
self.assertEqual(m1a.tolist(), [False, True, False])
629+
m1b = memoryview(b'\0\4\0').cast('?')
630+
self.assertEqual(m1b.tolist(), [False, True, False])
631+
self.assertEqual(m1a, m1b)
632+
# m1a and m1b are equivalent to [True, True, True]
633+
m2a = memoryview(b'\1\3\5').cast('?')
634+
self.assertEqual(m2a.tolist(), [True, True, True])
635+
m2b = memoryview(b'\2\4\6').cast('?')
636+
self.assertEqual(m2b.tolist(), [True, True, True])
637+
self.assertEqual(m2a, m2b)
626638

627639
# Test float formats
628640
for float_format in 'fd':
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix an undefined behavior in :class:`memoryview` when using the native
2+
boolean format (``?``) in :meth:`~memoryview.cast`. Previously, calling
3+
``memoryview(b).cast("?").tolist()`` incorrectly returned ``[False]``
4+
instead of ``[True]`` for any even byte *b*. Patch by Bénédikt Tran.

Objects/memoryobject.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,9 @@ fix_error_int(const char *fmt)
16761676
return -1;
16771677
}
16781678

1679+
// Return 0 if PTR represents "false", and 1 otherwise.
1680+
#define UNPACK_TO_BOOL(PTR) (memcmp((PTR), &(_Bool){0}, sizeof(_Bool)) != 0)
1681+
16791682
/* Accept integer objects or objects with an __index__() method. */
16801683
static long
16811684
pylong_as_ld(PyObject *item)
@@ -1811,7 +1814,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
18111814
case 'l': UNPACK_SINGLE(ld, ptr, long); goto convert_ld;
18121815

18131816
/* boolean */
1814-
case '?': UNPACK_SINGLE(ld, ptr, _Bool); goto convert_bool;
1817+
case '?': ld = UNPACK_TO_BOOL(ptr); goto convert_bool;
18151818

18161819
/* unsigned integers */
18171820
case 'H': UNPACK_SINGLE(lu, ptr, unsigned short); goto convert_lu;
@@ -3029,7 +3032,7 @@ unpack_cmp(const char *p, const char *q, char fmt,
30293032
case 'l': CMP_SINGLE(p, q, long); return equal;
30303033

30313034
/* boolean */
3032-
case '?': CMP_SINGLE(p, q, _Bool); return equal;
3035+
case '?': return UNPACK_TO_BOOL(p) == UNPACK_TO_BOOL(q);
30333036

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

0 commit comments

Comments
 (0)