Skip to content

Commit 3bb0994

Browse files
authored
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.
1 parent b73b5fb commit 3bb0994

File tree

13 files changed

+144
-102
lines changed

13 files changed

+144
-102
lines changed

Doc/c-api/typeobj.rst

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,25 @@ and :c:type:`PyType_Type` effectively act as defaults.)
11991199

12001200
.. versionadded:: 3.10
12011201

1202+
.. data:: Py_TPFLAGS_DISALLOW_INSTANTIATION
1203+
1204+
Disallow creating instances of the type: set
1205+
:c:member:`~PyTypeObject.tp_new` to NULL and don't create the ``__new__``
1206+
key in the type dictionary.
1207+
1208+
The flag must be set before creating the type, not after. For example, it
1209+
must be set before :c:func:`PyType_Ready` is called on the type.
1210+
1211+
The flag is set automatically on :ref:`static types <static-types>` if
1212+
:c:member:`~PyTypeObject.tp_base` is NULL or ``&PyBaseObject_Type`` and
1213+
:c:member:`~PyTypeObject.tp_new` is NULL.
1214+
1215+
**Inheritance:**
1216+
1217+
This flag is not inherited.
1218+
1219+
.. versionadded:: 3.10
1220+
12021221

12031222
.. c:member:: const char* PyTypeObject.tp_doc
12041223
@@ -1761,6 +1780,9 @@ and :c:type:`PyType_Type` effectively act as defaults.)
17611780
in :c:member:`~PyTypeObject.tp_new`, while for mutable types, most initialization should be
17621781
deferred to :c:member:`~PyTypeObject.tp_init`.
17631782

1783+
Set the :const:`Py_TPFLAGS_DISALLOW_INSTANTIATION` flag to disallow creating
1784+
instances of the type in Python.
1785+
17641786
**Inheritance:**
17651787

17661788
This field is inherited by subtypes, except it is not inherited by
@@ -2596,7 +2618,8 @@ A type that supports weakrefs, instance dicts, and hashing::
25962618
};
25972619

25982620
A str subclass that cannot be subclassed and cannot be called
2599-
to create instances (e.g. uses a separate factory func)::
2621+
to create instances (e.g. uses a separate factory func) using
2622+
:c:data:`Py_TPFLAGS_DISALLOW_INSTANTIATION` flag::
26002623

26012624
typedef struct {
26022625
PyUnicodeObject raw;
@@ -2609,8 +2632,7 @@ to create instances (e.g. uses a separate factory func)::
26092632
.tp_basicsize = sizeof(MyStr),
26102633
.tp_base = NULL, // set to &PyUnicode_Type in module init
26112634
.tp_doc = "my custom str",
2612-
.tp_flags = Py_TPFLAGS_DEFAULT,
2613-
.tp_new = NULL,
2635+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
26142636
.tp_repr = (reprfunc)myobj_repr,
26152637
};
26162638

Doc/whatsnew/3.10.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,6 +1708,10 @@ New Features
17081708
These functions allow to activate, deactivate and query the state of the garbage collector from C code without
17091709
having to import the :mod:`gc` module.
17101710
1711+
* Add a new :c:data:`Py_TPFLAGS_DISALLOW_INSTANTIATION` type flag to disallow
1712+
creating type instances.
1713+
(Contributed by Victor Stinner in :issue:`43916`.)
1714+
17111715
Porting to Python 3.10
17121716
----------------------
17131717

Include/object.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ given type object has a specified feature.
327327
#define Py_TPFLAGS_MAPPING (1 << 6)
328328
#endif
329329

330+
/* Disallow creating instances of the type: set tp_new to NULL and don't create
331+
* the "__new__" key in the type dictionary. */
332+
#define Py_TPFLAGS_DISALLOW_INSTANTIATION (1UL << 7)
333+
330334
/* Set if the type object is immutable: type attributes cannot be set nor deleted */
331335
#define Py_TPFLAGS_IMMUTABLETYPE (1UL << 8)
332336

Include/structseq.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ PyAPI_FUNC(void) PyStructSequence_InitType(PyTypeObject *type,
2727
PyAPI_FUNC(int) PyStructSequence_InitType2(PyTypeObject *type,
2828
PyStructSequence_Desc *desc);
2929
#endif
30+
#ifdef Py_BUILD_CORE
31+
extern int _PyStructSequence_InitType(
32+
PyTypeObject *type,
33+
PyStructSequence_Desc *desc,
34+
unsigned long tp_flags);
35+
#endif
3036
PyAPI_FUNC(PyTypeObject*) PyStructSequence_NewType(PyStructSequence_Desc *desc);
3137

3238
PyAPI_FUNC(PyObject *) PyStructSequence_New(PyTypeObject* type);

Lib/test/test_sys.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,11 +605,12 @@ def test_sys_flags(self):
605605
def assert_raise_on_new_sys_type(self, sys_attr):
606606
# Users are intentionally prevented from creating new instances of
607607
# sys.flags, sys.version_info, and sys.getwindowsversion.
608+
arg = sys_attr
608609
attr_type = type(sys_attr)
609610
with self.assertRaises(TypeError):
610-
attr_type()
611+
attr_type(arg)
611612
with self.assertRaises(TypeError):
612-
attr_type.__new__(attr_type)
613+
attr_type.__new__(attr_type, arg)
613614

614615
def test_sys_flags_no_instantiation(self):
615616
self.assert_raise_on_new_sys_type(sys.flags)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add a new :c:data:`Py_TPFLAGS_DISALLOW_INSTANTIATION` type flag to disallow
2+
creating type instances. Patch by Victor Stinner.

Modules/_curses_panel.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ static PyType_Slot PyCursesPanel_Type_slots[] = {
520520
static PyType_Spec PyCursesPanel_Type_spec = {
521521
.name = "_curses_panel.panel",
522522
.basicsize = sizeof(PyCursesPanelObject),
523-
.flags = Py_TPFLAGS_DEFAULT,
523+
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
524524
.slots = PyCursesPanel_Type_slots
525525
};
526526

@@ -656,7 +656,6 @@ _curses_panel_exec(PyObject *mod)
656656
if (state->PyCursesPanel_Type == NULL) {
657657
return -1;
658658
}
659-
((PyTypeObject *)state->PyCursesPanel_Type)->tp_new = NULL;
660659

661660
if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) {
662661
return -1;

Modules/_cursesmodule.c

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4793,25 +4793,18 @@ PyInit__curses(void)
47934793
#ifdef NCURSES_VERSION
47944794
/* ncurses_version */
47954795
if (NcursesVersionType.tp_name == NULL) {
4796-
if (PyStructSequence_InitType2(&NcursesVersionType,
4797-
&ncurses_version_desc) < 0)
4796+
if (_PyStructSequence_InitType(&NcursesVersionType,
4797+
&ncurses_version_desc,
4798+
Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
47984799
return NULL;
4800+
}
47994801
}
48004802
v = make_ncurses_version();
48014803
if (v == NULL) {
48024804
return NULL;
48034805
}
48044806
PyDict_SetItemString(d, "ncurses_version", v);
48054807
Py_DECREF(v);
4806-
4807-
/* prevent user from creating new instances */
4808-
NcursesVersionType.tp_init = NULL;
4809-
NcursesVersionType.tp_new = NULL;
4810-
if (PyDict_DelItemString(NcursesVersionType.tp_dict, "__new__") < 0 &&
4811-
PyErr_ExceptionMatches(PyExc_KeyError))
4812-
{
4813-
PyErr_Clear();
4814-
}
48154808
#endif /* NCURSES_VERSION */
48164809

48174810
SetDictInt("ERR", ERR);

Modules/_tkinter.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ static PyType_Spec PyTclObject_Type_spec = {
10021002
"_tkinter.Tcl_Obj",
10031003
sizeof(PyTclObject),
10041004
0,
1005-
Py_TPFLAGS_DEFAULT,
1005+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
10061006
PyTclObject_Type_slots,
10071007
};
10081008

@@ -3294,7 +3294,7 @@ static PyType_Spec Tktt_Type_spec = {
32943294
"_tkinter.tktimertoken",
32953295
sizeof(TkttObject),
32963296
0,
3297-
Py_TPFLAGS_DEFAULT,
3297+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
32983298
Tktt_Type_slots,
32993299
};
33003300

@@ -3349,7 +3349,7 @@ static PyType_Spec Tkapp_Type_spec = {
33493349
"_tkinter.tkapp",
33503350
sizeof(TkappObject),
33513351
0,
3352-
Py_TPFLAGS_DEFAULT,
3352+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
33533353
Tkapp_Type_slots,
33543354
};
33553355

@@ -3537,7 +3537,6 @@ PyInit__tkinter(void)
35373537
Py_DECREF(m);
35383538
return NULL;
35393539
}
3540-
((PyTypeObject *)o)->tp_new = NULL;
35413540
if (PyModule_AddObject(m, "TkappType", o)) {
35423541
Py_DECREF(o);
35433542
Py_DECREF(m);
@@ -3550,7 +3549,6 @@ PyInit__tkinter(void)
35503549
Py_DECREF(m);
35513550
return NULL;
35523551
}
3553-
((PyTypeObject *)o)->tp_new = NULL;
35543552
if (PyModule_AddObject(m, "TkttType", o)) {
35553553
Py_DECREF(o);
35563554
Py_DECREF(m);
@@ -3563,7 +3561,6 @@ PyInit__tkinter(void)
35633561
Py_DECREF(m);
35643562
return NULL;
35653563
}
3566-
((PyTypeObject *)o)->tp_new = NULL;
35673564
if (PyModule_AddObject(m, "Tcl_Obj", o)) {
35683565
Py_DECREF(o);
35693566
Py_DECREF(m);

Modules/_xxsubinterpretersmodule.c

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,12 @@ static PyTypeObject ChannelIDtype = {
17801780
0, /* tp_getattro */
17811781
0, /* tp_setattro */
17821782
0, /* tp_as_buffer */
1783-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
1783+
// Use Py_TPFLAGS_DISALLOW_INSTANTIATION so the type cannot be instantiated
1784+
// from Python code. We do this because there is a strong relationship
1785+
// between channel IDs and the channel lifecycle, so this limitation avoids
1786+
// related complications. Use the _channel_id() function instead.
1787+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
1788+
| Py_TPFLAGS_DISALLOW_INSTANTIATION, /* tp_flags */
17841789
channelid_doc, /* tp_doc */
17851790
0, /* tp_traverse */
17861791
0, /* tp_clear */
@@ -1791,19 +1796,6 @@ static PyTypeObject ChannelIDtype = {
17911796
0, /* tp_methods */
17921797
0, /* tp_members */
17931798
channelid_getsets, /* tp_getset */
1794-
0, /* tp_base */
1795-
0, /* tp_dict */
1796-
0, /* tp_descr_get */
1797-
0, /* tp_descr_set */
1798-
0, /* tp_dictoffset */
1799-
0, /* tp_init */
1800-
0, /* tp_alloc */
1801-
// Note that we do not set tp_new to channelid_new. Instead we
1802-
// set it to NULL, meaning it cannot be instantiated from Python
1803-
// code. We do this because there is a strong relationship between
1804-
// channel IDs and the channel lifecycle, so this limitation avoids
1805-
// related complications.
1806-
NULL, /* tp_new */
18071799
};
18081800

18091801

Objects/structseq.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,10 @@ initialize_members(PyStructSequence_Desc *desc, PyMemberDef* members,
459459
members[k].name = NULL;
460460
}
461461

462+
462463
int
463-
PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
464+
_PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc,
465+
unsigned long tp_flags)
464466
{
465467
PyMemberDef *members;
466468
Py_ssize_t n_members, n_unnamed_members;
@@ -488,7 +490,7 @@ PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
488490
type->tp_base = &PyTuple_Type;
489491
type->tp_methods = structseq_methods;
490492
type->tp_new = structseq_new;
491-
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC;
493+
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | tp_flags;
492494
type->tp_traverse = (traverseproc) structseq_traverse;
493495

494496
n_members = count_members(desc, &n_unnamed_members);
@@ -516,6 +518,12 @@ PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
516518
return 0;
517519
}
518520

521+
int
522+
PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
523+
{
524+
return _PyStructSequence_InitType(type, desc, 0);
525+
}
526+
519527
void
520528
PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc)
521529
{

0 commit comments

Comments
 (0)