mirror of
https://github.com/github/codeql.git
synced 2026-08-02 00:13:00 +02:00
Preparatory refactor for the shared-CFG dataflow migration. Adds the new Python CFG library additively, without changing any production behaviour. Library additions: - semmle.python.controlflow.internal.AstNodeImpl — mediates between the Python AST and the shared codeql.controlflow.ControlFlowGraph signature. Wraps Python's Stmt/Expr/Scope/Pattern and adds two synthetic kinds of node (BlockStmt for body slots, intermediate nodes for multi-operand boolean expressions). - semmle.python.controlflow.internal.Cfg — public facade re-exposing the same API surface as semmle/python/Flow.qll (ControlFlowNode, CallNode, BasicBlock, NameNode, DefinitionNode, CompareNode, ...), backed by the shared CFG. - lib/printCfgNew.ql — debug/visualisation query for the new CFG. - consistency-queries/CfgConsistency.ql — consistency query running the shared CFG's standard checks against Python. Shared library: - shared.controlflow.ControlFlowGraph — adds two defaulted getWhileElse / getForeachElse predicates to AstSig so Python can model while-else / for-else (no behavioural change for other languages). Test additions: - ControlFlow/bindings/* — annotation-driven SSA-binding tests for the new CFG (annassign, compound, comprehension, decorated, except_handler, imports, match_pattern, parameters, simple, type_params, walrus_starred, with_stmt, dead_under_no_raise). - ControlFlow/store-load/* — basic store/load coverage. - ControlFlow/evaluation-order/NewCfg*.ql — mirrors of the existing OldCfg evaluation-order self-validation suite, run against the new CFG via NewCfgImpl.qll. - Minor extensions to existing test_if.py / test_boolean.py + cosmetic .expected churn on a handful of OldCfg tests. No dataflow, SSA, or production query is migrated yet — that lands in follow-up PRs. The new CFG library has zero callers in lib/ and src/. Verified by: - All lib + src + consistency-queries compile clean (367 queries). - All 56 ControlFlow library-tests pass. - All 474 dataflow + PointsTo library-tests + consistency tests pass. - syntax_error/CONSISTENCY/CfgConsistency passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
102 lines
3.5 KiB
Plaintext
102 lines
3.5 KiB
Plaintext
/**
|
|
* Implementation of the evaluation-order CFG signature using the new
|
|
* shared control flow graph from AstNodeImpl.
|
|
*/
|
|
|
|
private import python as Py
|
|
import TimerUtils
|
|
private import semmle.python.controlflow.internal.AstNodeImpl as CfgImpl
|
|
private import codeql.controlflow.SuccessorType
|
|
|
|
private class NewControlFlowNode = CfgImpl::ControlFlowNode;
|
|
|
|
private class NewBasicBlock = CfgImpl::BasicBlock;
|
|
|
|
/** New (shared) CFG implementation of the evaluation-order signature. */
|
|
module NewCfg implements EvalOrderCfgSig {
|
|
class CfgNode instanceof NewControlFlowNode {
|
|
// Use the post-order representative for each AST node: the "after" node.
|
|
// For simple leaf nodes this is the merged before/after node. For
|
|
// post-order expressions this is the TAstNode. For pre-order expressions
|
|
// (and/or/not/ternary) this uses an AfterValueNode, which places the
|
|
// expression after its operands — matching the timer test expectations.
|
|
CfgNode() { NewControlFlowNode.super.isAfter(_) }
|
|
|
|
string toString() { result = NewControlFlowNode.super.toString() }
|
|
|
|
Py::Location getLocation() { result = NewControlFlowNode.super.getLocation() }
|
|
|
|
Py::AstNode getNode() {
|
|
result = CfgImpl::astNodeToPyNode(NewControlFlowNode.super.getAstNode())
|
|
}
|
|
|
|
CfgNode getASuccessor() { nextCfgNode(this, result) }
|
|
|
|
CfgNode getATrueSuccessor() {
|
|
NewControlFlowNode.super.isAfterTrue(_) and
|
|
// Only where there's also a false branch (true boolean split)
|
|
exists(NewControlFlowNode other | other.isAfterFalse(NewControlFlowNode.super.getAstNode())) and
|
|
nextCfgNodeFrom(this, result)
|
|
}
|
|
|
|
CfgNode getAFalseSuccessor() {
|
|
NewControlFlowNode.super.isAfterFalse(_) and
|
|
// Only where there's also a true branch (true boolean split)
|
|
exists(NewControlFlowNode other | other.isAfterTrue(NewControlFlowNode.super.getAstNode())) and
|
|
nextCfgNodeFrom(this, result)
|
|
}
|
|
|
|
CfgNode getAnExceptionalSuccessor() {
|
|
exists(NewControlFlowNode mid |
|
|
mid = NewControlFlowNode.super.getAnExceptionSuccessor() and
|
|
nextCfgNodeFrom(mid, result)
|
|
)
|
|
}
|
|
|
|
Py::Scope getScope() { result = NewControlFlowNode.super.getEnclosingCallable().asScope() }
|
|
|
|
BasicBlock getBasicBlock() {
|
|
exists(NewBasicBlock bb, int i | bb.getNode(i) = this and result = bb)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds if `next` is the nearest CfgNode reachable from `n` via
|
|
* one or more raw CFG successor edges, skipping non-CfgNode intermediaries.
|
|
*/
|
|
private predicate nextCfgNodeFrom(NewControlFlowNode n, CfgNode next) {
|
|
next = n.getASuccessor()
|
|
or
|
|
exists(NewControlFlowNode mid |
|
|
mid = n.getASuccessor() and
|
|
not mid instanceof CfgNode and
|
|
nextCfgNodeFrom(mid, next)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `next` is the nearest CfgNode successor of `n`,
|
|
* skipping synthetic intermediate nodes.
|
|
*/
|
|
private predicate nextCfgNode(CfgNode n, CfgNode next) { nextCfgNodeFrom(n, next) }
|
|
|
|
class BasicBlock instanceof NewBasicBlock {
|
|
string toString() { result = NewBasicBlock.super.toString() }
|
|
|
|
CfgNode getNode(int n) { result = NewBasicBlock.super.getNode(n) }
|
|
|
|
predicate reaches(BasicBlock bb) { this = bb or this.strictlyReaches(bb) }
|
|
|
|
predicate strictlyReaches(BasicBlock bb) { NewBasicBlock.super.getASuccessor+() = bb }
|
|
|
|
predicate strictlyDominates(BasicBlock bb) { NewBasicBlock.super.strictlyDominates(bb) }
|
|
}
|
|
|
|
CfgNode scopeGetEntryNode(Py::Scope s) {
|
|
exists(CfgImpl::ControlFlow::EntryNode entry |
|
|
entry.getEnclosingCallable().asScope() = s and
|
|
nextCfgNodeFrom(entry, result)
|
|
)
|
|
}
|
|
}
|