Skip to content

Commit 3b18427

Browse files
committed
gh-78724: raise RuntimeError's when calling methods on non-ready Struct()'s
Calling the ``Struct.__new__()`` dunder without required argument now is deprecated. Calling the ``Struct.__init__()`` dunder method on initialized object now also is deprecated.
1 parent ab62167 commit 3b18427

3 files changed

Lines changed: 67 additions & 8 deletions

File tree

Lib/test/test_struct.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import unittest
88
import struct
99
import sys
10+
import warnings
1011
import weakref
1112

1213
from test import support
@@ -575,7 +576,11 @@ def test_Struct_reinitialization(self):
575576
# Struct instance. This test can be used to detect the leak
576577
# when running with regrtest -L.
577578
s = struct.Struct('i')
578-
s.__init__('ii')
579+
with self.assertWarns(DeprecationWarning):
580+
s.__init__('ii')
581+
with warnings.catch_warnings():
582+
warnings.simplefilter("error", DeprecationWarning)
583+
self.assertRaises(DeprecationWarning, s.__init__, 'ii')
579584

580585
def check_sizeof(self, format_str, number_of_codes):
581586
# The size of 'PyStructObject'
@@ -783,7 +788,8 @@ class MyStruct(struct.Struct):
783788
def __init__(self):
784789
super().__init__('>h')
785790

786-
my_struct = MyStruct()
791+
with self.assertWarns(DeprecationWarning):
792+
my_struct = MyStruct()
787793
self.assertEqual(my_struct.pack(12345), b'\x30\x39')
788794

789795
def test_repr(self):
@@ -826,8 +832,19 @@ def __bool__(self):
826832
S.__init__('I')
827833
return True
828834

829-
self.assertRaises(RuntimeError, S.pack, Evil(), 1)
830-
self.assertRaises(RuntimeError, S.pack_into, buf, 0, Evil(), 1)
835+
with self.assertWarns(DeprecationWarning):
836+
self.assertRaises(RuntimeError, S.pack, Evil(), 1)
837+
self.assertRaises(RuntimeError, S.pack_into, buf, 0, Evil(), 1)
838+
839+
def test_operations_on_half_initialized_Struct(self):
840+
with self.assertWarns(DeprecationWarning):
841+
S = struct.Struct.__new__(struct.Struct)
842+
843+
spam = array.array('b', b' ')
844+
for attr in ['iter_unpack', 'pack', 'pack_into',
845+
'unpack', 'unpack_from']:
846+
meth = getattr(S, attr)
847+
self.assertRaises(RuntimeError, meth, spam)
831848

832849

833850
class UnpackIteratorTest(unittest.TestCase):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Prevent crashes due to using half-initialized :class:`~struct.Struct`
2+
objects. For example, created by ``Struct.__new__(Struct)``. Calling the
3+
``Struct.__new__()`` dunder without required argument now is deprecated.
4+
Calling :meth:`~object.__init__` dunder method of :class:`~struct.Struct`
5+
object on initialized object now also is deprecated. Patch by Sergey B
6+
Kirpichev.

Modules/_struct.c

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ typedef struct {
7171
PyObject *s_format;
7272
PyObject *weakreflist; /* List of weak references */
7373
PyMutex mutex; /* to prevent mutation during packing */
74+
bool ready;
7475
} PyStructObject;
7576

7677
#define PyStructObject_CAST(op) ((PyStructObject *)(op))
@@ -1763,6 +1764,12 @@ s_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17631764
{
17641765
PyObject *self;
17651766

1767+
if (PyTuple_GET_SIZE(args) != 1
1768+
&& PyErr_WarnEx(PyExc_DeprecationWarning,
1769+
"Struct().__new__() has one required argument", 1))
1770+
{
1771+
return NULL;
1772+
}
17661773
assert(type != NULL);
17671774
allocfunc alloc_func = PyType_GetSlot(type, Py_tp_alloc);
17681775
assert(alloc_func != NULL);
@@ -1775,6 +1782,7 @@ s_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17751782
s->s_size = -1;
17761783
s->s_len = -1;
17771784
s->mutex = (PyMutex){0};
1785+
s->ready = false;
17781786
}
17791787
return self;
17801788
}
@@ -1796,8 +1804,13 @@ static int
17961804
Struct___init___impl(PyStructObject *self, PyObject *format)
17971805
/*[clinic end generated code: output=b8e80862444e92d0 input=192a4575a3dde802]*/
17981806
{
1799-
int ret = 0;
1800-
1807+
if (self->ready
1808+
&& PyErr_WarnEx(PyExc_DeprecationWarning,
1809+
("Explicit call of __init__() on "
1810+
"initialized Struct()"), 1))
1811+
{
1812+
return -1;
1813+
}
18011814
if (PyUnicode_Check(format)) {
18021815
format = PyUnicode_AsASCIIString(format);
18031816
if (format == NULL)
@@ -1823,8 +1836,11 @@ Struct___init___impl(PyStructObject *self, PyObject *format)
18231836
"Call Struct.__init__() in struct.pack()");
18241837
return -1;
18251838
}
1826-
ret = prepare_s(self);
1827-
return ret;
1839+
if (prepare_s(self)) {
1840+
return -1;
1841+
}
1842+
self->ready = true;
1843+
return 0;
18281844
}
18291845

18301846
static int
@@ -1924,6 +1940,10 @@ Struct_unpack_impl(PyStructObject *self, Py_buffer *buffer)
19241940
/*[clinic end generated code: output=873a24faf02e848a input=3113f8e7038b2f6c]*/
19251941
{
19261942
_structmodulestate *state = get_struct_state_structinst(self);
1943+
if (!self->ready) {
1944+
PyErr_SetString(PyExc_RuntimeError, "Call unpack on non-initialized Struct()");
1945+
return NULL;
1946+
}
19271947
assert(self->s_codes != NULL);
19281948
if (buffer->len != self->s_size) {
19291949
PyErr_Format(state->StructError,
@@ -1956,6 +1976,10 @@ Struct_unpack_from_impl(PyStructObject *self, Py_buffer *buffer,
19561976
/*[clinic end generated code: output=57fac875e0977316 input=cafd4851d473c894]*/
19571977
{
19581978
_structmodulestate *state = get_struct_state_structinst(self);
1979+
if (!self->ready) {
1980+
PyErr_SetString(PyExc_RuntimeError, "Call unpack_from on non-initialized Struct()");
1981+
return NULL;
1982+
}
19591983
assert(self->s_codes != NULL);
19601984

19611985
if (offset < 0) {
@@ -2108,6 +2132,10 @@ Struct_iter_unpack_impl(PyStructObject *self, PyObject *buffer)
21082132
{
21092133
_structmodulestate *state = get_struct_state_structinst(self);
21102134
unpackiterobject *iter;
2135+
if (!self->ready) {
2136+
PyErr_SetString(PyExc_RuntimeError, "Call iter_unpack on non-initialized Struct()");
2137+
return NULL;
2138+
}
21112139

21122140
assert(self->s_codes != NULL);
21132141

@@ -2249,6 +2277,10 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
22492277

22502278
/* Validate arguments. */
22512279
soself = PyStructObject_CAST(self);
2280+
if (!soself->ready) {
2281+
PyErr_SetString(PyExc_RuntimeError, "Call pack on non-initialized Struct()");
2282+
return NULL;
2283+
}
22522284
assert(PyStruct_Check(self, state));
22532285
assert(soself->s_codes != NULL);
22542286
if (nargs != soself->s_len)
@@ -2295,6 +2327,10 @@ s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
22952327

22962328
/* Validate arguments. +1 is for the first arg as buffer. */
22972329
soself = PyStructObject_CAST(self);
2330+
if (!soself->ready) {
2331+
PyErr_SetString(PyExc_RuntimeError, "Call pack_into on non-initialized Struct()");
2332+
return NULL;
2333+
}
22982334
assert(PyStruct_Check(self, state));
22992335
assert(soself->s_codes != NULL);
23002336
if (nargs != (soself->s_len + 2))

0 commit comments

Comments
 (0)