Skip to content

Commit 3a4cd5a

Browse files
committed
Python: Add some CFG-validation queries
These use the annotated, self-verifying test files to check various consistency requirements. Some of these may be expressing the same thing in different ways, but it's fairly cheap to keep them around, so I have not attempted to produce a minimal set of queries for this.
1 parent b46d74d commit 3a4cd5a

12 files changed

+128
-0
lines changed

python/ql/test/library-tests/ControlFlow/evaluation-order/AllLiveReachable.expected

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Checks that every live (non-dead) annotation in the test function's
3+
* own scope is reachable from the function entry in the CFG.
4+
* Annotations in nested scopes (generators, async, lambdas, comprehensions)
5+
* have separate CFGs and are excluded from this check.
6+
*/
7+
8+
import python
9+
import TimerUtils
10+
11+
from TimerCfgNode a, TestFunction f
12+
where
13+
not a.isDead() and
14+
f = a.getTestFunction() and
15+
a.getScope() = f and
16+
not f.getEntryNode().getBasicBlock().reaches(a.getBasicBlock())
17+
select a, "Unreachable live annotation; entry of $@ does not reach this node", f, f.getName()

python/ql/test/library-tests/ControlFlow/evaluation-order/BasicBlockAnnotationGap.expected

Whitespace-only changes.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Checks that within a basic block, if a node is annotated then its
3+
* successor is also annotated (or excluded). A gap in annotations
4+
* within a basic block indicates a missing annotation, since there
5+
* are no branches to justify the gap.
6+
*
7+
* Nodes with exceptional successors are excluded, as the exception
8+
* edge leaves the basic block and the normal successor may be dead.
9+
*/
10+
11+
import python
12+
import TimerUtils
13+
14+
from TimerCfgNode a, ControlFlowNode succ
15+
where
16+
exists(BasicBlock bb, int i |
17+
a = bb.getNode(i) and
18+
succ = bb.getNode(i + 1)
19+
) and
20+
not succ instanceof TimerCfgNode and
21+
not isUnannotatable(succ.getNode()) and
22+
not isTimerMechanism(succ.getNode(), a.getTestFunction()) and
23+
not exists(a.getAnExceptionalSuccessor()) and
24+
succ.getNode() instanceof Expr
25+
select a, "Annotated node followed by unannotated $@ in the same basic block", succ,
26+
succ.getNode().toString()

python/ql/test/library-tests/ControlFlow/evaluation-order/ContiguousTimestamps.expected

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Checks that timestamps form a contiguous sequence {0, 1, ..., max}
3+
* within each test function. Every integer in the range must appear
4+
* in at least one annotation (live or dead).
5+
*/
6+
7+
import python
8+
import TimerUtils
9+
10+
from TestFunction f, int missing, int maxTs, TimerAnnotation maxAnn
11+
where
12+
maxTs = max(TimerAnnotation a | a.getTestFunction() = f | a.getATimestamp()) and
13+
maxAnn.getTestFunction() = f and
14+
maxAnn.getATimestamp() = maxTs and
15+
missing = [0 .. maxTs] and
16+
not exists(TimerAnnotation a | a.getTestFunction() = f and a.getATimestamp() = missing)
17+
select f, "Missing timestamp " + missing + " (max is $@)", maxAnn.getTimestampExpr(maxTs),
18+
maxTs.toString()

python/ql/test/library-tests/ControlFlow/evaluation-order/NoBackwardFlow.expected

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Checks that time never flows backward between consecutive timer annotations
3+
* in the CFG. For each pair of consecutive annotated nodes (A -> B), there must
4+
* exist timestamps a in A and b in B with a < b.
5+
*/
6+
7+
import python
8+
import TimerUtils
9+
10+
from TimerCfgNode a, TimerCfgNode b, int minA, int maxB
11+
where
12+
nextTimerAnnotation(a, b) and
13+
not a.isDead() and
14+
not b.isDead() and
15+
minA = min(a.getATimestamp()) and
16+
maxB = max(b.getATimestamp()) and
17+
minA >= maxB
18+
select a, "Backward flow: $@ flows to $@ (max timestamp $@)", a.getTimestampExpr(minA),
19+
minA.toString(), b, b.getNode().toString(), b.getTimestampExpr(maxB), maxB.toString()

python/ql/test/library-tests/ControlFlow/evaluation-order/NoSharedReachable.expected

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Checks that two annotations sharing a timestamp value are on
3+
* mutually exclusive CFG paths (neither can reach the other).
4+
*/
5+
6+
import python
7+
import TimerUtils
8+
9+
from TimerCfgNode a, TimerCfgNode b, int ts
10+
where
11+
a != b and
12+
not a.isDead() and
13+
not b.isDead() and
14+
a.getTestFunction() = b.getTestFunction() and
15+
ts = a.getATimestamp() and
16+
ts = b.getATimestamp() and
17+
(
18+
a.getBasicBlock().strictlyReaches(b.getBasicBlock())
19+
or
20+
exists(BasicBlock bb, int i, int j | a = bb.getNode(i) and b = bb.getNode(j) and i < j)
21+
)
22+
select a, "Shared timestamp $@ but this node reaches $@", a.getTimestampExpr(ts), ts.toString(), b,
23+
b.getNode().toString()

0 commit comments

Comments
 (0)