import os
import pickle
import re
+import textwrap
import time
import unittest
+from test.support import script_helper
class StructSeqTest(unittest.TestCase):
with self.assertRaisesRegex(TypeError, error_message):
copy.replace(r, st_mode=1, error=2)
+ def test_reference_cycle(self):
+ # gh-122527: Check that a structseq that's part of a reference cycle
+ # with its own type doesn't crash. Previously, if the type's dictionary
+ # was cleared first, the structseq instance would crash in the
+ # destructor.
+ script_helper.assert_python_ok("-c", textwrap.dedent(r"""
+ import time
+ t = time.gmtime()
+ type(t).refcyle = t
+ """))
+
if __name__ == "__main__":
unittest.main()
get_type_attr_as_size(tp, &_Py_ID(n_sequence_fields))
#define REAL_SIZE_TP(tp) \
get_type_attr_as_size(tp, &_Py_ID(n_fields))
-#define REAL_SIZE(op) REAL_SIZE_TP(Py_TYPE(op))
+#define REAL_SIZE(op) get_real_size((PyObject *)op)
#define UNNAMED_FIELDS_TP(tp) \
get_type_attr_as_size(tp, &_Py_ID(n_unnamed_fields))
#define UNNAMED_FIELDS(op) UNNAMED_FIELDS_TP(Py_TYPE(op))
+static Py_ssize_t
+get_real_size(PyObject *op)
+{
+ // Compute the real size from the visible size (i.e., Py_SIZE()) and the
+ // number of non-sequence fields accounted for in tp_basicsize.
+ Py_ssize_t hidden = Py_TYPE(op)->tp_basicsize - offsetof(PyStructSequence, ob_item);
+ return Py_SIZE(op) + hidden / sizeof(PyObject *);
+}
PyObject *
PyStructSequence_New(PyTypeObject *type)
PyObject_GC_UnTrack(obj);
PyTypeObject *tp = Py_TYPE(obj);
+ // gh-122527: We can't use REAL_SIZE_TP() or any macros that access the
+ // type's dictionary here, because the dictionary may have already been
+ // cleared by the garbage collector.
size = REAL_SIZE(obj);
for (i = 0; i < size; ++i) {
Py_XDECREF(obj->ob_item[i]);
static void
initialize_static_fields(PyTypeObject *type, PyStructSequence_Desc *desc,
- PyMemberDef *tp_members, unsigned long tp_flags)
+ PyMemberDef *tp_members, Py_ssize_t n_members,
+ unsigned long tp_flags)
{
type->tp_name = desc->name;
- type->tp_basicsize = sizeof(PyStructSequence) - sizeof(PyObject *);
+ // Account for hidden members in tp_basicsize because they are not
+ // included in the variable size.
+ Py_ssize_t n_hidden = n_members - desc->n_in_sequence;
+ type->tp_basicsize = sizeof(PyStructSequence) + (n_hidden - 1) * sizeof(PyObject *);
type->tp_itemsize = sizeof(PyObject *);
type->tp_dealloc = (destructor)structseq_dealloc;
type->tp_repr = (reprfunc)structseq_repr;
if (members == NULL) {
goto error;
}
- initialize_static_fields(type, desc, members, tp_flags);
+ initialize_static_fields(type, desc, members, n_members, tp_flags);
_Py_SetImmortal((PyObject *)type);
}
if (members == NULL) {
return -1;
}
- initialize_static_fields(type, desc, members, 0);
+ initialize_static_fields(type, desc, members, n_members, 0);
if (initialize_static_type(type, desc, n_members, n_unnamed_members) < 0) {
PyMem_Free(members);
return -1;
/* The name in this PyType_Spec is statically allocated so it is */
/* expected that it'll outlive the PyType_Spec */
spec.name = desc->name;
- spec.basicsize = sizeof(PyStructSequence) - sizeof(PyObject *);
+ Py_ssize_t hidden = n_members - desc->n_in_sequence;
+ spec.basicsize = (int)(sizeof(PyStructSequence) + (hidden - 1) * sizeof(PyObject *));
spec.itemsize = sizeof(PyObject *);
spec.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | tp_flags;
spec.slots = slots;