From e16bb226c08c8131e10748d7e4990a03e4d0220f Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 11 Mar 2026 23:02:09 +0000 Subject: [PATCH] Python: Fix resolution of relative imports from namespace packages The fix may look a bit obscure, so here's what's going on. When we see `from . import helper`, we create an `ImportExpr` with level equal to 1 (corresponding to the number of dots). To resolve such imports, we compute the name of the enclosing package, as part of `ImportExpr.qualifiedTopName()`. For this form of import expression, it is equivalent to `this.getEnclosingModule().getPackageName()`. But `qualifiedTopName` requires that `valid_module_name` holds for its result, and this was _not_ the case for namespace packages. To fix this, we extend `valid_module_name` to include the module names of _any_ folder, not just regular package (which are the ones where there's a `__init__.py` in the folder). Note that this doesn't simply include all folders -- only the ones that result in valid module names in Python. --- python/ql/lib/semmle/python/Import.qll | 4 ++++ .../import-resolution-namespace-relative/pkg/helper.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/Import.qll b/python/ql/lib/semmle/python/Import.qll index e8a7facccad..2f7fae95539 100644 --- a/python/ql/lib/semmle/python/Import.qll +++ b/python/ql/lib/semmle/python/Import.qll @@ -17,6 +17,10 @@ private predicate valid_module_name(string name) { exists(Module m | m.getName() = name) or exists(Builtin cmod | cmod.getClass() = Builtin::special("ModuleType") and cmod.getName() = name) + or + // Namespace packages may not have a corresponding Module entity, + // but their names are still valid for the purpose of import resolution. + name = moduleNameFromFile(any(Folder f)) } /** An artificial expression representing an import */ diff --git a/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py index 43167b8cfa7..b9407161e08 100644 --- a/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py +++ b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py @@ -1,2 +1,2 @@ def process(value): - sink(value) #$ MISSING: prints=source + sink(value) #$ prints=source