Skip to content

Commit e4c7a31

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, update downstream CFG consumers and tests, and make shared abrupt completion construction precise. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 6f546d6 commit e4c7a31

29 files changed

Lines changed: 9392 additions & 6203 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 & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -3,124 +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-
not e instanceof ParenExpr and
17-
exists(AstNode last, Completion c |
18-
CfgImpl::last(e, last, c) and
19-
last != e and
20-
c instanceof NormalCompletion
21-
)
22-
}
23-
24-
/**
25-
* All `Expr` nodes are `PostOrderTree`s
26-
*/
27-
query predicate nonPostOrderExpr(Expr e, string cls) {
28-
nonPostOrderExpr(e) and
29-
cls = e.getPrimaryQlClasses()
30-
}
31-
32-
/**
33-
* Holds if CFG scope `scope` lacks an initial AST node. Overrides shared consistency predicate.
34-
*/
35-
query predicate scopeNoFirst(CfgScope scope) {
36-
Consistency::scopeNoFirst(scope) and
37-
not scope =
38-
[
39-
any(AstNode f | not f.(Callable).hasBody()),
40-
any(AsyncBlockExpr b | not b.hasStmtList())
41-
]
42-
}
43-
44-
/** Holds if `be` is the `else` branch of a `let` statement that results in a panic. */
45-
private predicate letElsePanic(BlockExpr be) {
46-
be = any(LetStmt let).getLetElse().getBlockExpr() and
47-
exists(Completion c | CfgImpl::last(be, _, c) | completionIsNormal(c))
48-
}
49-
50-
/**
51-
* Holds if `node` is lacking a successor. Overrides shared consistency predicate.
52-
*/
53-
query predicate deadEnd(CfgImpl::Node node) {
54-
Consistency::deadEnd(node) and
55-
successfullyExtractedFile(node.getLocation().getFile()) and
56-
not letElsePanic(node.getAstNode())
57-
}
9+
import CfgImpl::ControlFlow::Consistency
5810

5911
pragma[nomagic]
6012
private predicate successfullyExtractedFile(File f) {
61-
not exists(ExtractionWarning ee | ee.getLocation().getFile() = f)
13+
not exists(ExtractionWarning warning | warning.getLocation().getFile() = f)
6214
}
6315

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

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

0 commit comments

Comments
 (0)