diff -r 6adac0d9b933 Include/moduleobject.h --- a/Include/moduleobject.h Sun Feb 09 13:33:53 2014 +0200 +++ b/Include/moduleobject.h Mon Feb 10 17:32:29 2014 +0200 @@ -25,6 +25,7 @@ PyAPI_FUNC(PyObject *) PyModule_GetFilenameObject(PyObject *); #ifndef Py_LIMITED_API PyAPI_FUNC(void) _PyModule_Clear(PyObject *); +PyAPI_FUNC(void) _PyModule_ClearDict(PyObject *); #endif PyAPI_FUNC(struct PyModuleDef*) PyModule_GetDef(PyObject*); PyAPI_FUNC(void*) PyModule_GetState(PyObject*); diff -r 6adac0d9b933 Include/pystate.h --- a/Include/pystate.h Sun Feb 09 13:33:53 2014 +0200 +++ b/Include/pystate.h Mon Feb 10 17:32:29 2014 +0200 @@ -33,7 +33,6 @@ int codecs_initialized; int fscodec_initialized; - #ifdef HAVE_DLOPEN int dlopenflags; #endif @@ -41,6 +40,7 @@ int tscdump; #endif + PyObject *builtins_copy; } PyInterpreterState; #endif diff -r 6adac0d9b933 Lib/test/test_builtin.py --- a/Lib/test/test_builtin.py Sun Feb 09 13:33:53 2014 +0200 +++ b/Lib/test/test_builtin.py Mon Feb 10 17:32:29 2014 +0200 @@ -16,6 +16,7 @@ import warnings from operator import neg from test.support import TESTFN, unlink, run_unittest, check_warnings +from test.script_helper import assert_python_ok try: import pty, signal except ImportError: @@ -1565,8 +1566,36 @@ data = 'The quick Brown fox Jumped over The lazy Dog'.split() self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0) + +class ShutdownTest(unittest.TestCase): + + def test_cleanup(self): + # Issue #19255: builtins are still available at shutdown + code = """if 1: + import builtins + import sys + + class C: + def __del__(self): + print("before") + # Check that builtins still exist + len(()) + print("after") + + c = C() + # Make this module survive until builtins and sys are cleaned + builtins.here = sys.modules[__name__] + sys.here = sys.modules[__name__] + # Create a reference loop so that this module needs to go + # through a GC phase. + here = sys.modules[__name__] + """ + rc, out, err = assert_python_ok("-c", code) + self.assertEqual(["before", "after"], out.decode().splitlines()) + + def test_main(verbose=None): - test_classes = (BuiltinTest, TestSorted) + test_classes = (BuiltinTest, TestSorted, ShutdownTest) run_unittest(*test_classes) diff -r 6adac0d9b933 Objects/moduleobject.c --- a/Objects/moduleobject.c Sun Feb 09 13:33:53 2014 +0200 +++ b/Objects/moduleobject.c Mon Feb 10 17:32:29 2014 +0200 @@ -272,6 +272,14 @@ void _PyModule_Clear(PyObject *m) { + PyObject *d = ((PyModuleObject *)m)->md_dict; + if (d != NULL) + _PyModule_ClearDict(d); +} + +void +_PyModule_ClearDict(PyObject *d) +{ /* To make the execution order of destructors for global objects a bit more predictable, we first zap all objects whose name starts with a single underscore, before we clear @@ -281,11 +289,6 @@ Py_ssize_t pos; PyObject *key, *value; - PyObject *d; - - d = ((PyModuleObject *)m)->md_dict; - if (d == NULL) - return; /* First, clear only names starting with a single underscore */ pos = 0; diff -r 6adac0d9b933 Python/import.c --- a/Python/import.c Sun Feb 09 13:33:53 2014 +0200 +++ b/Python/import.c Mon Feb 10 17:32:29 2014 +0200 @@ -47,9 +47,13 @@ void _PyImport_Init(void) { + PyInterpreterState *interp = PyThreadState_Get()->interp; initstr = PyUnicode_InternFromString("__init__"); if (initstr == NULL) Py_FatalError("Can't initialize import variables"); + interp->builtins_copy = PyDict_Copy(interp->builtins); + if (interp->builtins_copy == NULL) + Py_FatalError("Can't backup builtins dict"); } void @@ -301,6 +305,7 @@ PyObject *key, *value, *dict; PyInterpreterState *interp = PyThreadState_GET()->interp; PyObject *modules = interp->modules; + char **p; if (modules == NULL) return; /* Already done */ @@ -311,31 +316,22 @@ deleted *last* of all, they would come too late in the normal destruction order. Sigh. */ - value = PyDict_GetItemString(modules, "builtins"); - if (value != NULL && PyModule_Check(value)) { - dict = PyModule_GetDict(value); + if (Py_VerboseFlag) + PySys_WriteStderr("# clear builtins._\n"); + PyDict_SetItemString(interp->builtins, "_", Py_None); + + for (p = sys_deletes; *p != NULL; p++) { if (Py_VerboseFlag) - PySys_WriteStderr("# clear builtins._\n"); - PyDict_SetItemString(dict, "_", Py_None); + PySys_WriteStderr("# clear sys.%s\n", *p); + PyDict_SetItemString(interp->sysdict, *p, Py_None); } - value = PyDict_GetItemString(modules, "sys"); - if (value != NULL && PyModule_Check(value)) { - char **p; - PyObject *v; - dict = PyModule_GetDict(value); - for (p = sys_deletes; *p != NULL; p++) { - if (Py_VerboseFlag) - PySys_WriteStderr("# clear sys.%s\n", *p); - PyDict_SetItemString(dict, *p, Py_None); - } - for (p = sys_files; *p != NULL; p+=2) { - if (Py_VerboseFlag) - PySys_WriteStderr("# restore sys.%s\n", *p); - v = PyDict_GetItemString(dict, *(p+1)); - if (v == NULL) - v = Py_None; - PyDict_SetItemString(dict, *p, v); - } + for (p = sys_files; *p != NULL; p+=2) { + if (Py_VerboseFlag) + PySys_WriteStderr("# restore sys.%s\n", *p); + value = PyDict_GetItemString(interp->sysdict, *(p+1)); + if (value == NULL) + value = Py_None; + PyDict_SetItemString(interp->sysdict, *p, value); } /* First, delete __main__ */ @@ -347,6 +343,16 @@ PyDict_SetItemString(modules, "__main__", Py_None); } + /* Restore the original builtins dict, to ensure that any + user data gets cleared. */ + dict = PyDict_Copy(interp->builtins); + if (dict == NULL) + PyErr_Clear(); + PyDict_Clear(interp->builtins); + if (PyDict_Update(interp->builtins, interp->builtins_copy)) + PyErr_Clear(); + Py_XDECREF(dict); + /* The special treatment of "builtins" here is because even when it's not referenced as a module, its dictionary is referenced by almost every module's __builtins__. Since @@ -370,13 +376,11 @@ if (value->ob_refcnt != 1) continue; if (PyUnicode_Check(key) && PyModule_Check(value)) { - if (PyUnicode_CompareWithASCIIString(key, "builtins") == 0) - continue; - if (PyUnicode_CompareWithASCIIString(key, "sys") == 0) + dict = PyModule_GetDict(value); + if (dict == interp->builtins || dict == interp->sysdict) continue; if (Py_VerboseFlag) - PySys_FormatStderr( - "# cleanup[1] %U\n", key); + PySys_FormatStderr("# cleanup[1] %U\n", key); _PyModule_Clear(value); PyDict_SetItem(modules, key, Py_None); ndone++; @@ -388,9 +392,8 @@ pos = 0; while (PyDict_Next(modules, &pos, &key, &value)) { if (PyUnicode_Check(key) && PyModule_Check(value)) { - if (PyUnicode_CompareWithASCIIString(key, "builtins") == 0) - continue; - if (PyUnicode_CompareWithASCIIString(key, "sys") == 0) + dict = PyModule_GetDict(value); + if (dict == interp->builtins || dict == interp->sysdict) continue; if (Py_VerboseFlag) PySys_FormatStderr("# cleanup[2] %U\n", key); @@ -400,20 +403,12 @@ } /* Next, delete sys and builtins (in that order) */ - value = PyDict_GetItemString(modules, "sys"); - if (value != NULL && PyModule_Check(value)) { - if (Py_VerboseFlag) - PySys_WriteStderr("# cleanup sys\n"); - _PyModule_Clear(value); - PyDict_SetItemString(modules, "sys", Py_None); - } - value = PyDict_GetItemString(modules, "builtins"); - if (value != NULL && PyModule_Check(value)) { - if (Py_VerboseFlag) - PySys_WriteStderr("# cleanup builtins\n"); - _PyModule_Clear(value); - PyDict_SetItemString(modules, "builtins", Py_None); - } + if (Py_VerboseFlag) + PySys_WriteStderr("# cleanup sys\n"); + _PyModule_ClearDict(interp->sysdict); + if (Py_VerboseFlag) + PySys_WriteStderr("# cleanup builtins\n"); + _PyModule_ClearDict(interp->builtins); /* Finally, clear and delete the modules directory */ PyDict_Clear(modules); diff -r 6adac0d9b933 Python/pystate.c --- a/Python/pystate.c Sun Feb 09 13:33:53 2014 +0200 +++ b/Python/pystate.c Mon Feb 10 17:32:29 2014 +0200 @@ -72,6 +72,7 @@ interp->modules_by_index = NULL; interp->sysdict = NULL; interp->builtins = NULL; + interp->builtins_copy = NULL; interp->tstate_head = NULL; interp->codec_search_path = NULL; interp->codec_search_cache = NULL; @@ -115,6 +116,7 @@ Py_CLEAR(interp->modules_by_index); Py_CLEAR(interp->sysdict); Py_CLEAR(interp->builtins); + Py_CLEAR(interp->builtins_copy); Py_CLEAR(interp->importlib); }