Skip to content

Commit c3f52b4

Browse files
authored
bpo-44486: Make sure that modules always have a dictionary. (GH-26847)
* Make sure that modules always have a dictionary.
1 parent 35b773a commit c3f52b4

File tree

4 files changed

+66
-52
lines changed

4 files changed

+66
-52
lines changed

Lib/test/test_module.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ def test_uninitialized(self):
2222
# An uninitialized module has no __dict__ or __name__,
2323
# and __doc__ is None
2424
foo = ModuleType.__new__(ModuleType)
25-
self.assertTrue(foo.__dict__ is None)
26-
self.assertRaises(TypeError, dir, foo)
25+
self.assertTrue(isinstance(foo.__dict__, dict))
26+
self.assertEqual(dir(foo), [])
2727
try:
2828
s = foo.__name__
2929
self.fail("__name__ = %s" % repr(s))
@@ -318,15 +318,6 @@ def test_setting_annotations(self):
318318
del foo.__dict__['__annotations__']
319319

320320
def test_annotations_getset_raises(self):
321-
# module has no dict, all operations fail
322-
foo = ModuleType.__new__(ModuleType)
323-
with self.assertRaises(TypeError):
324-
print(foo.__annotations__)
325-
with self.assertRaises(TypeError):
326-
foo.__annotations__ = {}
327-
with self.assertRaises(TypeError):
328-
del foo.__annotations__
329-
330321
# double delete
331322
foo = ModuleType("foo")
332323
foo.__annotations__ = {}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Modules will always have a dictionary, even when created by
2+
``types.ModuleType.__new__()``

Objects/moduleobject.c

Lines changed: 61 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ module_init_dict(PyModuleObject *mod, PyObject *md_dict,
6464
_Py_IDENTIFIER(__package__);
6565
_Py_IDENTIFIER(__loader__);
6666

67-
if (md_dict == NULL)
68-
return -1;
67+
assert(md_dict != NULL);
6968
if (doc == NULL)
7069
doc = Py_None;
7170

@@ -87,19 +86,41 @@ module_init_dict(PyModuleObject *mod, PyObject *md_dict,
8786
return 0;
8887
}
8988

90-
91-
PyObject *
92-
PyModule_NewObject(PyObject *name)
89+
static PyModuleObject *
90+
new_module_notrack(PyTypeObject *mt)
9391
{
9492
PyModuleObject *m;
95-
m = PyObject_GC_New(PyModuleObject, &PyModule_Type);
93+
m = PyObject_GC_New(PyModuleObject, mt);
9694
if (m == NULL)
9795
return NULL;
9896
m->md_def = NULL;
9997
m->md_state = NULL;
10098
m->md_weaklist = NULL;
10199
m->md_name = NULL;
102100
m->md_dict = PyDict_New();
101+
if (m->md_dict != NULL) {
102+
return m;
103+
}
104+
Py_DECREF(m);
105+
return NULL;
106+
}
107+
108+
static PyObject *
109+
new_module(PyTypeObject *mt, PyObject *args, PyObject *kws)
110+
{
111+
PyObject *m = (PyObject *)new_module_notrack(mt);
112+
if (m != NULL) {
113+
PyObject_GC_Track(m);
114+
}
115+
return m;
116+
}
117+
118+
PyObject *
119+
PyModule_NewObject(PyObject *name)
120+
{
121+
PyModuleObject *m = new_module_notrack(&PyModule_Type);
122+
if (m == NULL)
123+
return NULL;
103124
if (module_init_dict(m, m->md_dict, name, NULL) != 0)
104125
goto fail;
105126
PyObject_GC_Track(m);
@@ -728,43 +749,42 @@ module_getattro(PyModuleObject *m, PyObject *name)
728749
return attr;
729750
}
730751
PyErr_Clear();
731-
if (m->md_dict) {
732-
_Py_IDENTIFIER(__getattr__);
733-
getattr = _PyDict_GetItemIdWithError(m->md_dict, &PyId___getattr__);
734-
if (getattr) {
735-
return PyObject_CallOneArg(getattr, name);
736-
}
737-
if (PyErr_Occurred()) {
738-
return NULL;
739-
}
740-
mod_name = _PyDict_GetItemIdWithError(m->md_dict, &PyId___name__);
741-
if (mod_name && PyUnicode_Check(mod_name)) {
742-
Py_INCREF(mod_name);
743-
PyObject *spec = _PyDict_GetItemIdWithError(m->md_dict, &PyId___spec__);
744-
if (spec == NULL && PyErr_Occurred()) {
745-
Py_DECREF(mod_name);
746-
return NULL;
747-
}
748-
Py_XINCREF(spec);
749-
if (_PyModuleSpec_IsInitializing(spec)) {
750-
PyErr_Format(PyExc_AttributeError,
751-
"partially initialized "
752-
"module '%U' has no attribute '%U' "
753-
"(most likely due to a circular import)",
754-
mod_name, name);
755-
}
756-
else {
757-
PyErr_Format(PyExc_AttributeError,
758-
"module '%U' has no attribute '%U'",
759-
mod_name, name);
760-
}
761-
Py_XDECREF(spec);
752+
assert(m->md_dict != NULL);
753+
_Py_IDENTIFIER(__getattr__);
754+
getattr = _PyDict_GetItemIdWithError(m->md_dict, &PyId___getattr__);
755+
if (getattr) {
756+
return PyObject_CallOneArg(getattr, name);
757+
}
758+
if (PyErr_Occurred()) {
759+
return NULL;
760+
}
761+
mod_name = _PyDict_GetItemIdWithError(m->md_dict, &PyId___name__);
762+
if (mod_name && PyUnicode_Check(mod_name)) {
763+
Py_INCREF(mod_name);
764+
PyObject *spec = _PyDict_GetItemIdWithError(m->md_dict, &PyId___spec__);
765+
if (spec == NULL && PyErr_Occurred()) {
762766
Py_DECREF(mod_name);
763767
return NULL;
764768
}
765-
else if (PyErr_Occurred()) {
766-
return NULL;
769+
Py_XINCREF(spec);
770+
if (_PyModuleSpec_IsInitializing(spec)) {
771+
PyErr_Format(PyExc_AttributeError,
772+
"partially initialized "
773+
"module '%U' has no attribute '%U' "
774+
"(most likely due to a circular import)",
775+
mod_name, name);
767776
}
777+
else {
778+
PyErr_Format(PyExc_AttributeError,
779+
"module '%U' has no attribute '%U'",
780+
mod_name, name);
781+
}
782+
Py_XDECREF(spec);
783+
Py_DECREF(mod_name);
784+
return NULL;
785+
}
786+
else if (PyErr_Occurred()) {
787+
return NULL;
768788
}
769789
PyErr_Format(PyExc_AttributeError,
770790
"module has no attribute '%U'", name);
@@ -948,7 +968,7 @@ PyTypeObject PyModule_Type = {
948968
0, /* tp_descr_set */
949969
offsetof(PyModuleObject, md_dict), /* tp_dictoffset */
950970
module___init__, /* tp_init */
951-
PyType_GenericAlloc, /* tp_alloc */
952-
PyType_GenericNew, /* tp_new */
971+
0, /* tp_alloc */
972+
new_module, /* tp_new */
953973
PyObject_GC_Del, /* tp_free */
954974
};

Python/ceval.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3337,6 +3337,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
33373337
_PyLoadAttrCache *cache1 = &caches[-1].load_attr;
33383338
DEOPT_IF(!PyModule_CheckExact(owner), LOAD_ATTR);
33393339
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict;
3340+
assert(dict != NULL);
33403341
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, LOAD_ATTR);
33413342
assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE);
33423343
assert(cache0->index < dict->ma_keys->dk_nentries);

0 commit comments

Comments
 (0)