diff --git a/Include/object.h b/Include/object.h index c772deaf57dbb5..62634025d7fc94 100644 --- a/Include/object.h +++ b/Include/object.h @@ -499,7 +499,9 @@ PyAPI_FUNC(unsigned long) PyType_GetFlags(PyTypeObject*); PyAPI_FUNC(int) PyType_Ready(PyTypeObject *); PyAPI_FUNC(PyObject *) PyType_GenericAlloc(PyTypeObject *, Py_ssize_t); PyAPI_FUNC(PyObject *) PyType_GenericNew(PyTypeObject *, - PyObject *, PyObject *); + PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) PyType_NullNew(PyTypeObject *, + PyObject *, PyObject *); #ifndef Py_LIMITED_API PyAPI_FUNC(const char *) _PyType_Name(PyTypeObject *); PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *); diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 3b442fe6a4b7aa..7934b34011892c 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -14,6 +14,7 @@ import sys import tempfile import unittest +import re from test.support import requires, import_module, verbose, SaveSignals @@ -306,6 +307,21 @@ def test_getmouse(self): curses.ungetmouse(0, 0, 0, 0, curses.BUTTON1_PRESSED) m = curses.getmouse() + @requires_curses_func('panel') + def test_panel_no_instantiation(self): + cant_create_message = re.escape("cannot create '_curses_panel.panel' instances") + object_message = re.escape( + 'object.__new__(_curses_panel.panel) is not safe, ' + 'use _curses_panel.panel.__new__()' + ) + + with self.assertRaisesRegex(TypeError, cant_create_message): + curses.panel.panel() + with self.assertRaisesRegex(TypeError, cant_create_message): + curses.panel.panel.__new__(curses.panel.panel) + with self.assertRaisesRegex(TypeError, object_message): + object.__new__(curses.panel.panel) + @requires_curses_func('panel') def test_userptr_without_set(self): w = curses.newwin(10, 10) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 336ae447a8de5e..c938f1efb21560 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -11,6 +11,7 @@ import sysconfig import locale import threading +import re # count the number of test runs, used to create unique # strings to intern in test_intern() @@ -541,10 +542,26 @@ 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. attr_type = type(sys_attr) - with self.assertRaises(TypeError): + attr_type_name = f'sys.{attr_type.__name__}' + + cant_create_message = re.escape(f"cannot create '{attr_type_name}' instances") + object_message = re.escape( + f'object.__new__({attr_type_name}) is not safe, ' + f'use {attr_type_name}.__new__()' + ) + tuple_message = re.escape( + f'tuple.__new__({attr_type_name}) is not safe, ' + f'use {attr_type_name}.__new__()' + ) + + with self.assertRaisesRegex(TypeError, cant_create_message): attr_type() - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, cant_create_message): attr_type.__new__(attr_type) + with self.assertRaisesRegex(TypeError, object_message): + object.__new__(attr_type) + with self.assertRaisesRegex(TypeError, tuple_message): + tuple.__new__(attr_type) def test_sys_flags_no_instantiation(self): self.assert_raise_on_new_sys_type(sys.flags) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 56848c1bf87eb5..a30535b6ce62c9 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -9,6 +9,7 @@ import types import unittest.mock import weakref +import re class TypesTests(unittest.TestCase): @@ -1652,5 +1653,41 @@ def coro(): 'close', 'throw'})) +class ObjectCreationTests(unittest.TestCase): + def test_simple_create(self): + class C: pass + self.assertIsInstance(C(), C) + self.assertIsInstance(C.__new__(C), C) + self.assertIsInstance(object.__new__(C), C) + + def test_create_static_class(self): + C = dict + self.assertIsInstance(C(), C) + self.assertIsInstance(C.__new__(C), C) + + message = re.escape( + 'object.__new__(dict) is not safe, ' + 'use dict.__new__()' + ) + with self.assertRaisesRegex(TypeError, message): + object.__new__(C), C + + def test_create_uncreatable(self): + C = types.GeneratorType + + cant_create_message = re.escape("cannot create 'generator' instances") + not_safe_message = re.escape( + 'object.__new__(generator) is not safe, ' + 'use generator.__new__()' + ) + + with self.assertRaisesRegex(TypeError, cant_create_message): + C() + with self.assertRaisesRegex(TypeError, cant_create_message): + C.__new__(C) + with self.assertRaisesRegex(TypeError, not_safe_message): + object.__new__(C) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/tkinter/test/test_tkinter/test_internals.py b/Lib/tkinter/test/test_tkinter/test_internals.py new file mode 100644 index 00000000000000..3faeace64a0b8c --- /dev/null +++ b/Lib/tkinter/test/test_tkinter/test_internals.py @@ -0,0 +1,38 @@ +import unittest, test.support +import _tkinter +import re + +class InternalsTest(unittest.TestCase): + def assert_raise_on_new(self, tkinter_type): + # Users are intentionally prevented from creating new instances of + # _tkinter.TkappType, _tkinter.Tcl_Obj and _tkinter.TkttType + tkinter_type_name = f'_tkinter.{tkinter_type.__name__}' + + cant_create_message = re.escape(f"cannot create '{tkinter_type_name}' instances") + object_message = re.escape( + f'object.__new__({tkinter_type_name}) is not safe, ' + f'use {tkinter_type_name}.__new__()' + ) + + with self.assertRaisesRegex(TypeError, cant_create_message): + tkinter_type() + with self.assertRaisesRegex(TypeError, cant_create_message): + tkinter_type.__new__(tkinter_type) + with self.assertRaisesRegex(TypeError, object_message): + object.__new__(tkinter_type) + + def test_tkapp_type_no_instantiation(self): + self.assert_raise_on_new(_tkinter.TkappType) + + def test_tck_obj_no_instantiation(self): + self.assert_raise_on_new(_tkinter.Tcl_Obj) + + def test_tktt_type_info_no_instantiation(self): + self.assert_raise_on_new(_tkinter.TkttType) + + +def test_main(): + test.support.run_unittest(InternalsTest,) + +if __name__ == "__main__": + test_main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-08-08-20-40-46.bpo-34284.jFqKLF.rst b/Misc/NEWS.d/next/Core and Builtins/2018-08-08-20-40-46.bpo-34284.jFqKLF.rst new file mode 100644 index 00000000000000..8fa799cf63c8ef --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-08-08-20-40-46.bpo-34284.jFqKLF.rst @@ -0,0 +1,4 @@ +Types that cannot be instantiated now don't have `tp_new` equal to NULL, +but equal to a common helper function that raises TypeError instead. +This is applied to all old types without `tp_new` and types +in the `sys` module that are derived from `tuple`. diff --git a/Modules/_curses_panel.c b/Modules/_curses_panel.c index 609718f65f15ab..2ab8b63013f711 100644 --- a/Modules/_curses_panel.c +++ b/Modules/_curses_panel.c @@ -477,6 +477,7 @@ static PyMethodDef PyCursesPanel_Methods[] = { /* -------------------------------------------------------*/ static PyType_Slot PyCursesPanel_Type_slots[] = { + {Py_tp_new, PyType_NullNew}, {Py_tp_dealloc, PyCursesPanel_Dealloc}, {Py_tp_methods, PyCursesPanel_Methods}, {0, 0}, @@ -640,7 +641,6 @@ PyInit__curses_panel(void) v = PyType_FromSpec(&PyCursesPanel_Type_spec); if (v == NULL) goto fail; - ((PyTypeObject *)v)->tp_new = NULL; _curses_panelstate(m)->PyCursesPanel_Type = v; import_curses(); diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 93d4dbc5f659a0..1bf21ac1cc31e3 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -906,6 +906,7 @@ static PyGetSetDef PyTclObject_getsetlist[] = { }; static PyType_Slot PyTclObject_Type_slots[] = { + {Py_tp_new, PyType_NullNew}, {Py_tp_dealloc, (destructor)PyTclObject_dealloc}, {Py_tp_repr, (reprfunc)PyTclObject_repr}, {Py_tp_str, (reprfunc)PyTclObject_str}, @@ -3206,6 +3207,7 @@ static PyMethodDef Tktt_methods[] = }; static PyType_Slot Tktt_Type_slots[] = { + {Py_tp_new, PyType_NullNew}, {Py_tp_dealloc, Tktt_Dealloc}, {Py_tp_repr, Tktt_Repr}, {Py_tp_methods, Tktt_methods}, @@ -3261,6 +3263,7 @@ static PyMethodDef Tkapp_methods[] = }; static PyType_Slot Tkapp_Type_slots[] = { + {Py_tp_new, PyType_NullNew}, {Py_tp_dealloc, Tkapp_Dealloc}, {Py_tp_methods, Tkapp_methods}, {0, 0} @@ -3459,7 +3462,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); @@ -3472,7 +3474,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); @@ -3485,7 +3486,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 a7a9d7bf9fc335..2164dfc5e3ae4f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -995,6 +995,15 @@ PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds) return type->tp_alloc(type, 0); } +PyObject * +PyType_NullNew(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyErr_Format(PyExc_TypeError, + "cannot create '%.100s' instances", + type->tp_name); + return NULL; +} + /* Helpers for subtyping */ static int @@ -2270,6 +2279,10 @@ valid_identifier(PyObject *s) static int object_init(PyObject *self, PyObject *args, PyObject *kwds); +/* Forward */ +static int +add_tp_new_wrapper(PyTypeObject *type); + static int type_init(PyObject *cls, PyObject *args, PyObject *kwds) { @@ -4842,7 +4855,17 @@ add_getset(PyTypeObject *type, PyGetSetDef *gsp) return 0; } -static void +static int +set_null_new(PyTypeObject *type) +{ + type->tp_new = PyType_NullNew; + if (add_tp_new_wrapper(type) < 0) + return -1; + + return 0; +} + +static int inherit_special(PyTypeObject *type, PyTypeObject *base) { @@ -4867,10 +4890,15 @@ inherit_special(PyTypeObject *type, PyTypeObject *base) 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_new == NULL) { + if (base != &PyBaseObject_Type || + (type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { + type->tp_new = base->tp_new; + } + else { + if (set_null_new(type) < 0) + return -1; + } } } if (type->tp_basicsize == 0) @@ -4903,6 +4931,8 @@ inherit_special(PyTypeObject *type, PyTypeObject *base) type->tp_flags |= Py_TPFLAGS_LIST_SUBCLASS; else if (PyType_IsSubtype(base, &PyDict_Type)) type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS; + + return 0; } static int @@ -5193,7 +5223,8 @@ PyType_Ready(PyTypeObject *type) /* Inherit special flags from dominant base */ if (type->tp_base != NULL) - inherit_special(type, type->tp_base); + if (inherit_special(type, type->tp_base) < 0) + goto error; /* Initialize tp_dict properly */ bases = type->tp_mro; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 177b8307626d64..9632f19c161c23 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2273,6 +2273,14 @@ static struct PyModuleDef sysmodule = { } while (0) +static void +prevent_user_creation(PyTypeObject *type) +{ + /* prevent user from creating new instances */ + type->tp_init = NULL; + type->tp_new = PyType_NullNew; +} + _PyInitError _PySys_BeginInit(PyObject **sysmod) { @@ -2370,12 +2378,7 @@ _PySys_BeginInit(PyObject **sysmod) } version_info = make_version_info(); SET_SYS_FROM_STRING("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(PyExc_KeyError)) - PyErr_Clear(); + prevent_user_creation(&VersionInfoType); /* implementation */ SET_SYS_FROM_STRING("implementation", make_impl_info(version_info)); @@ -2396,14 +2399,7 @@ _PySys_BeginInit(PyObject **sysmod) &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()); - res = PyDict_DelItemString(WindowsVersionType.tp_dict, "__new__"); - if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) { - PyErr_Clear(); - } + prevent_user_creation(&WindowsVersionType); #endif /* float repr style: 0.03 (short) vs 0.029999999999999999 (legacy) */ @@ -2493,16 +2489,7 @@ _PySys_EndInit(PyObject *sysdict, _PyMainInterpreterConfig *config) /* Set flags to their final values */ SET_SYS_FROM_STRING_INT_RESULT("flags", make_flags()); - /* 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(PyExc_KeyError)) { - return res; - } - PyErr_Clear(); - } + prevent_user_creation(&FlagsType); SET_SYS_FROM_STRING_INT_RESULT("dont_write_bytecode", PyBool_FromLong(Py_DontWriteBytecodeFlag));