Skip to content

Commit 21ab8eb

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.15] gh-85943: Fix BytesWarning in the struct format cache under -bb (GH-153627) (GH-153834)
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 38c3f11 commit 21ab8eb

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
@@ -2613,32 +2613,50 @@ static PyType_Spec PyStructType_spec = {
26132613
static int
26142614
cache_struct_converter(PyObject *module, PyObject *fmt, PyStructObject **ptr)
26152615
{
2616-
PyObject * s_object;
2616+
PyObject *s_object;
2617+
PyObject *key;
26172618
_structmodulestate *state = get_struct_state(module);
26182619

26192620
if (fmt == NULL) {
26202621
Py_SETREF(*ptr, NULL);
26212622
return 1;
26222623
}
26232624

2624-
if (PyDict_GetItemRef(state->cache, fmt, &s_object) < 0) {
2625+
/* Use a str cache key: an equal str and bytes would collide and be
2626+
compared, raising BytesWarning under -bb. */
2627+
if (PyBytes_Check(fmt)) {
2628+
key = PyUnicode_DecodeASCII(PyBytes_AS_STRING(fmt),
2629+
PyBytes_GET_SIZE(fmt), "surrogateescape");
2630+
if (key == NULL) {
2631+
return 0;
2632+
}
2633+
}
2634+
else {
2635+
key = Py_NewRef(fmt);
2636+
}
2637+
2638+
if (PyDict_GetItemRef(state->cache, key, &s_object) < 0) {
2639+
Py_DECREF(key);
26252640
return 0;
26262641
}
26272642
if (s_object != NULL) {
2643+
Py_DECREF(key);
26282644
*ptr = PyStructObject_CAST(s_object);
26292645
return Py_CLEANUP_SUPPORTED;
26302646
}
26312647

2632-
s_object = PyObject_CallOneArg(state->PyStructType, fmt);
2648+
s_object = PyObject_CallOneArg(state->PyStructType, key);
26332649
if (s_object != NULL) {
26342650
if (PyDict_GET_SIZE(state->cache) >= MAXCACHE)
26352651
PyDict_Clear(state->cache);
26362652
/* Attempt to cache the result */
2637-
if (PyDict_SetItem(state->cache, fmt, s_object) == -1)
2653+
if (PyDict_SetItem(state->cache, key, s_object) == -1)
26382654
PyErr_Clear();
2655+
Py_DECREF(key);
26392656
*ptr = (PyStructObject *)s_object;
26402657
return Py_CLEANUP_SUPPORTED;
26412658
}
2659+
Py_DECREF(key);
26422660
return 0;
26432661
}
26442662

0 commit comments

Comments
 (0)