]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-148390: fix undefined behavior of `memoryview(...).cast("?")` (GH-148454...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 8 May 2026 15:50:00 +0000 (17:50 +0200)
committerGitHub <noreply@github.com>
Fri, 8 May 2026 15:50:00 +0000 (17:50 +0200)
(cherry picked from commit 69e0a78e6edc3166c7a5b166955c0cefd1bacd5d)
(cherry picked from commit 454401c8e995dc15d5a096f35ff005746efcc915)

Co-authored-by: Stan Ulbrych <stan@python.org>
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 f8dd1231bb80098715649408c42287c77a50dbcb..9d1e186308ac6df756fd571e630d3b6c54a34838 100644 (file)
@@ -520,6 +520,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 535e0b3c1dc0271403ee4130188967fad8b215f7..b361d64d34d674c6c1a74c9c4c05da2a2c2dacaa 100644 (file)
@@ -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;
index 06e58e92184efa93ee87c2cd380d805f33302f17..7da6120711d5eaad41431c2e2bee418b5bacd902 100644 (file)
@@ -349,7 +349,7 @@ Modules/_testclinic.c       -       TestClass       -
 ##################################
 ## global non-objects to fix in builtin modules
 
-# <none>
+Objects/memoryobject.c -       bool_false      -
 
 
 ##################################