https://github.com/python/cpython/commit/05956df2c2dd15645acb627891314c06110c5c76
commit: 05956df2c2dd15645acb627891314c06110c5c76
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: encukou <[email protected]>
date: 2026-05-08T17:50:00+02:00
summary:

[3.13] gh-148390: fix undefined behavior of `memoryview(...).cast("?")` 
(GH-148454) (GH-149299)

(cherry picked from commit 69e0a78e6edc3166c7a5b166955c0cefd1bacd5d)
(cherry picked from commit 454401c8e995dc15d5a096f35ff005746efcc915)

Co-authored-by: Stan Ulbrych <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>

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

diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index f8dd1231bb8009..9d1e186308ac6d 100644
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -520,6 +520,28 @@ def test_array_assign(self):
         m[:] = new_a
         self.assertEqual(a, new_a)
 
+    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 535e0b3c1dc027..b361d64d34d674 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -1653,6 +1653,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)
@@ -1788,7 +1792,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;
@@ -2835,7 +2839,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 06e58e92184efa..7da6120711d5ea 100644
--- a/Tools/c-analyzer/cpython/globals-to-fix.tsv
+++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv
@@ -349,7 +349,7 @@ Modules/_testclinic.c       -       TestClass       -
 ##################################
 ## global non-objects to fix in builtin modules
 
-# <none>
+Objects/memoryobject.c -       bool_false      -
 
 
 ##################################

_______________________________________________
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