Skip to content

Commit 570744d

Browse files
committed
py/runtime: Make import-all support non-modules via __dict__/__all__.
Prior to this fix, `mp_import_all()` assumed that its argument was exactly a native module instance. That would lead to a crash if something else was passed in, eg a user class via a custom `__import__` implementation or by writing to `sys.modules`. MicroPython already supports injecting non-module objects into the import machinery, so it makes sense to round out that implementation by supporting `from x import *` where `x` is a non-module object. Fixes issue micropython#18639. Signed-off-by: Damien George <damien@micropython.org>
1 parent c9f747c commit 570744d

2 files changed

Lines changed: 81 additions & 5 deletions

File tree

py/runtime.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,28 +1592,39 @@ mp_obj_t mp_import_from(mp_obj_t module, qstr name) {
15921592
void mp_import_all(mp_obj_t module) {
15931593
DEBUG_printf("import all %p\n", module);
15941594

1595-
mp_map_t *map = &mp_obj_module_get_globals(module)->map;
1595+
mp_obj_t dest[2];
15961596

15971597
#if MICROPY_MODULE___ALL__
1598-
mp_map_elem_t *elem = mp_map_lookup(map, MP_OBJ_NEW_QSTR(MP_QSTR___all__), MP_MAP_LOOKUP);
1599-
if (elem != NULL) {
1598+
1599+
mp_load_method_maybe(module, MP_QSTR___all__, dest);
1600+
if (dest[0] != MP_OBJ_NULL) {
16001601
// When __all__ is defined, we must explicitly load all specified
16011602
// symbols, possibly invoking the module __getattr__ function
16021603
size_t len;
16031604
mp_obj_t *items;
1604-
mp_obj_get_array(elem->value, &len, &items);
1605+
mp_obj_get_array(dest[0], &len, &items);
16051606
for (size_t i = 0; i < len; i++) {
16061607
qstr qname = mp_obj_str_get_qstr(items[i]);
1607-
mp_obj_t dest[2];
16081608
mp_load_method(module, qname, dest);
16091609
mp_store_name(qname, dest[0]);
16101610
}
16111611
return;
16121612
}
16131613
#endif
16141614

1615+
#if MICROPY_CPYTHON_COMPAT
1616+
// Load the dict from the module. In MicroPython, if __dict__ is
1617+
// available then it always returns a native mp_obj_dict_t instance.
1618+
mp_load_method(module, MP_QSTR___dict__, dest);
1619+
#else
1620+
// Without MICROPY_CPYTHON_COMPAT __dict__ is not available, so just
1621+
// assume the given module is actually an mp_obj_module_t instance.
1622+
dest[0] = MP_OBJ_FROM_PTR(mp_obj_module_get_globals(module));
1623+
#endif
1624+
16151625
// By default, the set of public names includes all names found in the module's
16161626
// namespace which do not begin with an underscore character ('_')
1627+
mp_map_t *map = mp_obj_dict_get_map(dest[0]);
16171628
for (size_t i = 0; i < map->alloc; i++) {
16181629
if (mp_map_slot_is_filled(map, i)) {
16191630
// Entry in module global scope may be generated programmatically
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Test "from x import *" where x is something other than a module.
2+
3+
import sys
4+
5+
try:
6+
next(iter([]), 42)
7+
except TypeError:
8+
# Two-argument version of next() not supported. We are probably not at
9+
# MICROPY_CONFIG_ROM_LEVEL_BASIC_FEATURES which is needed for "import *".
10+
print("SKIP")
11+
raise SystemExit
12+
13+
print("== test with a class as a module ==")
14+
15+
16+
class M:
17+
x = "a1"
18+
19+
def __init__(self):
20+
self.x = "a2"
21+
22+
23+
sys.modules["mod"] = M
24+
from mod import *
25+
26+
print(x)
27+
28+
sys.modules["mod"] = M()
29+
from mod import *
30+
31+
print(x)
32+
33+
print("== test with a class as a module that overrides __all__ ==")
34+
35+
36+
class M:
37+
__all__ = ("y",)
38+
x = "b1"
39+
y = "b2"
40+
41+
def __init__(self):
42+
self.__all__ = ("x",)
43+
self.x = "b3"
44+
self.y = "b4"
45+
46+
47+
sys.modules["mod"] = M
48+
x = None
49+
from mod import *
50+
51+
print(x, y)
52+
53+
sys.modules["mod"] = M()
54+
from mod import *
55+
56+
print(x, y)
57+
58+
print("== test with objects that don't have a __dict__ ==")
59+
60+
sys.modules["mod"] = 1
61+
try:
62+
from mod import *
63+
# MicroPython raises AttributeError, CPython raises ImportError.
64+
except (AttributeError, ImportError):
65+
print("ImportError")

0 commit comments

Comments
 (0)