From 4e3363e884062e1ad6002fc8cc85201a2dea43d7 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 9 Jun 2003 18:42:19 +0000 Subject: Warn about creating global variables by __setattr__ that shadow builtin names. Unfortunately, this is not bulletproof since the module dictionary can be modified directly. --- Misc/NEWS | 6 +++++ Objects/moduleobject.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS b/Misc/NEWS index 6c9f5e5..35b35b8 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -43,6 +43,12 @@ Core and builtins instead of going through __getitem__. If __getitem__ access is preferred, then __iter__ can be overriden. +- Creating an attribute on a module (i.e. a global variable created by + __setattr__) that causes a builtin name to be shadowed now raises a + DeprecationWarning. In future versions of Python the effect may be + undefined (in order to allow for optimization of global and builtin + name lookups). + Extension modules ----------------- diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 812cbc4..5195388 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -198,6 +198,71 @@ module_repr(PyModuleObject *m) return PyString_FromFormat("", name, filename); } +static PyObject * +find_builtin_names(void) +{ + PyObject *builtins, *names, *key, *value; + int pos = 0; + builtins = PyEval_GetBuiltins(); + if (builtins == NULL || !PyDict_Check(builtins)) { + PyErr_SetString(PyExc_SystemError, "no builtins dict!"); + return NULL; + } + names = PyDict_New(); + if (names == NULL) + return NULL; + while (PyDict_Next(builtins, &pos, &key, &value)) { + if (PyString_Check(key) && + PyString_Size(key) > 0 && + PyString_AS_STRING(key)[0] != '_') { + if (PyDict_SetItem(names, key, Py_None) < 0) { + Py_DECREF(names); + return NULL; + } + } + } + return names; +} + +/* returns 0 or 1 (and -1 on error) */ +static int +shadows_builtin(PyObject *globals, PyObject *name) +{ + static PyObject *builtin_names = NULL; + if (builtin_names == NULL) { + builtin_names = find_builtin_names(); + if (builtin_names == NULL) + return -1; + } + if (!PyString_Check(name)) + return 0; + if (PyDict_GetItem(globals, name) == NULL && + PyDict_GetItem(builtin_names, name) != NULL) { + return 1; + } + else { + return 0; + } +} + +static int +module_setattr(PyObject *m, PyObject *name, PyObject *value) +{ + PyObject *globals = ((PyModuleObject *)m)->md_dict; + PyObject *builtins = PyEval_GetBuiltins(); + if (globals != NULL && globals != builtins) { + int shadows = shadows_builtin(globals, name); + if (shadows == 1) { + if (PyErr_Warn(PyExc_DeprecationWarning, + "assignment shadows builtin") < 0) + return -1; + } + else if (shadows == -1) + return -1; + } + return PyObject_GenericSetAttr(m, name, value); +} + /* We only need a traverse function, no clear function: If the module is in a cycle, md_dict will be cleared as well, which will break the cycle. */ @@ -234,7 +299,7 @@ PyTypeObject PyModule_Type = { 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ - PyObject_GenericSetAttr, /* tp_setattro */ + module_setattr, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ -- cgit v0.12