Compare commits

..

21 Commits

Author SHA1 Message Date
Sotiris Dragonas
61a5cece56 Merge branch 'main' into bazookamusic/range-analysis-bound-move-to-shared 2026-06-02 10:41:49 +02:00
BazookaMusic
566a92e555 formatting again 2026-06-02 10:41:10 +02:00
Tom Hvitved
9618e9b35c Merge pull request #21873 from hvitved/local-name-resolution
Shared: Local name resolution library
2026-06-01 20:51:07 +02:00
BazookaMusic
2a3cff382c more specific comment 2026-06-01 18:20:50 +02:00
BazookaMusic
c610af88d3 fix comment and add overlay[local?] 2026-06-01 18:18:37 +02:00
BazookaMusic
fa63dad1d1 change note 2026-06-01 18:16:51 +02:00
Sotiris Dragonas
019a5c01ad Merge branch 'main' into bazookamusic/range-analysis-bound-move-to-shared 2026-06-01 18:10:02 +02:00
BazookaMusic
c1c9287535 restore file header 2026-06-01 15:48:26 +02:00
BazookaMusic
d1226b71de formatting 2026-06-01 15:46:52 +02:00
BazookaMusic
71a363545a formatting 2026-06-01 15:24:06 +02:00
Tom Hvitved
d2f474d998 Address review comments 2026-06-01 08:30:01 +02:00
Tom Hvitved
caae5a8bf1 Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-29 14:24:45 +02:00
Tom Hvitved
09371339d7 Ruby: Adopt shared local name resolution library 2026-05-29 09:06:14 +02:00
Tom Hvitved
7718fe40a0 Ruby: Add more variable tests 2026-05-28 10:50:15 +02:00
Tom Hvitved
aeb82858d7 Rust: Run codegen 2026-05-28 10:50:13 +02:00
Tom Hvitved
c08cf81665 Rust: Adopt shared local name resolution library 2026-05-28 10:50:10 +02:00
Tom Hvitved
e06158629e Rust: More local variable tests 2026-05-28 10:50:05 +02:00
Tom Hvitved
3e09961662 Shared: Add local name binding library 2026-05-28 10:50:03 +02:00
BazookaMusic
cc12740c0e remove check for files in sync 2026-05-27 17:41:44 +02:00
BazookaMusic
acb5c0e70f missed changes 2026-05-27 17:23:45 +02:00
BazookaMusic
6042adebae move identical java and cs bound.qll to shared library 2026-05-27 17:23:28 +02:00
186 changed files with 4908 additions and 8978 deletions

View File

@@ -11,10 +11,6 @@
"java/ql/lib/semmle/code/java/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll",
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll"
],
"Bound Java/C#": [
"java/ql/lib/semmle/code/java/dataflow/Bound.qll",
"csharp/ql/lib/semmle/code/csharp/dataflow/Bound.qll"
],
"ModulusAnalysis Java/C#": [
"java/ql/lib/semmle/code/java/dataflow/ModulusAnalysis.qll",
"csharp/ql/lib/semmle/code/csharp/dataflow/ModulusAnalysis.qll"

View File

@@ -9,6 +9,7 @@ dependencies:
codeql/controlflow: ${workspace}
codeql/dataflow: ${workspace}
codeql/mad: ${workspace}
codeql/rangeanalysis: ${workspace}
codeql/ssa: ${workspace}
codeql/threat-models: ${workspace}
codeql/tutorial: ${workspace}

View File

@@ -4,67 +4,11 @@
overlay[local?]
module;
private import csharp as CS
private import internal.rangeanalysis.BoundSpecific
private import internal.rangeanalysis.BoundSpecific as BoundSpecific
private import codeql.rangeanalysis.Bound as SharedBound
private newtype TBound =
TBoundZero() or
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
TBoundExpr(Expr e) {
interestingExprBound(e) and
not exists(SsaVariable v | e = v.getAUse())
}
private module BoundImpl = SharedBound::Bound<CS::Location, BoundSpecific::BoundDefs>;
/**
* A bound that may be inferred for an expression plus/minus an integer delta.
*/
abstract class Bound extends TBound {
/** Gets a textual representation of this bound. */
abstract string toString();
/** Gets an expression that equals this bound plus `delta`. */
abstract Expr getExpr(int delta);
/** Gets an expression that equals this bound. */
Expr getExpr() { result = this.getExpr(0) }
/** Gets the location of this bound. */
abstract Location getLocation();
}
/**
* The bound that corresponds to the integer 0. This is used to represent all
* integer bounds as bounds are always accompanied by an added integer delta.
*/
class ZeroBound extends Bound, TBoundZero {
override string toString() { result = "0" }
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
override Location getLocation() { result.hasLocationInfo("", 0, 0, 0, 0) }
}
/**
* A bound corresponding to the value of an SSA variable.
*/
class SsaBound extends Bound, TBoundSsa {
/** Gets the SSA variable that equals this bound. */
SsaVariable getSsa() { this = TBoundSsa(result) }
override string toString() { result = this.getSsa().toString() }
override Expr getExpr(int delta) { result = this.getSsa().getAUse() and delta = 0 }
override Location getLocation() { result = this.getSsa().getLocation() }
}
/**
* A bound that corresponds to the value of a specific expression that might be
* interesting, but isn't otherwise represented by the value of an SSA variable.
*/
class ExprBound extends Bound, TBoundExpr {
override string toString() { result = this.getExpr().toString() }
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
override Location getLocation() { result = this.getExpr().getLocation() }
}
import BoundImpl

View File

@@ -1,22 +1,30 @@
/**
* Provides C#-specific definitions for bounds.
*/
overlay[local?]
module;
private import csharp as CS
private import semmle.code.csharp.dataflow.SSA::Ssa as Ssa
private import semmle.code.csharp.dataflow.internal.rangeanalysis.ConstantUtils as CU
private import semmle.code.csharp.dataflow.internal.rangeanalysis.RangeUtils as RU
private import semmle.code.csharp.dataflow.internal.rangeanalysis.SsaUtils as SU
private import codeql.rangeanalysis.Bound as SharedBound
class SsaVariable = SU::SsaVariable;
/** Provides C#-specific definitions for bounds. */
module BoundDefs implements SharedBound::BoundDefinitions<CS::Location> {
class Type = CS::Type;
class Expr = CS::ControlFlowNodes::ExprNode;
class SsaVariable = SU::SsaVariable;
class Location = CS::Location;
class SsaSourceVariable = Ssa::SourceVariable;
class IntegralType = CS::IntegralType;
class Expr = CS::ControlFlowNodes::ExprNode;
class ConstantIntegerExpr = CU::ConstantIntegerExpr;
class IntegralType = CS::IntegralType;
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
predicate interestingExprBound(Expr e) { CU::systemArrayLengthAccess(e.getExpr()) }
class ConstantIntegerExpr = CU::ConstantIntegerExpr;
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
predicate interestingExprBound(Expr e) { CU::systemArrayLengthAccess(e.getExpr()) }
}

View File

@@ -4,67 +4,10 @@
overlay[local?]
module;
private import internal.rangeanalysis.BoundSpecific
private import java as J
private import internal.rangeanalysis.BoundSpecific as BoundSpecific
private import codeql.rangeanalysis.Bound as SharedBound
private newtype TBound =
TBoundZero() or
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
TBoundExpr(Expr e) {
interestingExprBound(e) and
not exists(SsaVariable v | e = v.getAUse())
}
private module BoundImpl = SharedBound::Bound<J::Location, BoundSpecific::BoundDefs>;
/**
* A bound that may be inferred for an expression plus/minus an integer delta.
*/
abstract class Bound extends TBound {
/** Gets a textual representation of this bound. */
abstract string toString();
/** Gets an expression that equals this bound plus `delta`. */
abstract Expr getExpr(int delta);
/** Gets an expression that equals this bound. */
Expr getExpr() { result = this.getExpr(0) }
/** Gets the location of this bound. */
abstract Location getLocation();
}
/**
* The bound that corresponds to the integer 0. This is used to represent all
* integer bounds as bounds are always accompanied by an added integer delta.
*/
class ZeroBound extends Bound, TBoundZero {
override string toString() { result = "0" }
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
override Location getLocation() { result.hasLocationInfo("", 0, 0, 0, 0) }
}
/**
* A bound corresponding to the value of an SSA variable.
*/
class SsaBound extends Bound, TBoundSsa {
/** Gets the SSA variable that equals this bound. */
SsaVariable getSsa() { this = TBoundSsa(result) }
override string toString() { result = this.getSsa().toString() }
override Expr getExpr(int delta) { result = this.getSsa().getAUse() and delta = 0 }
override Location getLocation() { result = this.getSsa().getLocation() }
}
/**
* A bound that corresponds to the value of a specific expression that might be
* interesting, but isn't otherwise represented by the value of an SSA variable.
*/
class ExprBound extends Bound, TBoundExpr {
override string toString() { result = this.getExpr().toString() }
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
override Location getLocation() { result = this.getExpr().getLocation() }
}
import BoundImpl

View File

@@ -7,21 +7,26 @@ module;
private import java as J
private import semmle.code.java.dataflow.SSA as Ssa
private import semmle.code.java.dataflow.RangeUtils as RU
private import codeql.rangeanalysis.Bound as SharedBound
class SsaVariable extends Ssa::SsaDefinition {
/** Gets a use of this variable. */
Expr getAUse() { result = super.getARead() }
}
class Expr = J::Expr;
class Location = J::Location;
class IntegralType = J::IntegralType;
class ConstantIntegerExpr = RU::ConstantIntegerExpr;
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
predicate interestingExprBound(Expr e) {
e.(J::FieldRead).getField() instanceof J::ArrayLengthField
module BoundDefs implements SharedBound::BoundDefinitions<J::Location> {
class SsaVariable extends Ssa::SsaDefinition {
/** Gets a use of this variable. */
Expr getAUse() { result = super.getARead() }
}
class SsaSourceVariable = Ssa::SourceVariable;
class Type = J::Type;
class Expr = J::Expr;
class IntegralType = J::IntegralType;
class ConstantIntegerExpr = RU::ConstantIntegerExpr;
/** Holds if `e` is a bound expression and it is not an SSA variable read. */
predicate interestingExprBound(Expr e) {
e.(J::FieldRead).getField() instanceof J::ArrayLengthField
}
}

View File

@@ -1,2 +0,0 @@
import semmle.python.controlflow.internal.AstNodeImpl
import ControlFlow::Consistency

View File

@@ -213,11 +213,9 @@ class ExprWithPointsTo extends Expr {
* Gets what this expression might "refer-to" in the given `context`.
*/
predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) {
exists(ControlFlowNode this_, ControlFlowNode origin_ |
this_.getNode() = this and origin_.getNode() = origin
|
this_.(ControlFlowNodeWithPointsTo).refersTo(context, obj, cls, origin_)
)
this.getAFlowNode()
.(ControlFlowNodeWithPointsTo)
.refersTo(context, obj, cls, origin.getAFlowNode())
}
/**
@@ -228,11 +226,7 @@ class ExprWithPointsTo extends Expr {
*/
pragma[nomagic]
predicate refersTo(Object obj, AstNode origin) {
exists(ControlFlowNode this_, ControlFlowNode origin_ |
this_.getNode() = this and origin_.getNode() = origin
|
this_.(ControlFlowNodeWithPointsTo).refersTo(obj, origin_)
)
this.getAFlowNode().(ControlFlowNodeWithPointsTo).refersTo(obj, origin.getAFlowNode())
}
/**
@@ -246,22 +240,16 @@ class ExprWithPointsTo extends Expr {
* in the given `context`.
*/
predicate pointsTo(Context context, Value value, AstNode origin) {
exists(ControlFlowNode this_, ControlFlowNode origin_ |
this_.getNode() = this and origin_.getNode() = origin
|
this_.(ControlFlowNodeWithPointsTo).pointsTo(context, value, origin_)
)
this.getAFlowNode()
.(ControlFlowNodeWithPointsTo)
.pointsTo(context, value, origin.getAFlowNode())
}
/**
* Holds if this expression might "point-to" to `value` which is from `origin`.
*/
predicate pointsTo(Value value, AstNode origin) {
exists(ControlFlowNode this_, ControlFlowNode origin_ |
this_.getNode() = this and origin_.getNode() = origin
|
this_.(ControlFlowNodeWithPointsTo).pointsTo(value, origin_)
)
this.getAFlowNode().(ControlFlowNodeWithPointsTo).pointsTo(value, origin.getAFlowNode())
}
/**
@@ -487,10 +475,7 @@ class FunctionMetricsWithPointsTo extends FunctionMetrics {
not non_coupling_method(result) and
exists(Call call | call.getScope() = this |
exists(FunctionObject callee | callee.getFunction() = result |
exists(CallNode call_ |
call_.getNode() = call and
call_.getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
)
call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
)
or
exists(Attribute a | call.getFunc() = a |

View File

@@ -64,7 +64,7 @@ private predicate jump_to_defn(ControlFlowNode use, Definition defn) {
private predicate preferred_jump_to_defn(Expr use, Definition def) {
not use instanceof ClassExpr and
not use instanceof FunctionExpr and
exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, def))
jump_to_defn(use.getAFlowNode(), def)
}
private predicate unique_jump_to_defn(Expr use, Definition def) {
@@ -452,7 +452,7 @@ private predicate self_parameter_jump_to_defn_attribute(
* This exists primarily for testing use `getPreferredDefinition()` instead.
*/
Definition getADefinition(Expr use) {
exists(ControlFlowNode useNode | useNode.getNode() = use | jump_to_defn(useNode, result)) and
jump_to_defn(use.getAFlowNode(), result) and
not use instanceof Call and
not use.isArtificial() and
// Not the use itself

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* A new Python control flow graph implementation has been added under `semmle.python.controlflow.internal.Cfg` (backed by `AstNodeImpl.qll`), built on the shared `codeql.controlflow.ControlFlowGraph` library. It is not yet used by the dataflow library or any production query; the legacy CFG in `semmle/python/Flow.qll` remains the default. The new library is exposed for tests and for upcoming migrations.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* A new SSA adapter has been added under `semmle.python.dataflow.new.internal.SsaImpl`, built on the shared `codeql.ssa.Ssa` library and the new shared CFG (`semmle.python.controlflow.internal.Cfg`). It is not yet used by the dataflow library or any production query; the legacy ESSA SSA in `semmle/python/essa/*` remains the default. The new SSA adapter is exposed for tests and for the upcoming dataflow migration.

View File

@@ -1,5 +0,0 @@
---
category: deprecated
---
* The `AstNode.getAFlowNode()` predicate has been deprecated. Use `ControlFlowNode.getNode()` from the other direction instead: replace `e.getAFlowNode() = n` with `n.getNode() = e`. This is a preparatory step towards migrating the dataflow library off the legacy CFG; it has no semantic effect.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Simplified the internal predicates that detect `@staticmethod`, `@classmethod` and `@property` decorators to match the decorator's AST `Name` directly, rather than going through the CFG and requiring the name to resolve globally. Code that shadows these three builtin decorators at the module-scope will now be classified by the decorator name alone; in practice, shadowing these names is extremely rare and the call-graph results are unchanged.

View File

@@ -1,4 +0,0 @@
---
category: deprecated
---
* The `Function.getAReturnValueFlowNode()` predicate has been deprecated. Bind a `Return` node explicitly instead — `exists(Return ret | ret.getScope() = f and n.getNode() = ret.getValue())`. This is a preparatory step towards migrating the dataflow library off the legacy CFG; it has no semantic effect.

View File

@@ -1,45 +0,0 @@
/**
* @name Print CFG (New)
* @description Produces a representation of a file's Control Flow Graph
* using the new shared control flow library.
* This query is used by the VS Code extension.
* @id python/print-cfg
* @kind graph
* @tags ide-contextual-queries/print-cfg
*/
private import python as Py
import semmle.python.controlflow.internal.AstNodeImpl
external string selectedSourceFile();
private predicate selectedSourceFileAlias = selectedSourceFile/0;
external int selectedSourceLine();
private predicate selectedSourceLineAlias = selectedSourceLine/0;
external int selectedSourceColumn();
private predicate selectedSourceColumnAlias = selectedSourceColumn/0;
module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig<Py::File> {
predicate selectedSourceFile = selectedSourceFileAlias/0;
predicate selectedSourceLine = selectedSourceLineAlias/0;
predicate selectedSourceColumn = selectedSourceColumnAlias/0;
predicate cfgScopeSpan(
Ast::Callable callable, Py::File file, int startLine, int startColumn, int endLine,
int endColumn
) {
exists(Py::Scope scope |
scope = callable.asScope() and
file = scope.getLocation().getFile() and
scope.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn)
)
}
}
import ControlFlow::ViewCfgQuery<Py::File, ViewCfgQueryInput>

View File

@@ -16,26 +16,21 @@ abstract class AstNode extends AstNode_ {
/** Gets the scope that this node occurs in */
abstract Scope getScope();
/** Gets the location for this AST node */
cached
Location getLocation() { none() }
/**
* DEPRECATED: use `ControlFlowNode.getNode()` from the other direction instead;
* that is, replace `e.getAFlowNode() = n` with `n.getNode() = e`. This API is
* being removed to untangle the AST and CFG hierarchies in preparation for
* migrating the dataflow library off the legacy CFG.
*
* Gets a flow node corresponding directly to this node.
* NOTE: For some statements and other purely syntactic elements,
* there may not be a `ControlFlowNode`.
* there may not be a `ControlFlowNode`
*/
cached
deprecated ControlFlowNode getAFlowNode() {
ControlFlowNode getAFlowNode() {
Stages::AST::ref() and
py_flow_bb_node(result, this, _, _)
}
/** Gets the location for this AST node */
cached
Location getLocation() { none() }
/**
* Whether this syntactic element is artificial, that is it is generated
* by the compiler and is not present in the source

View File

@@ -28,9 +28,7 @@ class Expr extends Expr_, AstNode {
/** Whether this expression may have a side effect (as determined purely from its syntax) */
predicate hasSideEffects() {
/* If an exception raised by this expression handled, count that as a side effect */
exists(ControlFlowNode n | n.getNode() = this |
n.getASuccessor().getNode() instanceof ExceptStmt
)
this.getAFlowNode().getASuccessor().getNode() instanceof ExceptStmt
or
this.getASubExpression().hasSideEffects()
}
@@ -70,6 +68,8 @@ class Attribute extends Attribute_ {
/* syntax: Expr.name */
override Expr getASubExpression() { result = this.getObject() }
override AttrNode getAFlowNode() { result = super.getAFlowNode() }
/** Gets the name of this attribute. That is the `name` in `obj.name` */
string getName() { result = Attribute_.super.getAttr() }
@@ -96,6 +96,8 @@ class Subscript extends Subscript_ {
}
Expr getObject() { result = Subscript_.super.getValue() }
override SubscriptNode getAFlowNode() { result = super.getAFlowNode() }
}
/** A call expression, such as `func(...)` */
@@ -111,6 +113,8 @@ class Call extends Call_ {
override string toString() { result = this.getFunc().toString() + "()" }
override CallNode getAFlowNode() { result = super.getAFlowNode() }
/** Gets a tuple (*) argument of this call. */
Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() }
@@ -196,6 +200,8 @@ class IfExp extends IfExp_ {
override Expr getASubExpression() {
result = this.getTest() or result = this.getBody() or result = this.getOrelse()
}
override IfExprNode getAFlowNode() { result = super.getAFlowNode() }
}
/** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */
@@ -404,6 +410,8 @@ class PlaceHolder extends PlaceHolder_ {
override Expr getASubExpression() { none() }
override string toString() { result = "$" + this.getId() }
override NameNode getAFlowNode() { result = super.getAFlowNode() }
}
/** A tuple expression such as `( 1, 3, 5, 7, 9 )` */
@@ -470,6 +478,8 @@ class Name extends Name_ {
override string toString() { result = this.getId() }
override NameNode getAFlowNode() { result = super.getAFlowNode() }
override predicate isArtificial() {
/* Artificial variable names in comprehensions all start with "." */
this.getId().charAt(0) = "."
@@ -575,6 +585,8 @@ abstract class NameConstant extends Name, ImmutableLiteral {
override predicate isConstant() { any() }
override NameConstantNode getAFlowNode() { result = Name.super.getAFlowNode() }
override predicate isArtificial() { none() }
}

View File

@@ -1,7 +1,7 @@
overlay[local]
module;
import python as Py
import python
private import semmle.python.internal.CachedStages
private import codeql.controlflow.BasicBlock as BB
@@ -17,7 +17,7 @@ private import codeql.controlflow.BasicBlock as BB
*/
private predicate augstore(ControlFlowNode load, ControlFlowNode store) {
exists(Py::Expr load_store | exists(Py::AugAssign aa | aa.getTarget() = load_store) |
exists(Expr load_store | exists(AugAssign aa | aa.getTarget() = load_store) |
toAst(load) = load_store and
toAst(store) = load_store and
load.strictlyDominates(store)
@@ -25,7 +25,7 @@ private predicate augstore(ControlFlowNode load, ControlFlowNode store) {
}
/** A non-dispatched getNode() to avoid negative recursion issues */
private Py::AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) }
private AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _) }
/**
* A control flow node. Control flow nodes have a many-to-one relation with syntactic nodes,
@@ -35,19 +35,19 @@ private Py::AstNode toAst(ControlFlowNode n) { py_flow_bb_node(n, result, _, _)
class ControlFlowNode extends @py_flow_node {
/** Whether this control flow node is a load (including those in augmented assignments) */
predicate isLoad() {
exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this))
exists(Expr e | e = toAst(this) | py_expr_contexts(_, 3, e) and not augstore(_, this))
}
/** Whether this control flow node is a store (including those in augmented assignments) */
predicate isStore() {
exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this))
exists(Expr e | e = toAst(this) | py_expr_contexts(_, 5, e) or augstore(_, this))
}
/** Whether this control flow node is a delete */
predicate isDelete() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) }
predicate isDelete() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 2, e)) }
/** Whether this control flow node is a parameter */
predicate isParameter() { exists(Py::Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) }
predicate isParameter() { exists(Expr e | e = toAst(this) | py_expr_contexts(_, 4, e)) }
/** Whether this control flow node is a store in an augmented assignment */
predicate isAugStore() { augstore(_, this) }
@@ -57,61 +57,61 @@ class ControlFlowNode extends @py_flow_node {
/** Whether this flow node corresponds to a literal */
predicate isLiteral() {
toAst(this) instanceof Py::Bytes
toAst(this) instanceof Bytes
or
toAst(this) instanceof Py::Dict
toAst(this) instanceof Dict
or
toAst(this) instanceof Py::DictComp
toAst(this) instanceof DictComp
or
toAst(this) instanceof Py::Set
toAst(this) instanceof Set
or
toAst(this) instanceof Py::SetComp
toAst(this) instanceof SetComp
or
toAst(this) instanceof Py::Ellipsis
toAst(this) instanceof Ellipsis
or
toAst(this) instanceof Py::GeneratorExp
toAst(this) instanceof GeneratorExp
or
toAst(this) instanceof Py::Lambda
toAst(this) instanceof Lambda
or
toAst(this) instanceof Py::ListComp
toAst(this) instanceof ListComp
or
toAst(this) instanceof Py::List
toAst(this) instanceof List
or
toAst(this) instanceof Py::Num
toAst(this) instanceof Num
or
toAst(this) instanceof Py::Tuple
toAst(this) instanceof Tuple
or
toAst(this) instanceof Py::Unicode
toAst(this) instanceof Unicode
or
toAst(this) instanceof Py::NameConstant
toAst(this) instanceof NameConstant
}
/** Whether this flow node corresponds to an attribute expression */
predicate isAttribute() { toAst(this) instanceof Py::Attribute }
predicate isAttribute() { toAst(this) instanceof Attribute }
/** Whether this flow node corresponds to an subscript expression */
predicate isSubscript() { toAst(this) instanceof Py::Subscript }
predicate isSubscript() { toAst(this) instanceof Subscript }
/** Whether this flow node corresponds to an import member */
predicate isImportMember() { toAst(this) instanceof Py::ImportMember }
predicate isImportMember() { toAst(this) instanceof ImportMember }
/** Whether this flow node corresponds to a call */
predicate isCall() { toAst(this) instanceof Py::Call }
predicate isCall() { toAst(this) instanceof Call }
/** Whether this flow node is the first in a module */
predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Py::Module }
predicate isModuleEntry() { this.isEntryNode() and toAst(this) instanceof Module }
/** Whether this flow node corresponds to an import */
predicate isImport() { toAst(this) instanceof Py::ImportExpr }
predicate isImport() { toAst(this) instanceof ImportExpr }
/** Whether this flow node corresponds to a conditional expression */
predicate isIfExp() { toAst(this) instanceof Py::IfExp }
predicate isIfExp() { toAst(this) instanceof IfExp }
/** Whether this flow node corresponds to a function definition expression */
predicate isFunction() { toAst(this) instanceof Py::FunctionExpr }
predicate isFunction() { toAst(this) instanceof FunctionExpr }
/** Whether this flow node corresponds to a class definition expression */
predicate isClass() { toAst(this) instanceof Py::ClassExpr }
predicate isClass() { toAst(this) instanceof ClassExpr }
/** Gets a predecessor of this flow node */
ControlFlowNode getAPredecessor() { this = result.getASuccessor() }
@@ -123,25 +123,25 @@ class ControlFlowNode extends @py_flow_node {
ControlFlowNode getImmediateDominator() { py_idoms(this, result) }
/** Gets the syntactic element corresponding to this flow node */
Py::AstNode getNode() { py_flow_bb_node(this, result, _, _) }
AstNode getNode() { py_flow_bb_node(this, result, _, _) }
/** Gets a textual representation of this element. */
cached
string toString() {
Stages::AST::ref() and
// Since modules can have ambigous names, entry nodes can too, if we do not collate them.
exists(Py::Scope s | s.getEntryNode() = this |
exists(Scope s | s.getEntryNode() = this |
result = "Entry node for " + concat( | | s.toString(), ",")
)
or
exists(Py::Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString())
exists(Scope s | s.getANormalExit() = this | result = "Exit node for " + s.toString())
or
not exists(Py::Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and
not exists(Scope s | s.getEntryNode() = this or s.getANormalExit() = this) and
result = "ControlFlowNode for " + this.getNode().toString()
}
/** Gets the location of this ControlFlowNode */
Py::Location getLocation() { result = this.getNode().getLocation() }
Location getLocation() { result = this.getNode().getLocation() }
/** Whether this flow node is the first in its scope */
predicate isEntryNode() { py_scope_flow(this, _, -1) }
@@ -151,9 +151,9 @@ class ControlFlowNode extends @py_flow_node {
/** Gets the scope containing this flow node */
cached
Py::Scope getScope() {
Scope getScope() {
Stages::AST::ref() and
if this.getNode() instanceof Py::Scope
if this.getNode() instanceof Scope
then
/* Entry or exit node */
result = this.getNode()
@@ -161,7 +161,7 @@ class ControlFlowNode extends @py_flow_node {
}
/** Gets the enclosing module */
Py::Module getEnclosingModule() { result = this.getScope().getEnclosingModule() }
Module getEnclosingModule() { result = this.getScope().getEnclosingModule() }
/** Gets a successor for this node if the relevant condition is True. */
ControlFlowNode getATrueSuccessor() {
@@ -188,7 +188,7 @@ class ControlFlowNode extends @py_flow_node {
}
/** Whether the scope may be exited as a result of this node raising an exception */
predicate isExceptionalExit(Py::Scope s) { py_scope_flow(this, s, 1) }
predicate isExceptionalExit(Scope s) { py_scope_flow(this, s, 1) }
/** Whether this node is a normal (non-exceptional) exit */
predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) }
@@ -236,7 +236,7 @@ class ControlFlowNode extends @py_flow_node {
/* join-ordering helper for `getAChild() */
pragma[noinline]
private ControlFlowNode getExprChild(BasicBlock dom) {
this.getNode().(Py::Expr).getAChildNode() = result.getNode() and
this.getNode().(Expr).getAChildNode() = result.getNode() and
result.getBasicBlock().dominates(dom) and
not this instanceof UnaryExprNode
}
@@ -249,16 +249,16 @@ class ControlFlowNode extends @py_flow_node {
*/
private class AnyNode extends ControlFlowNode {
override Py::AstNode getNode() { result = super.getNode() }
override AstNode getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a call expression, such as `func(...)` */
class CallNode extends ControlFlowNode {
CallNode() { toAst(this) instanceof Py::Call }
CallNode() { toAst(this) instanceof Call }
/** Gets the flow node corresponding to the function expression for the call corresponding to this flow node */
ControlFlowNode getFunction() {
exists(Py::Call c |
exists(Call c |
this.getNode() = c and
c.getFunc() = result.getNode() and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -267,7 +267,7 @@ class CallNode extends ControlFlowNode {
/** Gets the flow node corresponding to the n'th positional argument of the call corresponding to this flow node */
ControlFlowNode getArg(int n) {
exists(Py::Call c |
exists(Call c |
this.getNode() = c and
c.getArg(n) = result.getNode() and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -276,7 +276,7 @@ class CallNode extends ControlFlowNode {
/** Gets the flow node corresponding to the named argument of the call corresponding to this flow node */
ControlFlowNode getArgByName(string name) {
exists(Py::Call c, Py::Keyword k |
exists(Call c, Keyword k |
this.getNode() = c and
k = c.getANamedArg() and
k.getValue() = result.getNode() and
@@ -292,7 +292,7 @@ class CallNode extends ControlFlowNode {
result = this.getArgByName(_)
}
override Py::Call getNode() { result = super.getNode() }
override Call getNode() { result = super.getNode() }
predicate isDecoratorCall() {
this.isClassDecoratorCall()
@@ -301,11 +301,11 @@ class CallNode extends ControlFlowNode {
}
predicate isClassDecoratorCall() {
exists(Py::ClassExpr cls | this.getNode() = cls.getADecoratorCall())
exists(ClassExpr cls | this.getNode() = cls.getADecoratorCall())
}
predicate isFunctionDecoratorCall() {
exists(Py::FunctionExpr func | this.getNode() = func.getADecoratorCall())
exists(FunctionExpr func | this.getNode() = func.getADecoratorCall())
}
/** Gets the first tuple (*) argument of this call, if any. */
@@ -323,11 +323,11 @@ class CallNode extends ControlFlowNode {
/** A control flow corresponding to an attribute expression, such as `value.attr` */
class AttrNode extends ControlFlowNode {
AttrNode() { toAst(this) instanceof Py::Attribute }
AttrNode() { toAst(this) instanceof Attribute }
/** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node */
ControlFlowNode getObject() {
exists(Py::Attribute a |
exists(Attribute a |
this.getNode() = a and
a.getObject() = result.getNode() and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -339,7 +339,7 @@ class AttrNode extends ControlFlowNode {
* with the matching name
*/
ControlFlowNode getObject(string name) {
exists(Py::Attribute a |
exists(Attribute a |
this.getNode() = a and
a.getObject() = result.getNode() and
a.getName() = name and
@@ -348,57 +348,57 @@ class AttrNode extends ControlFlowNode {
}
/** Gets the attribute name of the attribute expression corresponding to this flow node */
string getName() { exists(Py::Attribute a | this.getNode() = a and a.getName() = result) }
string getName() { exists(Attribute a | this.getNode() = a and a.getName() = result) }
override Py::Attribute getNode() { result = super.getNode() }
override Attribute getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a `from ... import ...` expression */
class ImportMemberNode extends ControlFlowNode {
ImportMemberNode() { toAst(this) instanceof Py::ImportMember }
ImportMemberNode() { toAst(this) instanceof ImportMember }
/**
* Gets the flow node corresponding to the module in the import-member expression corresponding to this flow node,
* with the matching name
*/
ControlFlowNode getModule(string name) {
exists(Py::ImportMember i | this.getNode() = i and i.getModule() = result.getNode() |
exists(ImportMember i | this.getNode() = i and i.getModule() = result.getNode() |
i.getName() = name and
result.getBasicBlock().dominates(this.getBasicBlock())
)
}
override Py::ImportMember getNode() { result = super.getNode() }
override ImportMember getNode() { result = super.getNode() }
}
/** A control flow node corresponding to an artificial expression representing an import */
class ImportExprNode extends ControlFlowNode {
ImportExprNode() { toAst(this) instanceof Py::ImportExpr }
ImportExprNode() { toAst(this) instanceof ImportExpr }
override Py::ImportExpr getNode() { result = super.getNode() }
override ImportExpr getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a `from ... import *` statement */
class ImportStarNode extends ControlFlowNode {
ImportStarNode() { toAst(this) instanceof Py::ImportStar }
ImportStarNode() { toAst(this) instanceof ImportStar }
/** Gets the flow node corresponding to the module in the import-star corresponding to this flow node */
ControlFlowNode getModule() {
exists(Py::ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() |
exists(ImportStar i | this.getNode() = i and i.getModuleExpr() = result.getNode() |
result.getBasicBlock().dominates(this.getBasicBlock())
)
}
override Py::ImportStar getNode() { result = super.getNode() }
override ImportStar getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a subscript expression, such as `value[slice]` */
class SubscriptNode extends ControlFlowNode {
SubscriptNode() { toAst(this) instanceof Py::Subscript }
SubscriptNode() { toAst(this) instanceof Subscript }
/** flow node corresponding to the value of the sequence in a subscript operation */
ControlFlowNode getObject() {
exists(Py::Subscript s |
exists(Subscript s |
this.getNode() = s and
s.getObject() = result.getNode() and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -407,23 +407,23 @@ class SubscriptNode extends ControlFlowNode {
/** flow node corresponding to the index in a subscript operation */
ControlFlowNode getIndex() {
exists(Py::Subscript s |
exists(Subscript s |
this.getNode() = s and
s.getIndex() = result.getNode() and
result.getBasicBlock().dominates(this.getBasicBlock())
)
}
override Py::Subscript getNode() { result = super.getNode() }
override Subscript getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a comparison operation, such as `x<y` */
class CompareNode extends ControlFlowNode {
CompareNode() { toAst(this) instanceof Py::Compare }
CompareNode() { toAst(this) instanceof Compare }
/** Whether left and right are a pair of operands for this comparison */
predicate operands(ControlFlowNode left, Py::Cmpop op, ControlFlowNode right) {
exists(Py::Compare c, Py::Expr eleft, Py::Expr eright |
predicate operands(ControlFlowNode left, Cmpop op, ControlFlowNode right) {
exists(Compare c, Expr eleft, Expr eright |
this.getNode() = c and left.getNode() = eleft and right.getNode() = eright
|
eleft = c.getLeft() and eright = c.getComparator(0) and op = c.getOp(0)
@@ -436,26 +436,26 @@ class CompareNode extends ControlFlowNode {
right.getBasicBlock().dominates(this.getBasicBlock())
}
override Py::Compare getNode() { result = super.getNode() }
override Compare getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a conditional expression such as, `body if test else orelse` */
class IfExprNode extends ControlFlowNode {
IfExprNode() { toAst(this) instanceof Py::IfExp }
IfExprNode() { toAst(this) instanceof IfExp }
/** flow node corresponding to one of the operands of an if-expression */
ControlFlowNode getAnOperand() { result = this.getAPredecessor() }
override Py::IfExp getNode() { result = super.getNode() }
override IfExp getNode() { result = super.getNode() }
}
/** A control flow node corresponding to an assignment expression such as `lhs := rhs`. */
class AssignmentExprNode extends ControlFlowNode {
AssignmentExprNode() { toAst(this) instanceof Py::AssignExpr }
AssignmentExprNode() { toAst(this) instanceof AssignExpr }
/** Gets the flow node corresponding to the left-hand side of the assignment expression */
ControlFlowNode getTarget() {
exists(Py::AssignExpr a |
exists(AssignExpr a |
this.getNode() = a and
a.getTarget() = result.getNode() and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -464,27 +464,27 @@ class AssignmentExprNode extends ControlFlowNode {
/** Gets the flow node corresponding to the right-hand side of the assignment expression */
ControlFlowNode getValue() {
exists(Py::AssignExpr a |
exists(AssignExpr a |
this.getNode() = a and
a.getValue() = result.getNode() and
result.getBasicBlock().dominates(this.getBasicBlock())
)
}
override Py::AssignExpr getNode() { result = super.getNode() }
override AssignExpr getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a binary expression, such as `x + y` */
class BinaryExprNode extends ControlFlowNode {
BinaryExprNode() { toAst(this) instanceof Py::BinaryExpr }
BinaryExprNode() { toAst(this) instanceof BinaryExpr }
/** flow node corresponding to one of the operands of a binary expression */
ControlFlowNode getAnOperand() { result = this.getLeft() or result = this.getRight() }
override Py::BinaryExpr getNode() { result = super.getNode() }
override BinaryExpr getNode() { result = super.getNode() }
ControlFlowNode getLeft() {
exists(Py::BinaryExpr b |
exists(BinaryExpr b |
this.getNode() = b and
result.getNode() = b.getLeft() and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -492,7 +492,7 @@ class BinaryExprNode extends ControlFlowNode {
}
ControlFlowNode getRight() {
exists(Py::BinaryExpr b |
exists(BinaryExpr b |
this.getNode() = b and
result.getNode() = b.getRight() and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -500,11 +500,11 @@ class BinaryExprNode extends ControlFlowNode {
}
/** Gets the operator of this binary expression node. */
Py::Operator getOp() { result = this.getNode().getOp() }
Operator getOp() { result = this.getNode().getOp() }
/** Whether left and right are a pair of operands for this binary expression */
predicate operands(ControlFlowNode left, Py::Operator op, ControlFlowNode right) {
exists(Py::BinaryExpr b, Py::Expr eleft, Py::Expr eright |
predicate operands(ControlFlowNode left, Operator op, ControlFlowNode right) {
exists(BinaryExpr b, Expr eleft, Expr eright |
this.getNode() = b and left.getNode() = eleft and right.getNode() = eright
|
eleft = b.getLeft() and eright = b.getRight() and op = b.getOp()
@@ -516,20 +516,20 @@ class BinaryExprNode extends ControlFlowNode {
/** A control flow node corresponding to a boolean shortcut (and/or) operation */
class BoolExprNode extends ControlFlowNode {
BoolExprNode() { toAst(this) instanceof Py::BoolExpr }
BoolExprNode() { toAst(this) instanceof BoolExpr }
/** flow node corresponding to one of the operands of a boolean expression */
ControlFlowNode getAnOperand() {
exists(Py::BoolExpr b | this.getNode() = b and result.getNode() = b.getAValue()) and
exists(BoolExpr b | this.getNode() = b and result.getNode() = b.getAValue()) and
this.getBasicBlock().dominates(result.getBasicBlock())
}
override Py::BoolExpr getNode() { result = super.getNode() }
override BoolExpr getNode() { result = super.getNode() }
}
/** A control flow node corresponding to a unary expression: (`+x`), (`-x`) or (`~x`) */
class UnaryExprNode extends ControlFlowNode {
UnaryExprNode() { toAst(this) instanceof Py::UnaryExpr }
UnaryExprNode() { toAst(this) instanceof UnaryExpr }
/**
* Gets flow node corresponding to the operand of a unary expression.
@@ -540,7 +540,7 @@ class UnaryExprNode extends ControlFlowNode {
*/
ControlFlowNode getOperand() { result = this.getAPredecessor() }
override Py::UnaryExpr getNode() { result = super.getNode() }
override UnaryExpr getNode() { result = super.getNode() }
override ControlFlowNode getAChild() { result = this.getAPredecessor() }
}
@@ -555,27 +555,27 @@ class DefinitionNode extends ControlFlowNode {
cached
DefinitionNode() {
Stages::AST::ref() and
exists(Py::Assign a | this.getNode() = a.getATarget())
exists(Assign a | a.getATarget().getAFlowNode() = this)
or
exists(Py::AssignExpr a | this.getNode() = a.getTarget())
exists(AssignExpr a | a.getTarget().getAFlowNode() = this)
or
exists(Py::AnnAssign a | this.getNode() = a.getTarget() and exists(a.getValue()))
exists(AnnAssign a | a.getTarget().getAFlowNode() = this and exists(a.getValue()))
or
exists(Py::Alias a | this.getNode() = a.getAsname())
exists(Alias a | a.getAsname().getAFlowNode() = this)
or
augstore(_, this)
or
// `x, y = 1, 2` where LHS is a combination of list or tuples
exists(Py::Assign a | this.getNode() = list_or_tuple_nested_element(a.getATarget()))
exists(Assign a | list_or_tuple_nested_element(a.getATarget()).getAFlowNode() = this)
or
exists(Py::For for | this.getNode() = for.getTarget())
exists(For for | for.getTarget().getAFlowNode() = this)
or
exists(Py::Parameter param | this.getNode() = param.asName() and exists(param.getDefault()))
exists(Parameter param | this = param.asName().getAFlowNode() and exists(param.getDefault()))
}
/** flow node corresponding to the value assigned for the definition corresponding to this flow node */
ControlFlowNode getValue() {
result.getNode() = assigned_value(this.getNode()) and
result = assigned_value(this.getNode()).getAFlowNode() and
(
result.getBasicBlock().dominates(this.getBasicBlock())
or
@@ -584,16 +584,16 @@ class DefinitionNode extends ControlFlowNode {
// since the default value for a parameter is evaluated in the same basic block as
// the function definition, but the parameter belongs to the basic block of the function,
// there is no dominance relationship between the two.
exists(Py::Parameter param | this.getNode() = param.asName())
exists(Parameter param | this = param.asName().getAFlowNode())
)
}
}
private Py::Expr list_or_tuple_nested_element(Py::Expr list_or_tuple) {
exists(Py::Expr elt |
elt = list_or_tuple.(Py::Tuple).getAnElt()
private Expr list_or_tuple_nested_element(Expr list_or_tuple) {
exists(Expr elt |
elt = list_or_tuple.(Tuple).getAnElt()
or
elt = list_or_tuple.(Py::List).getAnElt()
elt = list_or_tuple.(List).getAnElt()
|
result = elt
or
@@ -603,12 +603,12 @@ private Py::Expr list_or_tuple_nested_element(Py::Expr list_or_tuple) {
/**
* A control flow node corresponding to a deletion statement, such as `del x`.
* There can be multiple `DeletionNode`s for each `Py::Delete` such that each
* There can be multiple `DeletionNode`s for each `Delete` such that each
* target has own `DeletionNode`. The CFG for `del a, x.y` looks like:
* `NameNode('a') -> DeletionNode -> NameNode('b') -> AttrNode('y') -> DeletionNode`.
*/
class DeletionNode extends ControlFlowNode {
DeletionNode() { toAst(this) instanceof Py::Delete }
DeletionNode() { toAst(this) instanceof Delete }
/** Gets the unique target of this deletion node. */
ControlFlowNode getTarget() { result.getASuccessor() = this }
@@ -617,9 +617,9 @@ class DeletionNode extends ControlFlowNode {
/** A control flow node corresponding to a sequence (tuple or list) literal */
abstract class SequenceNode extends ControlFlowNode {
SequenceNode() {
toAst(this) instanceof Py::Tuple
toAst(this) instanceof Tuple
or
toAst(this) instanceof Py::List
toAst(this) instanceof List
}
/** Gets the control flow node for an element of this sequence */
@@ -632,11 +632,11 @@ abstract class SequenceNode extends ControlFlowNode {
/** A control flow node corresponding to a tuple expression such as `( 1, 3, 5, 7, 9 )` */
class TupleNode extends SequenceNode {
TupleNode() { toAst(this) instanceof Py::Tuple }
TupleNode() { toAst(this) instanceof Tuple }
override ControlFlowNode getElement(int n) {
Stages::AST::ref() and
exists(Py::Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and
exists(Tuple t | this.getNode() = t and result.getNode() = t.getElt(n)) and
(
result.getBasicBlock().dominates(this.getBasicBlock())
or
@@ -647,10 +647,10 @@ class TupleNode extends SequenceNode {
/** A control flow node corresponding to a list expression, such as `[ 1, 3, 5, 7, 9 ]` */
class ListNode extends SequenceNode {
ListNode() { toAst(this) instanceof Py::List }
ListNode() { toAst(this) instanceof List }
override ControlFlowNode getElement(int n) {
exists(Py::List l | this.getNode() = l and result.getNode() = l.getElt(n)) and
exists(List l | this.getNode() = l and result.getNode() = l.getElt(n)) and
(
result.getBasicBlock().dominates(this.getBasicBlock())
or
@@ -661,10 +661,10 @@ class ListNode extends SequenceNode {
/** A control flow node corresponding to a set expression, such as `{ 1, 3, 5, 7, 9 }` */
class SetNode extends ControlFlowNode {
SetNode() { toAst(this) instanceof Py::Set }
SetNode() { toAst(this) instanceof Set }
ControlFlowNode getAnElement() {
exists(Py::Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and
exists(Set s | this.getNode() = s and result.getNode() = s.getElt(_)) and
(
result.getBasicBlock().dominates(this.getBasicBlock())
or
@@ -675,20 +675,20 @@ class SetNode extends ControlFlowNode {
/** A control flow node corresponding to a dictionary literal, such as `{ 'a': 1, 'b': 2 }` */
class DictNode extends ControlFlowNode {
DictNode() { toAst(this) instanceof Py::Dict }
DictNode() { toAst(this) instanceof Dict }
/**
* Gets a key of this dictionary literal node, for those items that have keys
* E.g, in {'a':1, **b} this returns only 'a'
*/
ControlFlowNode getAKey() {
exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and
exists(Dict d | this.getNode() = d and result.getNode() = d.getAKey()) and
result.getBasicBlock().dominates(this.getBasicBlock())
}
/** Gets a value of this dictionary literal node */
ControlFlowNode getAValue() {
exists(Py::Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and
exists(Dict d | this.getNode() = d and result.getNode() = d.getAValue()) and
result.getBasicBlock().dominates(this.getBasicBlock())
}
}
@@ -712,23 +712,21 @@ class IterableNode extends ControlFlowNode {
}
}
private Py::AstNode assigned_value(Py::Expr lhs) {
private AstNode assigned_value(Expr lhs) {
/* lhs = result */
exists(Py::Assign a | a.getATarget() = lhs and result = a.getValue())
exists(Assign a | a.getATarget() = lhs and result = a.getValue())
or
/* lhs := result */
exists(Py::AssignExpr a | a.getTarget() = lhs and result = a.getValue())
exists(AssignExpr a | a.getTarget() = lhs and result = a.getValue())
or
/* lhs : annotation = result */
exists(Py::AnnAssign a | a.getTarget() = lhs and result = a.getValue())
exists(AnnAssign a | a.getTarget() = lhs and result = a.getValue())
or
/* import result as lhs */
exists(Py::Alias a | a.getAsname() = lhs and result = a.getValue())
exists(Alias a | a.getAsname() = lhs and result = a.getValue())
or
/* lhs += x => result = (lhs + x) */
exists(Py::AugAssign a, Py::BinaryExpr b |
b = a.getOperation() and result = b and lhs = b.getLeft()
)
exists(AugAssign a, BinaryExpr b | b = a.getOperation() and result = b and lhs = b.getLeft())
or
/*
* ..., lhs, ... = ..., result, ...
@@ -736,31 +734,31 @@ private Py::AstNode assigned_value(Py::Expr lhs) {
* ..., (..., lhs, ...), ... = ..., (..., result, ...), ...
*/
exists(Py::Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result))
exists(Assign a | nested_sequence_assign(a.getATarget(), a.getValue(), lhs, result))
or
/* for lhs in seq: => `result` is the `for` node, representing the `iter(next(seq))` operation. */
result.(Py::For).getTarget() = lhs
result.(For).getTarget() = lhs
or
exists(Py::Parameter param | lhs = param.asName() and result = param.getDefault())
exists(Parameter param | lhs = param.asName() and result = param.getDefault())
}
predicate nested_sequence_assign(
Py::Expr left_parent, Py::Expr right_parent, Py::Expr left_result, Py::Expr right_result
Expr left_parent, Expr right_parent, Expr left_result, Expr right_result
) {
exists(Py::Assign a |
exists(Assign a |
a.getATarget().getASubExpression*() = left_parent and
a.getValue().getASubExpression*() = right_parent
) and
exists(int i, Py::Expr left_elem, Py::Expr right_elem |
exists(int i, Expr left_elem, Expr right_elem |
(
left_elem = left_parent.(Py::Tuple).getElt(i)
left_elem = left_parent.(Tuple).getElt(i)
or
left_elem = left_parent.(Py::List).getElt(i)
left_elem = left_parent.(List).getElt(i)
) and
(
right_elem = right_parent.(Py::Tuple).getElt(i)
right_elem = right_parent.(Tuple).getElt(i)
or
right_elem = right_parent.(Py::List).getElt(i)
right_elem = right_parent.(List).getElt(i)
)
|
left_result = left_elem and right_result = right_elem
@@ -771,9 +769,9 @@ predicate nested_sequence_assign(
/** A flow node for a `for` statement. */
class ForNode extends ControlFlowNode {
ForNode() { toAst(this) instanceof Py::For }
ForNode() { toAst(this) instanceof For }
override Py::For getNode() { result = super.getNode() }
override For getNode() { result = super.getNode() }
/** Holds if this `for` statement causes iteration over `sequence` storing each step of the iteration in `target` */
predicate iterates(ControlFlowNode target, ControlFlowNode sequence) {
@@ -784,7 +782,7 @@ class ForNode extends ControlFlowNode {
/** Gets the sequence node for this `for` statement. */
ControlFlowNode getSequence() {
exists(Py::For for |
exists(For for |
toAst(this) = for and
for.getIter() = result.getNode()
|
@@ -794,7 +792,7 @@ class ForNode extends ControlFlowNode {
/** A possible `target` for this `for` statement, not accounting for loop unrolling */
private ControlFlowNode possibleTarget() {
exists(Py::For for |
exists(For for |
toAst(this) = for and
for.getTarget() = result.getNode() and
this.getBasicBlock().dominates(result.getBasicBlock())
@@ -811,11 +809,11 @@ class ForNode extends ControlFlowNode {
/** A flow node for a `raise` statement */
class RaiseStmtNode extends ControlFlowNode {
RaiseStmtNode() { toAst(this) instanceof Py::Raise }
RaiseStmtNode() { toAst(this) instanceof Raise }
/** Gets the control flow node for the exception raised by this raise statement */
ControlFlowNode getException() {
exists(Py::Raise r |
exists(Raise r |
r = toAst(this) and
r.getException() = toAst(result) and
result.getBasicBlock().dominates(this.getBasicBlock())
@@ -829,36 +827,36 @@ class RaiseStmtNode extends ControlFlowNode {
*/
class NameNode extends ControlFlowNode {
NameNode() {
exists(Py::Name n | py_flow_bb_node(this, n, _, _))
exists(Name n | py_flow_bb_node(this, n, _, _))
or
exists(Py::PlaceHolder p | py_flow_bb_node(this, p, _, _))
exists(PlaceHolder p | py_flow_bb_node(this, p, _, _))
}
/** Whether this flow node defines the variable `v`. */
predicate defines(Py::Variable v) {
exists(Py::Name d | this.getNode() = d and d.defines(v)) and
predicate defines(Variable v) {
exists(Name d | this.getNode() = d and d.defines(v)) and
not this.isLoad()
}
/** Whether this flow node deletes the variable `v`. */
predicate deletes(Py::Variable v) { exists(Py::Name d | this.getNode() = d and d.deletes(v)) }
predicate deletes(Variable v) { exists(Name d | this.getNode() = d and d.deletes(v)) }
/** Whether this flow node uses the variable `v`. */
predicate uses(Py::Variable v) {
predicate uses(Variable v) {
this.isLoad() and
exists(Py::Name u | this.getNode() = u and u.uses(v))
exists(Name u | this.getNode() = u and u.uses(v))
or
exists(Py::PlaceHolder u |
this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Py::Load
exists(PlaceHolder u |
this.getNode() = u and u.getVariable() = v and u.getCtx() instanceof Load
)
or
Scopes::use_of_global_variable(this, v.getScope(), v.getId())
}
string getId() {
result = this.getNode().(Py::Name).getId()
result = this.getNode().(Name).getId()
or
result = this.getNode().(Py::PlaceHolder).getId()
result = this.getNode().(PlaceHolder).getId()
}
/** Whether this is a use of a local variable. */
@@ -870,84 +868,82 @@ class NameNode extends ControlFlowNode {
/** Whether this is a use of a global (including builtin) variable. */
predicate isGlobal() { Scopes::use_of_global_variable(this, _, _) }
predicate isSelf() {
exists(Py::SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this)
}
predicate isSelf() { exists(SsaVariable selfvar | selfvar.isSelf() and selfvar.getAUse() = this) }
}
/** A control flow node corresponding to a named constant, one of `None`, `True` or `False`. */
class NameConstantNode extends NameNode {
NameConstantNode() { exists(Py::NameConstant n | py_flow_bb_node(this, n, _, _)) }
NameConstantNode() { exists(NameConstant n | py_flow_bb_node(this, n, _, _)) }
/*
* We ought to override uses as well, but that has
* a serious performance impact.
* deprecated predicate uses(Py::Variable v) { none() }
* deprecated predicate uses(Variable v) { none() }
*/
}
/** A control flow node corresponding to a starred expression, `*a`. */
class StarredNode extends ControlFlowNode {
StarredNode() { toAst(this) instanceof Py::Starred }
StarredNode() { toAst(this) instanceof Starred }
ControlFlowNode getValue() { toAst(result) = toAst(this).(Py::Starred).getValue() }
ControlFlowNode getValue() { toAst(result) = toAst(this).(Starred).getValue() }
}
/** The ControlFlowNode for an 'except' statement. */
class ExceptFlowNode extends ControlFlowNode {
ExceptFlowNode() { this.getNode() instanceof Py::ExceptStmt }
ExceptFlowNode() { this.getNode() instanceof ExceptStmt }
/**
* Gets the type handled by this exception handler.
* `Py::ExceptionType` in `except Py::ExceptionType as e:`
* `ExceptionType` in `except ExceptionType as e:`
*/
ControlFlowNode getType() {
exists(Py::ExceptStmt ex |
exists(ExceptStmt ex |
this.getBasicBlock().dominates(result.getBasicBlock()) and
ex = this.getNode() and
result.getNode() = ex.getType()
result = ex.getType().getAFlowNode()
)
}
/**
* Gets the name assigned to the handled exception, if any.
* `e` in `except Py::ExceptionType as e:`
* `e` in `except ExceptionType as e:`
*/
ControlFlowNode getName() {
exists(Py::ExceptStmt ex |
exists(ExceptStmt ex |
this.getBasicBlock().dominates(result.getBasicBlock()) and
ex = this.getNode() and
result.getNode() = ex.getName()
result = ex.getName().getAFlowNode()
)
}
}
/** The ControlFlowNode for an 'except*' statement. */
class ExceptGroupFlowNode extends ControlFlowNode {
ExceptGroupFlowNode() { this.getNode() instanceof Py::ExceptGroupStmt }
ExceptGroupFlowNode() { this.getNode() instanceof ExceptGroupStmt }
/**
* Gets the type handled by this exception handler.
* `Py::ExceptionType` in `except* Py::ExceptionType as e:`
* `ExceptionType` in `except* ExceptionType as e:`
*/
ControlFlowNode getType() {
this.getBasicBlock().dominates(result.getBasicBlock()) and
result.getNode() = this.getNode().(Py::ExceptGroupStmt).getType()
result = this.getNode().(ExceptGroupStmt).getType().getAFlowNode()
}
/**
* Gets the name assigned to the handled exception, if any.
* `e` in `except* Py::ExceptionType as e:`
* `e` in `except* ExceptionType as e:`
*/
ControlFlowNode getName() {
this.getBasicBlock().dominates(result.getBasicBlock()) and
result.getNode() = this.getNode().(Py::ExceptGroupStmt).getName()
result = this.getNode().(ExceptGroupStmt).getName().getAFlowNode()
}
}
private module Scopes {
private predicate fast_local(NameNode n) {
exists(Py::FastLocalVariable v |
exists(FastLocalVariable v |
n.uses(v) and
v.getScope() = n.getScope()
)
@@ -956,15 +952,15 @@ private module Scopes {
predicate local(NameNode n) {
fast_local(n)
or
exists(Py::SsaVariable var |
exists(SsaVariable var |
var.getAUse() = n and
n.getScope() instanceof Py::Class and
n.getScope() instanceof Class and
exists(var.getDefinition())
)
}
predicate non_local(NameNode n) {
exists(Py::FastLocalVariable flv |
exists(FastLocalVariable flv |
flv.getALoad() = n.getNode() and
not flv.getScope() = n.getScope()
)
@@ -972,20 +968,20 @@ private module Scopes {
// magic is fine, but we get questionable join-ordering of it
pragma[nomagic]
predicate use_of_global_variable(NameNode n, Py::Module scope, string name) {
predicate use_of_global_variable(NameNode n, Module scope, string name) {
n.isLoad() and
not non_local(n) and
not exists(Py::SsaVariable var | var.getAUse() = n |
var.getVariable() instanceof Py::FastLocalVariable
not exists(SsaVariable var | var.getAUse() = n |
var.getVariable() instanceof FastLocalVariable
or
n.getScope() instanceof Py::Class and
n.getScope() instanceof Class and
not maybe_undefined(var)
) and
name = n.getId() and
scope = n.getEnclosingModule()
}
private predicate maybe_undefined(Py::SsaVariable var) {
private predicate maybe_undefined(SsaVariable var) {
not exists(var.getDefinition()) and not py_ssa_phi(var, _)
or
var.getDefinition().isDelete()
@@ -1062,13 +1058,13 @@ class BasicBlock extends @py_flow_node {
private predicate oneNodeBlock() { this.firstNode() = this.getLastNode() }
private predicate startLocationInfo(string file, int line, int col) {
if this.firstNode().getNode() instanceof Py::Scope
if this.firstNode().getNode() instanceof Scope
then this.firstNode().getASuccessor().getLocation().hasLocationInfo(file, line, col, _, _)
else this.firstNode().getLocation().hasLocationInfo(file, line, col, _, _)
}
private predicate endLocationInfo(int endl, int endc) {
if this.getLastNode().getNode() instanceof Py::Scope and not this.oneNodeBlock()
if this.getLastNode().getNode() instanceof Scope and not this.oneNodeBlock()
then this.getLastNode().getAPredecessor().getLocation().hasLocationInfo(_, _, _, endl, endc)
else this.getLastNode().getLocation().hasLocationInfo(_, _, _, endl, endc)
}
@@ -1085,7 +1081,7 @@ class BasicBlock extends @py_flow_node {
/** Whether flow from this basic block reaches a normal exit from its scope */
predicate reachesExit() {
exists(Py::Scope s | s.getANormalExit().getBasicBlock() = this)
exists(Scope s | s.getANormalExit().getBasicBlock() = this)
or
this.getASuccessor().reachesExit()
}
@@ -1126,7 +1122,7 @@ class BasicBlock extends @py_flow_node {
/** Gets the scope of this block */
pragma[nomagic]
Py::Scope getScope() {
Scope getScope() {
exists(ControlFlowNode n | n.getBasicBlock() = this |
/* Take care not to use an entry or exit node as that node's scope will be the outer scope */
not py_scope_flow(n, _, -1) and
@@ -1149,17 +1145,17 @@ class BasicBlock extends @py_flow_node {
predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) }
/**
* Gets the `Py::ConditionBlock`, if any, that controls this block and
* does not control any other `Py::ConditionBlock`s that control this block.
* That is the `Py::ConditionBlock` that is closest dominator.
* Gets the `ConditionBlock`, if any, that controls this block and
* does not control any other `ConditionBlock`s that control this block.
* That is the `ConditionBlock` that is closest dominator.
*/
Py::ConditionBlock getImmediatelyControllingBlock() {
ConditionBlock getImmediatelyControllingBlock() {
result = this.nonControllingImmediateDominator*().getImmediateDominator()
}
private BasicBlock nonControllingImmediateDominator() {
result = this.getImmediateDominator() and
not result.(Py::ConditionBlock).controls(this, _)
not result.(ConditionBlock).controls(this, _)
}
/**
@@ -1179,7 +1175,7 @@ private class ControlFlowNodeAlias = ControlFlowNode;
final private class FinalBasicBlock = BasicBlock;
module Cfg implements BB::CfgSig<Py::Location> {
module Cfg implements BB::CfgSig<Location> {
private import codeql.controlflow.SuccessorType
class ControlFlowNode = ControlFlowNodeAlias;
@@ -1190,7 +1186,7 @@ module Cfg implements BB::CfgSig<Py::Location> {
// Using the location of the first node is simple
// and we just need a way to identify the basic block
// during debugging, so this will be serviceable.
Py::Location getLocation() { result = super.getNode(0).getLocation() }
Location getLocation() { result = super.getNode(0).getLocation() }
int length() { result = count(int i | exists(this.getNode(i))) }

View File

@@ -153,16 +153,8 @@ class Function extends Function_, Scope, AstNode {
override predicate contains(AstNode inner) { Scope.super.contains(inner) }
/**
* DEPRECATED: bind a `Return` node explicitly instead, e.g.
* `exists(Return ret | ret.getScope() = this and n.getNode() = ret.getValue())`.
* This API is being phased out together with `AstNode.getAFlowNode()` to
* untangle the AST and CFG hierarchies in preparation for migrating the
* dataflow library off the legacy CFG.
*
* Gets a control flow node for a return value of this function.
*/
deprecated ControlFlowNode getAReturnValueFlowNode() {
/** Gets a control flow node for a return value of this function */
ControlFlowNode getAReturnValueFlowNode() {
exists(Return ret |
ret.getScope() = this and
ret.getValue() = result.getNode()

View File

@@ -162,6 +162,8 @@ class ImportMember extends ImportMember_ {
string getImportedModuleName() {
result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName()
}
override ImportMemberNode getAFlowNode() { result = super.getAFlowNode() }
}
/** An import statement */

View File

@@ -46,23 +46,20 @@ class SelfAttributeRead extends SelfAttribute {
}
predicate guardedByHasattr() {
exists(Variable var, ControlFlowNode n, ControlFlowNode this_, ControlFlowNode obj_ |
this_.getNode() = this and obj_.getNode() = this.getObject()
|
var.getAUse() = obj_ and
exists(Variable var, ControlFlowNode n |
var.getAUse() = this.getObject().getAFlowNode() and
hasattr(n, var.getAUse(), this.getName()) and
n.strictlyDominates(this_)
n.strictlyDominates(this.getAFlowNode())
)
}
pragma[noinline]
predicate locallyDefined() {
exists(SelfAttributeStore store, ControlFlowNode store_, ControlFlowNode this_ |
store_.getNode() = store and this_.getNode() = this
|
exists(SelfAttributeStore store |
this.getName() = store.getName() and
this.getScope() = store.getScope() and
store_.strictlyDominates(this_)
this.getScope() = store.getScope()
|
store.getAFlowNode().strictlyDominates(this.getAFlowNode())
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,30 +5,24 @@ private import semmle.python.dataflow.new.DataFlow
private predicate constCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
exists(CompareNode cn | cn = g |
exists(ImmutableLiteral const, Cmpop op, ControlFlowNode c |
c.getNode() = const and
(
op = any(Eq eq) and branch = true
or
op = any(NotEq ne) and branch = false
)
|
cn.operands(c, op, node)
exists(ImmutableLiteral const, Cmpop op |
op = any(Eq eq) and branch = true
or
cn.operands(node, op, c)
op = any(NotEq ne) and branch = false
|
cn.operands(const.getAFlowNode(), op, node)
or
cn.operands(node, op, const.getAFlowNode())
)
or
exists(NameConstant const, Cmpop op, ControlFlowNode c |
c.getNode() = const and
(
op = any(Is is_) and branch = true
or
op = any(IsNot isn) and branch = false
)
|
cn.operands(c, op, node)
exists(NameConstant const, Cmpop op |
op = any(Is is_) and branch = true
or
cn.operands(node, op, c)
op = any(IsNot isn) and branch = false
|
cn.operands(const.getAFlowNode(), op, node)
or
cn.operands(node, op, const.getAFlowNode())
)
or
exists(IterableNode const_iterable, Cmpop op |

View File

@@ -228,7 +228,7 @@ private class ClassDefinitionAsAttrWrite extends AttrWrite, CfgNode {
override Node getValue() { result.asCfgNode() = node.getValue() }
override Node getObject() { result.asCfgNode().getNode() = cls }
override Node getObject() { result.asCfgNode() = cls.getAFlowNode() }
override ExprNode getAttributeNameExpr() { none() }

View File

@@ -256,12 +256,9 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
*/
overlay[local]
predicate isStaticmethod(Function func) {
// The decorator is *syntactically* a `Name` "staticmethod" — we don't
// care which variable it resolves to. `staticmethod` is a builtin and
// is almost never shadowed in a module-level scope; even if a class
// redefines `staticmethod` in its body, the class body has not started
// executing yet at the decorator position, so Python uses the builtin.
func.getADecorator().(Name).getId() = "staticmethod"
exists(NameNode id | id.getId() = "staticmethod" and id.isGlobal() |
func.getADecorator() = id.getNode()
)
}
/**
@@ -271,9 +268,9 @@ predicate isStaticmethod(Function func) {
*/
overlay[local]
predicate isClassmethod(Function func) {
// See `isStaticmethod` for the rationale for matching on the AST `Name`
// rather than going via the CFG and `isGlobal()`.
func.getADecorator().(Name).getId() = "classmethod"
exists(NameNode id | id.getId() = "classmethod" and id.isGlobal() |
func.getADecorator() = id.getNode()
)
or
exists(Class cls |
cls.getAMethod() = func and
@@ -288,8 +285,9 @@ predicate isClassmethod(Function func) {
/** Holds if the function `func` has a `property` decorator. */
overlay[local]
predicate hasPropertyDecorator(Function func) {
// See `isStaticmethod` for the rationale for matching on the AST `Name`.
func.getADecorator().(Name).getId() = "property"
exists(NameNode id | id.getId() = "property" and id.isGlobal() |
func.getADecorator() = id.getNode()
)
}
/**
@@ -1913,8 +1911,8 @@ abstract class ReturnNode extends Node {
class ExtractedReturnNode extends ReturnNode, CfgNode {
// See `TaintTrackingImplementation::returnFlowStep`
ExtractedReturnNode() {
node.getNode() = any(Return ret).getValue() or
node.getNode() = any(Yield yield)
node = any(Return ret).getValue().getAFlowNode() or
node = any(Yield yield).getAFlowNode()
}
override ReturnKind getKind() { any() }
@@ -1932,7 +1930,7 @@ class ExtractedReturnNode extends ReturnNode, CfgNode {
class YieldNodeInContextManagerFunction extends ReturnNode, CfgNode {
YieldNodeInContextManagerFunction() {
hasContextmanagerDecorator(node.getScope()) and
node.getNode() = any(Yield yield).getValue()
node = any(Yield yield).getValue().getAFlowNode()
}
override ReturnKind getKind() { any() }

View File

@@ -185,8 +185,8 @@ private predicate synthDictSplatArgumentNodeStoreStep(
*/
predicate yieldStoreStep(Node nodeFrom, Content c, Node nodeTo) {
exists(Yield yield |
nodeTo.asCfgNode().getNode() = yield and
nodeFrom.asCfgNode().getNode() = yield.getValue() and
nodeTo.asCfgNode() = yield.getAFlowNode() and
nodeFrom.asCfgNode() = yield.getValue().getAFlowNode() and
// TODO: Consider if this will also need to transfer dictionary content
// once dictionary comprehensions are supported.
c instanceof ListElementContent

View File

@@ -485,7 +485,7 @@ class ModuleVariableNode extends Node, TModuleVariableNode {
/** Gets a node that reads this variable, excluding reads that happen through `from ... import *`. */
Node getALocalRead() {
result.asCfgNode().getNode() = var.getALoad() and
result.asCfgNode() = var.getALoad().getAFlowNode() and
not result.getScope() = mod
}

View File

@@ -9,19 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.ImportStar
private import semmle.python.dataflow.new.TypeTracking
private import semmle.python.dataflow.new.internal.DataFlowPrivate
/**
* Holds if `init` is a package's `__init__.py` and `var` is a global variable in
* `init` whose name matches a submodule of the package.
*
* Inlined from `SsaSource::init_module_submodule_defn` to avoid pulling
* `semmle.python.essa.SsaDefinitions` into the new dataflow stack.
*/
private predicate initModuleSubmoduleDefn(GlobalVariable var, Module init) {
init.isPackageInit() and
exists(init.getPackage().getSubModule(var.getId())) and
var.getScope() = init
}
private import semmle.python.essa.SsaDefinitions
/**
* Python modules and the way imports are resolved are... complicated. Here's a crash course in how
@@ -338,7 +326,7 @@ module ImportResolution {
// imported yet.
exists(string submodule, Module package, EssaVariable var |
submodule = var.getName() and
initModuleSubmoduleDefn(var.getSourceVariable(), package) and
SsaSource::init_module_submodule_defn(var.getSourceVariable(), package.getEntryNode()) and
m = getModuleFromName(package.getPackageName() + "." + submodule) and
result.asCfgNode() = var.getDefinition().(EssaNodeDefinition).getDefiningNode()
)

View File

@@ -1,547 +0,0 @@
/**
* Provides the Python SSA implementation built on the new (shared) CFG.
*
* Mirrors the Java SSA adapter at
* `java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll`:
* an `InputSig` is defined in terms of positional `(BasicBlock, int)`
* variable references, and the shared
* `codeql.ssa.Ssa::Make<Location, Cfg, Input>` module is then
* instantiated.
*
* `SourceVariable` is the AST-level `Py::Variable`. Variable references
* are looked up via the CFG facade's `NameNode.defines`/`uses`/`deletes`
* predicates, which themselves are one-line bridges to AST-level
* `Name.defines`/`uses`/`deletes`.
*
* Implicit-entry definitions are inserted for:
* - non-local / global / builtin variables that are read in the scope
* but never assigned (no enclosing CFG node defines them),
* - captured variables (variables defined in an enclosing scope that
* are read inside the scope), and
* - parameters, but only if the corresponding parameter name is *not*
* itself a CFG node. With the C#-style parameter wiring already
* installed in `AstNodeImpl.qll`, parameter names *are* CFG nodes,
* so the regular `variableWrite` path handles them — no `i = -1`
* entry is needed for ordinary parameters.
*/
overlay[local?]
module;
private import python as Py
private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl
private import semmle.python.controlflow.internal.Cfg as Cfg
private import codeql.ssa.Ssa as SsaImplCommon
private import codeql.controlflow.BasicBlock as BB
/**
* Adapts the Python `Cfg` facade to the shared SSA library's `CfgSig`.
* All members are inherited from `Cfg::ControlFlowNode` and
* `Cfg::BasicBlock`.
*/
private module CfgForSsa implements BB::CfgSig<Py::Location> {
class ControlFlowNode = CfgImpl::ControlFlowNode;
class BasicBlock = CfgImpl::BasicBlock;
class EntryBasicBlock = CfgImpl::Cfg::EntryBasicBlock;
predicate dominatingEdge = CfgImpl::Cfg::dominatingEdge/2;
}
/**
* A source variable for SSA, wrapping a Python AST `Variable`.
*
* We only track variables that are read at least once in their scope —
* tracking write-only variables would be unnecessary work — *except*
* for module-scope globals, where the "read" can be external (e.g.
* `import mymodule; mymodule.x`). Such globals are tracked
* unconditionally so that import-resolution can find their defining
* write.
*/
private newtype TSsaSourceVariable =
TPyVar(Py::Variable v) {
// Has a use somewhere — read-relevant for SSA.
exists(Cfg::NameNode n | n.uses(v))
or
// Or has a deletion (treated as a write that destroys the value).
exists(Cfg::NameNode n | n.deletes(v))
or
// Or is a module-scope global written in this module — must be
// tracked even if never read locally, because importers may read
// it as an attribute on the module object.
v.getScope() instanceof Py::Module and
exists(Cfg::NameNode n | n.defines(v))
or
// Or is a parameter — parameters must always have a
// `ParameterDefinition` for dataflow argument-routing to work,
// even if the parameter is never read in its scope. Mirrors
// legacy ESSA's `ParameterDefinition` (which fired for every
// parameter binding regardless of liveness).
exists(Py::Parameter p | p.asName() = v.getAStore())
}
/**
* A source variable for SSA, wrapping a Python AST `Variable`.
*/
class SsaSourceVariable extends TSsaSourceVariable {
/** Gets the underlying Python AST variable. */
Py::Variable getVariable() { this = TPyVar(result) }
/** Gets the (textual) name of this variable. */
string getName() { result = this.getVariable().getId() }
/** Gets a textual representation of this source variable. */
string toString() { result = this.getVariable().toString() }
/** Gets the location of this source variable. */
Py::Location getLocation() { result = this.getVariable().getScope().getLocation() }
/** Gets the scope in which this variable lives. */
Py::Scope getScope() { result = this.getVariable().getScope() }
/**
* Gets a use of this variable as it appears in the source — a `NameNode`
* that loads or deletes the variable. Mirrors legacy
* `SsaSourceVariable.getASourceUse()`.
*/
Cfg::ControlFlowNode getASourceUse() {
exists(Cfg::NameNode n | result = n |
n.uses(this.getVariable()) or n.deletes(this.getVariable())
)
}
/**
* Gets an implicit use of this variable. The new SSA does not have
* implicit-use refinements, but we keep this for API parity — every
* normal-exit of the variable's scope counts as a sink, ensuring
* variables stay live to scope exit for taint-tracking.
*/
Cfg::ControlFlowNode getAnImplicitUse() {
result.isNormalExit() and result.getScope() = this.getScope()
}
/**
* Gets a use of this variable — either an explicit source use or an
* implicit use at scope exit. Mirrors legacy `SsaSourceVariable.getAUse()`.
*/
Cfg::ControlFlowNode getAUse() {
result = this.getASourceUse() or result = this.getAnImplicitUse()
}
}
/**
* Holds if `v` is a non-local read in scope `s`, in the sense that `s`
* uses `v` but does not write it within `s`. This includes globals,
* builtins, and variables captured from an enclosing function scope.
*
* The `Py::Variable` `v` lives in some defining scope (the module for
* globals, an outer function for closures, etc.); the reading scope
* `s` is the scope where the use of `v` occurs.
*/
private predicate nonLocalReadIn(Py::Variable v, Py::Scope s) {
exists(Cfg::NameNode n |
n.uses(v) and
n.getScope() = s and
not exists(Cfg::NameNode def | def.defines(v) and def.getScope() = s)
) and
// Match legacy ESSA: only create entry defs for variables that have
// at least one defining store somewhere — otherwise the entry def
// represents "nothing reaches here", which is the default anyway and
// introduces no useful flow. (Legacy's `ModuleVariable` required a
// store; this is the closure-aware generalisation.)
exists(Cfg::NameNode store | store.defines(v))
}
/**
* Holds if `bb` is the entry basic block of a scope where `v` should
* have an implicit entry definition. This covers:
* - non-local / global / builtin variables read in `s`, and
* - captured variables (defined in an enclosing scope but read in `s`).
*
* Each reading scope gets its own entry def, so a closure variable can
* have multiple entry defs across all functions/methods that read it.
*
* Parameters are *not* included: their bound `Name` is itself a CFG
* node (per the C#-style parameter wiring), so `variableWrite` fires at
* the parameter's natural CFG index.
*/
private predicate hasEntryDefIn(SsaSourceVariable v, CfgImpl::BasicBlock bb) {
exists(Py::Scope s |
nonLocalReadIn(v.getVariable(), s) and
bb = entryBlock(s)
)
}
/**
* Gets the entry basic block of scope `s`, where implicit entry
* definitions are placed (at synthetic index `-1`).
*/
private CfgImpl::BasicBlock entryBlock(Py::Scope s) {
exists(CfgImpl::ControlFlowNode entry |
entry instanceof CfgImpl::ControlFlow::EntryNode and
entry.getEnclosingCallable().asScope() = s and
result = entry.getBasicBlock()
)
}
/**
* The SSA `InputSig` for Python. References are positional
* `(BasicBlock, int)` pairs into the new CFG.
*/
private module SsaImplInput implements SsaImplCommon::InputSig<Py::Location, CfgImpl::BasicBlock> {
class SourceVariable = SsaSourceVariable;
predicate variableWrite(CfgImpl::BasicBlock bb, int i, SourceVariable v, boolean certain) {
// Explicit binding at a CFG node — includes assignments,
// parameter Names (wired in via the C# pattern), exception-handler
// `as`-bindings, import aliases, and match-pattern captures.
exists(Cfg::NameNode n |
bb.getNode(i) = n and
n.defines(v.getVariable()) and
certain = true
)
or
// `del x` — removes the binding. Modelled as a certain write that
// makes any subsequent read invalid.
exists(Cfg::NameNode n |
bb.getNode(i) = n and
n.deletes(v.getVariable()) and
certain = true
)
or
// Implicit entry definition for non-local / captured / global /
// builtin variables read in some scope. Each reading scope's entry
// block gets one such write, allowing closures: e.g. when `x` is a
// parameter of an outer function and read inside a nested
// function, both scopes get entry defs for `x`.
hasEntryDefIn(v, bb) and
i = -1 and
certain = true
or
// `from X import *` — possibly rebinds every name in the importing
// scope. Modelled as an uncertain write at the import-star's CFG
// position for every variable that lives in (or is referenced
// from) the same scope as the import-star. Mirrors legacy ESSA's
// `ImportStarRefinement` (see `essa/SsaDefinitions.qll`'s
// `import_star_refinement` predicate). The write is uncertain so
// that prior definitions of the variable remain available — the
// shared-SSA `SsaUncertainWrite` merges the new value with the
// immediately preceding definition.
exists(Cfg::ImportStarNode imp |
bb.getNode(i) = imp and
certain = false and
(
v.getVariable().getScope() = imp.getScope()
or
// Variable is defined in some other scope but referenced in
// the same scope as the import-star (matches legacy clause 2:
// `other.uses(v) and def.getScope() = other.getScope()`).
exists(Cfg::NameNode other |
other.uses(v.getVariable()) and
imp.getScope() = other.getScope()
)
)
)
}
predicate variableRead(CfgImpl::BasicBlock bb, int i, SourceVariable v, boolean certain) {
// Explicit source use — a `Name` load or a `del x` of the variable.
exists(Cfg::NameNode n |
bb.getNode(i) = n and
n.uses(v.getVariable()) and
certain = true
)
or
// Synthetic use at the normal exit of the variable's defining scope.
// This keeps every variable live to scope exit so that callers (e.g.
// `module_export` in ImportResolution.qll, or taint-tracking pass-through
// through unread locals) can ask "which definition reaches end of
// scope?". Mirrors legacy ESSA's `SsaSourceVariable.getAUse()` which
// included `getScope().getANormalExit()`.
exists(Cfg::ControlFlowNode exit |
exit.isNormalExit() and
exit.getScope() = v.getVariable().getScope() and
bb.getNode(i) = exit and
certain = true
)
}
}
/**
* The shared SSA instantiation for Python.
*
* Members:
* - `Definition` — the union of explicit, uncertain, and phi definitions
* - `WriteDefinition`, `UncertainWriteDefinition`, `PhiNode`
* - the standard SSA predicates (`getAUse`, `getAnUltimateDefinition`, ...).
*/
module Ssa = SsaImplCommon::Make<Py::Location, CfgForSsa, SsaImplInput>;
final class Definition = Ssa::Definition;
final class WriteDefinition = Ssa::WriteDefinition;
final class UncertainWriteDefinition = Ssa::UncertainWriteDefinition;
final class PhiNode = Ssa::PhiNode;
// ===========================================================================
// ESSA-shaped adapter layer
//
// The dataflow library (`python/ql/lib/semmle/python/dataflow/new/`) and
// related modules (`ApiGraphs.qll`, etc.) consume the legacy ESSA API
// (`EssaVariable`, `EssaDefinition`, `AssignmentDefinition`,
// `ScopeEntryDefinition`, `ParameterDefinition`, `WithDefinition`,
// `PhiFunction`, plus the `AdjacentUses` module). To migrate them off
// the legacy CFG, we expose the same API surface on top of the
// shared SSA built above.
//
// This adapter is intentionally narrow: it covers only the predicates
// that new dataflow consumes. The richer legacy ESSA — refinement
// nodes, attribute refinements, edge refinements — stays available
// via `semmle.python.essa.Essa` for points-to / legacy code.
// ===========================================================================
/**
* Gets the CFG node at which a write definition's binding takes place.
*
* For ordinary writes (assignment, deletion, parameter) this is the
* canonical CFG node of the bound Name. For implicit entry definitions
* (synthesised at position `-1` of a scope's entry BB) this is the
* scope's entry node.
*/
private Cfg::ControlFlowNode writeDefNode(Ssa::WriteDefinition def) {
exists(CfgImpl::BasicBlock bb, int i | def.definesAt(_, bb, i) |
i >= 0 and result = bb.getNode(i)
or
i = -1 and result = bb.getNode(0)
)
}
/**
* A write definition whose binding has a corresponding CFG node — i.e.
* everything that's not a phi node. Mirrors legacy ESSA's
* `EssaNodeDefinition`.
*/
class EssaNodeDefinition extends Ssa::WriteDefinition {
/** Gets the CFG node where this definition's binding takes place. */
Cfg::ControlFlowNode getDefiningNode() { result = writeDefNode(this) }
/** Gets the variable defined here (legacy name). */
SsaSourceVariable getVariable() { result = this.getSourceVariable() }
/** Gets the enclosing scope. */
Py::Scope getScope() {
exists(Cfg::ControlFlowNode n | n = this.getDefiningNode() | result = n.getScope())
}
/**
* Holds if this definition defines source variable `v` at CFG node
* `defNode`. Flatter form of `getSourceVariable()` +
* `getDefiningNode()`, matching legacy ESSA's `definedBy`.
*/
predicate definedBy(SsaSourceVariable v, Cfg::ControlFlowNode defNode) {
v = this.getSourceVariable() and defNode = this.getDefiningNode()
}
}
/**
* An assignment definition: any binding where the value being assigned
* is statically known via `Cfg::DefinitionNode.getValue()`. Includes
* plain assignments, walrus, annotated assignments, augmented
* assignments, import aliases (`import x` / `from m import x [as y]`),
* `with ... as x`, and for-target bindings (where `getValue()` returns
* the iter expression's CFG node). Excludes parameter bindings —
* those are modelled by `ParameterDefinition`.
*/
class AssignmentDefinition extends EssaNodeDefinition {
AssignmentDefinition() {
exists(Cfg::NameNode n | n = this.getDefiningNode() |
exists(n.(Cfg::DefinitionNode).getValue()) and
not n.(Cfg::ControlFlowNode).isParameter()
)
}
/** Gets the CFG node for the value being assigned, if statically known. */
Cfg::ControlFlowNode getValue() {
result = this.getDefiningNode().(Cfg::DefinitionNode).getValue()
}
}
/**
* A parameter definition — the binding of a parameter name in a
* function's scope.
*/
class ParameterDefinition extends EssaNodeDefinition {
ParameterDefinition() { this.getDefiningNode().isParameter() }
/** Gets the AST `Parameter` (a `Py::Name` in param context). */
Py::Name getParameter() { result = this.getDefiningNode().getNode() }
}
/**
* A definition introduced by a `with ... as x:` clause.
*/
class WithDefinition extends EssaNodeDefinition {
WithDefinition() {
exists(Cfg::NameNode n, Py::With w |
n = this.getDefiningNode() and
w.getOptionalVars() = n.getNode()
)
}
}
/**
* An assignment where the LHS is a tuple/list and the RHS is unpacked:
* `a, b = (1, 2)` or `a, *rest = xs`. The SSA def lives at the inner
* `Name` CFG node, but for IterableUnpacking integration we expose
* the enclosing `StarredNode` as the `getDefiningNode()` for `*rest`
* patterns — mirroring legacy ESSA's `multi_assignment_definition`,
* which placed the def at the StarredNode CFG node.
*/
class MultiAssignmentDefinition extends EssaNodeDefinition {
MultiAssignmentDefinition() {
exists(Cfg::NameNode n | n = super.getDefiningNode() |
exists(Py::Assign a, Py::Expr lhs |
a.getATarget() = lhs and
(lhs instanceof Py::Tuple or lhs instanceof Py::List) and
lhs.getASubExpression+() = n.getNode()
)
or
// For-loop with tuple/list target: `for a, b in xs:` —
// tuple-unpacking semantics applies to the for-target.
exists(Py::For f, Py::Expr lhs |
f.getTarget() = lhs and
(lhs instanceof Py::Tuple or lhs instanceof Py::List) and
lhs.getASubExpression+() = n.getNode()
)
)
}
override Cfg::ControlFlowNode getDefiningNode() {
// Default: the underlying `Name` CFG node (where the SSA def lives).
not exists(Cfg::StarredNode s |
s.getNode().(Py::Starred).getValue() = super.getDefiningNode().getNode()
) and
result = super.getDefiningNode()
or
// Exception: for `*rest`, expose the enclosing `Starred` CFG node
// so that `IterableUnpacking::iterableUnpackingStarredElementStoreStep`
// can attach the rest-list to it.
exists(Cfg::StarredNode s |
s.getNode().(Py::Starred).getValue() = super.getDefiningNode().getNode()
|
result = s
)
}
}
/**
* An implicit entry definition for a non-local / captured / global /
* builtin variable read in a scope but not defined there.
*
* Inherits from `EssaNodeDefinition` and exposes the scope's entry node
* as its defining node (matching legacy ESSA semantics).
*/
class ScopeEntryDefinition extends EssaNodeDefinition {
ScopeEntryDefinition() {
exists(CfgImpl::BasicBlock bb |
this.definesAt(_, bb, -1) and
bb instanceof CfgImpl::Cfg::EntryBasicBlock
)
}
/** Gets the enclosing scope (the scope whose entry block this def is in). */
override Py::Scope getScope() {
exists(CfgImpl::BasicBlock bb |
this.definesAt(_, bb, -1) and
result = bb.getNode(0).(Cfg::ControlFlowNode).getScope()
)
}
}
/** A phi node (alias matching legacy naming). */
class PhiFunction extends PhiNode {
/**
* Gets an input to this phi function (a definition that flows into
* the phi from one of its predecessor blocks). Mirrors legacy
* ESSA's `PhiFunction.getAnInput()`.
*/
Ssa::Definition getAnInput() { Ssa::phiHasInputFromBlock(this, result, _) }
}
/** Base class for all ESSA definitions (legacy-shaped). */
class EssaDefinition = Ssa::Definition;
/**
* An adapter representing a single SSA-defined "variable" — wrapping
* one `Ssa::Definition`. Mirrors legacy `EssaVariable` API.
*/
class EssaVariable extends Ssa::Definition {
/** Gets the underlying SSA definition (legacy name). */
Ssa::Definition getDefinition() { result = this }
/**
* Gets a CFG node where this definition is used. Includes regular
* `Name` reads as well as the synthetic scope-exit "use" registered
* via `SsaImplInput::variableRead` — mirrors legacy ESSA's
* `EssaVariable.getAUse()` which inherited the synthetic exit-use
* from `SsaSourceVariable`.
*/
Cfg::ControlFlowNode getAUse() {
exists(CfgImpl::BasicBlock bb, int i |
Ssa::ssaDefReachesRead(this.getSourceVariable(), this, bb, i) and
bb.getNode(i) = result
)
}
/** Gets the (textual) name of the underlying variable. */
string getName() { result = this.getSourceVariable().getVariable().getId() }
/** Gets the scope in which this variable lives. */
Py::Scope getScope() { result = this.getSourceVariable().getVariable().getScope() }
/** Gets an ultimate non-phi ancestor of this definition. */
EssaVariable getAnUltimateDefinition() {
if this instanceof PhiNode
then
exists(Ssa::Definition input |
Ssa::phiHasInputFromBlock(this, input, _) and
result = input.(EssaVariable).getAnUltimateDefinition()
)
else result = this
}
}
/**
* Adjacent use-use and def-use relations exposed by the shared SSA
* library. Provides the same interface as legacy
* `semmle.python.essa.SsaCompute::AdjacentUses`.
*/
module AdjacentUses {
/** Holds if `nodeFrom` and `nodeTo` are adjacent uses of the same SSA variable. */
predicate adjacentUseUse(Cfg::NameNode nodeFrom, Cfg::NameNode nodeTo) {
exists(SsaSourceVariable v, CfgImpl::BasicBlock bb1, int i1, CfgImpl::BasicBlock bb2, int i2 |
Ssa::adjacentUseUse(bb1, i1, bb2, i2, v, _) and
nodeFrom = bb1.getNode(i1) and
nodeTo = bb2.getNode(i2)
)
}
/** Holds if `use` is a first use of definition `def`. */
predicate firstUse(Ssa::Definition def, Cfg::NameNode use) {
exists(CfgImpl::BasicBlock bb, int i |
Ssa::firstUse(def, bb, i, _) and
use = bb.getNode(i)
)
}
/**
* Holds if `use` is any reachable use of definition `def`. Combines
* `firstUse` with transitive use-use adjacency.
*/
predicate useOfDef(Ssa::Definition def, Cfg::NameNode use) {
firstUse(def, use)
or
exists(Cfg::NameNode mid | useOfDef(def, mid) and adjacentUseUse(mid, use))
}
}

View File

@@ -94,10 +94,8 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
Node returnOf(Node callable, SummaryComponent return) {
return = FlowSummaryImpl::Private::SummaryComponent::return() and
// `result` should be the return value of a callable expression (lambda or function) referenced by `callable`
exists(Return ret |
ret.getScope() = callable.getALocalSource().asExpr().(CallableExpr).getInnerScope() and
result.asCfgNode().getNode() = ret.getValue()
)
result.asCfgNode() =
callable.getALocalSource().asExpr().(CallableExpr).getInnerScope().getAReturnValueFlowNode()
}
// Relating callables to nodes

View File

@@ -61,7 +61,7 @@ private module CaptureInput implements Shared::InputSig<Location, Cfg::BasicBloc
class VariableWrite extends ControlFlowNode {
CapturedVariable v;
VariableWrite() { exists(DefinitionNode d | d.getNode() = v.getAStore() | this = d.getValue()) }
VariableWrite() { this = v.getAStore().getAFlowNode().(DefinitionNode).getValue() }
CapturedVariable getVariable() { result = v }
@@ -71,7 +71,7 @@ private module CaptureInput implements Shared::InputSig<Location, Cfg::BasicBloc
class VariableRead extends Expr {
CapturedVariable v;
VariableRead() { this.getNode() = v.getALoad() }
VariableRead() { this = v.getALoad().getAFlowNode() }
CapturedVariable getVariable() { result = v }
}

View File

@@ -448,7 +448,8 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi
context = TNoParam() and
src = TTaintTrackingNode_(retval, TNoParam(), path, kind, this) and
node.asCfgNode() = call and
retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue()
retval.asCfgNode() =
any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
) and
edgeLabel = "return"
}
@@ -470,7 +471,8 @@ class TaintTrackingImplementation extends string instanceof TaintTracking::Confi
this.callContexts(call, src, pyfunc, context, callee) and
retnode = TTaintTrackingNode_(retval, callee, path, kind, this) and
node.asCfgNode() = call and
retval.asCfgNode().getNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue()
retval.asCfgNode() =
any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
) and
edgeLabel = "call"
}
@@ -714,10 +716,8 @@ private class EssaTaintTracking extends string instanceof TaintTracking::Configu
src = TTaintTrackingNode_(srcnode, context, path, srckind, this) and
path.noAttribute()
|
srcnode.asCfgNode().getNode() = assign.getValue() and
exists(SequenceNode left_parent | left_parent.getNode() = assign.getATarget() |
depth = iterable_unpacking_descent(left_parent, defn.getDefiningNode())
) and
assign.getValue().getAFlowNode() = srcnode.asCfgNode() and
depth = iterable_unpacking_descent(assign.getATarget().getAFlowNode(), defn.getDefiningNode()) and
kind = taint_at_depth(srckind, depth)
)
}
@@ -964,7 +964,7 @@ private TaintKind taint_at_depth(SequenceKind parent_kind, int depth) {
* - with `left_defn` = `*y`, `left_parent` = `((x, *y), ...)`, result = 1
*/
int iterable_unpacking_descent(SequenceNode left_parent, ControlFlowNode left_defn) {
exists(Assign a | left_parent.getNode() = a.getATarget().getASubExpression*()) and
exists(Assign a | a.getATarget().getASubExpression*().getAFlowNode() = left_parent) and
left_parent.getAnElement() = left_defn and
// Handle `a, *b = some_iterable`
if left_defn instanceof StarredNode then result = 0 else result = 1

View File

@@ -56,7 +56,7 @@ module SsaSource {
predicate with_definition(Variable v, ControlFlowNode defn) {
exists(With with, Name var |
with.getOptionalVars() = var and
defn.getNode() = var
var.getAFlowNode() = defn
|
var = v.getAStore()
)
@@ -67,7 +67,7 @@ module SsaSource {
predicate pattern_capture_definition(Variable v, ControlFlowNode defn) {
exists(MatchCapturePattern capture, Name var |
capture.getVariable() = var and
defn.getNode() = var
var.getAFlowNode() = defn
|
var = v.getAStore()
)
@@ -78,7 +78,7 @@ module SsaSource {
predicate pattern_alias_definition(Variable v, ControlFlowNode defn) {
exists(MatchAsPattern pattern, Name var |
pattern.getAlias() = var and
defn.getNode() = var
var.getAFlowNode() = defn
|
var = v.getAStore()
)

View File

@@ -59,7 +59,7 @@ module Bottle {
override Parameter getARoutedParameter() { none() }
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
}
}
@@ -73,10 +73,7 @@ module Bottle {
/** A response returned by a view callable. */
class BottleReturnResponse extends Http::Server::HttpResponse::Range {
BottleReturnResponse() {
exists(Return ret |
ret.getScope() = any(View::ViewCallable vc) and
this.asCfgNode().getNode() = ret.getValue()
)
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode()
}
override DataFlow::Node getBody() { result = this }

View File

@@ -2872,10 +2872,7 @@ module PrivateDjango {
DataFlow::CfgNode
{
DjangoRedirectViewGetRedirectUrlReturn() {
exists(Return ret |
ret.getScope() = any(GetRedirectUrlFunction f) and
node.getNode() = ret.getValue()
)
node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode()
}
override DataFlow::Node getRedirectLocation() { result = this }

View File

@@ -129,7 +129,7 @@ module FastApi {
result in [this.getArg(0), this.getArgByName("path")]
}
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
override string getFramework() { result = "FastAPI" }
@@ -309,10 +309,7 @@ module FastApi {
FastApiRouteSetup routeSetup;
FastApiRequestHandlerReturn() {
exists(Return ret |
ret.getScope() = routeSetup.getARequestHandler() and
node.getNode() = ret.getValue()
)
node = routeSetup.getARequestHandler().getAReturnValueFlowNode()
}
override DataFlow::Node getBody() { result = this }

View File

@@ -371,7 +371,7 @@ module Flask {
result in [this.getArg(0), this.getArgByName("rule")]
}
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
}
/**
@@ -536,7 +536,7 @@ module Flask {
FlaskRouteHandlerReturn() {
exists(Function routeHandler |
routeHandler = any(FlaskRouteSetup rs).getARequestHandler() and
exists(Return ret | ret.getScope() = routeHandler and node.getNode() = ret.getValue()) and
node = routeHandler.getAReturnValueFlowNode() and
not this instanceof Flask::Response::InstanceSource
)
}

View File

@@ -38,7 +38,7 @@ private module FlaskAdmin {
result in [this.getArg(0), this.getArgByName("url")]
}
override Function getARequestHandler() { node.getNode() = result.getADecorator() }
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
}
/**
@@ -71,7 +71,7 @@ private module FlaskAdmin {
override Function getARequestHandler() {
exists(Flask::FlaskViewClass cls |
node.getNode() = cls.getADecorator() and
cls.getADecorator().getAFlowNode() = node and
result = cls.getARequestHandler()
)
}

View File

@@ -166,10 +166,7 @@ module Pyramid {
/** A response returned by a view callable. */
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
PyramidReturnResponse() {
exists(Return ret |
ret.getScope() = any(View::ViewCallable vc) and
this.asCfgNode().getNode() = ret.getValue()
) and
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and
not this = instance()
}

View File

@@ -2254,9 +2254,8 @@ module StdlibPrivate {
DataFlow::CfgNode
{
WsgirefSimpleServerApplicationReturn() {
exists(WsgirefSimpleServerApplication requestHandler, Return ret |
ret.getScope() = requestHandler and
node.getNode() = ret.getValue()
exists(WsgirefSimpleServerApplication requestHandler |
node = requestHandler.getAReturnValueFlowNode()
)
}

View File

@@ -182,10 +182,7 @@ private module Twisted {
DataFlow::CfgNode
{
TwistedResourceRenderMethodReturn() {
exists(Return ret |
ret.getScope() = any(TwistedResourceRenderMethod meth) and
this.asCfgNode().getNode() = ret.getValue()
)
this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode()
}
override DataFlow::Node getBody() { result = this }

View File

@@ -77,7 +77,7 @@ module Stages {
or
exists(any(AstExtended::AstNode n).getParentNode())
or
exists(PyFlow::ControlFlowNode cfg, AstExtended::AstNode n | cfg.getNode() = n)
exists(any(AstExtended::AstNode n).getAFlowNode())
or
exists(any(PyFlow::BasicBlock b).getImmediateDominator())
or

View File

@@ -56,9 +56,8 @@ abstract class CallableObjectInternal extends ObjectInternal {
/** A Python function. */
class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFunctionObject {
override Function getScope() {
exists(CallableExpr expr, ControlFlowNode exprCfg |
exprCfg.getNode() = expr and
this = TPythonFunctionObject(exprCfg) and
exists(CallableExpr expr |
this = TPythonFunctionObject(expr.getAFlowNode()) and
result = expr.getInnerScope()
)
}
@@ -81,12 +80,11 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
pragma[nomagic]
override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) {
exists(Function func, Return ret, ControlFlowNode rval, ControlFlowNode forigin |
exists(Function func, ControlFlowNode rval, ControlFlowNode forigin |
func = this.getScope() and
callee.appliesToScope(func)
|
ret.getScope() = func and
rval.getNode() = ret.getValue() and
rval = func.getAReturnValueFlowNode() and
PointsToInternal::pointsTo(rval, callee, obj, forigin) and
origin = CfgOrigin::fromCfgNode(forigin)
)
@@ -162,11 +160,10 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
}
private BasicBlock blockReturningNone(Function func) {
exists(Return ret, ControlFlowNode ret_ |
exists(Return ret |
not exists(ret.getValue()) and
ret.getScope() = func and
ret_.getNode() = ret and
result = ret_.getBasicBlock()
result = ret.getAFlowNode().getBasicBlock()
)
}

View File

@@ -113,9 +113,8 @@ abstract class ClassObjectInternal extends ObjectInternal {
class PythonClassObjectInternal extends ClassObjectInternal, TPythonClassObject {
/** Gets the scope for this Python class */
Class getScope() {
exists(ClassExpr expr, ControlFlowNode exprCfg |
exprCfg.getNode() = expr and
this = TPythonClassObject(exprCfg) and
exists(ClassExpr expr |
this = TPythonClassObject(expr.getAFlowNode()) and
result = expr.getInnerScope()
)
}

View File

@@ -745,12 +745,7 @@ class PythonFunctionValue extends FunctionValue {
override int maxParameters() { result = this.getScope().getMaxPositionalArguments() }
/** Gets a control flow node corresponding to a return statement in this function */
ControlFlowNode getAReturnedNode() {
exists(Return ret |
ret.getScope() = this.getScope() and
result.getNode() = ret.getValue()
)
}
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }
override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }

View File

@@ -387,7 +387,7 @@ private PythonClassObjectInternal abcMetaClassObject() {
private predicate neither_class_nor_static_method(Function f) {
not exists(f.getADecorator())
or
exists(ControlFlowNode deco | deco.getNode() = f.getADecorator() |
exists(ControlFlowNode deco | deco = f.getADecorator().getAFlowNode() |
exists(ObjectInternal o | PointsToInternal::pointsTo(deco, _, o, _) |
o != ObjectInternal::staticMethod() and
o != ObjectInternal::classMethod()

View File

@@ -711,7 +711,7 @@ private module InterModulePointsTo {
ControlFlowNode f, PointsToContext context, ObjectInternal value, ControlFlowNode origin
) {
exists(string name, ImportExpr i |
f.getNode() = i and
i.getAFlowNode() = f and
i.getImportedModuleName() = name and
PointsToInternal::module_imported_as(value, name) and
origin = f and
@@ -2118,9 +2118,8 @@ module Types {
result.getBuiltin() = cls.getBuiltin().getBaseClass() and n = 0
or
exists(Class pycls | pycls = cls.(PythonClassObjectInternal).getScope() |
exists(ObjectInternal base, ControlFlowNode baseNode |
baseNode.getNode() = pycls.getBase(n) and
PointsToInternal::pointsTo(baseNode, _, base, _)
exists(ObjectInternal base |
PointsToInternal::pointsTo(pycls.getBase(n).getAFlowNode(), _, base, _)
|
result = base and base != ObjectInternal::unknown()
or
@@ -2224,10 +2223,7 @@ module Types {
}
private ControlFlowNode decorator_call_callee(PythonClassObjectInternal cls) {
exists(CallNode deco |
deco.getNode() = cls.getScope().getADecorator() and
result = deco.getFunction()
)
result = cls.getScope().getADecorator().getAFlowNode().(CallNode).getFunction()
}
private boolean has_six_add_metaclass(PythonClassObjectInternal cls) {
@@ -2266,7 +2262,7 @@ module Types {
}
private EssaVariable metaclass_var(Class cls) {
result.getASourceUse().getNode() = cls.getMetaClass()
result.getASourceUse() = cls.getMetaClass().getAFlowNode()
or
major_version() = 2 and
not exists(cls.getMetaClass()) and

View File

@@ -181,7 +181,7 @@ class ClassObject extends Object {
)
}
ControlFlowNode declaredMetaClass() { result.getNode() = this.getPyClass().getMetaClass() }
ControlFlowNode declaredMetaClass() { result = this.getPyClass().getMetaClass().getAFlowNode() }
/** Has type inference failed to compute the full class hierarchy for this class for the reason given. */
predicate failedInference(string reason) { Types::failedInference(this.theClass(), reason) }
@@ -195,9 +195,8 @@ class ClassObject extends Object {
* It is guaranteed that getProbableSingletonInstance() returns at most one Object for each ClassObject.
*/
Object getProbableSingletonInstance() {
exists(ControlFlowNodeWithPointsTo use, Expr origin, ControlFlowNode origin_ |
origin_.getNode() = origin and
use.refersTo(result, this, origin_)
exists(ControlFlowNodeWithPointsTo use, Expr origin |
use.refersTo(result, this, origin.getAFlowNode())
|
this.hasStaticallyUniqueInstance() and
/* Ensure that original expression will be executed only one. */

View File

@@ -427,7 +427,7 @@ class ExceptFlowNodeWithPointsTo extends ExceptFlowNode {
}
private ControlFlowNodeWithPointsTo element_from_tuple_objectapi(Object tuple) {
exists(Tuple t | t = tuple.getOrigin() and result.getNode() = t.getAnElt())
exists(Tuple t | t = tuple.getOrigin() and result = t.getAnElt().getAFlowNode())
}
/**

View File

@@ -36,8 +36,8 @@ class RangeIterationVariableFact extends PointsToExtension {
RangeIterationVariableFact() {
exists(For f, ControlFlowNode iterable |
iterable.getBasicBlock().dominates(this.(ControlFlowNode).getBasicBlock()) and
iterable.getNode() = f.getIter() and
this.(ControlFlowNode).getNode() = f.getTarget() and
f.getIter().getAFlowNode() = iterable and
f.getTarget().getAFlowNode() = this and
exists(ObjectInternal range |
PointsTo::pointsTo(iterable, _, range, _) and
range.getClass() = ObjectInternal::builtin("range")

View File

@@ -137,10 +137,7 @@ class PyFunctionObject extends FunctionObject {
/** Gets a control flow node corresponding to the value of a return statement */
ControlFlowNodeWithPointsTo getAReturnedNode() {
exists(Return ret |
ret.getScope() = this.getFunction() and
result.getNode() = ret.getValue()
)
result = this.getFunction().getAReturnValueFlowNode()
}
override string descriptiveString() {
@@ -173,7 +170,7 @@ class PyFunctionObject extends FunctionObject {
predicate unconditionallyReturnsParameter(int n) {
exists(SsaVariable pvar |
exists(Parameter p | p = this.getFunction().getArg(n) |
pvar.getDefinition().getNode() = p.asName()
p.asName().getAFlowNode() = pvar.getDefinition()
) and
exists(NameNode rval |
rval = pvar.getAUse() and

View File

@@ -337,7 +337,7 @@ class TupleObject extends SequenceObject {
or
this instanceof TupleNode
or
exists(Function func | this.(ControlFlowNode).getNode() = func.getVararg())
exists(Function func | func.getVararg().getAFlowNode() = this)
}
}
@@ -352,9 +352,7 @@ module TupleObject {
}
class NonEmptyTupleObject extends TupleObject {
NonEmptyTupleObject() {
exists(Function func | this.(ControlFlowNode).getNode() = func.getVararg())
}
NonEmptyTupleObject() { exists(Function func | func.getVararg().getAFlowNode() = this) }
override boolean booleanValue() { result = true }
}

View File

@@ -48,11 +48,9 @@ class CheckClass extends ClassObject {
self_dict = sub.getObject()
or
/* Indirect assignment via temporary variable */
exists(SsaVariable v, ControlFlowNode subObjCfg, ControlFlowNode selfDictCfg |
subObjCfg.getNode() = sub.getObject() and selfDictCfg.getNode() = self_dict
|
v.getAUse() = subObjCfg and
v.getDefinition().(DefinitionNode).getValue() = selfDictCfg
exists(SsaVariable v |
v.getAUse() = sub.getObject().getAFlowNode() and
v.getDefinition().(DefinitionNode).getValue() = self_dict.getAFlowNode()
)
) and
a.getATarget() = sub and
@@ -64,10 +62,9 @@ class CheckClass extends ClassObject {
pragma[nomagic]
private predicate monkeyPatched(string name) {
exists(Attribute a, ControlFlowNode objCfg |
objCfg.getNode() = a.getObject() and
exists(Attribute a |
a.getCtx() instanceof Store and
PointsTo::points_to(objCfg, _, this, _, _) and
PointsTo::points_to(a.getObject().getAFlowNode(), _, this, _, _) and
a.getName() = name
)
}
@@ -87,9 +84,9 @@ class CheckClass extends ClassObject {
}
predicate interestingUndefined(SelfAttributeRead a) {
exists(string name, ControlFlowNode aCfg | name = a.getName() and aCfg.getNode() = a |
exists(string name | name = a.getName() |
this.interestingContext(a, name) and
not this.definedInBlock(aCfg.getBasicBlock(), name)
not this.definedInBlock(a.getAFlowNode().getBasicBlock(), name)
)
}
@@ -112,9 +109,8 @@ class CheckClass extends ClassObject {
pragma[nomagic]
private predicate definitionInBlock(BasicBlock b, string name) {
exists(SelfAttributeStore sa, ControlFlowNode saCfg |
saCfg.getNode() = sa and
saCfg.getBasicBlock() = b and
exists(SelfAttributeStore sa |
sa.getAFlowNode().getBasicBlock() = b and
sa.getName() = name and
sa.getClass() = this.getPyClass()
)

View File

@@ -15,9 +15,7 @@
import python
import semmle.python.ApiGraphs
predicate doesnt_reraise(ExceptStmt ex) {
exists(ControlFlowNode exCfg | exCfg.getNode() = ex | exCfg.getBasicBlock().reachesExit())
}
predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() }
predicate catches_base_exception(ExceptStmt ex) {
ex.getType() = API::builtin("BaseException").getAValueReachableFromSource().asExpr()

View File

@@ -116,7 +116,7 @@ FunctionValue get_function_or_initializer(Value func_or_cls) {
predicate illegally_named_parameter_objectapi(Call call, Object func, string name) {
not func.isC() and
name = call.getANamedArgumentName() and
exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call_objectapi(func)) and
call.getAFlowNode() = get_a_call_objectapi(func) and
not get_function_or_initializer_objectapi(func).isLegalArgumentName(name)
}
@@ -124,7 +124,7 @@ predicate illegally_named_parameter_objectapi(Call call, Object func, string nam
predicate illegally_named_parameter(Call call, Value func, string name) {
not func.isBuiltin() and
name = call.getANamedArgumentName() and
exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call(func)) and
call.getAFlowNode() = get_a_call(func) and
not get_function_or_initializer(func).isLegalArgumentName(name)
}
@@ -146,9 +146,7 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) {
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassObject and
exists(ControlFlowNode callCfg | callCfg.getNode() = call |
callCfg = get_a_call_objectapi(callable)
) and
call.getAFlowNode() = get_a_call_objectapi(callable) and
limit = func.minParameters() - 1
)
}
@@ -174,7 +172,7 @@ predicate too_few_args(Call call, Value callable, int limit) {
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassValue and
exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call(callable)) and
call.getAFlowNode() = get_a_call(callable) and
limit = func.minParameters() - 1
)
}
@@ -193,9 +191,7 @@ predicate too_many_args_objectapi(Call call, Object callable, int limit) {
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassObject and
exists(ControlFlowNode callCfg | callCfg.getNode() = call |
callCfg = get_a_call_objectapi(callable)
) and
call.getAFlowNode() = get_a_call_objectapi(callable) and
limit = func.maxParameters() - 1
) and
positional_arg_count_for_call_objectapi(call, callable) > limit
@@ -215,7 +211,7 @@ predicate too_many_args(Call call, Value callable, int limit) {
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassValue and
exists(ControlFlowNode callCfg | callCfg.getNode() = call | callCfg = get_a_call(callable)) and
call.getAFlowNode() = get_a_call(callable) and
limit = func.maxParameters() - 1
) and
positional_arg_count_for_call(call, callable) > limit

View File

@@ -36,15 +36,11 @@ where
exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and
(
exists(BasicBlock b, int i1, int i2 |
b.getNode(i1).getNode() = k1 and
b.getNode(i2).getNode() = k2 and
k1.getAFlowNode() = b.getNode(i1) and
k2.getAFlowNode() = b.getNode(i2) and
i1 < i2
)
or
exists(ControlFlowNode k1Cfg, ControlFlowNode k2Cfg |
k1Cfg.getNode() = k1 and k2Cfg.getNode() = k2
|
k1Cfg.getBasicBlock().strictlyDominates(k2Cfg.getBasicBlock())
)
k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock())
)
select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten"

View File

@@ -98,18 +98,16 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en
}
private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) {
exists(CallNode call, ControlFlowNode fmtCfg |
call.getNode() = format_expr and fmtCfg.getNode() = fmt
|
exists(CallNode call | call = format_expr.getAFlowNode() |
call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::named("format")) and
call.getArg(0).(ControlFlowNodeWithPointsTo).pointsTo(_, fmtCfg) and
call.getArg(0).(ControlFlowNodeWithPointsTo).pointsTo(_, fmt.getAFlowNode()) and
args = count(format_expr.getAnArg()) - 1
or
call.getFunction()
.(AttrNode)
.getObject("format")
.(ControlFlowNodeWithPointsTo)
.pointsTo(_, fmtCfg) and
.pointsTo(_, fmt.getAFlowNode()) and
args = count(format_expr.getAnArg())
)
}

View File

@@ -15,7 +15,7 @@ import python
/** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */
predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) {
exists(CompareNode fcomp | fcomp.getNode() = comp |
exists(CompareNode fcomp | fcomp = comp.getAFlowNode() |
fcomp.operands(left, op, right) and
(op instanceof Is or op instanceof IsNot)
)

View File

@@ -5,7 +5,7 @@ private import LegacyPointsTo
/** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */
predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) {
exists(CompareNode fcomp | fcomp.getNode() = comp |
exists(CompareNode fcomp | fcomp = comp.getAFlowNode() |
fcomp.operands(left, op, right) and
(op instanceof Is or op instanceof IsNot)
)

View File

@@ -19,7 +19,7 @@ where
// Only relevant for Python 2, as all later versions implement true division
major_version() = 2 and
exists(BinaryExprNode bin, Value lval, Value rval |
bin.getNode() = div and
bin = div.getAFlowNode() and
bin.getNode().getOp() instanceof Div and
bin.getLeft().(ControlFlowNodeWithPointsTo).pointsTo(lval, left) and
lval.getClass() = ClassValue::int_() and

View File

@@ -19,9 +19,7 @@ where
exists(Function init | init.isInitMethod() and r.getScope() = init) and
r.getValue() = rv and
not rv.pointsTo(Value::none_()) and
not exists(FunctionValue f, ControlFlowNode rvCfg | rvCfg.getNode() = rv |
f.getACall() = rvCfg and f.neverReturns()
) and
not exists(FunctionValue f | f.getACall() = rv.getAFlowNode() | f.neverReturns()) and
// to avoid double reporting, don't trigger if returning result from other __init__ function
not exists(Attribute meth | meth = rv.(Call).getFunc() | meth.getName() = "__init__")
select r, "Explicit return in __init__ method."

View File

@@ -69,12 +69,7 @@ where
returns_meaningful_value(callee) and
not wrapped_in_try_except(call) and
exists(int unused |
unused =
count(ExprStmt e |
exists(ControlFlowNode eValCfg | eValCfg.getNode() = e.getValue() |
eValCfg = callee.getACall()
)
) and
unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and
total = count(callee.getACall())
|
percentage_used = (100.0 * (total - unused) / total).floor()

View File

@@ -138,12 +138,12 @@ predicate function_opens_file(FunctionValue f) {
f = Value::named("open")
or
exists(EssaVariable v, Return ret | ret.getScope() = f.getScope() |
v.getNode() = ret.getValue().getAUse() and
ret.getValue().getAFlowNode() = v.getAUse() and
var_is_open(v, _)
)
or
exists(Return ret, FunctionValue callee | ret.getScope() = f.getScope() |
callee.getNode() = ret.getValue().getACall() and
ret.getValue().getAFlowNode() = callee.getACall() and
function_opens_file(callee)
)
}

View File

@@ -94,7 +94,7 @@ class CredentialSink extends DataFlow::Node {
this.(DataFlow::ArgumentNode).argumentOf(_, pos)
)
or
exists(Keyword k | k.getArg() = name and this.asCfgNode().getNode() = k.getValue())
exists(Keyword k | k.getArg() = name and k.getValue().getAFlowNode() = this.asCfgNode())
or
exists(CompareNode cmp, NameNode n | n.getId() = name |
cmp.operands(this.asCfgNode(), any(Eq eq), n)

View File

@@ -25,7 +25,7 @@ from
For loop, ControlFlowNodeWithPointsTo iter, Value str, Value seq, ControlFlowNode seq_origin,
ControlFlowNode str_origin
where
iter.getNode() = loop.getIter() and
loop.getIter().getAFlowNode() = iter and
iter.pointsTo(str, str_origin) and
iter.pointsTo(seq, seq_origin) and
has_string_type(str) and

View File

@@ -15,7 +15,7 @@
import python
predicate loop_variable_ssa(For f, Variable v, SsaVariable s) {
s.getDefinition().getNode() = f.getTarget() and v = s.getVariable()
f.getTarget().getAFlowNode() = s.getDefinition() and v = s.getVariable()
}
predicate variableUsedInNestedLoops(For inner, For outer, Variable v, Name n) {

View File

@@ -16,7 +16,7 @@ private import LegacyPointsTo
from For loop, ControlFlowNodeWithPointsTo iter, Value v, ClassValue t, ControlFlowNode origin
where
iter.getNode() = loop.getIter() and
loop.getIter().getAFlowNode() = iter and
iter.pointsTo(_, v, origin) and
v.getClass() = t and
not t.isIterable() and

View File

@@ -24,13 +24,11 @@ predicate func_with_side_effects(Expr e) {
}
predicate call_with_side_effect(Call e) {
exists(ControlFlowNode eCfg | eCfg.getNode() = e |
eCfg =
API::moduleImport("subprocess")
.getMember(["call", "check_call", "check_output"])
.getACall()
.asCfgNode()
)
e.getAFlowNode() =
API::moduleImport("subprocess")
.getMember(["call", "check_call", "check_output"])
.getACall()
.asCfgNode()
}
predicate probable_side_effect(Expr e) {

View File

@@ -133,11 +133,7 @@ class ListComprehensionDeclaration extends ListComp {
major_version() = 2 and
this.getIterationVariable(_).getId() = result.getId() and
result.getScope() = this.getScope() and
exists(ControlFlowNode thisCfg, ControlFlowNode resultCfg |
thisCfg.getNode() = this and resultCfg.getNode() = result
|
thisCfg.strictlyReaches(resultCfg)
) and
this.getAFlowNode().strictlyReaches(result.getAFlowNode()) and
result.isUse()
}

View File

@@ -13,21 +13,18 @@
import python
import Definition
from
ListComprehensionDeclaration l, Name use, Name defn, ControlFlowNode lCfg, ControlFlowNode useCfg
from ListComprehensionDeclaration l, Name use, Name defn
where
use = l.getALeakedVariableUse() and
defn = l.getDefinition() and
lCfg.getNode() = l and
useCfg.getNode() = use and
lCfg.strictlyReaches(useCfg) and
l.getAFlowNode().strictlyReaches(use.getAFlowNode()) and
/* Make sure we aren't in a loop, as the variable may be redefined */
not useCfg.strictlyReaches(lCfg) and
not use.getAFlowNode().strictlyReaches(l.getAFlowNode()) and
not l.contains(use) and
not use.deletes(_) and
not exists(SsaVariable v |
v.getAUse() = useCfg and
not v.getDefinition().strictlyDominates(lCfg)
v.getAUse() = use.getAFlowNode() and
not v.getDefinition().strictlyDominates(l.getAFlowNode())
)
select use,
use.getId() + " may have a different value in Python 3, as the $@ will not be in scope.", defn,

View File

@@ -26,11 +26,8 @@ private Stmt loop_probably_defines(Variable v) {
/** Holds if the variable used by `use` is probably defined in a loop */
predicate probably_defined_in_loop(Name use) {
exists(Stmt loop, ControlFlowNode loopCfg, ControlFlowNode useCfg |
loop = loop_probably_defines(use.getVariable()) and
loopCfg.getNode() = loop and
useCfg.getNode() = use and
loopCfg.strictlyReaches(useCfg)
exists(Stmt loop | loop = loop_probably_defines(use.getVariable()) |
loop.getAFlowNode().strictlyReaches(use.getAFlowNode())
)
}

View File

@@ -24,8 +24,8 @@ predicate multiply_defined(AstNode asgn1, AstNode asgn2, Variable v) {
forex(Definition def, Definition redef |
def.getVariable() = v and
def.getNode() = asgn1 and
redef.getNode() = asgn2
def = asgn1.getAFlowNode() and
redef = asgn2.getAFlowNode()
|
def.isUnused() and
def.getARedef() = redef and

View File

@@ -88,9 +88,7 @@ predicate implicit_repeat(For f) {
* E.g. gets `x` from `{ y for y in x }`.
*/
ControlFlowNode get_comp_iterable(For f) {
exists(Comp c, ControlFlowNode cCfg |
c.getFunction().getStmt(0) = f and cCfg.getNode() = c and cCfg.getAPredecessor() = result
)
exists(Comp c | c.getFunction().getStmt(0) = f | c.getAFlowNode().getAPredecessor() = result)
}
from For f, Variable v, string msg

View File

@@ -19,10 +19,9 @@ private predicate loop_entry_variables(EssaVariable pred, EssaVariable succ) {
private predicate loop_entry_edge(BasicBlock pred, BasicBlock loop) {
pred = loop.getAPredecessor() and
pred = loop.getImmediateDominator() and
exists(Stmt s, ControlFlowNode sCfg |
exists(Stmt s |
loop_probably_executes_at_least_once(s) and
sCfg.getNode() = s and
sCfg.getBasicBlock() = loop
s.getAFlowNode().getBasicBlock() = loop
)
}

View File

@@ -27,7 +27,7 @@ predicate guarded_against_name_error(Name u) {
|
globals.getFunc().(Name).getId() = "globals" and
guard.controls(controlled, _) and
exists(ControlFlowNode uCfg | uCfg.getNode() = u | controlled.contains(uCfg))
controlled.contains(u.getAFlowNode())
)
}
@@ -101,18 +101,18 @@ predicate undefined_use(Name u) {
}
private predicate first_use_in_a_block(Name use) {
exists(GlobalVariable v, BasicBlock b, int i, ControlFlowNode useCfg | useCfg.getNode() = use |
i = min(int j | b.getNode(j).getNode() = v.getALoad()) and b.getNode(i) = useCfg
exists(GlobalVariable v, BasicBlock b, int i |
i = min(int j | b.getNode(j).getNode() = v.getALoad()) and b.getNode(i) = use.getAFlowNode()
)
}
predicate first_undefined_use(Name use) {
undefined_use(use) and
exists(GlobalVariable v, ControlFlowNode useCfg | v.getALoad() = use and useCfg.getNode() = use |
exists(GlobalVariable v | v.getALoad() = use |
first_use_in_a_block(use) and
not exists(ControlFlowNode other |
other.getNode() = v.getALoad() and
other.getBasicBlock().strictlyDominates(useCfg.getBasicBlock())
other.getBasicBlock().strictlyDominates(use.getAFlowNode().getBasicBlock())
)
)
}

View File

@@ -18,8 +18,8 @@ private import semmle.python.types.ImportTime
/* Local variable part */
predicate initialized_as_local(PlaceHolder use) {
exists(SsaVariableWithPointsTo l, Function f, ControlFlowNode useCfg |
f = use.getScope() and useCfg.getNode() = use and l.getAUse() = useCfg
exists(SsaVariableWithPointsTo l, Function f |
f = use.getScope() and l.getAUse() = use.getAFlowNode()
|
l.getVariable() instanceof LocalVariable and
not l.maybeUndefined()

View File

@@ -54,7 +54,7 @@ predicate unused_global(Name unused, GlobalVariable v) {
u.uses(v)
|
// That is reachable from this definition, directly
exists(ControlFlowNode uCfg | uCfg.getNode() = u | defn.strictlyReaches(uCfg))
defn.strictlyReaches(u.getAFlowNode())
or
// indirectly
defn.getBasicBlock().reachesExit() and u.getScope() != unused.getScope()

View File

@@ -48,17 +48,15 @@ class Symbol extends TSymbol {
AstNode find() {
this = TModule(result)
or
exists(Symbol s, string name, ControlFlowNode resultCfg |
this = TMember(s, name) and resultCfg.getNode() = result
|
exists(Symbol s, string name | this = TMember(s, name) |
exists(ClassObject cls |
s.resolvesTo() = cls and
cls.attributeRefersTo(name, _, resultCfg)
cls.attributeRefersTo(name, _, result.getAFlowNode())
)
or
exists(ModuleObject m |
s.resolvesTo() = m and
m.attributeRefersTo(name, _, resultCfg)
m.attributeRefersTo(name, _, result.getAFlowNode())
)
)
}

View File

@@ -80,11 +80,10 @@ class VersionGuard extends ConditionBlock {
VersionGuard() { this.getLastNode() instanceof VersionTest }
}
from ImportExpr ie, ControlFlowNode ieCfg
from ImportExpr ie
where
ieCfg.getNode() = ie and
not ie.(ExprWithPointsTo).refersTo(_) and
exists(Context c | c.appliesTo(ieCfg)) and
exists(Context c | c.appliesTo(ie.getAFlowNode())) and
not ok_to_fail(ie) and
not exists(VersionGuard guard | guard.controls(ieCfg.getBasicBlock(), _))
not exists(VersionGuard guard | guard.controls(ie.getAFlowNode().getBasicBlock(), _))
select ie, "Unable to resolve import of '" + ie.getImportedModuleName() + "'."

View File

@@ -11,13 +11,13 @@ import python
import semmle.python.pointsto.PointsTo
predicate points_to_failure(Expr e) {
exists(ControlFlowNode f | f.getNode() = e | not PointsTo::pointsTo(f, _, _, _))
exists(ControlFlowNode f | f = e.getAFlowNode() | not PointsTo::pointsTo(f, _, _, _))
}
predicate key_points_to_failure(Expr e) {
points_to_failure(e) and
not points_to_failure(e.getASubExpression()) and
not exists(SsaVariable ssa, ControlFlowNode eCfg | eCfg.getNode() = e and ssa.getAUse() = eCfg |
not exists(SsaVariable ssa | ssa.getAUse() = e.getAFlowNode() |
points_to_failure(ssa.getAnUltimateDefinition().getDefinition().getNode())
) and
not exists(Assign a | a.getATarget() = e)

View File

@@ -12,5 +12,5 @@ import python
private import LegacyPointsTo
from Expr e
where exists(ControlFlowNodeWithPointsTo f | f.getNode() = e | not f.refersTo(_))
where exists(ControlFlowNodeWithPointsTo f | f = e.getAFlowNode() | not f.refersTo(_))
select e, "Expression does not 'point-to' any object."

View File

@@ -131,7 +131,7 @@ module ModificationOfParameterWithDefault {
exists(DeletionNode d | d.getTarget().(SubscriptNode).getObject() = this.asCfgNode())
or
// augmented assignment to the value
exists(AugAssign a | this.asCfgNode().getNode() = a.getTarget())
exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode())
or
// modifying function call
exists(DataFlow::CallCfgNode c, DataFlow::AttrRead a | c.getFunction() = a |

View File

@@ -5,7 +5,5 @@
import python
select count(Comprehension c |
count(c.toString()) != 1 or
count(c.getLocation()) != 1 or
not exists(ControlFlowNode n | n.getNode() = c)
count(c.toString()) != 1 or count(c.getLocation()) != 1 or not exists(c.getAFlowNode())
)

View File

@@ -45,15 +45,13 @@ private class VersionGuardedNode extends DataFlow::Node {
VersionGuardedNode() {
version in [2, 3] and
exists(If parent, CompareNode c, ControlFlowNode litCfg |
parent.getBody().contains(this.asExpr()) and
litCfg.getNode() = any(IntegerLiteral lit | lit.getValue() = version)
|
exists(If parent, CompareNode c | parent.getBody().contains(this.asExpr()) |
c.operands(API::moduleImport("sys")
.getMember("version_info")
.getASubscript()
.asSource()
.asCfgNode(), any(Eq eq), litCfg)
.asCfgNode(), any(Eq eq),
any(IntegerLiteral lit | lit.getValue() = version).getAFlowNode())
)
}

View File

@@ -1,4 +0,0 @@
consistencyOverview
| deadEnd | 1 |
deadEnd
| without_loop.py:7:5:7:9 | Break |

View File

@@ -9,7 +9,7 @@ Expr assignedValue(Name n) {
from Name def, DefinitionNode d
where
d.getNode() = def and
d = def.getAFlowNode() and
exists(assignedValue(def)) and
not d.getValue().getNode() = assignedValue(def)
select def.toString(), assignedValue(def)

View File

@@ -1,32 +0,0 @@
/**
* Phase -1 of the dataflow CFG migration: verifies that every variable
* binding visible to the AST (`Name.defines(v)`) corresponds to a CFG node
* in the new CFG (`semmle.python.controlflow.internal.AstNodeImpl`).
*
* The expected tag is `cfgdefines=<name>`. Each binding annotation in the
* test sources looks like `# $ cfgdefines=x` for a binding currently
* covered by the new CFG, or `# $ MISSING: cfgdefines=x` for a binding
* that is known to be uncovered (a "red" test case that should be
* green-flipped once the corresponding `cfg-ext-*` extension lands).
*/
import python
import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl
import utils.test.InlineExpectationsTest
module CfgBindingsTest implements TestSig {
string getARelevantTag() { result = "cfgdefines" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Name n, Variable v, CfgImpl::ControlFlowNode cfg |
n.defines(v) and
cfg.getAstNode().asExpr() = n and
location = n.getLocation() and
element = n.toString() and
tag = "cfgdefines" and
value = v.getId()
)
}
}
import MakeTest<CfgBindingsTest>

View File

@@ -1,13 +0,0 @@
# Annotated assignment (PEP 526). Both with and without an initializer.
a: int = 1 # $ cfgdefines=a
b: str = "hi" # $ cfgdefines=b
# Annotation without value: the AST records `c` as defined,
# and the new CFG now visits it via the AnnAssignStmt wrapper.
c: int # $ cfgdefines=c
class K: # $ cfgdefines=K
field: int = 0 # $ cfgdefines=field

View File

@@ -1,14 +0,0 @@
# Compound (tuple/list) assignment targets — actually wired in the new CFG.
a, b = (1, 2) # $ cfgdefines=a cfgdefines=b
[c, d] = [3, 4] # $ cfgdefines=c cfgdefines=d
# Nested unpacking.
(e, (f, g)) = (1, (2, 3)) # $ cfgdefines=e cfgdefines=f cfgdefines=g
# Star unpacking.
h, *i = [1, 2, 3] # $ cfgdefines=h cfgdefines=i
# Chained assignment with compound target.
j = k, l = (5, 6) # $ cfgdefines=j cfgdefines=k cfgdefines=l

View File

@@ -1,21 +0,0 @@
# Comprehension and `for` loop targets — wired in the new CFG.
# Comprehensions are nested function scopes with a synthetic `.0` parameter
# bound to the iterable.
# Bare-name `for` target.
for i in range(3): # $ cfgdefines=i
pass
# Compound `for` target.
for k, v in [(1, 2)]: # $ cfgdefines=k cfgdefines=v
pass
# Comprehension targets.
_ = [x for x in range(3)] # $ cfgdefines=_ cfgdefines=x cfgdefines=.0
_ = {y: z for y, z in []} # $ cfgdefines=_ cfgdefines=y cfgdefines=z cfgdefines=.0
_ = (a for a in []) # $ cfgdefines=_ cfgdefines=a cfgdefines=.0
# Nested comprehensions.
_ = [b for c in [] for b in c] # $ cfgdefines=_ cfgdefines=c cfgdefines=b cfgdefines=.0

View File

@@ -1,52 +0,0 @@
# Dead bindings under the "no expressions raise" CFG abstraction.
#
# The new CFG does not currently model raise edges from arbitrary
# expressions. As a consequence, code that is only reachable through
# exception flow is (correctly) classified as dead and has no CFG node.
# Variable bindings in dead code do not need CFG nodes - SSA / dataflow
# over dead code is moot.
#
# These tests act as a regression guard: the bindings below intentionally
# have no `cfgdefines=` annotations. If raise modelling is later added,
# the BindingsTest infrastructure will surface the new CFG nodes as
# unexpected results, and this file will need to be revisited.
def f(obj): # $ cfgdefines=f cfgdefines=obj
try:
return len(obj)
except TypeError:
pass
# The first try's body always returns; its except handler does not
# raise or otherwise transfer control, so under "no expressions
# raise" the only paths out of the try-statement are dead. Everything
# below is unreachable.
try:
hint = type(obj).__length_hint__
except AttributeError:
return None
return hint
def g(): # $ cfgdefines=g
try:
raise Exception("inner")
except:
raise Exception("outer")
else:
# Unreachable: the inner try body always raises, so the `else:`
# clause never runs.
hit_inner_else = True
def h(cache, key): # $ cfgdefines=h cfgdefines=cache cfgdefines=key
try:
return cache[key]
except KeyError:
pass
# Same pattern as `f`: dead under "no expressions raise".
value = compute(key)
cache[key] = value
return value

View File

@@ -1,30 +0,0 @@
# Decorated `def`/`class` — wired in the new CFG.
def deco(f): # $ cfgdefines=deco cfgdefines=f
return f
@deco
def decorated_func(): # $ cfgdefines=decorated_func
pass
@deco
class DecoratedClass: # $ cfgdefines=DecoratedClass
pass
# Stacked decorators.
@deco
@deco
def doubly(): # $ cfgdefines=doubly
pass
# Inside a class body.
class Outer: # $ cfgdefines=Outer
@staticmethod
def inner(): # $ cfgdefines=inner
pass

View File

@@ -1,19 +0,0 @@
# Exception-handler name bindings. These are already wired in the new
# CFG provided the try body can raise; `raise` statements are reliably
# treated as exception sources.
try:
raise ValueError("oops")
except ValueError as e: # $ cfgdefines=e
pass
try:
raise TypeError("oops")
except (TypeError, KeyError) as err: # $ cfgdefines=err
pass
# Exception groups (Python 3.11+).
try:
raise ValueError("oops")
except* ValueError as eg: # $ cfgdefines=eg
pass

View File

@@ -1,14 +0,0 @@
# Import aliases — all bound names below are now reachable via the new
# CFG's `ImportStmt` wrapper.
import os # $ cfgdefines=os
import os.path # $ cfgdefines=os
import os as o # $ cfgdefines=o
from os import path # $ cfgdefines=path
from os import path as p # $ cfgdefines=p
from os import sep, linesep # $ cfgdefines=sep cfgdefines=linesep
from os import (
getcwd, # $ cfgdefines=getcwd
getcwdb, # $ cfgdefines=getcwdb
)

View File

@@ -1,24 +0,0 @@
# Match-statement pattern bindings — wired in the new CFG.
def f(subject): # $ cfgdefines=f cfgdefines=subject
match subject:
case x: # $ cfgdefines=x
pass
case [a, b]: # $ cfgdefines=a cfgdefines=b
pass
case {"k": v}: # $ cfgdefines=v
pass
case Point(p, q): # $ cfgdefines=p cfgdefines=q
pass
case [_, *rest]: # $ cfgdefines=rest
pass
case (1 | 2) as n: # $ cfgdefines=n
pass
class Point: # $ cfgdefines=Point
__match_args__ = ("x", "y") # $ cfgdefines=__match_args__
x: int # $ cfgdefines=x
y: int # $ cfgdefines=y

View File

@@ -1,42 +0,0 @@
# Function parameters.
def positional(a, b): # $ cfgdefines=positional cfgdefines=a cfgdefines=b
pass
def with_default(x=1, y=2): # $ cfgdefines=with_default cfgdefines=x cfgdefines=y
pass
def with_vararg(*args): # $ cfgdefines=with_vararg cfgdefines=args
pass
def with_kwarg(**kwargs): # $ cfgdefines=with_kwarg cfgdefines=kwargs
pass
def with_kwonly(*, k1, k2=5): # $ cfgdefines=with_kwonly cfgdefines=k1 cfgdefines=k2
pass
def kitchen_sink(a, b=2, *args, k1, k2=5, **kw): # $ cfgdefines=kitchen_sink cfgdefines=a cfgdefines=b cfgdefines=args cfgdefines=k1 cfgdefines=k2 cfgdefines=kw
pass
# Methods get `self` / `cls`.
class C: # $ cfgdefines=C
def method(self, x): # $ cfgdefines=method cfgdefines=self cfgdefines=x
pass
@classmethod
def cmethod(cls, x): # $ cfgdefines=cmethod cfgdefines=cls cfgdefines=x
pass
# Lambda parameter.
_ = lambda p: p + 1 # $ cfgdefines=_ cfgdefines=p
# PEP 570 positional-only.
def pos_only(a, b, /, c): # $ cfgdefines=pos_only cfgdefines=a cfgdefines=b cfgdefines=c
pass

View File

@@ -1,14 +0,0 @@
# Simple bindings that should already work in the new CFG.
# No MISSING annotations expected.
x = 1 # $ cfgdefines=x
y = x + 1 # $ cfgdefines=y
def f(): # $ cfgdefines=f
pass
class C: # $ cfgdefines=C
pass
# Re-assignment.
x = 2 # $ cfgdefines=x

View File

@@ -1,21 +0,0 @@
# PEP 695 type parameters (Python 3.12+).
# PEP 695 type-param names on `def`/`class` bind in an annotation scope
# that nests the function/class body — they have no CFG node in the
# enclosing scope (matching the legacy CFG).
def func[T](x: T) -> T: # $ cfgdefines=func cfgdefines=x
return x
class Box[T]: # $ cfgdefines=Box
item: T # $ cfgdefines=item
# Multi-parameter, with bound and variadics.
def multi[T: int, *Ts, **P](x: T, *args: *Ts, **kwargs: P.kwargs) -> T: # $ cfgdefines=multi cfgdefines=x cfgdefines=args cfgdefines=kwargs
return x
# `type` statement (PEP 695).
type Alias[T] = list[T] # $ cfgdefines=Alias cfgdefines=T

Some files were not shown because too many files have changed in this diff Show More