Python: Port ModuleImportsItself.ql

Uses the existing machinery in ImportResolution.qll, after adding a few
convenience predicates.

The new modelling actually manages to find a result that the old
points-to analysis did not. Apart from that there are no test changes.
This commit is contained in:
Taus
2026-02-25 16:01:10 +00:00
parent e2eb69ce8d
commit 603d37cd60
3 changed files with 39 additions and 12 deletions

View File

@@ -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()
}
}

View File

@@ -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."

View File

@@ -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. |