diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll b/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll index f3943f53f86..71ca8f958e9 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/ImportResolution.qll @@ -377,4 +377,30 @@ module ImportResolution { } Module getModule(DataFlow::CfgNode node) { node = getModuleReference(result) } + + /** Holds if module `importer` directly imports module `imported`. */ + predicate imports(Module importer, Module imported) { + getImmediateModuleReference(imported).getScope() = importer + } + + /** + * Holds if the import statement `i` causes module `imported` to be imported. + * For `from pkg import submodule`, both `pkg` and `pkg.submodule` are considered imported. + */ + predicate importedBy(ImportingStmt i, Module imported) { + exists(Alias a | a = i.(Import).getAName() | + getImmediateModuleReference(imported).asExpr() = a.getAsname() + ) + or + exists(ImportMember im | im = i.(Import).getAName().getValue() | + getImmediateModuleReference(imported).asExpr() = im.getModule() + ) + or + getImmediateModuleReference(imported).asExpr() = i.(ImportStar).getModule().(ImportExpr) + } + + /** Gets a user-friendly name for module `m`, using the package name for `__init__` modules. */ + string moduleName(Module m) { + if m.isPackageInit() then result = m.getPackageName() else result = m.getName() + } } diff --git a/python/ql/src/Imports/ModuleImportsItself.ql b/python/ql/src/Imports/ModuleImportsItself.ql index 97fb68791d1..f7c5e6006ff 100644 --- a/python/ql/src/Imports/ModuleImportsItself.ql +++ b/python/ql/src/Imports/ModuleImportsItself.ql @@ -12,19 +12,19 @@ */ import python -private import LegacyPointsTo +import semmle.python.dataflow.new.DataFlow +private import semmle.python.dataflow.new.internal.ImportResolution -predicate modules_imports_itself(ImportingStmt i, ModuleValue m) { - i.getEnclosingModule() = m.getScope() and - m = - max(string s, ModuleValue m_ | - s = i.getAnImportedModuleName() and - m_.importedAs(s) - | - m_ order by s.length() - ) +predicate modules_imports_itself(ImportingStmt i, Module m) { + m = i.getEnclosingModule() and + ImportResolution::importedBy(i, m) and + // Exclude `from m import submodule` where the imported member is a submodule of m + not exists(ImportMember im | im = i.(Import).getAName().getValue() | + ImportResolution::getImmediateModuleReference(m).asExpr() = im.getModule() and + ImportResolution::importedBy(i, any(Module sub | sub != m)) + ) } -from ImportingStmt i, ModuleValue m +from ImportingStmt i, Module m where modules_imports_itself(i, m) -select i, "The module '" + m.getName() + "' imports itself." +select i, "The module '" + ImportResolution::moduleName(m) + "' imports itself." diff --git a/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.expected b/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.expected index 903b0c24857..ba9959ab286 100644 --- a/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.expected +++ b/python/ql/test/query-tests/Imports/PyCheckerTests/ModuleImportsItself.expected @@ -1,5 +1,6 @@ | imports_test.py:8:1:8:19 | Import | The module 'imports_test' imports itself. | | pkg_notok/__init__.py:4:1:4:16 | Import | The module 'pkg_notok' imports itself. | +| pkg_notok/__init__.py:10:1:10:20 | Import | The module 'pkg_notok' imports itself. | | pkg_notok/__init__.py:12:1:12:25 | Import | The module 'pkg_notok' imports itself. | | pkg_notok/__init__.py:13:1:13:37 | Import | The module 'pkg_notok' imports itself. | | pkg_notok/__init__.py:14:1:14:23 | from pkg_notok import * | The module 'pkg_notok' imports itself. |