Skip to content

Commit f8298e5

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[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 190d2ff) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1b060a6 commit f8298e5

3 files changed

Lines changed: 36 additions & 4 deletions

File tree

Lib/test/test_struct.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,16 @@ def test_calcsize(self):
181181
self.assertGreaterEqual(struct.calcsize('n'), struct.calcsize('i'))
182182
self.assertGreaterEqual(struct.calcsize('n'), struct.calcsize('P'))
183183

184+
def test_cache_bytes_vs_str_bb(self):
185+
# Mixing str and bytes formats must not raise BytesWarning under -bb.
186+
code = (
187+
'import struct\n'
188+
'struct.calcsize(b"!d"); struct.calcsize("!d")\n'
189+
'struct.calcsize(">d"); struct.calcsize(b">d")\n'
190+
'struct.Struct(b"i"); struct.Struct("i")\n'
191+
)
192+
assert_python_ok('-bb', '-c', code)
193+
184194
def test_integers(self):
185195
# Integer tests (bBhHiIlLqQnN).
186196
import binascii
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :mod:`struct` functions raising :exc:`BytesWarning` under the ``-bb``
2+
command line option when a :class:`str` format is used after an equal
3+
:class:`bytes` format (or vice versa). The internal format cache no longer
4+
mixes :class:`str` and :class:`bytes` keys.

Modules/_struct.c

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2504,32 +2504,50 @@ static PyType_Spec PyStructType_spec = {
25042504
static int
25052505
cache_struct_converter(PyObject *module, PyObject *fmt, PyStructObject **ptr)
25062506
{
2507-
PyObject * s_object;
2507+
PyObject *s_object;
2508+
PyObject *key;
25082509
_structmodulestate *state = get_struct_state(module);
25092510

25102511
if (fmt == NULL) {
25112512
Py_SETREF(*ptr, NULL);
25122513
return 1;
25132514
}
25142515

2515-
if (PyDict_GetItemRef(state->cache, fmt, &s_object) < 0) {
2516+
/* Use a str cache key: an equal str and bytes would collide and be
2517+
compared, raising BytesWarning under -bb. */
2518+
if (PyBytes_Check(fmt)) {
2519+
key = PyUnicode_DecodeASCII(PyBytes_AS_STRING(fmt),
2520+
PyBytes_GET_SIZE(fmt), "surrogateescape");
2521+
if (key == NULL) {
2522+
return 0;
2523+
}
2524+
}
2525+
else {
2526+
key = Py_NewRef(fmt);
2527+
}
2528+
2529+
if (PyDict_GetItemRef(state->cache, key, &s_object) < 0) {
2530+
Py_DECREF(key);
25162531
return 0;
25172532
}
25182533
if (s_object != NULL) {
2534+
Py_DECREF(key);
25192535
*ptr = PyStructObject_CAST(s_object);
25202536
return Py_CLEANUP_SUPPORTED;
25212537
}
25222538

2523-
s_object = PyObject_CallOneArg(state->PyStructType, fmt);
2539+
s_object = PyObject_CallOneArg(state->PyStructType, key);
25242540
if (s_object != NULL) {
25252541
if (PyDict_GET_SIZE(state->cache) >= MAXCACHE)
25262542
PyDict_Clear(state->cache);
25272543
/* Attempt to cache the result */
2528-
if (PyDict_SetItem(state->cache, fmt, s_object) == -1)
2544+
if (PyDict_SetItem(state->cache, key, s_object) == -1)
25292545
PyErr_Clear();
2546+
Py_DECREF(key);
25302547
*ptr = (PyStructObject *)s_object;
25312548
return Py_CLEANUP_SUPPORTED;
25322549
}
2550+
Py_DECREF(key);
25332551
return 0;
25342552
}
25352553

0 commit comments

Comments
 (0)