Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,18 @@ def test_endian_table_init_subinterpreters(self):
results = executor.map(exec, [code] * 5)
self.assertListEqual(list(results), [None] * 5)

def test_operations_on_half_initialized_Struct(self):
S = struct.Struct.__new__(struct.Struct)

spam = array.array('b', b' ')
self.assertRaises(RuntimeError, S.iter_unpack, 1)
Comment thread
skirpichev marked this conversation as resolved.
Outdated
self.assertRaises(RuntimeError, S.pack, 1)
self.assertRaises(RuntimeError, S.pack_into, 1)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S.pack_into(buffer, offset, v1, v2, ...)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I think that argument processing in the struct's functions/methods should be refactored and transformed to the AC. E.g. S.pack_info could check required arguments before validation of the soself struct.

Though, this belongs to a separate issue.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AC cleanup goes to #143673

self.assertRaises(RuntimeError, S.unpack, spam)
self.assertRaises(RuntimeError, S.unpack_from, spam)
self.assertRaises(RuntimeError, getattr, S, 'format')
self.assertEqual(S.size, -1)


class UnpackIteratorTest(unittest.TestCase):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Raise :exc:`RuntimeError`'s when user attempts to call methods on
half-initialized :class:`~struct.Struct` objects, For example, created by
``Struct.__new__(Struct)``. Patch by Sergey B Kirpichev.
32 changes: 20 additions & 12 deletions Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -1698,8 +1698,6 @@ prepare_s(PyStructObject *self)
return -1;
}

self->s_size = size;
self->s_len = len;
codes = PyMem_Malloc((ncodes + 1) * sizeof(formatcode));
if (codes == NULL) {
PyErr_NoMemory();
Expand All @@ -1709,6 +1707,8 @@ prepare_s(PyStructObject *self)
if (self->s_codes != NULL)
PyMem_Free(self->s_codes);
self->s_codes = codes;
self->s_size = size;
self->s_len = len;

s = fmt;
size = 0;
Expand Down Expand Up @@ -1794,8 +1794,6 @@ static int
Struct___init___impl(PyStructObject *self, PyObject *format)
/*[clinic end generated code: output=b8e80862444e92d0 input=192a4575a3dde802]*/
{
int ret = 0;

Comment thread
skirpichev marked this conversation as resolved.
if (PyUnicode_Check(format)) {
format = PyUnicode_AsASCIIString(format);
if (format == NULL)
Expand All @@ -1816,8 +1814,10 @@ Struct___init___impl(PyStructObject *self, PyObject *format)

Py_SETREF(self->s_format, format);

ret = prepare_s(self);
return ret;
if (prepare_s(self)) {
return -1;
}
return 0;
Comment thread
skirpichev marked this conversation as resolved.
Outdated
}

static int
Expand Down Expand Up @@ -1897,6 +1897,14 @@ s_unpack_internal(PyStructObject *soself, const char *startfrom,
return NULL;
}

#define ENSURE_STRUCT_IS_READY(self) \
do { \
if (!(self)->s_codes) { \
PyErr_SetString(PyExc_RuntimeError, \
"Struct object is not initialized"); \
return NULL; \
} \
} while (0);

/*[clinic input]
Struct.unpack
Expand All @@ -1917,7 +1925,7 @@ Struct_unpack_impl(PyStructObject *self, Py_buffer *buffer)
/*[clinic end generated code: output=873a24faf02e848a input=3113f8e7038b2f6c]*/
{
_structmodulestate *state = get_struct_state_structinst(self);
assert(self->s_codes != NULL);
ENSURE_STRUCT_IS_READY(self);
if (buffer->len != self->s_size) {
PyErr_Format(state->StructError,
"unpack requires a buffer of %zd bytes",
Expand Down Expand Up @@ -1949,7 +1957,7 @@ Struct_unpack_from_impl(PyStructObject *self, Py_buffer *buffer,
/*[clinic end generated code: output=57fac875e0977316 input=cafd4851d473c894]*/
{
_structmodulestate *state = get_struct_state_structinst(self);
assert(self->s_codes != NULL);
ENSURE_STRUCT_IS_READY(self);

if (offset < 0) {
if (offset + self->s_size > 0) {
Expand Down Expand Up @@ -2101,8 +2109,7 @@ Struct_iter_unpack_impl(PyStructObject *self, PyObject *buffer)
{
_structmodulestate *state = get_struct_state_structinst(self);
unpackiterobject *iter;

assert(self->s_codes != NULL);
ENSURE_STRUCT_IS_READY(self);

if (self->s_size == 0) {
PyErr_Format(state->StructError,
Expand Down Expand Up @@ -2242,8 +2249,8 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)

/* Validate arguments. */
soself = PyStructObject_CAST(self);
ENSURE_STRUCT_IS_READY(soself);
assert(PyStruct_Check(self, state));
assert(soself->s_codes != NULL);
if (nargs != soself->s_len)
{
PyErr_Format(state->StructError,
Expand Down Expand Up @@ -2285,8 +2292,8 @@ s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs)

/* Validate arguments. +1 is for the first arg as buffer. */
soself = PyStructObject_CAST(self);
ENSURE_STRUCT_IS_READY(soself);
assert(PyStruct_Check(self, state));
assert(soself->s_codes != NULL);
if (nargs != (soself->s_len + 2))
{
if (nargs == 0) {
Expand Down Expand Up @@ -2373,6 +2380,7 @@ static PyObject *
s_get_format(PyObject *op, void *Py_UNUSED(closure))
{
PyStructObject *self = PyStructObject_CAST(op);
ENSURE_STRUCT_IS_READY(self);
return PyUnicode_FromStringAndSize(PyBytes_AS_STRING(self->s_format),
PyBytes_GET_SIZE(self->s_format));
}
Expand Down
Loading