mirror of
https://github.com/github/codeql.git
synced 2026-06-24 14:17:05 +02:00
Flips the Python dataflow trunk from the legacy CFG (semmle/python/Flow.qll) and legacy ESSA SSA (semmle/python/essa/*) to the new shared CFG facade (semmle.python.controlflow.internal.Cfg) and the new SSA adapter (semmle.python.dataflow.new.internal.SsaImpl), both introduced additively in the preceding PRs in this stack. This is the trunk-flip equivalent of the original draft PR #21894 (kept around as documentation), rebased on top of the four preparatory PRs: P1: Remove AstNode.getAFlowNode() and rewrite callers (#21919). P2: Qualify Flow.qll's AST references with Py:: prefix (#21920). P3: Add new shared-CFG-backed control flow graph (#21921). P4: Add new shared-SSA-backed SSA adapter (#21923). The Python dataflow library (semmle/python/dataflow/new/) now imports the new CFG facade and SSA adapter. All CFG-typed predicates (ControlFlowNode, CallNode, BasicBlock, NameNode, AttrNode, ...) are qualified with the Cfg:: prefix; SSA references switch from EssaVariable/EssaDefinition to SsaImpl::Definition/SourceVariable. GuardNode is redesigned to use the new CFG's outcome-node model (isAfterTrue / isAfterFalse) instead of the legacy ConditionBlock + flipped indirection. Only BarrierGuard<...> is preserved as public API. Framework files (Bottle, FastApi, Django, Tornado, Pyramid, Stdlib, ...) are updated to take CFG nodes from the new facade. A handful of dataflow consistency tweaks for the new CFG: - Augmented-assignment targets are treated as both load and store. - 'from X import *' produces uncertain SSA writes for unknown names. - CFG nodes are canonicalised so dataflow does not see equivalent pre/post-order pairs as distinct nodes. Two AST tweaks for the new CFG: - AstNodeImpl: omit PEP 695 type-parameter names from FunctionDefExpr / ClassDefExpr children. - ImportResolution: drop the legacy essa import. Test churn (~175 files): reblessed library- and query-test .expected files reflect slightly different CFG granularity, different toString output, and a handful of true alert deltas in security queries. Verification: all 367 lib + src + consistency-queries compile clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
251 lines
7.5 KiB
Plaintext
251 lines
7.5 KiB
Plaintext
overlay[local]
|
|
module;
|
|
|
|
import python
|
|
private import semmle.python.types.Builtins
|
|
private import semmle.python.internal.CachedStages
|
|
private import semmle.python.controlflow.internal.Cfg as Cfg
|
|
|
|
/**
|
|
* 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 Cfg::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()
|
|
}
|
|
}
|