Files
2026-07-27 17:37:36 +02:00

119 lines
4.9 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 {
// We must pick a *unique* representative CFG node for each AST node. The
// shared CFG has several nodes per AST node (before / in-post-order / after
// / after-value splits), but the timer test framework keys annotations on
// `getNode()` and assumes one CFG node per annotated AST node. Without a
// filter, an annotated `f()` would map to both `f()` and `After f()`, which
// breaks two framework invariants: (1) the "no shared reachable" check
// requires that two distinct nodes sharing a timestamp be mutually
// unreachable (true/false branches of a condition), but `Before f()`,
// `f()` and `After f()` share the annotation's timestamp *and* lie on one
// linear path; and (2) the annotation walk (`nextTimerAnnotation`) halts at
// the first reachable representative, so a second node for the same AST
// node would stall the walk on the same timestamp instead of advancing to
// the next evaluation event.
//
// We use the "after" node (`isAfter`) rather than the canonical `injects`
// node, because `injects` represents short-circuit / conditional
// expressions (`and`/`or`/`not`/ternary) by their *before* node, placing
// them ahead of their operands — wrong for evaluation order. `isAfter`
// instead picks the post-evaluation node: the merged before/after node for
// simple leaves, the `TAfterNode` for post-order expressions, and the
// `AfterValueNode`(s) for pre-order conditionals, all positioned after the
// operands. The two value-split nodes of a conditional are genuinely
// distinct evaluation outcomes (handled by `getATrueSuccessor` /
// `getAFalseSuccessor`), so they do not violate the uniqueness assumption.
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 | bb.getNode(_) = 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)
)
}
}