-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
bpo-33330: PyImport_Cleanup check for exc leaks #7068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* Add "assert(!PyErr_Occurred());" assertions to PyImport_Cleanup() and _PyGC_CollectNoFail() to make sure that these functions don't leak exceptions. * Replace also PyImport_GetModuleDict() call with interp->modules to prevent a fatal error if modules is already NULL.
The commit d393c1b replaced interp->modules with PyImport_GetModuleDict(), but this function fails with a fatal error if modules is NULL. I'm not sure that it was a deliberate change. Moreover, this commit has been partially reverted (interp->modules is back!). |
PyImport_Cleanup() is part of the public API, but it's documented as "Empty the module table. For internal use only." :-( Why do we have public API for internal use only? Likely a mistake from the past. |
@@ -396,15 +396,18 @@ static const char * const sys_files[] = { | |||
void | |||
PyImport_Cleanup(void) | |||
{ | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if it is worth to call PyErr_WriteUnraisable()
here. This will expose some information about the exception raised outside of this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contract of this function is that the caller must not hold an exception. If the assertion fails, I would prefer to fix the caller rather than working around the bug using PyErr_WriteUnraisable().
The point is not only to catch bugs in the current code, but also ease debug when the callers code will be modified. Python shutdown process is very fragile and error prone.
@@ -1595,13 +1595,15 @@ _PyGC_CollectNoFail(void) | |||
during interpreter shutdown (and then never finish it). | |||
See http://bugs.python.org/issue8713#msg195178 for an example. | |||
*/ | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yesterday I wrote a patch that improves errors handling in the garbage collector. bpo-33612 is the result of testing it. And I have added similar asserts in _PyGC_CollectNoFail()
(but the patch is not completed yet, maybe I'll move them).
At least move the second assert inside the "else" block.
@@ -477,6 +481,7 @@ PyImport_Cleanup(void) | |||
if (PyDict_CheckExact(modules)) { | |||
pos = 0; | |||
while (PyDict_Next(modules, &pos, &key, &value)) { | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither PyDict_Next()
nor any other calls before can raise an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the theory. In practice, something called before might leak an exception. It's to catch bugs "just in case". The assertion is cheap. Debugging an "exception leak" can be very painful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no code between this line and previous error check that can raise an exception and don't silence it. I have checked this.
@@ -492,6 +497,7 @@ PyImport_Cleanup(void) | |||
PyErr_WriteUnraisable(NULL); | |||
continue; | |||
} | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyObject_GetIter()
, PyIter_Next()
and PyObject_GetItem()
can raise and exception if corresponding slots are implemented incorrectly in extensions. assert
looks good here.
@@ -502,6 +508,7 @@ PyImport_Cleanup(void) | |||
Py_DECREF(iterator); | |||
} | |||
} | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This never fails.
@@ -524,6 +531,8 @@ PyImport_Cleanup(void) | |||
PyErr_Clear(); | |||
} | |||
Py_XDECREF(dict); | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This never fails.
@@ -543,6 +552,7 @@ PyImport_Cleanup(void) | |||
references left to it), we need to delete the "builtins" | |||
module last. Likewise, we don't delete sys until the very | |||
end because it is implicitly referenced (e.g. by print). */ | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This never fails.
@@ -559,6 +569,7 @@ PyImport_Cleanup(void) | |||
Py_INCREF(mod); | |||
if (Py_VerboseFlag && PyUnicode_Check(name)) | |||
PySys_FormatStderr("# cleanup[3] wiping %U\n", name); | |||
assert(!PyErr_Occurred()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This never fails.
I would left only few asserts after calls that can raise exceptions if the third-party code has bugs. And one at the end of |
The last 2 years, I added many "assert(!PyErr_Occurred());" in ceval.c and object.c, and it helped me a lot to detect some very old bugs. The rationale here is the same. |
PyObject *weaklist = NULL; | ||
const char * const *p; | ||
|
||
if (modules == NULL) | ||
PyObject *modules = interp->modules; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather avoid this, if possible. Under what conditions does the use of PyImport_GetModuleDict() cause a fatal error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you call PyImport_Cleanup() twice. In Python 3.6, calling PyImport_Cleanup() a second time does nothing: that's the purpose of the test on the next line.
PyImport_Cleanup() is part of the public API, even if it's documented as "please don't call this function" ;-) I would prefer to restore Python 3.6 behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks for clarifying. LGTM then. I still plan on landing #3606 at some point, but at that point I'll have to deal with recent interp->modules
usage anyway. :)
See also #7126. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, except that GC is covered by a separate PR.
I will wait until #7126 is merged, and then rebase this PR on top of it. |
Why not rebase and merge this PR? |
It's just that I wrote it 2 months ago, and I lost interest in this minor enhancement in the meanwhile. I'm trying to reduce my number of open PRs. Feel free to reuse my patch to create a new PR if you want. |
and _PyGC_CollectNoFail() to make sure that these functions don't
leak exceptions.
prevent a fatal error if modules is already NULL.
https://bugs.python.org/issue33330