bpo-43916: Add Py_TPFLAGS_DISALLOW_INSTANTIATION type flag (GH-25721)

Add a new Py_TPFLAGS_DISALLOW_INSTANTIATION type flag to disallow
creating type instances: set tp_new to NULL and don't create the
"__new__" key in the type dictionary.

The flag is set automatically on static types if tp_base is NULL or
&PyBaseObject_Type and tp_new is NULL.

Use the flag on the following types:

* _curses.ncurses_version type
* _curses_panel.panel
* _tkinter.Tcl_Obj
* _tkinter.tkapp
* _tkinter.tktimertoken
* _xxsubinterpretersmodule.ChannelID
* sys.flags type
* sys.getwindowsversion() type
* sys.version_info type

Update MyStr example in the C API documentation to use
Py_TPFLAGS_DISALLOW_INSTANTIATION.

Add _PyStructSequence_InitType() function to create a structseq type
with the Py_TPFLAGS_DISALLOW_INSTANTIATION flag set.

type_new() calls _PyType_CheckConsistency() at exit.
This commit is contained in:
Victor Stinner 2021-04-30 12:46:15 +02:00 committed by GitHub
parent b73b5fb9ea
commit 3bb09947ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 144 additions and 102 deletions

View file

@ -158,6 +158,11 @@ _PyType_CheckConsistency(PyTypeObject *type)
CHECK(!(type->tp_flags & Py_TPFLAGS_READYING));
CHECK(type->tp_dict != NULL);
if (type->tp_flags & Py_TPFLAGS_DISALLOW_INSTANTIATION) {
CHECK(type->tp_new == NULL);
CHECK(_PyDict_ContainsId(type->tp_dict, &PyId___new__) == 0);
}
return 1;
#undef CHECK
}
@ -1111,8 +1116,7 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
if (type->tp_new == NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"cannot create '%.100s' instances",
type->tp_name);
"cannot create '%s' instances", type->tp_name);
return NULL;
}
@ -3185,6 +3189,8 @@ type_new_impl(type_new_ctx *ctx)
if (type_new_init_subclass(type, ctx->kwds) < 0) {
goto error;
}
assert(_PyType_CheckConsistency(type));
return (PyObject *)type;
error:
@ -5651,7 +5657,6 @@ type_add_getset(PyTypeObject *type)
static void
inherit_special(PyTypeObject *type, PyTypeObject *base)
{
/* Copying tp_traverse and tp_clear is connected to the GC flags */
if (!(type->tp_flags & Py_TPFLAGS_HAVE_GC) &&
(base->tp_flags & Py_TPFLAGS_HAVE_GC) &&
@ -5662,23 +5667,7 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
if (type->tp_clear == NULL)
type->tp_clear = base->tp_clear;
}
{
/* The condition below could use some explanation.
It appears that tp_new is not inherited for static types
whose base class is 'object'; this seems to be a precaution
so that old extension types don't suddenly become
callable (object.__new__ wouldn't insure the invariants
that the extension type's own factory function ensures).
Heap types, of course, are under our control, so they do
inherit tp_new; static extension types that specify some
other built-in type as the default also
inherit object.__new__. */
if (base != &PyBaseObject_Type ||
(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
if (type->tp_new == NULL)
type->tp_new = base->tp_new;
}
}
if (type->tp_basicsize == 0)
type->tp_basicsize = base->tp_basicsize;
@ -5941,6 +5930,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
}
static int add_operators(PyTypeObject *);
static int add_tp_new_wrapper(PyTypeObject *type);
static int
@ -5991,6 +5981,7 @@ type_ready_set_bases(PyTypeObject *type)
type->tp_base = base;
}
}
assert(type->tp_base != NULL || type == &PyBaseObject_Type);
/* Now the only way base can still be NULL is if type is
* &PyBaseObject_Type. */
@ -6249,6 +6240,50 @@ type_ready_add_subclasses(PyTypeObject *type)
}
// Set tp_new and the "__new__" key in the type dictionary.
// Use the Py_TPFLAGS_DISALLOW_INSTANTIATION flag.
static int
type_ready_set_new(PyTypeObject *type)
{
PyTypeObject *base = type->tp_base;
/* The condition below could use some explanation.
It appears that tp_new is not inherited for static types whose base
class is 'object'; this seems to be a precaution so that old extension
types don't suddenly become callable (object.__new__ wouldn't insure the
invariants that the extension type's own factory function ensures).
Heap types, of course, are under our control, so they do inherit tp_new;
static extension types that specify some other built-in type as the
default also inherit object.__new__. */
if (type->tp_new == NULL
&& base == &PyBaseObject_Type
&& !(type->tp_flags & Py_TPFLAGS_HEAPTYPE))
{
type->tp_flags |= Py_TPFLAGS_DISALLOW_INSTANTIATION;
}
if (!(type->tp_flags & Py_TPFLAGS_DISALLOW_INSTANTIATION)) {
if (type->tp_new != NULL) {
// If "__new__" key does not exists in the type dictionary,
// set it to tp_new_wrapper().
if (add_tp_new_wrapper(type) < 0) {
return -1;
}
}
else {
// tp_new is NULL: inherit tp_new from base
type->tp_new = base->tp_new;
}
}
else {
// Py_TPFLAGS_DISALLOW_INSTANTIATION sets tp_new to NULL
type->tp_new = NULL;
}
return 0;
}
static int
type_ready(PyTypeObject *type)
{
@ -6275,6 +6310,9 @@ type_ready(PyTypeObject *type)
if (type_ready_mro(type) < 0) {
return -1;
}
if (type_ready_set_new(type) < 0) {
return -1;
}
if (type_ready_fill_dict(type) < 0) {
return -1;
}
@ -6898,8 +6936,8 @@ tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds)
"__new__() called with non-type 'self'");
return NULL;
}
type = (PyTypeObject *)self;
if (!PyTuple_Check(args) || PyTuple_GET_SIZE(args) < 1) {
PyErr_Format(PyExc_TypeError,
"%s.__new__(): not enough arguments",
@ -6961,16 +6999,18 @@ static struct PyMethodDef tp_new_methoddef[] = {
static int
add_tp_new_wrapper(PyTypeObject *type)
{
PyObject *func;
int r = _PyDict_ContainsId(type->tp_dict, &PyId___new__);
if (r > 0)
if (r > 0) {
return 0;
if (r < 0)
}
if (r < 0) {
return -1;
func = PyCFunction_NewEx(tp_new_methoddef, (PyObject *)type, NULL);
if (func == NULL)
}
PyObject *func = PyCFunction_NewEx(tp_new_methoddef, (PyObject *)type, NULL);
if (func == NULL) {
return -1;
}
r = _PyDict_SetItemId(type->tp_dict, &PyId___new__, func);
Py_DECREF(func);
return r;
@ -8558,11 +8598,6 @@ add_operators(PyTypeObject *type)
Py_DECREF(descr);
}
}
if (type->tp_new != NULL) {
if (add_tp_new_wrapper(type) < 0) {
return -1;
}
}
return 0;
}