]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-148390: fix undefined behavior of `memoryview(...).cast("?")` (#148454)
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>
Wed, 15 Apr 2026 11:42:20 +0000 (13:42 +0200)
committerGitHub <noreply@github.com>
Wed, 15 Apr 2026 11:42:20 +0000 (11:42 +0000)
Lib/test/test_memoryview.py
Misc/NEWS.d/next/Core_and_Builtins/2026-04-12-17-27-28.gh-issue-148390.MAhw7F.rst [new file with mode: 0644]
Objects/memoryobject.c
Tools/c-analyzer/cpython/globals-to-fix.tsv
Tools/ubsan/suppressions.txt

index 22b9f6af758f88e18c9164acb9757380b33ac27b..820574584b56b255bb92771d3f759207f15a9da9 100644 (file)
@@ -648,6 +648,28 @@ class ArrayMemoryviewTest(unittest.TestCase,
                 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 (file)
index 0000000..8819646
--- /dev/null
@@ -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.
index 4cbbb7eb7cd0fdf91db96e125cd4897145165533..d0f414f7c4209bd625883fe0724d4194101b241b 100644 (file)
@@ -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;
index d645d2b6150d3414b4d5ba6edd5fc557c3b1aca3..74ca562824012b9c751b706aa557a23cdcdd0480 100644 (file)
@@ -357,7 +357,7 @@ Modules/_testclinic.c       -       TestClass       -
 ##################################
 ## global non-objects to fix in builtin modules
 
-# <none>
+Objects/memoryobject.c -       bool_false      -
 
 
 ##################################
index 9a5f20364261fec74101daca43130111950d4c7a..a00e256b3336182dfdae7310cb41642f8ab991dc 100644 (file)
@@ -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