https://github.com/python/cpython/commit/190d2ffbb969ad571fd12d4988b37519091194d5
commit: 190d2ffbb969ad571fd12d4988b37519091194d5
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-07-17T10:49:11+03:00
summary:

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

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.

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 f9803abb3db91f..3809f2d2643895 100644
--- a/Lib/test/test_struct.py
+++ b/Lib/test/test_struct.py
@@ -182,6 +182,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 260dc85faf9fb9..352312fb0b4c19 100644
--- a/Modules/_struct.c
+++ b/Modules/_struct.c
@@ -2642,7 +2642,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) {
@@ -2650,24 +2651,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