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]