https://github.com/python/cpython/commit/69e0a78e6edc3166c7a5b166955c0cefd1bacd5d
commit: 69e0a78e6edc3166c7a5b166955c0cefd1bacd5d
branch: main
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2026-04-15T11:42:20Z
summary:

gh-148390: fix undefined behavior of `memoryview(...).cast("?")` (#148454)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-27-28.gh-issue-148390.MAhw7F.rst
M Lib/test/test_memoryview.py
M Objects/memoryobject.c
M Tools/c-analyzer/cpython/globals-to-fix.tsv
M Tools/ubsan/suppressions.txt

diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index 22b9f6af758f88..820574584b56b2 100644
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -648,6 +648,28 @@ def check_equal(view, is_equal):
                 m = memoryview(data).cast(complex_format)
                 check_equal(m, True)
 
+    def test_boolean_format(self):
+        # Test '?' format (keep all the checks below for UBSan)
+        # See github.com/python/cpython/issues/148390.
+
+        # m1a and m1b are equivalent to [False, True, False]
+        m1a = memoryview(b'\0\2\0').cast('?')
+        self.assertEqual(m1a.tolist(), [False, True, False])
+        m1b = memoryview(b'\0\4\0').cast('?')
+        self.assertEqual(m1b.tolist(), [False, True, False])
+        self.assertEqual(m1a, m1b)
+
+        # m2a and m2b are equivalent to [True, True, True]
+        m2a = memoryview(b'\1\3\5').cast('?')
+        self.assertEqual(m2a.tolist(), [True, True, True])
+        m2b = memoryview(b'\2\4\6').cast('?')
+        self.assertEqual(m2b.tolist(), [True, True, True])
+        self.assertEqual(m2a, m2b)
+
+        allbytes = bytes(range(256))
+        allbytes = memoryview(allbytes).cast('?')
+        self.assertEqual(allbytes.tolist(), [False] + [True] * 255)
+
 
 class BytesMemorySliceTest(unittest.TestCase,
     BaseMemorySliceTests, BaseBytesMemoryTests):
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-27-28.gh-issue-148390.MAhw7F.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-27-28.gh-issue-148390.MAhw7F.rst
new file mode 100644
index 00000000000000..881964673307cc
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-27-28.gh-issue-148390.MAhw7F.rst
@@ -0,0 +1,5 @@
+Fix an undefined behavior in :class:`memoryview` when using the native
+boolean format (``?``) in :meth:`~memoryview.cast`. Previously, on some
+common platforms, calling ``memoryview(b).cast("?").tolist()`` incorrectly
+returned ``[False]`` instead of ``[True]`` for any even byte *b*.
+Patch by Bénédikt Tran.
diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c
index 4cbbb7eb7cd0fd..d0f414f7c4209b 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -1676,6 +1676,10 @@ fix_error_int(const char *fmt)
     return -1;
 }
 
+// UNPACK_TO_BOOL: Return 0 if PTR represents "false", and 1 otherwise.
+static const _Bool bool_false = 0;
+#define UNPACK_TO_BOOL(PTR) (memcmp((PTR), &bool_false, sizeof(_Bool)) != 0)
+
 /* Accept integer objects or objects with an __index__() method. */
 static long
 pylong_as_ld(PyObject *item)
@@ -1811,7 +1815,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, 
const char *fmt)
     case 'l': UNPACK_SINGLE(ld, ptr, long); goto convert_ld;
 
     /* boolean */
-    case '?': UNPACK_SINGLE(ld, ptr, _Bool); goto convert_bool;
+    case '?': ld = UNPACK_TO_BOOL(ptr); goto convert_bool;
 
     /* unsigned integers */
     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,
     case 'l': CMP_SINGLE(p, q, long); return equal;
 
     /* boolean */
-    case '?': CMP_SINGLE(p, q, _Bool); return equal;
+    case '?': return UNPACK_TO_BOOL(p) == UNPACK_TO_BOOL(q);
 
     /* unsigned integers */
     case 'H': CMP_SINGLE(p, q, unsigned short); return equal;
diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv 
b/Tools/c-analyzer/cpython/globals-to-fix.tsv
index d645d2b6150d34..74ca562824012b 100644
--- a/Tools/c-analyzer/cpython/globals-to-fix.tsv
+++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv
@@ -357,7 +357,7 @@ Modules/_testclinic.c       -       TestClass       -
 ##################################
 ## global non-objects to fix in builtin modules
 
-# <none>
+Objects/memoryobject.c -       bool_false      -
 
 
 ##################################
diff --git a/Tools/ubsan/suppressions.txt b/Tools/ubsan/suppressions.txt
index 9a5f20364261fe..a00e256b333618 100644
--- a/Tools/ubsan/suppressions.txt
+++ b/Tools/ubsan/suppressions.txt
@@ -9,9 +9,6 @@
 # Objects/object.c:97:5: runtime error: member access within null pointer of 
type 'PyThreadState' (aka 'struct _ts')
 null:Objects/object.c
 
-# Objects/memoryobject.c:3032:15: runtime error: load of value 2, which is not 
a valid value for type 'bool'
-bool:Objects/memoryobject.c
-
 # Modules/_ctypes/cfield.c:644:1: runtime error: left shift of 1 by 63 places 
cannot be represented in type 'int64_t' (aka 'long')
 shift-base:Modules/_ctypes/cfield.c
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to