From c8a0fbf2a6851a060270e53c045b2b7d1c06bf95 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 17 Jul 2026 11:26:14 +0300 Subject: [PATCH] [3.13] gh-85943: Fix BytesWarning in the struct format cache under -bb (GH-153627) (GH-153836) 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: Claude Opus 4.8 (1M context) --- Lib/test/test_struct.py | 10 +++++++ ...6-07-12-00-00-00.gh-issue-85943.8f906e.rst | 4 +++ Modules/_struct.c | 26 ++++++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-12-00-00-00.gh-issue-85943.8f906e.rst diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 460e9e94aacf..a5a1edcb0d19 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -176,6 +176,16 @@ class StructTest(unittest.TestCase): 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 000000000000..0a93bf2e7b80 --- /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 1195f6f0d743..36d5ece390e8 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2289,7 +2289,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) { @@ -2297,24 +2298,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 *)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; } -- 2.47.3