https://github.com/python/cpython/commit/f8298e59cdd6fb28f6b6b9d0aaef2117349006e4
commit: f8298e59cdd6fb28f6b6b9d0aaef2117349006e4
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-07-17T08:16:34Z
summary:

[3.14] gh-85943: Fix BytesWarning in the struct format cache under -bb 
(GH-153627) (GH-153835)

Normalize bytes format strings to str before using them as the cache key,
so that equal str and bytes formats no longer collide and get compared.
(cherry picked from commit 190d2ffbb969ad571fd12d4988b37519091194d5)

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-12-00-00-00.gh-issue-85943.8f906e.rst
M Lib/test/test_struct.py
M Modules/_struct.c

diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py
index d2a30804bedb2c..e18bad9c990826 100644
--- a/Lib/test/test_struct.py
+++ b/Lib/test/test_struct.py
@@ -181,6 +181,16 @@ def test_calcsize(self):
         self.assertGreaterEqual(struct.calcsize('n'), struct.calcsize('i'))
         self.assertGreaterEqual(struct.calcsize('n'), struct.calcsize('P'))
 
+    def test_cache_bytes_vs_str_bb(self):
+        # Mixing str and bytes formats must not raise BytesWarning under -bb.
+        code = (
+            'import struct\n'
+            'struct.calcsize(b"!d"); struct.calcsize("!d")\n'
+            'struct.calcsize(">d"); struct.calcsize(b">d")\n'
+            'struct.Struct(b"i"); struct.Struct("i")\n'
+        )
+        assert_python_ok('-bb', '-c', code)
+
     def test_integers(self):
         # Integer tests (bBhHiIlLqQnN).
         import binascii
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-12-00-00-00.gh-issue-85943.8f906e.rst 
b/Misc/NEWS.d/next/Library/2026-07-12-00-00-00.gh-issue-85943.8f906e.rst
new file mode 100644
index 00000000000000..0a93bf2e7b803e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-12-00-00-00.gh-issue-85943.8f906e.rst
@@ -0,0 +1,4 @@
+Fix :mod:`struct` functions raising :exc:`BytesWarning` under the ``-bb``
+command line option when a :class:`str` format is used after an equal
+:class:`bytes` format (or vice versa).  The internal format cache no longer
+mixes :class:`str` and :class:`bytes` keys.
diff --git a/Modules/_struct.c b/Modules/_struct.c
index efeebb98e4b34d..614512fe35f047 100644
--- a/Modules/_struct.c
+++ b/Modules/_struct.c
@@ -2504,7 +2504,8 @@ static PyType_Spec PyStructType_spec = {
 static int
 cache_struct_converter(PyObject *module, PyObject *fmt, PyStructObject **ptr)
 {
-    PyObject * s_object;
+    PyObject *s_object;
+    PyObject *key;
     _structmodulestate *state = get_struct_state(module);
 
     if (fmt == NULL) {
@@ -2512,24 +2513,41 @@ cache_struct_converter(PyObject *module, PyObject *fmt, 
PyStructObject **ptr)
         return 1;
     }
 
-    if (PyDict_GetItemRef(state->cache, fmt, &s_object) < 0) {
+    /* Use a str cache key: an equal str and bytes would collide and be
+       compared, raising BytesWarning under -bb. */
+    if (PyBytes_Check(fmt)) {
+        key = PyUnicode_DecodeASCII(PyBytes_AS_STRING(fmt),
+                                    PyBytes_GET_SIZE(fmt), "surrogateescape");
+        if (key == NULL) {
+            return 0;
+        }
+    }
+    else {
+        key = Py_NewRef(fmt);
+    }
+
+    if (PyDict_GetItemRef(state->cache, key, &s_object) < 0) {
+        Py_DECREF(key);
         return 0;
     }
     if (s_object != NULL) {
+        Py_DECREF(key);
         *ptr = PyStructObject_CAST(s_object);
         return Py_CLEANUP_SUPPORTED;
     }
 
-    s_object = PyObject_CallOneArg(state->PyStructType, fmt);
+    s_object = PyObject_CallOneArg(state->PyStructType, key);
     if (s_object != NULL) {
         if (PyDict_GET_SIZE(state->cache) >= MAXCACHE)
             PyDict_Clear(state->cache);
         /* Attempt to cache the result */
-        if (PyDict_SetItem(state->cache, fmt, s_object) == -1)
+        if (PyDict_SetItem(state->cache, key, s_object) == -1)
             PyErr_Clear();
+        Py_DECREF(key);
         *ptr = (PyStructObject *)s_object;
         return Py_CLEANUP_SUPPORTED;
     }
+    Py_DECREF(key);
     return 0;
 }
 

_______________________________________________
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