Python: Fully remove points-to from Flow.qll

Gets rid of a bunch of predicates relating to reachability (which
depended on the modelling of exceptions, which uses points-to), moving
them to `LegacyPointsTo`. In the process, we gained a new class
`BasicBlockWithPointsTo`.
This commit is contained in:
Taus
2025-10-31 14:05:33 +00:00
parent 7176898503
commit 21e74a3f01
4 changed files with 62 additions and 57 deletions

View File

@@ -106,6 +106,24 @@ class ControlFlowNodeWithPointsTo extends ControlFlowNode {
// for that variable. // for that variable.
exists(SsaVariable v | v.getAUse() = this | varHasCompletePointsToSet(v)) exists(SsaVariable v | v.getAUse() = this | varHasCompletePointsToSet(v))
} }
/** Whether it is unlikely that this ControlFlowNode can be reached */
predicate unlikelyReachable() {
not start_bb_likely_reachable(this.getBasicBlock())
or
exists(BasicBlock b |
start_bb_likely_reachable(b) and
not end_bb_likely_reachable(b) and
// If there is an unlikely successor edge earlier in the BB
// than this node, then this node must be unreachable.
exists(ControlFlowNode p, int i, int j |
p.(RaisingNode).unlikelySuccessor(_) and
p = b.getNode(i) and
this = b.getNode(j) and
i < j
)
)
}
} }
/** /**
@@ -134,6 +152,45 @@ private predicate varHasCompletePointsToSet(SsaVariable var) {
) )
} }
private predicate start_bb_likely_reachable(BasicBlock b) {
exists(Scope s | s.getEntryNode() = b.getNode(_))
or
exists(BasicBlock pred |
pred = b.getAPredecessor() and
end_bb_likely_reachable(pred) and
not pred.getLastNode().(RaisingNode).unlikelySuccessor(b)
)
}
private predicate end_bb_likely_reachable(BasicBlock b) {
start_bb_likely_reachable(b) and
not exists(ControlFlowNode p, ControlFlowNode s |
p.(RaisingNode).unlikelySuccessor(s) and
p = b.getNode(_) and
s = b.getNode(_) and
not p = b.getLastNode()
)
}
/**
* An extension of `BasicBlock` that provides points-to related methods.
*/
class BasicBlockWithPointsTo extends BasicBlock {
/**
* Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ.
*/
predicate unlikelySuccessor(BasicBlockWithPointsTo succ) {
this.getLastNode().(RaisingNode).unlikelySuccessor(succ.firstNode())
or
not end_bb_likely_reachable(this) and succ = this.getASuccessor()
}
/**
* Whether (as inferred by type inference) this basic block is likely to be reachable.
*/
predicate likelyReachable() { start_bb_likely_reachable(this) }
}
/** /**
* An extension of `Expr` that provides points-to predicates. * An extension of `Expr` that provides points-to predicates.
*/ */

View File

@@ -1,7 +1,6 @@
import python import python
private import semmle.python.internal.CachedStages private import semmle.python.internal.CachedStages
private import codeql.controlflow.BasicBlock as BB private import codeql.controlflow.BasicBlock as BB
private import LegacyPointsTo
/* /*
* Note about matching parent and child nodes and CFG splitting: * Note about matching parent and child nodes and CFG splitting:
@@ -191,24 +190,6 @@ class ControlFlowNode extends @py_flow_node {
/** Whether this node is a normal (non-exceptional) exit */ /** Whether this node is a normal (non-exceptional) exit */
predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) } predicate isNormalExit() { py_scope_flow(this, _, 0) or py_scope_flow(this, _, 2) }
/** Whether it is unlikely that this ControlFlowNode can be reached */
predicate unlikelyReachable() {
not start_bb_likely_reachable(this.getBasicBlock())
or
exists(BasicBlock b |
start_bb_likely_reachable(b) and
not end_bb_likely_reachable(b) and
// If there is an unlikely successor edge earlier in the BB
// than this node, then this node must be unreachable.
exists(ControlFlowNode p, int i, int j |
p.(RaisingNode).unlikelySuccessor(_) and
p = b.getNode(i) and
this = b.getNode(j) and
i < j
)
)
}
/** Whether this strictly dominates other. */ /** Whether this strictly dominates other. */
pragma[inline] pragma[inline]
predicate strictlyDominates(ControlFlowNode other) { predicate strictlyDominates(ControlFlowNode other) {
@@ -1005,7 +986,8 @@ class BasicBlock extends @py_flow_node {
) )
} }
private ControlFlowNode firstNode() { result = this } /** Gets the first node in this basic block */
ControlFlowNode firstNode() { result = this }
/** Gets the last node in this basic block */ /** Gets the last node in this basic block */
ControlFlowNode getLastNode() { ControlFlowNode getLastNode() {
@@ -1094,15 +1076,6 @@ class BasicBlock extends @py_flow_node {
) )
} }
/**
* Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ.
*/
predicate unlikelySuccessor(BasicBlock succ) {
this.getLastNode().(RaisingNode).unlikelySuccessor(succ.firstNode())
or
not end_bb_likely_reachable(this) and succ = this.getASuccessor()
}
/** Holds if this basic block strictly reaches the other. Is the start of other reachable from the end of this. */ /** Holds if this basic block strictly reaches the other. Is the start of other reachable from the end of this. */
cached cached
predicate strictlyReaches(BasicBlock other) { predicate strictlyReaches(BasicBlock other) {
@@ -1113,11 +1086,6 @@ class BasicBlock extends @py_flow_node {
/** Holds if this basic block reaches the other. Is the start of other reachable from the end of this. */ /** Holds if this basic block reaches the other. Is the start of other reachable from the end of this. */
predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) } predicate reaches(BasicBlock other) { this = other or this.strictlyReaches(other) }
/**
* Whether (as inferred by type inference) this basic block is likely to be reachable.
*/
predicate likelyReachable() { start_bb_likely_reachable(this) }
/** /**
* Gets the `ConditionBlock`, if any, that controls this block and * Gets the `ConditionBlock`, if any, that controls this block and
* does not control any other `ConditionBlock`s that control this block. * does not control any other `ConditionBlock`s that control this block.
@@ -1145,26 +1113,6 @@ class BasicBlock extends @py_flow_node {
} }
} }
private predicate start_bb_likely_reachable(BasicBlock b) {
exists(Scope s | s.getEntryNode() = b.getNode(_))
or
exists(BasicBlock pred |
pred = b.getAPredecessor() and
end_bb_likely_reachable(pred) and
not pred.getLastNode().(RaisingNode).unlikelySuccessor(b)
)
}
private predicate end_bb_likely_reachable(BasicBlock b) {
start_bb_likely_reachable(b) and
not exists(ControlFlowNode p, ControlFlowNode s |
p.(RaisingNode).unlikelySuccessor(s) and
p = b.getNode(_) and
s = b.getNode(_) and
not p = b.getLastNode()
)
}
private class ControlFlowNodeAlias = ControlFlowNode; private class ControlFlowNodeAlias = ControlFlowNode;
final private class FinalBasicBlock = BasicBlock; final private class FinalBasicBlock = BasicBlock;

View File

@@ -29,9 +29,9 @@ class FunctionMetrics extends Function {
*/ */
int getCyclomaticComplexity() { int getCyclomaticComplexity() {
exists(int e, int n | exists(int e, int n |
n = count(BasicBlock b | b = this.getABasicBlock() and b.likelyReachable()) and n = count(BasicBlockWithPointsTo b | b = this.getABasicBlock() and b.likelyReachable()) and
e = e =
count(BasicBlock b1, BasicBlock b2 | count(BasicBlockWithPointsTo b1, BasicBlockWithPointsTo b2 |
b1 = this.getABasicBlock() and b1 = this.getABasicBlock() and
b1.likelyReachable() and b1.likelyReachable() and
b2 = this.getABasicBlock() and b2 = this.getABasicBlock() and

View File

@@ -92,7 +92,7 @@ class SsaVariable extends @py_ssa_var {
} }
/** Gets the incoming edges for a Phi node, pruned of unlikely edges. */ /** Gets the incoming edges for a Phi node, pruned of unlikely edges. */
private BasicBlock getAPrunedPredecessorBlockForPhi() { private BasicBlockWithPointsTo getAPrunedPredecessorBlockForPhi() {
result = this.getAPredecessorBlockForPhi() and result = this.getAPredecessorBlockForPhi() and
not result.unlikelySuccessor(this.getDefinition().getBasicBlock()) not result.unlikelySuccessor(this.getDefinition().getBasicBlock())
} }