mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
Fix some crashes
```
import types
types.LazyImportType({}, "3", 0)
```
```
def f():
exec("lazy import json")
f()
```
This commit is contained in:
parent
a05b50dbca
commit
ac80f2d978
3 changed files with 73 additions and 3 deletions
|
|
@ -190,6 +190,38 @@ def test_lazy_import_type_exposed(self):
|
||||||
self.assertTrue(hasattr(types, 'LazyImportType'))
|
self.assertTrue(hasattr(types, 'LazyImportType'))
|
||||||
self.assertEqual(types.LazyImportType.__name__, 'lazy_import')
|
self.assertEqual(types.LazyImportType.__name__, 'lazy_import')
|
||||||
|
|
||||||
|
def test_lazy_import_type_invalid_fromlist_type(self):
|
||||||
|
"""LazyImportType should reject invalid fromlist types."""
|
||||||
|
# fromlist must be None, a string, or a tuple - not an int
|
||||||
|
with self.assertRaises(TypeError) as cm:
|
||||||
|
types.LazyImportType({}, "module", 0)
|
||||||
|
self.assertIn("fromlist must be None, a string, or a tuple", str(cm.exception))
|
||||||
|
|
||||||
|
# Also test with other invalid types
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
types.LazyImportType({}, "module", []) # list not allowed
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
types.LazyImportType({}, "module", {"x": 1}) # dict not allowed
|
||||||
|
|
||||||
|
def test_lazy_import_type_valid_fromlist(self):
|
||||||
|
"""LazyImportType should accept valid fromlist types."""
|
||||||
|
# None is valid (implicit)
|
||||||
|
lazy1 = types.LazyImportType({}, "module")
|
||||||
|
self.assertIsNotNone(lazy1)
|
||||||
|
|
||||||
|
# Explicit None is valid
|
||||||
|
lazy2 = types.LazyImportType({}, "module", None)
|
||||||
|
self.assertIsNotNone(lazy2)
|
||||||
|
|
||||||
|
# String is valid
|
||||||
|
lazy3 = types.LazyImportType({}, "module", "attr")
|
||||||
|
self.assertIsNotNone(lazy3)
|
||||||
|
|
||||||
|
# Tuple is valid
|
||||||
|
lazy4 = types.LazyImportType({}, "module", ("attr1", "attr2"))
|
||||||
|
self.assertIsNotNone(lazy4)
|
||||||
|
|
||||||
|
|
||||||
class SyntaxRestrictionTests(unittest.TestCase):
|
class SyntaxRestrictionTests(unittest.TestCase):
|
||||||
"""Tests for syntax restrictions on lazy imports."""
|
"""Tests for syntax restrictions on lazy imports."""
|
||||||
|
|
@ -230,6 +262,35 @@ def test_lazy_import_func(self):
|
||||||
with self.assertRaises(SyntaxError):
|
with self.assertRaises(SyntaxError):
|
||||||
import test.test_import.data.lazy_imports.lazy_import_func
|
import test.test_import.data.lazy_imports.lazy_import_func
|
||||||
|
|
||||||
|
def test_lazy_import_exec_in_function(self):
|
||||||
|
"""lazy import via exec() inside a function should raise SyntaxError."""
|
||||||
|
# exec() inside a function creates a non-module-level context
|
||||||
|
# where lazy imports are not allowed
|
||||||
|
def f():
|
||||||
|
exec("lazy import json")
|
||||||
|
|
||||||
|
with self.assertRaises(SyntaxError) as cm:
|
||||||
|
f()
|
||||||
|
self.assertIn("only allowed at module level", str(cm.exception))
|
||||||
|
|
||||||
|
def test_lazy_import_exec_at_module_level(self):
|
||||||
|
"""lazy import via exec() at module level should work."""
|
||||||
|
# exec() at module level (globals == locals) should allow lazy imports
|
||||||
|
code = textwrap.dedent("""
|
||||||
|
import sys
|
||||||
|
exec("lazy import json")
|
||||||
|
# Should be lazy - not loaded yet
|
||||||
|
assert 'json' not in sys.modules
|
||||||
|
print("OK")
|
||||||
|
""")
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-c", code],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}")
|
||||||
|
self.assertIn("OK", result.stdout)
|
||||||
|
|
||||||
|
|
||||||
class EagerImportInLazyModeTests(unittest.TestCase):
|
class EagerImportInLazyModeTests(unittest.TestCase):
|
||||||
"""Tests for imports that should remain eager even in lazy mode."""
|
"""Tests for imports that should remain eager even in lazy mode."""
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,14 @@ _PyLazyImport_New(PyObject *builtins, PyObject *from, PyObject *attr)
|
||||||
PyErr_BadArgument();
|
PyErr_BadArgument();
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (attr == Py_None) {
|
if (attr == Py_None || attr == NULL) {
|
||||||
attr = NULL;
|
attr = NULL;
|
||||||
}
|
}
|
||||||
assert(!attr || PyObject_IsTrue(attr));
|
else if (!PyUnicode_Check(attr) && !PyTuple_Check(attr)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"lazy_import: fromlist must be None, a string, or a tuple");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
m = PyObject_GC_New(PyLazyImportObject, &PyLazyImport_Type);
|
m = PyObject_GC_New(PyLazyImportObject, &PyLazyImport_Type);
|
||||||
if (m == NULL) {
|
if (m == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
||||||
|
|
@ -4358,7 +4358,12 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate,
|
||||||
|
|
||||||
PyInterpreterState *interp = tstate->interp;
|
PyInterpreterState *interp = tstate->interp;
|
||||||
_PyInterpreterFrame *frame = _PyEval_GetFrame();
|
_PyInterpreterFrame *frame = _PyEval_GetFrame();
|
||||||
assert(frame != NULL && frame->f_globals == frame->f_locals); // should only be called in global scope
|
if (frame == NULL || frame->f_globals != frame->f_locals) {
|
||||||
|
Py_DECREF(abs_name);
|
||||||
|
PyErr_SetString(PyExc_SyntaxError,
|
||||||
|
"'lazy import' is only allowed at module level");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the filter disables the lazy import.
|
// Check if the filter disables the lazy import.
|
||||||
// We must hold a reference to the filter while calling it to prevent
|
// We must hold a reference to the filter while calling it to prevent
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue