mirror of
https://github.com/github/codeql.git
synced 2026-06-23 05:37:02 +02:00
Preparatory refactor for the shared-CFG dataflow migration. Deprecates the AstNode.getAFlowNode() cached predicate on the public Python QL API and rewrites all ~140 internal callers across lib/, src/, test/, and tools/ from `expr.getAFlowNode() = cfgNode` to `cfgNode.getNode() = expr`, using ControlFlowNode.getNode() which already exists in Flow.qll. The predicate itself is preserved (with a deprecation note pointing at the new pattern) so external users do not experience churn — they can migrate at their own pace and the AST/CFG hierarchies still get the intended untangling once the deprecation eventually elapses. Semantic noop verified by: - All 361 lib/ + src/ queries compile clean. - All 122 ControlFlow + PointsTo library-tests pass. - All 64 dataflow library-tests pass. - All 113 Variables/Exceptions/Expressions/Statements/Functions/Imports/ Security/CWE-798/ModificationOfParameterWithDefault query-tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
250 lines
7.4 KiB
Plaintext
250 lines
7.4 KiB
Plaintext
overlay[local]
|
|
module;
|
|
|
|
import python
|
|
private import semmle.python.types.Builtins
|
|
private import semmle.python.internal.CachedStages
|
|
|
|
/**
|
|
* An alias in an import statement, the `mod as name` part of `import mod as name`. May be artificial;
|
|
* `import x` is transformed into `import x as x`
|
|
*/
|
|
class Alias extends Alias_ {
|
|
Location getLocation() { result = this.getValue().getLocation() }
|
|
}
|
|
|
|
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 */
|
|
class ImportExpr extends ImportExpr_ {
|
|
private string basePackageName(int n) {
|
|
n = 1 and result = this.getEnclosingModule().getPackageName()
|
|
or
|
|
exists(string bpnm1 |
|
|
bpnm1 = this.basePackageName(n - 1) and
|
|
bpnm1.matches("%.%") and
|
|
result = bpnm1.regexpReplaceAll("\\.[^.]*$", "")
|
|
)
|
|
}
|
|
|
|
private predicate implicitRelativeImportsAllowed() {
|
|
// relative imports are no longer allowed in Python 3
|
|
major_version() < 3 and
|
|
// and can be explicitly turned off in later versions of Python 2
|
|
not this.getEnclosingModule().hasFromFuture("absolute_import")
|
|
}
|
|
|
|
/**
|
|
* Gets the level of this import.
|
|
*
|
|
* The language specifies level as -1 if relative imports are to be tried first, 0 for absolute imports,
|
|
* and level > 0 for explicit relative imports.
|
|
*/
|
|
override int getLevel() {
|
|
exists(int l | l = super.getLevel() |
|
|
l > 0 and result = l
|
|
or
|
|
/* The extractor may set level to 0 even though relative imports apply */
|
|
l = 0 and
|
|
(if this.implicitRelativeImportsAllowed() then result = -1 else result = 0)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* If this import is relative, and relative imports are allowed, compute
|
|
* the name of the topmost module that will be imported.
|
|
*/
|
|
private string relativeTopName() {
|
|
this.getLevel() = -1 and
|
|
result = this.basePackageName(1) + "." + this.getTopName() and
|
|
valid_module_name(result)
|
|
}
|
|
|
|
private string qualifiedTopName() {
|
|
if this.getLevel() <= 0
|
|
then result = this.getTopName()
|
|
else (
|
|
result = this.basePackageName(this.getLevel()) and
|
|
valid_module_name(result)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the name by which the lowest level module or package is imported.
|
|
* NOTE: This is the name that used to import the module,
|
|
* which may not be the name of the module.
|
|
*/
|
|
string bottomModuleName() {
|
|
result = this.relativeTopName() + this.remainderOfName()
|
|
or
|
|
not exists(this.relativeTopName()) and
|
|
result = this.qualifiedTopName() + this.remainderOfName()
|
|
}
|
|
|
|
/** Gets the name of topmost module or package being imported */
|
|
string topModuleName() {
|
|
result = this.relativeTopName()
|
|
or
|
|
not exists(this.relativeTopName()) and
|
|
result = this.qualifiedTopName()
|
|
}
|
|
|
|
/**
|
|
* Gets the full name of the module resulting from evaluating this import.
|
|
* NOTE: This is the name that used to import the module,
|
|
* which may not be the name of the module.
|
|
*/
|
|
string getImportedModuleName() {
|
|
exists(string bottomName | bottomName = this.bottomModuleName() |
|
|
if this.isTop() then result = this.topModuleName() else result = bottomName
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the names of the modules that may be imported by this import.
|
|
* For example this predicate would return 'x' and 'x.y' for `import x.y`
|
|
*/
|
|
string getAnImportedModuleName() {
|
|
result = this.bottomModuleName()
|
|
or
|
|
result = this.getAnImportedModuleName().regexpReplaceAll("\\.[^.]*$", "")
|
|
}
|
|
|
|
override Expr getASubExpression() { none() }
|
|
|
|
override predicate hasSideEffects() { any() }
|
|
|
|
private string getTopName() { result = this.getName().regexpReplaceAll("\\..*", "") }
|
|
|
|
private string remainderOfName() {
|
|
not exists(this.getName()) and result = ""
|
|
or
|
|
this.getLevel() <= 0 and result = this.getName().regexpReplaceAll("^[^\\.]*", "")
|
|
or
|
|
this.getLevel() > 0 and result = "." + this.getName()
|
|
}
|
|
|
|
/**
|
|
* Whether this import is relative, that is not absolute.
|
|
* See https://www.python.org/dev/peps/pep-0328/
|
|
*/
|
|
predicate isRelative() {
|
|
/* Implicit */
|
|
exists(this.relativeTopName())
|
|
or
|
|
/* Explicit */
|
|
this.getLevel() > 0
|
|
}
|
|
}
|
|
|
|
/** A `from ... import ...` expression */
|
|
class ImportMember extends ImportMember_ {
|
|
override Expr getASubExpression() { result = this.getModule() }
|
|
|
|
override predicate hasSideEffects() {
|
|
/* Strictly this only has side-effects if the module is a package */
|
|
any()
|
|
}
|
|
|
|
/**
|
|
* Gets the full name of the module resulting from evaluating this import.
|
|
* NOTE: This is the name that used to import the module,
|
|
* which may not be the name of the module.
|
|
*/
|
|
string getImportedModuleName() {
|
|
result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName()
|
|
}
|
|
|
|
deprecated override ImportMemberNode getAFlowNode() { result = super.getAFlowNode() }
|
|
}
|
|
|
|
/** An import statement */
|
|
class Import extends Import_ {
|
|
/* syntax: import modname */
|
|
private ImportExpr getAModuleExpr() {
|
|
result = this.getAName().getValue()
|
|
or
|
|
result = this.getAName().getValue().(ImportMember).getModule()
|
|
}
|
|
|
|
/** Whether this a `from ... import ...` statement */
|
|
predicate isFromImport() { this.getAName().getValue() instanceof ImportMember }
|
|
|
|
override Expr getASubExpression() {
|
|
result = this.getAModuleExpr() or
|
|
result = this.getAName().getAsname() or
|
|
result = this.getAName().getValue()
|
|
}
|
|
|
|
override Stmt getASubStatement() { none() }
|
|
|
|
/**
|
|
* Gets the name of an imported module.
|
|
* For example, for the import statement `import bar` which
|
|
* is a relative import in package "foo", this would return
|
|
* "foo.bar".
|
|
* The import statement `from foo import bar` would return
|
|
* `foo` and `foo.bar`
|
|
*/
|
|
string getAnImportedModuleName() {
|
|
result = this.getAModuleExpr().getAnImportedModuleName()
|
|
or
|
|
exists(ImportMember m, string modname |
|
|
m = this.getAName().getValue() and
|
|
modname = m.getModule().(ImportExpr).getImportedModuleName()
|
|
|
|
|
result = modname
|
|
or
|
|
result = modname + "." + m.getName()
|
|
)
|
|
}
|
|
}
|
|
|
|
/** An import * statement */
|
|
class ImportStar extends ImportStar_ {
|
|
/* syntax: from modname import * */
|
|
cached
|
|
ImportExpr getModuleExpr() {
|
|
Stages::AST::ref() and
|
|
result = this.getModule()
|
|
or
|
|
result = this.getModule().(ImportMember).getModule()
|
|
}
|
|
|
|
override string toString() { result = "from " + this.getModuleExpr().getName() + " import *" }
|
|
|
|
override Expr getASubExpression() { result = this.getModule() }
|
|
|
|
override Stmt getASubStatement() { none() }
|
|
|
|
/** Gets the name of the imported module. */
|
|
string getImportedModuleName() { result = this.getModuleExpr().getImportedModuleName() }
|
|
}
|
|
|
|
/**
|
|
* A statement that imports a module. This can be any statement that includes the `import` keyword,
|
|
* such as `import sys`, `from sys import version` or `from sys import *`.
|
|
*/
|
|
class ImportingStmt extends Stmt {
|
|
ImportingStmt() {
|
|
this instanceof Import
|
|
or
|
|
this instanceof ImportStar
|
|
}
|
|
|
|
/** Gets the name of an imported module. */
|
|
string getAnImportedModuleName() {
|
|
result = this.(Import).getAnImportedModuleName()
|
|
or
|
|
result = this.(ImportStar).getImportedModuleName()
|
|
}
|
|
}
|