FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-158820: Add null checks to avoid segfaults with lazy imports at shutdown by brittanyrey · Pull Request #158821 · python/cpython · GitHub

Repository navigation

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (1) .py  (1) .rst  (1) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
55 changes: 55 additions & 0 deletions Lib/test/test_lazy_import/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2807,5 +2807,60 @@ def test_fails_without_lazy(self):
self.assertIn("ImportError", result.stderr)


@support.requires_subprocess()
class LazyImportFinalizationTests(unittest.TestCase):
"""Destructors that use lazy imports after finalization freed their state."""

def test_declare(self):
code = textwrap.dedent("""
import builtins, os

class Canary:
def __del__(self, write=os.write, exec=exec,
builtins={"__lazy_import__": builtins.__lazy_import__}):
exec("lazy import a.b", {"__builtins__": builtins})
write(1, b"ok")

# Only this placeholder keeps the canary alive.
exec("lazy import pkg.mod", {"__builtins__": {
"__lazy_import__": builtins.__lazy_import__, "c": Canary()}})
""")
self.assertEqual(assert_python_ok("-c", code).out, b"ok")

def test_resolve(self):
code = textwrap.dedent("""
import contextvars, json, os, types

def safe_import(name, *args, allowed={"json": json}):
return allowed[name]

# Not in sys.modules, with builtins that still work at shutdown.
ns = types.ModuleType("ns")
ns.json = __lazy_import__("json", {"__builtins__": {"__import__": safe_import}})

class Canary:
def __del__(self, write=os.write, type=type, ns=ns):
write(1, type(ns.json).__name__.encode())

contextvars.ContextVar("v").set(Canary())
""")
self.assertEqual(assert_python_ok("-c", code).out, b"module")

def test_set_lazy_attributes(self):
code = textwrap.dedent("""
import _imp, os, sys

class Canary:
def __del__(self, write=os.write,
set_lazy=_imp._set_lazy_attributes):
set_lazy(None, "mod")
write(1, b"ok")

# Freed while sys.lazy_modules is being cleared.
sys.lazy_modules.add(Canary())
""")
self.assertEqual(assert_python_ok("-c", code).out, b"ok")


if __name__ == '__main__':
unittest.main()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a crash when a :ref:`lazy import <lazy-imports>` is declared or resolved
by a finalizer running during interpreter shutdown.
15 changes: 12 additions & 3 deletions Python/import.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ _PyImport_ClearLazyModules(PyInterpreterState *interp)
int
_PyImport_DiscardLazyModule(PyInterpreterState *interp, PyObject *name)
{
if (LAZY_MODULES(interp) == NULL) {
return 0;
}
return PySet_Discard(LAZY_MODULES(interp), name);
}

Expand Down Expand Up @@ -4179,7 +4182,10 @@ lazy_modules_add(PyThreadState *tstate, PyObject *name,
else {
Py_XDECREF(mod);
}
return loaded ? 0 : PySet_Add(LAZY_MODULES(tstate->interp), name);
if (loaded || LAZY_MODULES(tstate->interp) == NULL) {
return 0;
}
return PySet_Add(LAZY_MODULES(tstate->interp), name);
}

// Ensure a dict of pending submodule names exists for the parent.
Expand Down Expand Up @@ -4213,7 +4219,10 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name, PyObject *source)
{
PyDictObject *pending =
(PyDictObject *)LAZY_PENDING_SUBMODULES(tstate->interp);
assert(pending != NULL);
// Finalizers can still run after finalize_modules() cleared the dict.
if (pending == NULL) {
Comment thread
brittanyrey marked this conversation as resolved.
return 0;
}
Py_ssize_t end = PyUnicode_GET_LENGTH(name);
while (true) {
Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, end, -1);
Expand Down Expand Up @@ -5575,7 +5584,7 @@ _imp__set_lazy_attributes_impl(PyObject *module, PyObject *modobj,
/*[clinic end generated code: output=3369bb3242b1f043 input=900339e013ab2b82]*/
{
PyInterpreterState *interp = _PyInterpreterState_GET();
if (PySet_Discard(LAZY_MODULES(interp), name) < 0) {
if (_PyImport_DiscardLazyModule(interp, name) < 0) {
Comment thread
brittanyrey marked this conversation as resolved.
return NULL;
}
if (_PyImport_ClearLazySubmodule(_PyThreadState_GET(), name, 0) < 0) {
Expand Down
Loading

Back | FazBrowse Home | New Git URL