]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-148390: fix undefined behavior of `memoryview(...).cast("?")` (GH-148454...
authorStan Ulbrych <stan@python.org>
Fri, 1 May 2026 15:43:59 +0000 (16:43 +0100)
committerGitHub <noreply@github.com>
Fri, 1 May 2026 15:43:59 +0000 (17:43 +0200)
(cherry picked from commit 69e0a78e6edc3166c7a5b166955c0cefd1bacd5d)

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
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

index 656318668e6d6e77fa799e35fde76e0460518aaa..7b04a4ddd837350436ab93b7d3367ab680b454af 100644 (file)
@@ -575,6 +575,28 @@ class ArrayMemoryviewTest(unittest.TestCase,
         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 (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 1bc03c254f05d00abd9fa87f539dc3cbbc06c99f..032060163c942921742eb73de5e9884c8d016b6b 100644 (file)
@@ -1672,6 +1672,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)
@@ -1807,7 +1811,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;
@@ -2989,7 +2993,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 3c3cb2f9c86f16beba37ede9a1a06e986a6a2e11..515577516f759e07b1c1575cb234cd3ccd02f3f2 100644 (file)
@@ -354,7 +354,7 @@ Modules/_testclinic.c       -       TestClass       -
 ##################################
 ## global non-objects to fix in builtin modules
 
-# <none>
+Objects/memoryobject.c -       bool_false      -
 
 
 ##################################