gh-121654: Add PyType_Freeze() function (#122457)

Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
Victor Stinner 2024-10-25 11:12:48 +02:00 committed by GitHub
parent da8673da36
commit db96327203
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 162 additions and 13 deletions

View File

@ -413,6 +413,20 @@ The following functions and structs are used to create
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is no longer allowed.
.. c:function:: int PyType_Freeze(PyTypeObject *type)
Make a type immutable: set the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag.
All base classes of *type* must be immutable.
On success, return ``0``.
On error, set an exception and return ``-1``.
The type must not be used before it's made immutable. For example, type
instances must not be created before the type is made immutable.
.. versionadded:: 3.14
.. raw:: html
<!-- Keep old URL fragments working (see gh-97908) -->

View File

@ -684,6 +684,7 @@ func,PyTuple_Size,3.2,,
data,PyTuple_Type,3.2,,
type,PyTypeObject,3.2,,opaque
func,PyType_ClearCache,3.2,,
func,PyType_Freeze,3.14,,
func,PyType_FromMetaclass,3.12,,
func,PyType_FromModuleAndSpec,3.10,,
func,PyType_FromSpec,3.2,,

View File

@ -777,6 +777,9 @@ New features
(Contributed by Victor Stinner in :gh:`124502`.)
* Add :c:func:`PyType_Freeze` function to make a type immutable.
(Contributed by Victor Stinner in :gh:`121654`.)
Porting to Python 3.14
----------------------

View File

@ -796,6 +796,10 @@ static inline int PyType_CheckExact(PyObject *op) {
PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
#endif
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,66 @@
from test.support import import_helper
import unittest
_testcapi = import_helper.import_module('_testcapi')
class TypeTests(unittest.TestCase):
def test_freeze(self):
# test PyType_Freeze()
type_freeze = _testcapi.type_freeze
# simple case, no inherante
class MyType:
pass
MyType.attr = "mutable"
type_freeze(MyType)
err_msg = "cannot set 'attr' attribute of immutable type 'MyType'"
with self.assertRaisesRegex(TypeError, err_msg):
# the class is now immutable
MyType.attr = "immutable"
# test MRO: PyType_Freeze() requires base classes to be immutable
class A: pass
class B: pass
class C(B): pass
class D(A, C): pass
self.assertEqual(D.mro(), [D, A, C, B, object])
with self.assertRaises(TypeError):
type_freeze(D)
type_freeze(A)
type_freeze(B)
type_freeze(C)
# all parent classes are now immutable, so D can be made immutable
# as well
type_freeze(D)
def test_freeze_meta(self):
"""test PyType_Freeze() with overridden MRO"""
type_freeze = _testcapi.type_freeze
class Base:
value = 1
class Meta(type):
def mro(cls):
return (cls, Base, object)
class FreezeThis(metaclass=Meta):
"""This has `Base` in the MRO, but not tp_bases"""
self.assertEqual(FreezeThis.value, 1)
with self.assertRaises(TypeError):
type_freeze(FreezeThis)
Base.value = 2
self.assertEqual(FreezeThis.value, 2)
type_freeze(Base)
with self.assertRaises(TypeError):
Base.value = 3
type_freeze(FreezeThis)
self.assertEqual(FreezeThis.value, 2)

View File

@ -713,6 +713,7 @@ SYMBOL_NAMES = (
"PyTuple_Size",
"PyTuple_Type",
"PyType_ClearCache",
"PyType_Freeze",
"PyType_FromMetaclass",
"PyType_FromModuleAndSpec",
"PyType_FromSpec",

View File

@ -0,0 +1,2 @@
Add :c:func:`PyType_Freeze` function to make a type immutable. Patch by
Victor Stinner.

View File

@ -2538,3 +2538,5 @@
added = '3.14'
[function.PyUnicode_Equal]
added = '3.14'
[function.PyType_Freeze]
added = '3.14'

View File

@ -3310,6 +3310,7 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args))
Py_RETURN_NONE;
}
// Used by `finalize_thread_hang`.
#ifdef _POSIX_THREADS
static void finalize_thread_hang_cleanup_callback(void *Py_UNUSED(arg)) {
@ -3339,6 +3340,20 @@ finalize_thread_hang(PyObject *self, PyObject *callback)
}
static PyObject *
type_freeze(PyObject *module, PyObject *args)
{
PyTypeObject *type;
if (!PyArg_ParseTuple(args, "O!", &PyType_Type, &type)) {
return NULL;
}
if (PyType_Freeze(type) < 0) {
return NULL;
}
Py_RETURN_NONE;
}
static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
{"test_config", test_config, METH_NOARGS},
@ -3479,6 +3494,7 @@ static PyMethodDef TestMethods[] = {
{"function_set_warning", function_set_warning, METH_NOARGS},
{"test_critical_sections", test_critical_sections, METH_NOARGS},
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
{"type_freeze", type_freeze, METH_VARARGS},
{NULL, NULL} /* sentinel */
};

View File

@ -4637,6 +4637,32 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type)
return 1;
}
static int
check_immutable_bases(const char *type_name, PyObject *bases, int skip_first)
{
Py_ssize_t i = 0;
if (skip_first) {
// When testing the MRO, skip the type itself
i = 1;
}
for (; i<PyTuple_GET_SIZE(bases); i++) {
PyTypeObject *b = (PyTypeObject*)PyTuple_GET_ITEM(bases, i);
if (!b) {
return -1;
}
if (!_PyType_HasFeature(b, Py_TPFLAGS_IMMUTABLETYPE)) {
PyErr_Format(
PyExc_TypeError,
"Creating immutable type %s from mutable base %N",
type_name, b
);
return -1;
}
}
return 0;
}
/* Set *dest to the offset specified by a special "__*offset__" member.
* Return 0 on success, -1 on failure.
*/
@ -4820,20 +4846,9 @@ PyType_FromMetaclass(
* and only heap types can be mutable.)
*/
if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) {
for (int i=0; i<PyTuple_GET_SIZE(bases); i++) {
PyTypeObject *b = (PyTypeObject*)PyTuple_GET_ITEM(bases, i);
if (!b) {
if (check_immutable_bases(spec->name, bases, 0) < 0) {
goto finally;
}
if (!_PyType_HasFeature(b, Py_TPFLAGS_IMMUTABLETYPE)) {
PyErr_Format(
PyExc_TypeError,
"Creating immutable type %s from mutable base %N",
spec->name, b
);
goto finally;
}
}
}
/* Calculate the metaclass */
@ -11319,6 +11334,30 @@ add_operators(PyTypeObject *type)
}
int
PyType_Freeze(PyTypeObject *type)
{
// gh-121654: Check the __mro__ instead of __bases__
PyObject *mro = type_get_mro(type, NULL);
if (!PyTuple_Check(mro)) {
Py_DECREF(mro);
PyErr_SetString(PyExc_TypeError, "unable to get the type MRO");
return -1;
}
int check = check_immutable_bases(type->tp_name, mro, 1);
Py_DECREF(mro);
if (check < 0) {
return -1;
}
type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
PyType_Modified(type);
return 0;
}
/* Cooperative 'super' */
typedef struct {

1
PC/python3dll.c generated
View File

@ -646,6 +646,7 @@ EXPORT_FUNC(PyTuple_Pack)
EXPORT_FUNC(PyTuple_SetItem)
EXPORT_FUNC(PyTuple_Size)
EXPORT_FUNC(PyType_ClearCache)
EXPORT_FUNC(PyType_Freeze)
EXPORT_FUNC(PyType_FromMetaclass)
EXPORT_FUNC(PyType_FromModuleAndSpec)
EXPORT_FUNC(PyType_FromSpec)