Skip to content

Commit 8cc8c5e

Browse files
hvitvedCopilot
andcommitted
Migrate Rust to shared control-flow graph
Replace Rust's legacy completion and splitting CFG with the shared control-flow framework, and update downstream CFG consumers and tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 2e4caa1 commit 8cc8c5e

28 files changed

Lines changed: 9354 additions & 6162 deletions

File tree

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,71 @@
11
private import codeql.controlflow.BasicBlock as BB
22
private import codeql.Locations
3+
private import codeql.rust.elements.internal.generated.Raw
4+
private import codeql.rust.elements.internal.generated.Synth
35
private import codeql.rust.controlflow.ControlFlowGraph as ControlFlowGraph
46
private import internal.ControlFlowGraphImpl as CfgImpl
5-
private import CfgImpl::BasicBlocks as BasicBlocksImpl
67

7-
class BasicBlock = BasicBlocksImpl::BasicBlock;
8+
class BasicBlock = CfgImpl::Cfg::BasicBlock;
89

9-
final class EntryBasicBlock = BasicBlocksImpl::EntryBasicBlock;
10+
/** A basic block containing the entry node of a callable. */
11+
final class EntryBasicBlock extends BasicBlock {
12+
EntryBasicBlock() { this.getFirstNode() instanceof CfgImpl::ControlFlow::EntryNode }
13+
}
14+
15+
/** A basic block containing the final exit node of a callable. */
16+
final class ExitBasicBlock extends BasicBlock {
17+
ExitBasicBlock() { this.getLastNode() instanceof CfgImpl::ControlFlow::ExitNode }
18+
}
19+
20+
/** A basic block containing an annotated normal or exceptional exit node. */
21+
final class AnnotatedExitBasicBlock extends BasicBlock {
22+
AnnotatedExitBasicBlock() { this.getANode() instanceof CfgImpl::ControlFlow::AnnotatedExitNode }
23+
24+
/** Holds if this block represents normal exit. */
25+
predicate isNormal() { this.getANode() instanceof CfgImpl::ControlFlow::NormalExitNode }
26+
}
1027

11-
final class ExitBasicBlock = BasicBlocksImpl::ExitBasicBlock;
28+
/** A basic block whose last node is a condition. */
29+
final class ConditionBasicBlock extends BasicBlock {
30+
ConditionBasicBlock() { this.getLastNode().(ControlFlowGraph::CfgNode).isCondition() }
31+
}
32+
33+
/** A basic block whose first node joins multiple predecessors. */
34+
final class JoinBasicBlock extends BasicBlock {
35+
JoinBasicBlock() { this.getFirstNode().(ControlFlowGraph::CfgNode).isJoin() }
36+
37+
/** Gets the `i`th predecessor of this join block in a stable arbitrary order. */
38+
JoinPredecessorBasicBlock getJoinBlockPredecessor(int i) {
39+
result =
40+
rank[i + 1](JoinPredecessorBasicBlock pred, int id, string tag |
41+
pred = this.getAPredecessor() and
42+
id = getNodeId(pred.getFirstNode()) and
43+
tag = pred.getFirstNode().getIdTag()
44+
|
45+
pred order by id, tag
46+
)
47+
}
48+
}
1249

13-
final class AnnotatedExitBasicBlock = BasicBlocksImpl::AnnotatedExitBasicBlock;
50+
/** A predecessor basic block of a join block. */
51+
final class JoinPredecessorBasicBlock extends BasicBlock {
52+
JoinPredecessorBasicBlock() { this.getASuccessor() instanceof JoinBasicBlock }
53+
}
1454

15-
final class ConditionBasicBlock = BasicBlocksImpl::ConditionBasicBlock;
55+
private predicate id(Raw::AstNode x, Raw::AstNode y) { x = y }
1656

17-
final class JoinBasicBlock = BasicBlocksImpl::JoinBasicBlock;
57+
private predicate idOfDbAstNode(Raw::AstNode x, int y) = equivalenceRelation(id/2)(x, y)
1858

19-
final class JoinPredecessorBasicBlock = BasicBlocksImpl::JoinPredecessorBasicBlock;
59+
private int getNodeId(CfgImpl::ControlFlowNode node) {
60+
idOfDbAstNode(Synth::convertAstNodeToRaw(node.getAstNode()), result)
61+
}
2062

2163
module Cfg implements BB::CfgSig<Location> {
22-
class ControlFlowNode = ControlFlowGraph::CfgNode;
64+
class ControlFlowNode = CfgImpl::Cfg::ControlFlowNode;
2365

24-
class BasicBlock = BasicBlocksImpl::BasicBlock;
66+
class BasicBlock = CfgImpl::Cfg::BasicBlock;
2567

26-
class EntryBasicBlock = BasicBlocksImpl::EntryBasicBlock;
68+
class EntryBasicBlock = CfgImpl::Cfg::EntryBasicBlock;
2769

28-
predicate dominatingEdge = BasicBlocksImpl::dominatingEdge/2;
70+
predicate dominatingEdge = CfgImpl::Cfg::dominatingEdge/2;
2971
}

‎rust/ql/lib/codeql/rust/controlflow/CfgNodes.qll‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@
55

66
private import rust
77
private import ControlFlowGraph
8-
private import internal.ControlFlowGraphImpl as CfgImpl
98
private import internal.CfgNodes
109
import Nodes
1110

12-
class AstCfgNode = CfgImpl::AstCfgNode;
11+
/**
12+
* A CFG node that uniquely represents an AST node.
13+
*/
14+
class AstCfgNode extends CfgNode {
15+
AstCfgNode() { this.injects(_) }
16+
17+
/** Gets the represented AST node. */
18+
AstNode getAstNode() { this.injects(result) }
19+
}
1320

14-
class ExitCfgNode = CfgImpl::ExitNode;
21+
/** A CFG node representing the final exit from a callable. */
22+
class ExitCfgNode extends CfgNode instanceof ControlFlow::ExitNode { }
1523

16-
class AnnotatedExitCfgNode = CfgImpl::AnnotatedExitNode;
24+
/** A CFG node representing a normal or exceptional exit from a callable. */
25+
class AnnotatedExitCfgNode extends CfgNode instanceof ControlFlow::AnnotatedExitNode { }
1726

1827
/** A variable access. */
1928
final class VariableAccessCfgNode extends PathExprBaseCfgNode {
Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
/** Provides classes representing the control flow graph. */
22

3-
private import internal.ControlFlowGraphImpl
4-
private import internal.Scope as Scope
3+
private import internal.ControlFlowGraphImpl as CfgImpl
54
import codeql.controlflow.SuccessorType
65

7-
final class CfgScope = Scope::CfgScope;
6+
final class CfgScope = CfgImpl::CfgScope;
87

9-
final class CfgNode = Node;
8+
/**
9+
* A node in the control flow graph.
10+
*/
11+
class CfgNode extends CfgImpl::ControlFlowNode {
12+
/** Gets the CFG scope containing this node. */
13+
CfgScope getScope() { result = this.getEnclosingCallable() }
14+
15+
/** Holds if this node has conditional successors. */
16+
predicate isCondition() { exists(this.getASuccessor(any(ConditionalSuccessor t))) }
17+
18+
/** Holds if this node has more than one predecessor. */
19+
predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
20+
21+
/** Holds if this node has more than one successor. */
22+
predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
23+
}
24+
25+
module ControlFlow = CfgImpl::ControlFlow;

‎rust/ql/lib/codeql/rust/controlflow/internal/CfgConsistency.qll‎

Lines changed: 9 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,123 +3,30 @@
33
*/
44

55
private import rust
6-
private import codeql.rust.controlflow.internal.ControlFlowGraphImpl::Consistency as Consistency
7-
import Consistency
8-
private import codeql.rust.controlflow.ControlFlowGraph
96
private import codeql.rust.controlflow.internal.ControlFlowGraphImpl as CfgImpl
10-
private import codeql.rust.controlflow.internal.Completion
117
private import codeql.rust.controlflow.internal.CfgNodes::Consistency as CfgNodes
128
private import codeql.rust.Diagnostics
13-
14-
private predicate nonPostOrderExpr(Expr e) {
15-
not e instanceof LetExpr and
16-
exists(AstNode last, Completion c |
17-
CfgImpl::last(e, last, c) and
18-
last != e and
19-
c instanceof NormalCompletion
20-
)
21-
}
22-
23-
/**
24-
* All `Expr` nodes are `PostOrderTree`s
25-
*/
26-
query predicate nonPostOrderExpr(Expr e, string cls) {
27-
nonPostOrderExpr(e) and
28-
cls = e.getPrimaryQlClasses()
29-
}
30-
31-
/**
32-
* Holds if CFG scope `scope` lacks an initial AST node. Overrides shared consistency predicate.
33-
*/
34-
query predicate scopeNoFirst(CfgScope scope) {
35-
Consistency::scopeNoFirst(scope) and
36-
not scope =
37-
[
38-
any(AstNode f | not f.(Callable).hasBody()),
39-
any(AsyncBlockExpr b | not b.hasStmtList())
40-
]
41-
}
42-
43-
/** Holds if `be` is the `else` branch of a `let` statement that results in a panic. */
44-
private predicate letElsePanic(BlockExpr be) {
45-
be = any(LetStmt let).getLetElse().getBlockExpr() and
46-
exists(Completion c | CfgImpl::last(be, _, c) | completionIsNormal(c))
47-
}
48-
49-
/**
50-
* Holds if `node` is lacking a successor. Overrides shared consistency predicate.
51-
*/
52-
query predicate deadEnd(CfgImpl::Node node) {
53-
Consistency::deadEnd(node) and
54-
successfullyExtractedFile(node.getLocation().getFile()) and
55-
not letElsePanic(node.getAstNode())
56-
}
9+
import CfgImpl::ControlFlow::Consistency
5710

5811
pragma[nomagic]
5912
private predicate successfullyExtractedFile(File f) {
60-
not exists(ExtractionWarning ee | ee.getLocation().getFile() = f)
13+
not exists(ExtractionWarning warning | warning.getLocation().getFile() = f)
6114
}
6215

63-
query predicate missingCfgChild(CfgNode parent, string pred, int i, AstNode child) {
16+
query predicate missingCfgChild(CfgImpl::ControlFlowNode parent, string pred, int i, AstNode child) {
6417
CfgNodes::missingCfgChild(parent, pred, i, child) and
65-
successfullyExtractedFile(child.getLocation().getFile()) and
66-
not exists(AstNode last, CfgImpl::Completion c | CfgImpl::last(child, last, c) |
67-
// In for example `if (a && true) ...`, there is no edge from the CFG node
68-
// for `true` into the `[false] a && true` node.
69-
strictcount(ConditionalSuccessor cs | exists(last.getACfgNode().getASuccessor(cs)) | cs) = 1
70-
or
71-
// In for example `x && return`, there is no edge from the node for
72-
// `return` into the `&&` node.
73-
not c instanceof CfgImpl::NormalCompletion
74-
)
18+
successfullyExtractedFile(child.getLocation().getFile())
7519
}
7620

7721
/**
7822
* Gets counts of control flow graph inconsistencies of each type.
7923
*/
8024
int getCfgInconsistencyCounts(string type) {
81-
// total results from all the CFG consistency query predicates in:
82-
// - `codeql.rust.controlflow.internal.CfgConsistency` (this file)
83-
// - `shared.controlflow.codeql.controlflow.Cfg`
84-
type = "Non-unique set representation" and
85-
result = count(CfgImpl::Splits ss | nonUniqueSetRepresentation(ss, _) | ss)
86-
or
87-
type = "Splitting invariant 2" and
88-
result = count(AstNode n | breakInvariant2(n, _, _, _, _, _) | n)
89-
or
90-
type = "Splitting invariant 3" and
91-
result = count(AstNode n | breakInvariant3(n, _, _, _, _, _) | n)
92-
or
93-
type = "Splitting invariant 4" and
94-
result = count(AstNode n | breakInvariant4(n, _, _, _, _, _) | n)
95-
or
96-
type = "Splitting invariant 5" and
97-
result = count(AstNode n | breakInvariant5(n, _, _, _, _, _) | n)
98-
or
99-
type = "Multiple successors of the same type" and
100-
result = count(CfgNode n | multipleSuccessors(n, _, _) | n)
101-
or
102-
type = "Simple and normal successors" and
103-
result = count(CfgNode n | simpleAndNormalSuccessors(n, _, _, _, _) | n)
104-
or
105-
type = "Dead end" and
106-
result = count(CfgNode n | deadEnd(n) | n)
107-
or
108-
type = "Non-unique split kind" and
109-
result = count(CfgImpl::SplitImpl si | nonUniqueSplitKind(si, _) | si)
110-
or
111-
type = "Non-unique list order" and
112-
result = count(CfgImpl::SplitKind sk | nonUniqueListOrder(sk, _) | sk)
113-
or
114-
type = "Multiple toStrings" and
115-
result = count(CfgNode n | multipleToString(n) | n)
116-
or
117-
type = "CFG scope lacks initial AST node" and
118-
result = count(CfgScope s | scopeNoFirst(s) | s)
119-
or
120-
type = "Non-PostOrderTree Expr node" and
121-
result = count(Expr e | nonPostOrderExpr(e) | e)
25+
CfgImpl::ControlFlow::Consistency::consistencyOverview(type, result)
12226
or
12327
type = "Missing CFG child" and
124-
result = count(CfgNode parent, string pred, int child | missingCfgChild(parent, pred, child, _))
28+
result =
29+
count(CfgImpl::ControlFlowNode parent, string pred, int child |
30+
missingCfgChild(parent, pred, child, _)
31+
)
12532
}

0 commit comments

Comments
 (0)