Skip to content

Commit ee5382d

Browse files
authored
Merge pull request github#12193 from RasmusWL/import-resolution-fixup
Python: Fix `from <pkg> import *` import resolution
2 parents c72dbc4 + 7e16fa9 commit ee5382d

File tree

4 files changed

+35
-6
lines changed

4 files changed

+35
-6
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Fixed module resolution so we properly recognize that in `from <pkg> import *`, where `<pkg>` is a package, the actual imports are made from the `<pkg>/__init__.py` file.

python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,22 @@ module ImportResolution {
167167
)
168168
}
169169

170+
/**
171+
* Gets the (most likely) module for the name `name`, if any.
172+
*
173+
* Handles the fact that for the name `<pkg>` representing a package the actual module
174+
* is `<pkg>.__init__`.
175+
*
176+
* See `isPreferredModuleForName` for more details on what "most likely" module means.
177+
*/
178+
pragma[inline]
179+
private Module getModuleFromName(string name) {
180+
isPreferredModuleForName(result.getFile(), name + ["", ".__init__"])
181+
}
182+
183+
/** Gets the module from which attributes are imported by `i`. */
170184
Module getModuleImportedByImportStar(ImportStar i) {
171-
isPreferredModuleForName(result.getFile(), i.getImportedModuleName())
185+
result = getModuleFromName(i.getImportedModuleName())
172186
}
173187

174188
/**
@@ -223,7 +237,7 @@ module ImportResolution {
223237
exists(string module_name | result = getReferenceToModuleName(module_name) |
224238
// Depending on whether the referenced module is a package or not, we may need to add a
225239
// trailing `.__init__` to the module name.
226-
isPreferredModuleForName(m.getFile(), module_name + ["", ".__init__"])
240+
m = getModuleFromName(module_name)
227241
or
228242
// Module defined via `sys.modules`
229243
m = sys_modules_module_with_name(module_name)
@@ -234,7 +248,7 @@ module ImportResolution {
234248
ar.accesses(getModuleReference(p), attr_name) and
235249
result = ar
236250
|
237-
isPreferredModuleForName(m.getFile(), p.getPackageName() + "." + attr_name + ["", ".__init__"])
251+
m = getModuleFromName(p.getPackageName() + "." + attr_name)
238252
)
239253
or
240254
// This is also true for attributes that come from reexports.
@@ -248,8 +262,7 @@ module ImportResolution {
248262
exists(string submodule, Module package |
249263
SsaSource::init_module_submodule_defn(result.asVar().getSourceVariable(),
250264
package.getEntryNode()) and
251-
isPreferredModuleForName(m.getFile(),
252-
package.getPackageName() + "." + submodule + ["", ".__init__"])
265+
m = getModuleFromName(package.getPackageName() + "." + submodule)
253266
)
254267
}
255268

python/ql/test/experimental/import-resolution/main.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,17 @@ def local_import():
8484
check("clashing_attr", clashing_attr, "clashing_attr", globals()) #$ prints=clashing_attr SPURIOUS: prints="<module attr_clash.clashing_attr>"
8585
check("non_clashing_submodule", non_clashing_submodule, "<module attr_clash.non_clashing_submodule>", globals()) #$ prints="<module attr_clash.non_clashing_submodule>"
8686

87+
88+
# check that import * from an __init__ file works
89+
from package.subpackage2 import *
90+
check("subpackage2_attr", subpackage2_attr, "subpackage2_attr", globals()) #$ prints=subpackage2_attr
91+
92+
8793
exit(__file__)
8894

8995
print()
9096

9197
if status() == 0:
9298
print("PASS")
9399
else:
94-
print("FAIL")
100+
sys.exit("FAIL")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from trace import *
2+
enter(__file__)
3+
4+
subpackage2_attr = "subpackage2_attr"
5+
6+
exit(__file__)

0 commit comments

Comments
 (0)