Skip to content

Commit 8af4eed

Browse files
picnixzStanFromIreland
authored andcommitted
[3.14] pythongh-148390: fix undefined behavior of memoryview(...).cast("?") (pythonGH-148454)
(cherry picked from commit 69e0a78) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 2fdccb3 commit 8af4eed

File tree

4 files changed

+34
-3
lines changed

4 files changed

+34
-3
lines changed

Lib/test/test_memoryview.py

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

578+
def test_boolean_format(self):
579+
# Test '?' format (keep all the checks below for UBSan)
580+
# See github.com/python/cpython/issues/148390.
581+
582+
# m1a and m1b are equivalent to [False, True, False]
583+
m1a = memoryview(b'\0\2\0').cast('?')
584+
self.assertEqual(m1a.tolist(), [False, True, False])
585+
m1b = memoryview(b'\0\4\0').cast('?')
586+
self.assertEqual(m1b.tolist(), [False, True, False])
587+
self.assertEqual(m1a, m1b)
588+
589+
# m2a and m2b are equivalent to [True, True, True]
590+
m2a = memoryview(b'\1\3\5').cast('?')
591+
self.assertEqual(m2a.tolist(), [True, True, True])
592+
m2b = memoryview(b'\2\4\6').cast('?')
593+
self.assertEqual(m2b.tolist(), [True, True, True])
594+
self.assertEqual(m2a, m2b)
595+
596+
allbytes = bytes(range(256))
597+
allbytes = memoryview(allbytes).cast('?')
598+
self.assertEqual(allbytes.tolist(), [False] + [True] * 255)
599+
578600

579601
class BytesMemorySliceTest(unittest.TestCase,
580602
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
@@ -1672,6 +1672,10 @@ fix_error_int(const char *fmt)
16721672
return -1;
16731673
}
16741674

1675+
// UNPACK_TO_BOOL: Return 0 if PTR represents "false", and 1 otherwise.
1676+
static const _Bool bool_false = 0;
1677+
#define UNPACK_TO_BOOL(PTR) (memcmp((PTR), &bool_false, sizeof(_Bool)) != 0)
1678+
16751679
/* Accept integer objects or objects with an __index__() method. */
16761680
static long
16771681
pylong_as_ld(PyObject *item)
@@ -1807,7 +1811,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
18071811
case 'l': UNPACK_SINGLE(ld, ptr, long); goto convert_ld;
18081812

18091813
/* boolean */
1810-
case '?': UNPACK_SINGLE(ld, ptr, _Bool); goto convert_bool;
1814+
case '?': ld = UNPACK_TO_BOOL(ptr); goto convert_bool;
18111815

18121816
/* unsigned integers */
18131817
case 'H': UNPACK_SINGLE(lu, ptr, unsigned short); goto convert_lu;
@@ -2989,7 +2993,7 @@ unpack_cmp(const char *p, const char *q, char fmt,
29892993
case 'l': CMP_SINGLE(p, q, long); return equal;
29902994

29912995
/* boolean */
2992-
case '?': CMP_SINGLE(p, q, _Bool); return equal;
2996+
case '?': return UNPACK_TO_BOOL(p) == UNPACK_TO_BOOL(q);
29932997

29942998
/* unsigned integers */
29952999
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
@@ -354,7 +354,7 @@ Modules/_testclinic.c - TestClass -
354354
##################################
355355
## global non-objects to fix in builtin modules
356356

357-
# <none>
357+
Objects/memoryobject.c - bool_false -
358358

359359

360360
##################################

0 commit comments

Comments
 (0)