Skip to content

Commit 7bb1f37

Browse files
authored
[mypyc] Avoid crash when importing unknown module with from import (#10550)
Fixes mypyc/mypyc#851 This fixes a bug where code compiled with mypyc would crash on from imports (from x import y) if: * y is a module * mypy doesn't know that y is a module (due to an ignore_missing_imports configuration option or something else) The bug was caused by using getattr to import modules (i.e. y = getattr(x, 'y')) and changing this to import x.y as y when it can determine that y is a module. This doesn't work when we don't know that y is a module. I changed the from import handling to use something similar to the method shown in the __import__ docs. I also removed the special casing of from imports for modules (from x import y where y is a module) mentioned earlier, because these changes make that special casing unnecessary.
1 parent 4028203 commit 7bb1f37

File tree

6 files changed

+522
-418
lines changed

6 files changed

+522
-418
lines changed

mypyc/irbuild/builder.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
SetAttr, LoadStatic, InitStatic, NAMESPACE_MODULE, RaiseStandardError
3636
)
3737
from mypyc.ir.rtypes import (
38-
RType, RTuple, RInstance, int_rprimitive, dict_rprimitive,
38+
RType, RTuple, RInstance, c_int_rprimitive, int_rprimitive, dict_rprimitive,
3939
none_rprimitive, is_none_rprimitive, object_rprimitive, is_object_rprimitive,
4040
str_rprimitive, is_tagged, is_list_rprimitive, is_tuple_rprimitive, c_pyssize_t_rprimitive
4141
)
@@ -45,7 +45,9 @@
4545
from mypyc.primitives.list_ops import to_list, list_pop_last, list_get_item_unsafe_op
4646
from mypyc.primitives.dict_ops import dict_get_item_op, dict_set_item_op
4747
from mypyc.primitives.generic_ops import py_setattr_op, iter_op, next_op
48-
from mypyc.primitives.misc_ops import import_op, check_unpack_count_op, get_module_dict_op
48+
from mypyc.primitives.misc_ops import (
49+
import_op, check_unpack_count_op, get_module_dict_op, import_extra_args_op
50+
)
4951
from mypyc.crash import catch_errors
5052
from mypyc.options import CompilerOptions
5153
from mypyc.errors import Errors
@@ -286,19 +288,45 @@ def add_to_non_ext_dict(self, non_ext: NonExtClassInfo,
286288
key_unicode = self.load_str(key)
287289
self.call_c(dict_set_item_op, [non_ext.dict, key_unicode, val], line)
288290

291+
def gen_import_from(self, id: str, line: int, imported: List[str]) -> None:
292+
self.imports[id] = None
293+
294+
globals_dict = self.load_globals_dict()
295+
null = Integer(0, dict_rprimitive, line)
296+
names_to_import = self.new_list_op([self.load_str(name) for name in imported], line)
297+
298+
level = Integer(0, c_int_rprimitive, line)
299+
value = self.call_c(
300+
import_extra_args_op,
301+
[self.load_str(id), globals_dict, null, names_to_import, level],
302+
line,
303+
)
304+
self.add(InitStatic(value, id, namespace=NAMESPACE_MODULE))
305+
289306
def gen_import(self, id: str, line: int) -> None:
290307
self.imports[id] = None
291308

292309
needs_import, out = BasicBlock(), BasicBlock()
293-
first_load = self.load_module(id)
294-
comparison = self.translate_is_op(first_load, self.none_object(), 'is not', line)
295-
self.add_bool_branch(comparison, out, needs_import)
310+
self.check_if_module_loaded(id, line, needs_import, out)
296311

297312
self.activate_block(needs_import)
298313
value = self.call_c(import_op, [self.load_str(id)], line)
299314
self.add(InitStatic(value, id, namespace=NAMESPACE_MODULE))
300315
self.goto_and_activate(out)
301316

317+
def check_if_module_loaded(self, id: str, line: int,
318+
needs_import: BasicBlock, out: BasicBlock) -> None:
319+
"""Generate code that checks if the module `id` has been loaded yet.
320+
321+
Arguments:
322+
id: name of module to check if imported
323+
line: line number that the import occurs on
324+
needs_import: the BasicBlock that is run if the module has not been loaded yet
325+
out: the BasicBlock that is run if the module has already been loaded"""
326+
first_load = self.load_module(id)
327+
comparison = self.translate_is_op(first_load, self.none_object(), 'is not', line)
328+
self.add_bool_branch(comparison, out, needs_import)
329+
302330
def get_module(self, module: str, line: int) -> Value:
303331
# Python 3.7 has a nice 'PyImport_GetModule' function that we can't use :(
304332
mod_dict = self.call_c(get_module_dict_op, [], line)

mypyc/irbuild/statement.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None:
172172

173173
id = importlib.util.resolve_name('.' * node.relative + node.id, module_package)
174174

175-
builder.gen_import(id, node.line)
175+
imported = [name for name, _ in node.names]
176+
builder.gen_import_from(id, node.line, imported)
176177
module = builder.load_module(id)
177178

178179
# Copy everything into our module's dict.
@@ -181,12 +182,6 @@ def transform_import_from(builder: IRBuilder, node: ImportFrom) -> None:
181182
# This probably doesn't matter much and the code runs basically right.
182183
globals = builder.load_globals_dict()
183184
for name, maybe_as_name in node.names:
184-
# If one of the things we are importing is a module,
185-
# import it as a module also.
186-
fullname = id + '.' + name
187-
if fullname in builder.graph or fullname in module_state.suppressed:
188-
builder.gen_import(fullname, node.line)
189-
190185
as_name = maybe_as_name or name
191186
obj = builder.py_get_attr(module, name, node.line)
192187
builder.gen_method_call(

mypyc/primitives/misc_ops.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from mypyc.ir.ops import ERR_NEVER, ERR_MAGIC, ERR_FALSE
44
from mypyc.ir.rtypes import (
55
bool_rprimitive, object_rprimitive, str_rprimitive, object_pointer_rprimitive,
6-
int_rprimitive, dict_rprimitive, c_int_rprimitive, bit_rprimitive, c_pyssize_t_rprimitive
6+
int_rprimitive, dict_rprimitive, c_int_rprimitive, bit_rprimitive, c_pyssize_t_rprimitive,
7+
list_rprimitive,
78
)
89
from mypyc.primitives.registry import (
910
function_op, custom_op, load_address_op, ERR_NEG_INT
@@ -113,6 +114,15 @@
113114
c_function_name='PyImport_Import',
114115
error_kind=ERR_MAGIC)
115116

117+
# Import with extra arguments (used in from import handling)
118+
import_extra_args_op = custom_op(
119+
arg_types=[str_rprimitive, dict_rprimitive, dict_rprimitive,
120+
list_rprimitive, c_int_rprimitive],
121+
return_type=object_rprimitive,
122+
c_function_name='PyImport_ImportModuleLevelObject',
123+
error_kind=ERR_MAGIC
124+
)
125+
116126
# Get the sys.modules dictionary
117127
get_module_dict_op = custom_op(
118128
arg_types=[],

0 commit comments

Comments
 (0)