https://github.com/python/cpython/commit/8fd4435cd9860bd4c2e22acced7a721a2ad9d99f
commit: 8fd4435cd9860bd4c2e22acced7a721a2ad9d99f
branch: 3.13
author: Sergey B Kirpichev <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-12T14:45:52+02:00
summary:

[3.13] gh-78724: Raise RuntimeError's when calling methods on non-ready 
Struct()'s (GH-143643) (GH-143714)

(cherry picked from commit 515ae4078dffa0b74e5e5431462c2f4fe4563ffa)

files:
A Misc/NEWS.d/next/Library/2026-01-10-10-04-08.gh-issue-78724.xkXfxX.rst
M Lib/test/test_struct.py
M Modules/_struct.c

diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py
index e3ddaa090316ff..7241a80dd67672 100644
--- a/Lib/test/test_struct.py
+++ b/Lib/test/test_struct.py
@@ -786,6 +786,19 @@ def test_repr(self):
         s = struct.Struct('=i2H')
         self.assertEqual(repr(s), f'Struct({s.format!r})')
 
+    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, spam)
+        self.assertRaises(RuntimeError, S.pack, 1)
+        self.assertRaises(RuntimeError, S.pack_into, spam, 1)
+        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):
     """
     Tests for iterative unpacking (struct.Struct.iter_unpack).
diff --git 
a/Misc/NEWS.d/next/Library/2026-01-10-10-04-08.gh-issue-78724.xkXfxX.rst 
b/Misc/NEWS.d/next/Library/2026-01-10-10-04-08.gh-issue-78724.xkXfxX.rst
new file mode 100644
index 00000000000000..8a4bec4e1653d1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-01-10-10-04-08.gh-issue-78724.xkXfxX.rst
@@ -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.
diff --git a/Modules/_struct.c b/Modules/_struct.c
index a9f8fa4bbdeca3..7505afcd7c6b0d 100644
--- a/Modules/_struct.c
+++ b/Modules/_struct.c
@@ -1498,8 +1498,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();
@@ -1509,6 +1507,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;
@@ -1695,6 +1695,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
@@ -1715,7 +1723,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",
@@ -1747,7 +1755,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) {
@@ -1890,8 +1898,7 @@ Struct_iter_unpack(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,
@@ -2032,8 +2039,8 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t 
nargs)
 
     /* Validate arguments. */
     soself = (PyStructObject *)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,
@@ -2077,8 +2084,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 *)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) {
@@ -2164,6 +2171,7 @@ s_pack_into(PyObject *self, PyObject *const *args, 
Py_ssize_t nargs)
 static PyObject *
 s_get_format(PyStructObject *self, void *unused)
 {
+    ENSURE_STRUCT_IS_READY(self);
     return PyUnicode_FromStringAndSize(PyBytes_AS_STRING(self->s_format),
                                        PyBytes_GET_SIZE(self->s_format));
 }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to