mirror of
https://github.com/github/codeql.git
synced 2026-05-27 17:41:24 +02:00
Python: bring Cfg.qll's facade to API parity with Flow.qll
Adds the methods and type-narrowing overrides needed for Cfg.qll to be
a drop-in replacement for Flow.qll's CFG API surface:
* 'override getNode()' type narrowing on all AST-shape subclasses
(CallNode -> Py::Call, AttrNode -> Py::Attribute, ImportExprNode
-> Py::ImportExpr, etc.). This lets callers chain methods like
'iexpr.getNode().isRelative()' that previously failed because
'getNode()' returned the generic AstNode.
* 'ControlFlowNode.isBranch()' -- true and/or false successor exists.
* 'ControlFlowNode.getAChild()' -- CFG-level child traversal via the
AST's getAChildNode, with dominance constraint.
* 'ControlFlowNode.strictlyReaches(other)' -- node-level reachability.
* 'NameNode.isSelf()' -- AST-level approximation: uses the 'Variable'
that is the first parameter of an enclosing method.
* 'BinaryExprNode.operands(left, op, right)' + 'getAnOperand()'.
* 'BoolExprNode.getAnOperand()'.
* 'ForNode.getSequence()' (alias for 'getIter') and
'ForNode.iterates(target, sequence)'.
* 'ForNode' / 'RaiseStmtNode' type-narrowing overrides.
* 'ExceptFlowNode.getName()' / 'ExceptGroupFlowNode.getName()'
-- the bound 'as'-name CFG node.
* 'DictNode.getAKey()' (only 'getAValue' was present).
These additions are independent of the dataflow-migration approach
(option 4 vs option 5). They close the API-parity gap identified
during the Option-5 investigation; with them in place, hundreds of
type-resolution errors that previously appeared when swapping Cfg for
Flow at the python.qll level go away.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -213,6 +213,31 @@ class ControlFlowNode extends CfgImpl::ControlFlowNode {
|
||||
/** Holds if this flow node corresponds to a class definition expression. */
|
||||
predicate isClass() { toAst(this) instanceof Py::ClassExpr }
|
||||
|
||||
/**
|
||||
* Holds if this flow node is a branch (i.e. has both a true and a
|
||||
* false successor).
|
||||
*/
|
||||
predicate isBranch() { exists(this.getATrueSuccessor()) or exists(this.getAFalseSuccessor()) }
|
||||
|
||||
/**
|
||||
* Gets a CFG child of this node, defined as a CFG node whose AST node
|
||||
* is a child of this CFG node's AST node, restricted to nodes that
|
||||
* dominate this one (so the child has been evaluated by the time we
|
||||
* reach this node).
|
||||
*
|
||||
* Mirrors `Flow.qll`'s `getAChild`. UnaryExprNode is excluded because
|
||||
* its operand is its CFG predecessor (handled separately).
|
||||
*/
|
||||
pragma[nomagic]
|
||||
ControlFlowNode getAChild() {
|
||||
toAst(this).(Py::Expr).getAChildNode() = toAst(result) and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock()) and
|
||||
not this instanceof UnaryExprNode
|
||||
}
|
||||
|
||||
/** Holds if this flow node strictly reaches `other`. */
|
||||
predicate strictlyReaches(ControlFlowNode other) { this.getASuccessor+() = other }
|
||||
|
||||
/** Internal: raw successor predicate that does NOT skip non-canonical nodes. */
|
||||
CfgImpl::ControlFlowNode getASuccessorRaw() { result = super.getASuccessor() }
|
||||
}
|
||||
@@ -422,6 +447,23 @@ class NameNode extends ControlFlowNode {
|
||||
|
||||
/** Holds if this is a use of a global (including builtin) variable. */
|
||||
predicate isGlobal() { exists(Py::Variable v | this.uses(v) and v instanceof Py::GlobalVariable) }
|
||||
|
||||
/**
|
||||
* Holds if this is a use of `self` — the first parameter of an
|
||||
* enclosing method.
|
||||
*
|
||||
* AST-level approximation: matches when the Name uses a `Variable`
|
||||
* that is the first parameter of an enclosing `Function` defined
|
||||
* inside a `Class`.
|
||||
*/
|
||||
predicate isSelf() {
|
||||
exists(Py::Variable v, Py::Function f, Py::Class c |
|
||||
this.uses(v) and
|
||||
f = c.getAMethod() and
|
||||
v.getScope() = f and
|
||||
v = f.getArg(0).(Py::Name).getVariable()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a named constant (`None`, `True`, `False`). */
|
||||
@@ -433,6 +475,8 @@ class NameConstantNode extends NameNode {
|
||||
class CallNode extends ControlFlowNode {
|
||||
CallNode() { toAst(this) instanceof Py::Call }
|
||||
|
||||
override Py::Call getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the underlying Python `Call`. */
|
||||
Py::Call getCall() { result = toAst(this) }
|
||||
|
||||
@@ -501,6 +545,8 @@ class CallNode extends ControlFlowNode {
|
||||
class AttrNode extends ControlFlowNode {
|
||||
AttrNode() { toAst(this) instanceof Py::Attribute }
|
||||
|
||||
override Py::Attribute getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the flow node for the object of the attribute expression. */
|
||||
ControlFlowNode getObject() {
|
||||
exists(Py::Attribute a |
|
||||
@@ -527,12 +573,16 @@ class AttrNode extends ControlFlowNode {
|
||||
/** A control flow node corresponding to an import statement (`import x`). */
|
||||
class ImportExprNode extends ControlFlowNode {
|
||||
ImportExprNode() { toAst(this) instanceof Py::ImportExpr }
|
||||
|
||||
override Py::ImportExpr getNode() { result = super.getNode() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a `from ... import name` expression. */
|
||||
class ImportMemberNode extends ControlFlowNode {
|
||||
ImportMemberNode() { toAst(this) instanceof Py::ImportMember }
|
||||
|
||||
override Py::ImportMember getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the flow node for the module being imported from, with the matching name. */
|
||||
ControlFlowNode getModule(string name) {
|
||||
exists(Py::ImportMember i |
|
||||
@@ -548,6 +598,8 @@ class ImportMemberNode extends ControlFlowNode {
|
||||
class ImportStarNode extends ControlFlowNode {
|
||||
ImportStarNode() { toAst(this) instanceof Py::ImportStar }
|
||||
|
||||
override Py::ImportStar getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the flow node for the module being imported from. */
|
||||
ControlFlowNode getModule() {
|
||||
exists(Py::ImportStar i |
|
||||
@@ -562,6 +614,8 @@ class ImportStarNode extends ControlFlowNode {
|
||||
class SubscriptNode extends ControlFlowNode {
|
||||
SubscriptNode() { toAst(this) instanceof Py::Subscript }
|
||||
|
||||
override Py::Subscript getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the flow node for the value being subscripted. */
|
||||
ControlFlowNode getObject() {
|
||||
exists(Py::Subscript s |
|
||||
@@ -585,6 +639,8 @@ class SubscriptNode extends ControlFlowNode {
|
||||
class CompareNode extends ControlFlowNode {
|
||||
CompareNode() { toAst(this) instanceof Py::Compare }
|
||||
|
||||
override Py::Compare getNode() { result = super.getNode() }
|
||||
|
||||
/** Holds if `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 |
|
||||
@@ -605,6 +661,8 @@ class CompareNode extends ControlFlowNode {
|
||||
class IfExprNode extends ControlFlowNode {
|
||||
IfExprNode() { toAst(this) instanceof Py::IfExp }
|
||||
|
||||
override Py::IfExp getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the flow node for one of the operands of an if-expression. */
|
||||
ControlFlowNode getAnOperand() { result = this.getAPredecessor() }
|
||||
}
|
||||
@@ -613,6 +671,8 @@ class IfExprNode extends ControlFlowNode {
|
||||
class AssignmentExprNode extends ControlFlowNode {
|
||||
AssignmentExprNode() { toAst(this) instanceof Py::AssignExpr }
|
||||
|
||||
override Py::AssignExpr getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the flow node for the left-hand side. */
|
||||
ControlFlowNode getTarget() {
|
||||
exists(Py::AssignExpr a |
|
||||
@@ -636,6 +696,8 @@ class AssignmentExprNode extends ControlFlowNode {
|
||||
class BinaryExprNode extends ControlFlowNode {
|
||||
BinaryExprNode() { toAst(this) instanceof Py::BinaryExpr }
|
||||
|
||||
override Py::BinaryExpr getNode() { result = super.getNode() }
|
||||
|
||||
ControlFlowNode getLeft() {
|
||||
exists(Py::BinaryExpr be |
|
||||
be = toAst(this) and
|
||||
@@ -653,19 +715,40 @@ class BinaryExprNode extends ControlFlowNode {
|
||||
}
|
||||
|
||||
Py::Operator getOp() { result = toAst(this).(Py::BinaryExpr).getOp() }
|
||||
|
||||
/** Holds if `left` and `right` are the operands and `op` is the operator. */
|
||||
predicate operands(ControlFlowNode left, Py::Operator op, ControlFlowNode right) {
|
||||
left = this.getLeft() and right = this.getRight() and op = this.getOp()
|
||||
}
|
||||
|
||||
/** Gets either operand. */
|
||||
ControlFlowNode getAnOperand() { result = this.getLeft() or result = this.getRight() }
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a boolean expression (`a and b`, `a or b`). */
|
||||
class BoolExprNode extends ControlFlowNode {
|
||||
BoolExprNode() { toAst(this) instanceof Py::BoolExpr }
|
||||
|
||||
override Py::BoolExpr getNode() { result = super.getNode() }
|
||||
|
||||
Py::Boolop getOp() { result = toAst(this).(Py::BoolExpr).getOp() }
|
||||
|
||||
/** Gets any operand of this boolean expression. */
|
||||
ControlFlowNode getAnOperand() {
|
||||
exists(Py::BoolExpr be |
|
||||
be = toAst(this) and
|
||||
be.getAValue() = toAst(result) and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a unary expression (`-x`, `not x`, etc.). */
|
||||
class UnaryExprNode extends ControlFlowNode {
|
||||
UnaryExprNode() { toAst(this) instanceof Py::UnaryExpr }
|
||||
|
||||
override Py::UnaryExpr getNode() { result = super.getNode() }
|
||||
|
||||
ControlFlowNode getOperand() {
|
||||
exists(Py::UnaryExpr u |
|
||||
u = toAst(this) and
|
||||
@@ -709,11 +792,16 @@ class DeletionNode extends ControlFlowNode {
|
||||
class ForNode extends ControlFlowNode {
|
||||
ForNode() { exists(Py::For f | toAst(this) = f.getIter()) }
|
||||
|
||||
override Py::For getNode() { exists(Py::For f | toAst(this) = f.getIter() | result = f) }
|
||||
|
||||
/** Gets the iterable expression. */
|
||||
ControlFlowNode getIter() {
|
||||
result = this and result = result // canonical "after" of the iterable
|
||||
}
|
||||
|
||||
/** Gets the sequence expression (alias for `getIter()`, matches legacy Flow naming). */
|
||||
ControlFlowNode getSequence() { result = this.getIter() }
|
||||
|
||||
/** Gets the target (loop variable) of the `for` loop. */
|
||||
ControlFlowNode getTarget() {
|
||||
exists(Py::For f |
|
||||
@@ -721,12 +809,19 @@ class ForNode extends ControlFlowNode {
|
||||
f.getTarget() = toAst(result)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `target` is the loop variable and `sequence` is the iterable. */
|
||||
predicate iterates(ControlFlowNode target, ControlFlowNode sequence) {
|
||||
target = this.getTarget() and sequence = this.getSequence()
|
||||
}
|
||||
}
|
||||
|
||||
/** A control flow node corresponding to a `raise` statement. */
|
||||
class RaiseStmtNode extends ControlFlowNode {
|
||||
RaiseStmtNode() { toAst(this) instanceof Py::Raise }
|
||||
|
||||
override Py::Raise getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the exception expression, if any. */
|
||||
ControlFlowNode getException() {
|
||||
exists(Py::Raise r |
|
||||
@@ -755,6 +850,9 @@ class StarredNode extends ControlFlowNode {
|
||||
class ExceptFlowNode extends ControlFlowNode {
|
||||
ExceptFlowNode() { exists(Py::ExceptStmt e | toAst(this) = e.getName()) }
|
||||
|
||||
/** Gets the CFG node for the bound `as`-name itself. */
|
||||
ControlFlowNode getName() { result = this }
|
||||
|
||||
/** Gets the type expression of this exception handler. */
|
||||
ControlFlowNode getType() {
|
||||
exists(Py::ExceptStmt e |
|
||||
@@ -768,6 +866,9 @@ class ExceptFlowNode extends ControlFlowNode {
|
||||
/** A control flow node corresponding to an `except*` clause's name binding. */
|
||||
class ExceptGroupFlowNode extends ControlFlowNode {
|
||||
ExceptGroupFlowNode() { exists(Py::ExceptGroupStmt e | toAst(this) = e.getName()) }
|
||||
|
||||
/** Gets the CFG node for the bound `as`-name itself. */
|
||||
ControlFlowNode getName() { result = this }
|
||||
}
|
||||
|
||||
/** Abstract base class for sequence nodes (tuple, list). */
|
||||
@@ -823,6 +924,15 @@ class SetNode extends ControlFlowNode {
|
||||
class DictNode extends ControlFlowNode {
|
||||
DictNode() { toAst(this) instanceof Py::Dict }
|
||||
|
||||
/** Gets the flow node for a key of the dict. */
|
||||
ControlFlowNode getAKey() {
|
||||
exists(Py::Dict d |
|
||||
d = toAst(this) and
|
||||
d.getAKey() = toAst(result) and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the flow node for a value of the dict. */
|
||||
ControlFlowNode getAValue() {
|
||||
exists(Py::Dict d |
|
||||
|
||||
Reference in New Issue
Block a user