diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 85f0262d101381..01509f0aede85e 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -1199,6 +1199,17 @@ and :c:type:`PyType_Type` effectively act as defaults.) .. versionadded:: 3.10 + .. data:: Py_TPFLAGS_DISABLE_NEW + + Disallow creating instances of the type. For example, calling its + constructor raises a :exc:`TypeError`. + + **Inheritance:** + + This flag is not inherited. + + .. versionadded:: 3.10 + .. c:member:: const char* PyTypeObject.tp_doc @@ -2596,7 +2607,8 @@ A type that supports weakrefs, instance dicts, and hashing:: }; A str subclass that cannot be subclassed and cannot be called -to create instances (e.g. uses a separate factory func):: +to create instances (e.g. uses a separate factory func) using +:c:data:`Py_TPFLAGS_DISABLE_NEW` flag:: typedef struct { PyUnicodeObject raw; @@ -2609,8 +2621,7 @@ to create instances (e.g. uses a separate factory func):: .tp_basicsize = sizeof(MyStr), .tp_base = NULL, // set to &PyUnicode_Type in module init .tp_doc = "my custom str", - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = NULL, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISABLE_NEW, .tp_repr = (reprfunc)myobj_repr, }; diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 2580a0368d0b34..e9834e2a04a319 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -1708,6 +1708,10 @@ New Features These functions allow to activate, deactivate and query the state of the garbage collector from C code without having to import the :mod:`gc` module. +* Add a new :c:data:`Py_TPFLAGS_DISABLE_NEW` type flag to disallow creating + instances of a type. + (Contributed by Victor Stinner in :issue:`43916`.) + Porting to Python 3.10 ---------------------- diff --git a/Include/object.h b/Include/object.h index d8476f9213760d..39e1af7e75ecd2 100644 --- a/Include/object.h +++ b/Include/object.h @@ -320,6 +320,10 @@ Code can use PyType_HasFeature(type_ob, flag_value) to test whether the given type object has a specified feature. */ +/* Disallow creating instances of the type. For example, calling its + * constructor raises a TypeError */ +#define Py_TPFLAGS_DISABLE_NEW (1UL << 7) + /* Set if the type object is immutable: type attributes cannot be set nor deleted */ #define Py_TPFLAGS_IMMUTABLETYPE (1UL << 8) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 9e9c874445c27d..1236aa723b1995 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -905,11 +905,11 @@ def test_get_fips_mode(self): def test_internal_types(self): # internal types like _hashlib.HASH are not constructable with self.assertRaisesRegex( - TypeError, "cannot create 'HASH' instance" + TypeError, "cannot create '_hashlib.HASH' instance" ): HASH() with self.assertRaisesRegex( - TypeError, "cannot create 'HASHXOF' instance" + TypeError, "cannot create '_hashlib.HASHXOF' instance" ): HASHXOF() diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index adf52adbf22afa..22d74e9a1138a1 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -440,7 +440,7 @@ def test_withmodule(self): def test_internal_types(self): # internal types like _hashlib.C_HMAC are not constructable with self.assertRaisesRegex( - TypeError, "cannot create 'HMAC' instance" + TypeError, "cannot create '_hashlib.HMAC' instance" ): C_HMAC() diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index ee39375af3160e..1fd5247a91bb51 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -605,11 +605,12 @@ def test_sys_flags(self): def assert_raise_on_new_sys_type(self, sys_attr): # Users are intentionally prevented from creating new instances of # sys.flags, sys.version_info, and sys.getwindowsversion. + arg = sys_attr attr_type = type(sys_attr) with self.assertRaises(TypeError): - attr_type() + attr_type(arg) with self.assertRaises(TypeError): - attr_type.__new__(attr_type) + attr_type.__new__(attr_type, arg) def test_sys_flags_no_instantiation(self): self.assert_raise_on_new_sys_type(sys.flags) diff --git a/Misc/NEWS.d/next/C API/2021-04-29-17-35-48.bpo-43916.wvWt23.rst b/Misc/NEWS.d/next/C API/2021-04-29-17-35-48.bpo-43916.wvWt23.rst new file mode 100644 index 00000000000000..066c2f048dbd47 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-04-29-17-35-48.bpo-43916.wvWt23.rst @@ -0,0 +1,2 @@ +Add a new :c:data:`Py_TPFLAGS_DISABLE_NEW` type flag to disallow creating +instances of a type. Patch by Victor Stinner. diff --git a/Modules/_curses_panel.c b/Modules/_curses_panel.c index 94caf8c93bc8c3..c04f18a110cc25 100644 --- a/Modules/_curses_panel.c +++ b/Modules/_curses_panel.c @@ -520,7 +520,7 @@ static PyType_Slot PyCursesPanel_Type_slots[] = { static PyType_Spec PyCursesPanel_Type_spec = { .name = "_curses_panel.panel", .basicsize = sizeof(PyCursesPanelObject), - .flags = Py_TPFLAGS_DEFAULT, + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISABLE_NEW, .slots = PyCursesPanel_Type_slots }; @@ -656,7 +656,6 @@ _curses_panel_exec(PyObject *mod) if (state->PyCursesPanel_Type == NULL) { return -1; } - ((PyTypeObject *)state->PyCursesPanel_Type)->tp_new = NULL; if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) { return -1; diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index d221cf1a92520c..3708ed02601d36 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -4794,8 +4794,11 @@ PyInit__curses(void) /* ncurses_version */ if (NcursesVersionType.tp_name == NULL) { if (PyStructSequence_InitType2(&NcursesVersionType, - &ncurses_version_desc) < 0) + &ncurses_version_desc) < 0) { return NULL; + } + /* prevent user from creating new instances */ + NcursesVersionType.tp_flags |= Py_TPFLAGS_DISABLE_NEW; } v = make_ncurses_version(); if (v == NULL) { @@ -4803,15 +4806,6 @@ PyInit__curses(void) } PyDict_SetItemString(d, "ncurses_version", v); Py_DECREF(v); - - /* prevent user from creating new instances */ - NcursesVersionType.tp_init = NULL; - NcursesVersionType.tp_new = NULL; - if (PyDict_DelItemString(NcursesVersionType.tp_dict, "__new__") < 0 && - PyErr_ExceptionMatches(PyExc_KeyError)) - { - PyErr_Clear(); - } #endif /* NCURSES_VERSION */ SetDictInt("ERR", ERR); diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 870ee89fdafc63..16a9458bad9455 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -118,15 +118,6 @@ _setException(PyObject *exc) } /* LCOV_EXCL_STOP */ -/* {Py_tp_new, NULL} doesn't block __new__ */ -static PyObject * -_disabled_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) -{ - PyErr_Format(PyExc_TypeError, - "cannot create '%.100s' instances", _PyType_Name(type)); - return NULL; -} - static PyObject* py_digest_name(const EVP_MD *md) { @@ -590,7 +581,6 @@ static PyType_Slot EVPtype_slots[] = { {Py_tp_doc, (char *)hashtype_doc}, {Py_tp_methods, EVP_methods}, {Py_tp_getset, EVP_getseters}, - {Py_tp_new, _disabled_new}, {0, 0}, }; @@ -598,7 +588,7 @@ static PyType_Spec EVPtype_spec = { "_hashlib.HASH", /*tp_name*/ sizeof(EVPobject), /*tp_basicsize*/ 0, /*tp_itemsize*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISABLE_NEW, EVPtype_slots }; @@ -740,7 +730,6 @@ static PyType_Slot EVPXOFtype_slots[] = { {Py_tp_doc, (char *)hashxoftype_doc}, {Py_tp_methods, EVPXOF_methods}, {Py_tp_getset, EVPXOF_getseters}, - {Py_tp_new, _disabled_new}, {0, 0}, }; @@ -748,7 +737,7 @@ static PyType_Spec EVPXOFtype_spec = { "_hashlib.HASHXOF", /*tp_name*/ sizeof(EVPobject), /*tp_basicsize*/ 0, /*tp_itemsize*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISABLE_NEW, EVPXOFtype_slots }; @@ -1734,14 +1723,13 @@ static PyType_Slot HMACtype_slots[] = { {Py_tp_dealloc,(destructor)_hmac_dealloc}, {Py_tp_methods, HMAC_methods}, {Py_tp_getset, HMAC_getset}, - {Py_tp_new, _disabled_new}, {0, NULL} }; PyType_Spec HMACtype_spec = { "_hashlib.HMAC", /* name */ sizeof(HMACobject), /* basicsize */ - .flags = Py_TPFLAGS_DEFAULT, + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISABLE_NEW, .slots = HMACtype_slots, }; diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 46d6a6e0954f51..aa69b0d016f0cd 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1002,7 +1002,7 @@ static PyType_Spec PyTclObject_Type_spec = { "_tkinter.Tcl_Obj", sizeof(PyTclObject), 0, - Py_TPFLAGS_DEFAULT, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISABLE_NEW, PyTclObject_Type_slots, }; @@ -3294,7 +3294,7 @@ static PyType_Spec Tktt_Type_spec = { "_tkinter.tktimertoken", sizeof(TkttObject), 0, - Py_TPFLAGS_DEFAULT, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISABLE_NEW, Tktt_Type_slots, }; @@ -3349,7 +3349,7 @@ static PyType_Spec Tkapp_Type_spec = { "_tkinter.tkapp", sizeof(TkappObject), 0, - Py_TPFLAGS_DEFAULT, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISABLE_NEW, Tkapp_Type_slots, }; @@ -3537,7 +3537,6 @@ PyInit__tkinter(void) Py_DECREF(m); return NULL; } - ((PyTypeObject *)o)->tp_new = NULL; if (PyModule_AddObject(m, "TkappType", o)) { Py_DECREF(o); Py_DECREF(m); @@ -3550,7 +3549,6 @@ PyInit__tkinter(void) Py_DECREF(m); return NULL; } - ((PyTypeObject *)o)->tp_new = NULL; if (PyModule_AddObject(m, "TkttType", o)) { Py_DECREF(o); Py_DECREF(m); @@ -3563,7 +3561,6 @@ PyInit__tkinter(void) Py_DECREF(m); return NULL; } - ((PyTypeObject *)o)->tp_new = NULL; if (PyModule_AddObject(m, "Tcl_Obj", o)) { Py_DECREF(o); Py_DECREF(m); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e1c8be4b815452..e399d529a51e1c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1040,9 +1040,9 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) } } - if (type->tp_new == NULL) { + if (type->tp_new == NULL || type->tp_flags & Py_TPFLAGS_DISABLE_NEW) { _PyErr_Format(tstate, PyExc_TypeError, - "cannot create '%.100s' instances", + "cannot create '%s' instances", type->tp_name); return NULL; } @@ -6824,8 +6824,15 @@ tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds) "__new__() called with non-type 'self'"); return NULL; } - type = (PyTypeObject *)self; + + if (type->tp_flags & Py_TPFLAGS_DISABLE_NEW) { + PyErr_Format(PyExc_TypeError, + "cannot create '%s' instances", + type->tp_name); + return NULL; + } + if (!PyTuple_Check(args) || PyTuple_GET_SIZE(args) < 1) { PyErr_Format(PyExc_TypeError, "%s.__new__(): not enough arguments", diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 911c2d967b010a..82274bbd6d461b 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2814,16 +2814,11 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) &version_info_desc) < 0) { goto type_init_failed; } + /* prevent user from creating new instances */ + VersionInfoType.tp_flags |= Py_TPFLAGS_DISABLE_NEW; } version_info = make_version_info(tstate); SET_SYS("version_info", version_info); - /* prevent user from creating new instances */ - VersionInfoType.tp_init = NULL; - VersionInfoType.tp_new = NULL; - res = PyDict_DelItemString(VersionInfoType.tp_dict, "__new__"); - if (res < 0 && _PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { - _PyErr_Clear(tstate); - } /* implementation */ SET_SYS("implementation", make_impl_info(version_info)); @@ -2833,33 +2828,20 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) if (PyStructSequence_InitType2(&FlagsType, &flags_desc) < 0) { goto type_init_failed; } + /* prevent user from creating new instances */ + FlagsType.tp_flags |= Py_TPFLAGS_DISABLE_NEW; } SET_SYS("flags", make_flags(tstate->interp)); - /* prevent user from creating new instances */ - FlagsType.tp_init = NULL; - FlagsType.tp_new = NULL; - res = PyDict_DelItemString(FlagsType.tp_dict, "__new__"); - if (res < 0) { - if (!_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { - goto err_occurred; - } - _PyErr_Clear(tstate); - } #if defined(MS_WINDOWS) /* getwindowsversion */ - if (WindowsVersionType.tp_name == 0) + if (WindowsVersionType.tp_name == 0) { if (PyStructSequence_InitType2(&WindowsVersionType, &windows_version_desc) < 0) { goto type_init_failed; } - /* prevent user from creating new instances */ - WindowsVersionType.tp_init = NULL; - WindowsVersionType.tp_new = NULL; - assert(!_PyErr_Occurred(tstate)); - res = PyDict_DelItemString(WindowsVersionType.tp_dict, "__new__"); - if (res < 0 && _PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { - _PyErr_Clear(tstate); + /* prevent user from creating new instances */ + WindowsVersionType.tp_flags |= Py_TPFLAGS_DISABLE_NEW; } #endif