Skip to content

Commit

Permalink
pythongh-123909: PyType_From*: Disallow metaclasses with custom tp_new
Browse files Browse the repository at this point in the history
  • Loading branch information
encukou committed Sep 11, 2024
1 parent fb1b51a commit 4877284
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 46 deletions.
29 changes: 20 additions & 9 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -345,8 +345,12 @@ The following functions and structs are used to create
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.
:c:member:`~PyTypeObject.tp_new` is deprecated.
.. versionchanged:: 3.14
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is no longer allowed.
.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
Expand All @@ -362,24 +366,31 @@ The following functions and structs are used to create
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.
:c:member:`~PyTypeObject.tp_new` is deprecated.
.. versionchanged:: 3.14
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is no longer allowed.
.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)
Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, NULL)``.
.. versionchanged:: 3.12
The function now finds and uses a metaclass corresponding to the
base classes provided in *Py_tp_base[s]* slots.
Previously, only :class:`type` instances were returned.
The function now finds and uses a metaclass corresponding to the provided
base classes. Previously, only :class:`type` instances were returned.
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.
:c:member:`~PyTypeObject.tp_new` is deprecated.
.. versionchanged:: 3.14
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is no longer allowed.
.. raw:: html
Expand Down
12 changes: 3 additions & 9 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,22 +722,16 @@ def test_heaptype_with_custom_metaclass_custom_new(self):
with self.assertRaisesRegex(TypeError, msg):
t = _testcapi.pytype_fromspec_meta(metaclass)

def test_heaptype_with_custom_metaclass_deprecation(self):
def test_heaptype_base_with_custom_metaclass(self):
metaclass = _testcapi.HeapCTypeMetaclassCustomNew

# gh-103968: a metaclass with custom tp_new is deprecated, but still
# allowed for functions that existed in 3.11
# (PyType_FromSpecWithBases is used here).
class Base(metaclass=metaclass):
pass

# Class creation from C
with warnings_helper.check_warnings(
('.* _testcapi.Subclass .* custom tp_new.*in Python 3.14.*', DeprecationWarning),
):
msg = "Metaclasses with custom tp_new are not supported."
with self.assertRaisesRegex(TypeError, msg):
sub = _testcapi.make_type_with_base(Base)
self.assertTrue(issubclass(sub, Base))
self.assertIsInstance(sub, metaclass)

def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
for weakref_cls in (_testcapi.HeapCTypeWithWeakref,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:c:func:`PyType_FromSpec`, :c:func:`PyType_FromSpecWithBases` and
:c:func:`PyType_FromModuleAndSpec` will now fail if the metaclass of the new
type has custom :c:member:`~PyTypeObject.tp_new`.
38 changes: 10 additions & 28 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4668,10 +4668,10 @@ special_offset_from_member(
return -1;
}

static PyObject *
_PyType_FromMetaclass_impl(
PyObject *
PyType_FromMetaclass(
PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in, int _allow_tp_new)
PyType_Spec *spec, PyObject *bases_in)
{
/* Invariant: A non-NULL value in one of these means this function holds
* a strong reference or owns allocated memory.
Expand Down Expand Up @@ -4848,21 +4848,10 @@ _PyType_FromMetaclass_impl(
goto finally;
}
if (metaclass->tp_new && metaclass->tp_new != PyType_Type.tp_new) {
if (_allow_tp_new) {
if (PyErr_WarnFormat(
PyExc_DeprecationWarning, 1,
"Type %s uses PyType_Spec with a metaclass that has custom "
"tp_new. This is deprecated and will no longer be allowed in "
"Python 3.14.", spec->name) < 0) {
goto finally;
}
}
else {
PyErr_SetString(
PyExc_TypeError,
"Metaclasses with custom tp_new are not supported.");
goto finally;
}
PyErr_SetString(
PyExc_TypeError,
"Metaclasses with custom tp_new are not supported.");
goto finally;
}

/* Calculate best base, and check that all bases are type objects */
Expand Down Expand Up @@ -5109,29 +5098,22 @@ _PyType_FromMetaclass_impl(
return (PyObject*)res;
}

PyObject *
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in)
{
return _PyType_FromMetaclass_impl(metaclass, module, spec, bases_in, 0);
}

PyObject *
PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
{
return _PyType_FromMetaclass_impl(NULL, module, spec, bases, 1);
return PyType_FromMetaclass(NULL, module, spec, bases);
}

PyObject *
PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
{
return _PyType_FromMetaclass_impl(NULL, NULL, spec, bases, 1);
return PyType_FromMetaclass(NULL, NULL, spec, bases);
}

PyObject *
PyType_FromSpec(PyType_Spec *spec)
{
return _PyType_FromMetaclass_impl(NULL, NULL, spec, NULL, 1);
return PyType_FromMetaclass(NULL, NULL, spec, NULL);
}

PyObject *
Expand Down

0 comments on commit 4877284

Please sign in to comment.