mirror of
https://github.com/github/codeql.git
synced 2026-07-29 23:00:09 +02:00
119 lines
4.9 KiB
Plaintext
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)
|
|
)
|
|
}
|
|
}
|