Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Lib/test/test_import/test_lazy_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,21 @@ def test_dunder_lazy_import_builtins(self):
self.assertNotIn("test.test_import.data.lazy_imports.basic2", sys.modules)
self.assertEqual(dunder_lazy_import_builtins.basic.basic2, 42)

def test_dunder_lazy_import_argument_validation(self):
"""__lazy_import__ should strictly validate argument types to avoid SystemError."""
invalid_type_scenarios = [
(123, {}, {}, [], 0, "argument 1 must be str"),
('os', 1, {}, [], 0, "argument 2 must be dict"),
('os', {}, "not_a_dict", [], 0, "argument 3 must be dict"),
('os', {}, {}, 42, 0, "argument 4 must be a list or tuple"),
('os', {}, {}, "string_instead_of_list", 0, "argument 4 must be a list or tuple"),
]

for name, glbs, lcls, flist, lvl, msg in invalid_type_scenarios:
with self.subTest(case=msg):
with self.assertRaisesRegex(TypeError, msg):
__lazy_import__(name, glbs, lcls, flist, lvl)


class SysLazyImportsAPITests(unittest.TestCase):
"""Tests for sys lazy imports API functions."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed a :exc:`SystemError` in :func:`__lazy_import__` caused by passing
invalid argument types. Added proper type validation for *name*, *globals*,
*locals*, and *fromlist* arguments.
31 changes: 31 additions & 0 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,37 @@ builtin___lazy_import___impl(PyObject *module, PyObject *name,
{
PyObject *builtins;
PyThreadState *tstate = PyThreadState_GET();

// Validate input arguments
Copy link
Member

@picnixz picnixz Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick suggestion: check that we accept the same input types as PyImport_ImportModuleLevelObject does. I assume that __import__ and __lazy_import__ should behave similarly. I don't know if this is already the case with your PR but you could also refactor the logic of that validation in a single function that would be used in PyImport_ImportModuleLevelObject as well (so that we have similar error messages) (or just cc the logic).

if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"__lazy_import__() argument 1 must be str, not %.200s",
Py_TYPE(name)->tp_name);
return NULL;
}

if (globals != NULL && !PyDict_Check(globals)) {
PyErr_Format(PyExc_TypeError,
"__lazy_import__() argument 2 must be dict, not %.200s",
Py_TYPE(globals)->tp_name);
return NULL;
}

if (locals != NULL && !PyDict_Check(locals)) {
PyErr_Format(PyExc_TypeError,
"__lazy_import__() argument 3 must be dict, not %.200s",
Py_TYPE(locals)->tp_name);
return NULL;
}

if (fromlist != NULL && fromlist != Py_None &&
!PyList_Check(fromlist) && !PyTuple_Check(fromlist)) {
PyErr_Format(PyExc_TypeError,
"__lazy_import__() argument 4 must be a list or tuple, not %.200s",
Py_TYPE(fromlist)->tp_name);
return NULL;
}

if (globals == NULL) {
globals = PyEval_GetGlobals();
}
Expand Down
Loading