Skip to content

Commit 13ae98e

Browse files
committed
Python: Fix submodule exported under wrong name (when attribute clash)
1 parent 3739072 commit 13ae98e

File tree

2 files changed

+15
-3
lines changed

2 files changed

+15
-3
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,21 @@ module ImportResolution {
307307
module_reexport(reexporter, attr_name, m)
308308
)
309309
or
310-
// Submodules that are implicitly defined with relative imports of the form `from .foo import ...`.
311-
// In practice, we create a definition for each module in a package, even if it is not imported.
310+
// submodules of packages will be available as `<pkg>.<submodule>` after doing
311+
// `import <pkg>.<submodule>` at least once in the program, or can be directly
312+
// imported with `from <pkg> import <submodule>` (even with an empty
313+
// `<pkg>.__init__` file).
314+
//
315+
// Until an import of `<pkg>.<submodule>` is executed, it is technically possible
316+
// that `<pkg>.<submodule>` (or `from <pkg> import <submodule>`) can refer to an
317+
// attribute set in `<pkg>.__init__`.
318+
//
319+
// Therefore, if there is an attribute defined in `<pkg>.__init__` with the same
320+
// name as a submodule, we always consider that this attribute _could_ be a
321+
// reference to the submodule, even if we don't know that the submodule has been
322+
// imported yet.
312323
exists(string submodule, Module package |
324+
submodule = result.asVar().getName() and
313325
SsaSource::init_module_submodule_defn(result.asVar().getSourceVariable(),
314326
package.getEntryNode()) and
315327
m = getModuleFromName(package.getPackageName() + "." + submodule)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def local_import():
8181

8282

8383
from attr_clash import clashing_attr, non_clashing_submodule #$ imports=attr_clash.clashing_attr as=clashing_attr imports=attr_clash.non_clashing_submodule as=non_clashing_submodule
84-
check("clashing_attr", clashing_attr, "clashing_attr", globals()) #$ prints=clashing_attr SPURIOUS: prints="<module attr_clash.clashing_attr>" SPURIOUS: prints="<module attr_clash.__init__>" SPURIOUS: prints="<module attr_clash.non_clashing_submodule>"
84+
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

8787
# check that import * only imports the __all__ attributes

0 commit comments

Comments
 (0)