Skip to content

Commit 9fe6034

Browse files
authored
gh-112358: Fix Python 3.12 regression with subclassing struct.Struct. (#112424)
Revert commit c8c0afc (PR #94532), which moved `struct.Struct` initialisation from `Struct.__init__` to `Struct.__new__`. This caused issues with code in the wild that subclasses `struct.Struct`.
1 parent 3faf8e5 commit 9fe6034

File tree

4 files changed

+52
-48
lines changed

4 files changed

+52
-48
lines changed

Lib/test/test_struct.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -700,20 +700,6 @@ def test__struct_types_immutable(self):
700700
with self.assertRaises(TypeError):
701701
cls.x = 1
702702

703-
@support.cpython_only
704-
def test__struct_Struct__new__initialized(self):
705-
# See https://github.com/python/cpython/issues/78724
706-
707-
s = struct.Struct.__new__(struct.Struct, "b")
708-
s.unpack_from(b"abcd")
709-
710-
@support.cpython_only
711-
def test__struct_Struct_subclassing(self):
712-
class Bob(struct.Struct):
713-
pass
714-
715-
s = Bob("b")
716-
s.unpack_from(b"abcd")
717703

718704
def test_issue35714(self):
719705
# Embedded null characters should not be allowed in format strings.
@@ -774,6 +760,15 @@ def test_error_propagation(fmt_str):
774760
test_error_propagation('N')
775761
test_error_propagation('n')
776762

763+
def test_struct_subclass_instantiation(self):
764+
# Regression test for https://github.com/python/cpython/issues/112358
765+
class MyStruct(struct.Struct):
766+
def __init__(self):
767+
super().__init__('>h')
768+
769+
my_struct = MyStruct()
770+
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
771+
777772
def test_repr(self):
778773
s = struct.Struct('=i2H')
779774
self.assertEqual(repr(s), f'Struct({s.format!r})')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Revert change to :class:`struct.Struct` initialization that broke some cases
2+
of subclassing.

Modules/_struct.c

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,9 +1553,28 @@ prepare_s(PyStructObject *self)
15531553
return -1;
15541554
}
15551555

1556+
static PyObject *
1557+
s_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1558+
{
1559+
PyObject *self;
1560+
1561+
assert(type != NULL);
1562+
allocfunc alloc_func = PyType_GetSlot(type, Py_tp_alloc);
1563+
assert(alloc_func != NULL);
1564+
1565+
self = alloc_func(type, 0);
1566+
if (self != NULL) {
1567+
PyStructObject *s = (PyStructObject*)self;
1568+
s->s_format = Py_NewRef(Py_None);
1569+
s->s_codes = NULL;
1570+
s->s_size = -1;
1571+
s->s_len = -1;
1572+
}
1573+
return self;
1574+
}
1575+
15561576
/*[clinic input]
1557-
@classmethod
1558-
Struct.__new__
1577+
Struct.__init__
15591578
15601579
format: object
15611580
@@ -1567,49 +1586,36 @@ the format string.
15671586
See help(struct) for more on format strings.
15681587
[clinic start generated code]*/
15691588

1570-
static PyObject *
1571-
Struct_impl(PyTypeObject *type, PyObject *format)
1572-
/*[clinic end generated code: output=49468b044e334308 input=8b91868eb1df0e28]*/
1589+
static int
1590+
Struct___init___impl(PyStructObject *self, PyObject *format)
1591+
/*[clinic end generated code: output=b8e80862444e92d0 input=192a4575a3dde802]*/
15731592
{
1574-
allocfunc alloc = PyType_GetSlot(type, Py_tp_alloc);
1575-
assert(alloc != NULL);
1576-
PyStructObject *self = (PyStructObject *)alloc(type, 0);
1577-
1578-
if (self == NULL) {
1579-
return NULL;
1580-
}
1593+
int ret = 0;
15811594

15821595
if (PyUnicode_Check(format)) {
15831596
format = PyUnicode_AsASCIIString(format);
1584-
if (format == NULL) {
1585-
Py_DECREF(self);
1586-
return NULL;
1587-
}
1597+
if (format == NULL)
1598+
return -1;
15881599
}
15891600
else {
15901601
Py_INCREF(format);
15911602
}
15921603

15931604
if (!PyBytes_Check(format)) {
15941605
Py_DECREF(format);
1595-
Py_DECREF(self);
15961606
PyErr_Format(PyExc_TypeError,
15971607
"Struct() argument 1 must be a str or bytes object, "
15981608
"not %.200s",
15991609
_PyType_Name(Py_TYPE(format)));
1600-
return NULL;
1610+
return -1;
16011611
}
16021612

1603-
self->s_format = format;
1613+
Py_SETREF(self->s_format, format);
16041614

1605-
if (prepare_s(self) < 0) {
1606-
Py_DECREF(self);
1607-
return NULL;
1608-
}
1609-
return (PyObject *)self;
1615+
ret = prepare_s(self);
1616+
return ret;
16101617
}
16111618

1612-
16131619
static int
16141620
s_clear(PyStructObject *s)
16151621
{
@@ -2219,8 +2225,9 @@ static PyType_Slot PyStructType_slots[] = {
22192225
{Py_tp_methods, s_methods},
22202226
{Py_tp_members, s_members},
22212227
{Py_tp_getset, s_getsetlist},
2222-
{Py_tp_new, Struct},
2228+
{Py_tp_init, Struct___init__},
22232229
{Py_tp_alloc, PyType_GenericAlloc},
2230+
{Py_tp_new, s_new},
22242231
{Py_tp_free, PyObject_GC_Del},
22252232
{0, 0},
22262233
};

Modules/clinic/_struct.c.h

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)