bpo-37409: fix relative import with no parent (#14956)

Relative imports use resolve_name to get the absolute target name,
which first seeks the current module's absolute package name from the globals:
If __package__ (and __spec__.parent) are missing then
import uses __name__, truncating the last segment if
the module is a submodule rather than a package __init__.py
(which it guesses from whether __path__ is defined).

The __name__ attempt should fail if there is no parent package (top level modules),
if __name__ is '__main__' (-m entry points), or both (scripts).
That is, if both __name__ has no subcomponents and the module does not seem
to be a package __init__ module then import should fail.
This commit is contained in:
Ben Lewis 2019-09-11 20:09:47 +10:00 committed by Brett Cannon
parent 6472ece5a0
commit 92420b3e67
5 changed files with 25 additions and 11 deletions

View file

@ -160,6 +160,10 @@ def test_import(self):
self.assertRaises(TypeError, __import__, 1, 2, 3, 4)
self.assertRaises(ValueError, __import__, '')
self.assertRaises(TypeError, __import__, 'sys', name='sys')
# Relative import outside of a package with no __package__ or __spec__ (bpo-37409).
self.assertRaises(ImportError, __import__, '',
{'__package__': None, '__spec__': None, '__name__': '__main__'},
locals={}, fromlist=('foo',), level=1)
# embedded null character
self.assertRaises(ModuleNotFoundError, __import__, 'string\x00')

View file

@ -772,6 +772,11 @@ def check_relative():
ns = dict(__package__=object())
self.assertRaises(TypeError, check_relative)
def test_parentless_import_shadowed_by_global(self):
# Test as if this were done from the REPL where this error most commonly occurs (bpo-37409).
script_helper.assert_python_failure('-W', 'ignore', '-c',
"foo = 1; from . import foo")
def test_absolute_import_without_future(self):
# If explicit relative import syntax is used, then do not try
# to perform an absolute import in the face of failure.

View file

@ -975,6 +975,7 @@ Alain Leufroy
Mark Levinson
Mark Levitt
Ivan Levkivskyi
Ben Lewis
William Lewis
Akira Li
Robert Li

View file

@ -0,0 +1,2 @@
Ensure explicit relative imports from interactive sessions and scripts (having no parent package) always raise ImportError, rather than treating the current module as the package.
Patch by Ben Lewis.

View file

@ -1646,23 +1646,20 @@ resolve_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level
if (dot == -2) {
goto error;
}
if (dot >= 0) {
PyObject *substr = PyUnicode_Substring(package, 0, dot);
if (substr == NULL) {
goto error;
}
Py_SETREF(package, substr);
else if (dot == -1) {
goto no_parent_error;
}
PyObject *substr = PyUnicode_Substring(package, 0, dot);
if (substr == NULL) {
goto error;
}
Py_SETREF(package, substr);
}
}
last_dot = PyUnicode_GET_LENGTH(package);
if (last_dot == 0) {
_PyErr_SetString(tstate, PyExc_ImportError,
"attempted relative import "
"with no known parent package");
goto error;
goto no_parent_error;
}
for (level_up = 1; level_up < level; level_up += 1) {
@ -1688,6 +1685,11 @@ resolve_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level
Py_DECREF(base);
return abs_name;
no_parent_error:
_PyErr_SetString(tstate, PyExc_ImportError,
"attempted relative import "
"with no known parent package");
error:
Py_XDECREF(package);
return NULL;