diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py index 6fb3c0ea3e2..1e17313a680 100644 --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -301,6 +301,21 @@ class WarnTests(unittest.TestCase): warning_tests.__name__ = module_name sys.argv = argv + def test_warn_explicit_type_errors(self): + # warn_explicit() shoud error out gracefully if it is given objects + # of the wrong types. + # lineno is expected to be an integer. + self.assertRaises(TypeError, self.module.warn_explicit, + None, UserWarning, None, None) + # Either 'message' needs to be an instance of Warning or 'category' + # needs to be a subclass. + self.assertRaises(TypeError, self.module.warn_explicit, + None, None, None, 1) + # 'registry' must be a dict or None. + self.assertRaises((TypeError, AttributeError), + self.module.warn_explicit, + None, Warning, None, 1, registry=42) + class CWarnTests(BaseTest, WarnTests): diff --git a/Lib/warnings.py b/Lib/warnings.py index 9353bfa1095..bcd702c9937 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -188,6 +188,7 @@ def warn(message, category=None, stacklevel=1): def warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None): + lineno = int(lineno) if module is None: module = filename or "" if module[-3:].lower() == ".py": diff --git a/Misc/NEWS b/Misc/NEWS index 0656041f8a2..0ba9a9c001c 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -27,6 +27,11 @@ What's new in Python 3.0b1? Core and Builtins ----------------- +- Issue #3211: warnings.warn_explicit() did not guard against its 'registry' + argument being anything other than a dict or None. Also fixed a bug in error + handling when 'message' and 'category' were both set to None, triggering a + bus error. + - Issue #3100: Corrected a crash on deallocation of a subclassed weakref which holds the last (strong) reference to its referent. diff --git a/Python/_warnings.c b/Python/_warnings.c index 6cc493bea58..23223faa1bd 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -280,6 +280,11 @@ warn_explicit(PyObject *category, PyObject *message, PyObject *item = Py_None; const char *action; int rc; + + if (registry && !PyDict_Check(registry) && (registry != Py_None)) { + PyErr_SetString(PyExc_TypeError, "'registry' must be a dict"); + return NULL; + } /* Normalize module. */ if (module == NULL) { @@ -303,6 +308,8 @@ warn_explicit(PyObject *category, PyObject *message, else { text = message; message = PyObject_CallFunction(category, "O", message); + if (message == NULL) + goto cleanup; } lineno_obj = PyLong_FromLong(lineno); @@ -314,7 +321,7 @@ warn_explicit(PyObject *category, PyObject *message, if (key == NULL) goto cleanup; - if (registry != NULL) { + if ((registry != NULL) && (registry != Py_None)) { rc = already_warned(registry, key, 0); if (rc == -1) goto cleanup; @@ -336,12 +343,13 @@ warn_explicit(PyObject *category, PyObject *message, is "always". */ rc = 0; if (strcmp(action, "always") != 0) { - if (registry != NULL && PyDict_SetItem(registry, key, Py_True) < 0) + if (registry != NULL && registry != Py_None && + PyDict_SetItem(registry, key, Py_True) < 0) goto cleanup; else if (strcmp(action, "ignore") == 0) goto return_none; else if (strcmp(action, "once") == 0) { - if (registry == NULL) { + if (registry == NULL || registry == Py_None) { registry = get_once_registry(); if (registry == NULL) goto cleanup; @@ -351,7 +359,7 @@ warn_explicit(PyObject *category, PyObject *message, } else if (strcmp(action, "module") == 0) { /* registry[(text, category, 0)] = 1 */ - if (registry != NULL) + if (registry != NULL && registry != Py_None) rc = update_registry(registry, text, category, 0); } else if (strcmp(action, "default") != 0) { @@ -435,7 +443,7 @@ warn_explicit(PyObject *category, PyObject *message, Py_XDECREF(text); Py_XDECREF(lineno_obj); Py_DECREF(module); - Py_DECREF(message); + Py_XDECREF(message); return result; /* Py_None or NULL. */ }