Skip to content

Commit

Permalink
Use PyType_GetDict in Python 3.12+
Browse files Browse the repository at this point in the history
Previously we were incorrectly accessing `tp_dict` in mixin classes when
defining a `Struct` class on Python 3.12+. This could result in a
segfault if a user tried to create a `Struct` class that also subclassed
from an interpreter builtin type (like `Exception`). Now we error nicely
instead.
  • Loading branch information
jcrist committed Oct 14, 2024
1 parent 7ade469 commit f4a0c63
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 3 deletions.
13 changes: 10 additions & 3 deletions msgspec/_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ ms_popcount(uint64_t i) { \
#define SET_SIZE(obj, size) (((PyVarObject *)obj)->ob_size = size)
#endif

/* In Python 3.12+, tp_dict is NULL for some core types, PyType_GetDict returns
* a borrowed reference to the interpreter or cls mapping */
#if PY312_PLUS
#define MS_GET_TYPE_DICT(a) PyType_GetDict(a)
#else
#define MS_GET_TYPE_DICT(a) ((a)->tp_dict)
#endif

#if PY313_PLUS
#define MS_UNICODE_EQ(a, b) (PyUnicode_Compare(a, b) == 0)
#else
Expand Down Expand Up @@ -5527,10 +5535,9 @@ structmeta_collect_base(StructMetaInfo *info, MsgspecState *mod, PyObject *base)

static const char *attrs[] = {"__init__", "__new__"};
Py_ssize_t nattrs = 2;
PyObject *tp_dict = MS_GET_TYPE_DICT((PyTypeObject *)base);
for (Py_ssize_t i = 0; i < nattrs; i++) {
if (PyDict_GetItemString(
((PyTypeObject *)base)->tp_dict, attrs[i]) != NULL
) {
if (PyDict_GetItemString(tp_dict, attrs[i]) != NULL) {
PyErr_Format(PyExc_TypeError, "Struct base classes cannot define %s", attrs[i]);
return -1;
}
Expand Down
6 changes: 6 additions & 0 deletions tests/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ def __new__(self):
class Test(Struct, Mixin):
pass

def test_mixin_builtin_type_errors(self):
with pytest.raises(TypeError):

class Test(Struct, Exception):
pass


def test_struct_subclass_forbids_non_types():
# Currently this failcase is handled by CPython's internals, but it's good
Expand Down

0 comments on commit f4a0c63

Please sign in to comment.