Skip to content

Commit 69e0a78

Browse files
authored
gh-148390: fix undefined behavior of memoryview(...).cast("?") (#148454)
1 parent aee63e2 commit 69e0a78

File tree

5 files changed

+34
-6
lines changed

5 files changed

+34
-6
lines changed

Lib/test/test_memoryview.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,28 @@ def check_equal(view, is_equal):
648648
m = memoryview(data).cast(complex_format)
649649
check_equal(m, True)
650650

651+
def test_boolean_format(self):
652+
# Test '?' format (keep all the checks below for UBSan)
653+
# See github.com/python/cpython/issues/148390.
654+
655+
# m1a and m1b are equivalent to [False, True, False]
656+
m1a = memoryview(b'\0\2\0').cast('?')
657+
self.assertEqual(m1a.tolist(), [False, True, False])
658+
m1b = memoryview(b'\0\4\0').cast('?')
659+
self.assertEqual(m1b.tolist(), [False, True, False])
660+
self.assertEqual(m1a, m1b)
661+
662+
# m2a and m2b are equivalent to [True, True, True]
663+
m2a = memoryview(b'\1\3\5').cast('?')
664+
self.assertEqual(m2a.tolist(), [True, True, True])
665+
m2b = memoryview(b'\2\4\6').cast('?')
666+
self.assertEqual(m2b.tolist(), [True, True, True])
667+
self.assertEqual(m2a, m2b)
668+
669+
allbytes = bytes(range(256))
670+
allbytes = memoryview(allbytes).cast('?')
671+
self.assertEqual(allbytes.tolist(), [False] + [True] * 255)
672+
651673

652674
class BytesMemorySliceTest(unittest.TestCase,
653675
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
@@ -1676,6 +1676,10 @@ fix_error_int(const char *fmt)
16761676
return -1;
16771677
}
16781678

1679+
// UNPACK_TO_BOOL: Return 0 if PTR represents "false", and 1 otherwise.
1680+
static const _Bool bool_false = 0;
1681+
#define UNPACK_TO_BOOL(PTR) (memcmp((PTR), &bool_false, sizeof(_Bool)) != 0)
1682+
16791683
/* Accept integer objects or objects with an __index__() method. */
16801684
static long
16811685
pylong_as_ld(PyObject *item)
@@ -1811,7 +1815,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
18111815
case 'l': UNPACK_SINGLE(ld, ptr, long); goto convert_ld;
18121816

18131817
/* boolean */
1814-
case '?': UNPACK_SINGLE(ld, ptr, _Bool); goto convert_bool;
1818+
case '?': ld = UNPACK_TO_BOOL(ptr); goto convert_bool;
18151819

18161820
/* unsigned integers */
18171821
case 'H': UNPACK_SINGLE(lu, ptr, unsigned short); goto convert_lu;
@@ -3029,7 +3033,7 @@ unpack_cmp(const char *p, const char *q, char fmt,
30293033
case 'l': CMP_SINGLE(p, q, long); return equal;
30303034

30313035
/* boolean */
3032-
case '?': CMP_SINGLE(p, q, _Bool); return equal;
3036+
case '?': return UNPACK_TO_BOOL(p) == UNPACK_TO_BOOL(q);
30333037

30343038
/* unsigned integers */
30353039
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
@@ -357,7 +357,7 @@ Modules/_testclinic.c - TestClass -
357357
##################################
358358
## global non-objects to fix in builtin modules
359359

360-
# <none>
360+
Objects/memoryobject.c - bool_false -
361361

362362

363363
##################################

Tools/ubsan/suppressions.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
# Objects/object.c:97:5: runtime error: member access within null pointer of type 'PyThreadState' (aka 'struct _ts')
1010
null:Objects/object.c
1111

12-
# Objects/memoryobject.c:3032:15: runtime error: load of value 2, which is not a valid value for type 'bool'
13-
bool:Objects/memoryobject.c
14-
1512
# Modules/_ctypes/cfield.c:644:1: runtime error: left shift of 1 by 63 places cannot be represented in type 'int64_t' (aka 'long')
1613
shift-base:Modules/_ctypes/cfield.c
1714

0 commit comments

Comments
 (0)