Skip to content

Commit fea6017

Browse files
authored
Merge pull request github#17415 from paldepind/rust-control-flow-graph
Rust: Basic control flow graph setup
2 parents 4398421 + 857edb7 commit fea6017

File tree

11 files changed

+790
-0
lines changed

11 files changed

+790
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
private import rust
2+
private import ControlFlowGraph
3+
private import internal.SuccessorType
4+
private import internal.ControlFlowGraphImpl as Impl
5+
private import codeql.rust.generated.Raw
6+
private import codeql.rust.generated.Synth
7+
8+
final class BasicBlock = BasicBlockImpl;
9+
10+
/**
11+
* A basic block, that is, a maximal straight-line sequence of control flow nodes
12+
* without branches or joins.
13+
*/
14+
private class BasicBlockImpl extends TBasicBlockStart {
15+
/** Gets the scope of this basic block. */
16+
CfgScope getScope() { result = this.getAPredecessor().getScope() }
17+
18+
/** Gets an immediate successor of this basic block, if any. */
19+
BasicBlock getASuccessor() { result = this.getASuccessor(_) }
20+
21+
/** Gets an immediate successor of this basic block of a given type, if any. */
22+
BasicBlock getASuccessor(SuccessorType t) {
23+
result.getFirstNode() = this.getLastNode().getASuccessor(t)
24+
}
25+
26+
/** Gets an immediate predecessor of this basic block, if any. */
27+
BasicBlock getAPredecessor() { result.getASuccessor() = this }
28+
29+
/** Gets an immediate predecessor of this basic block of a given type, if any. */
30+
BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
31+
32+
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
33+
CfgNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
34+
35+
/** Gets a control flow node in this basic block. */
36+
CfgNode getANode() { result = this.getNode(_) }
37+
38+
/** Gets the first control flow node in this basic block. */
39+
CfgNode getFirstNode() { this = TBasicBlockStart(result) }
40+
41+
/** Gets the last control flow node in this basic block. */
42+
CfgNode getLastNode() { result = this.getNode(this.length() - 1) }
43+
44+
/** Gets the length of this basic block. */
45+
int length() { result = strictcount(this.getANode()) }
46+
47+
predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
48+
49+
predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
50+
51+
predicate dominates(BasicBlock bb) {
52+
bb = this or
53+
this.strictlyDominates(bb)
54+
}
55+
56+
predicate inDominanceFrontier(BasicBlock df) {
57+
this.dominatesPredecessor(df) and
58+
not this.strictlyDominates(df)
59+
}
60+
61+
/**
62+
* Holds if this basic block dominates a predecessor of `df`.
63+
*/
64+
private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
65+
66+
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
67+
68+
predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
69+
70+
predicate postDominates(BasicBlock bb) {
71+
this.strictlyPostDominates(bb) or
72+
this = bb
73+
}
74+
75+
/** Holds if this basic block is in a loop in the control flow graph. */
76+
predicate inLoop() { this.getASuccessor+() = this }
77+
78+
/** Gets a textual representation of this basic block. */
79+
string toString() { result = this.getFirstNode().toString() }
80+
81+
/** Gets the location of this basic block. */
82+
Location getLocation() { result = this.getFirstNode().getLocation() }
83+
}
84+
85+
cached
86+
private module Cached {
87+
/** Internal representation of basic blocks. */
88+
cached
89+
newtype TBasicBlock = TBasicBlockStart(CfgNode cfn) { startsBB(cfn) }
90+
91+
/** Holds if `cfn` starts a new basic block. */
92+
private predicate startsBB(CfgNode cfn) {
93+
not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
94+
or
95+
cfn.isJoin()
96+
or
97+
cfn.getAPredecessor().isBranch()
98+
}
99+
100+
/**
101+
* Holds if `succ` is a control flow successor of `pred` within
102+
* the same basic block.
103+
*/
104+
private predicate intraBBSucc(CfgNode pred, CfgNode succ) {
105+
succ = pred.getASuccessor() and
106+
not startsBB(succ)
107+
}
108+
109+
/**
110+
* Holds if `cfn` is the `i`th node in basic block `bb`.
111+
*
112+
* In other words, `i` is the shortest distance from a node `bb`
113+
* that starts a basic block to `cfn` along the `intraBBSucc` relation.
114+
*/
115+
cached
116+
predicate bbIndex(CfgNode bbStart, CfgNode cfn, int i) =
117+
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
118+
119+
/**
120+
* Holds if the first node of basic block `succ` is a control flow
121+
* successor of the last node of basic block `pred`.
122+
*/
123+
private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
124+
125+
/** Holds if `dom` is an immediate dominator of `bb`. */
126+
cached
127+
predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
128+
idominance(entryBB/1, succBB/2)(_, dom, bb)
129+
130+
/** Holds if `pred` is a basic block predecessor of `succ`. */
131+
private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
132+
133+
/** Holds if `bb` is an exit basic block that represents normal exit. */
134+
private predicate normalExitBB(BasicBlock bb) {
135+
bb.getANode().(Impl::AnnotatedExitNode).isNormal()
136+
}
137+
138+
/** Holds if `dom` is an immediate post-dominator of `bb`. */
139+
cached
140+
predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
141+
idominance(normalExitBB/1, predBB/2)(_, dom, bb)
142+
143+
/**
144+
* Gets the `i`th predecessor of join block `jb`, with respect to some
145+
* arbitrary order.
146+
*/
147+
cached
148+
JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) {
149+
result =
150+
rank[i + 1](JoinBlockPredecessor jbp |
151+
jbp = jb.getAPredecessor()
152+
|
153+
jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp)
154+
)
155+
}
156+
}
157+
158+
private import Cached
159+
160+
/** Holds if `bb` is an entry basic block. */
161+
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof Impl::EntryNode }
162+
163+
/**
164+
* An entry basic block, that is, a basic block whose first node is
165+
* an entry node.
166+
*/
167+
class EntryBasicBlock extends BasicBlockImpl {
168+
EntryBasicBlock() { entryBB(this) }
169+
170+
override CfgScope getScope() {
171+
this.getFirstNode() = any(Impl::EntryNode node | node.getScope() = result)
172+
}
173+
}
174+
175+
/**
176+
* An annotated exit basic block, that is, a basic block whose last node is
177+
* an annotated exit node.
178+
*/
179+
class AnnotatedExitBasicBlock extends BasicBlockImpl {
180+
private boolean normal;
181+
182+
AnnotatedExitBasicBlock() {
183+
exists(Impl::AnnotatedExitNode n |
184+
n = this.getANode() and
185+
if n.isNormal() then normal = true else normal = false
186+
)
187+
}
188+
189+
/** Holds if this block represent a normal exit. */
190+
final predicate isNormal() { normal = true }
191+
}
192+
193+
/**
194+
* An exit basic block, that is, a basic block whose last node is
195+
* an exit node.
196+
*/
197+
class ExitBasicBlock extends BasicBlockImpl {
198+
ExitBasicBlock() { this.getLastNode() instanceof Impl::ExitNode }
199+
}
200+
201+
private module JoinBlockPredecessors {
202+
private predicate id(Raw::AstNode x, Raw::AstNode y) { x = y }
203+
204+
private predicate idOfDbAstNode(Raw::AstNode x, int y) = equivalenceRelation(id/2)(x, y)
205+
206+
// TODO: does not work if fresh ipa entities (`ipa: on:`) turn out to be first of the block
207+
private predicate idOf(AstNode x, int y) { idOfDbAstNode(Synth::convertAstNodeToRaw(x), y) }
208+
209+
int getId(JoinBlockPredecessor jbp) {
210+
idOf(jbp.getFirstNode().(Impl::AstCfgNode).getAstNode(), result)
211+
or
212+
idOf(jbp.(EntryBasicBlock).getScope(), result)
213+
}
214+
215+
string getSplitString(JoinBlockPredecessor jbp) {
216+
result = jbp.getFirstNode().(Impl::AstCfgNode).getSplitsString()
217+
or
218+
not exists(jbp.getFirstNode().(Impl::AstCfgNode).getSplitsString()) and
219+
result = ""
220+
}
221+
}
222+
223+
/** A basic block with more than one predecessor. */
224+
class JoinBlock extends BasicBlockImpl {
225+
JoinBlock() { this.getFirstNode().isJoin() }
226+
227+
/**
228+
* Gets the `i`th predecessor of this join block, with respect to some
229+
* arbitrary order.
230+
*/
231+
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) }
232+
}
233+
234+
/** A basic block that is an immediate predecessor of a join block. */
235+
class JoinBlockPredecessor extends BasicBlockImpl {
236+
JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
237+
}
238+
239+
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
240+
class ConditionBlock extends BasicBlockImpl {
241+
ConditionBlock() { this.getLastNode().isCondition() }
242+
243+
/**
244+
* Holds if basic block `succ` is immediately controlled by this basic
245+
* block with conditional value `s`. That is, `succ` is an immediate
246+
* successor of this block, and `succ` can only be reached from
247+
* the callable entry point by going via the `s` edge out of this basic block.
248+
*/
249+
pragma[nomagic]
250+
predicate immediatelyControls(BasicBlock succ, SuccessorType s) {
251+
succ = this.getASuccessor(s) and
252+
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred))
253+
}
254+
255+
/**
256+
* Holds if basic block `controlled` is controlled by this basic block with
257+
* conditional value `s`. That is, `controlled` can only be reached from
258+
* the callable entry point by going via the `s` edge out of this basic block.
259+
*/
260+
predicate controls(BasicBlock controlled, BooleanSuccessor s) {
261+
exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled))
262+
}
263+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/** Provides classes representing the control flow graph. */
2+
3+
private import rust
4+
private import internal.ControlFlowGraphImpl
5+
private import internal.Completion
6+
private import internal.SuccessorType
7+
private import internal.Scope as Scope
8+
private import BasicBlocks
9+
10+
final class CfgScope = Scope::CfgScope;
11+
12+
/**
13+
* A control flow node.
14+
*
15+
* A control flow node is a node in the control flow graph (CFG). There is a
16+
* many-to-one relationship between CFG nodes and AST nodes.
17+
*
18+
* Only nodes that can be reached from an entry point are included in the CFG.
19+
*/
20+
final class CfgNode extends Node {
21+
/** Gets the file of this control flow node. */
22+
File getFile() { result = this.getLocation().getFile() }
23+
24+
/** Gets a successor node of a given type, if any. */
25+
CfgNode getASuccessor(SuccessorType t) { result = super.getASuccessor(t) }
26+
27+
/** Gets an immediate successor, if any. */
28+
CfgNode getASuccessor() { result = this.getASuccessor(_) }
29+
30+
/** Gets an immediate predecessor node of a given flow type, if any. */
31+
CfgNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
32+
33+
/** Gets an immediate predecessor, if any. */
34+
CfgNode getAPredecessor() { result = this.getAPredecessor(_) }
35+
36+
/** Gets the basic block that this control flow node belongs to. */
37+
BasicBlock getBasicBlock() { result.getANode() = this }
38+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
private import codeql.util.Boolean
2+
private import codeql.rust.controlflow.ControlFlowGraph
3+
private import rust
4+
private import SuccessorType
5+
6+
private newtype TCompletion =
7+
TSimpleCompletion() or
8+
TBooleanCompletion(Boolean b) or
9+
TReturnCompletion()
10+
11+
/** A completion of a statement or an expression. */
12+
abstract class Completion extends TCompletion {
13+
/** Gets a textual representation of this completion. */
14+
abstract string toString();
15+
16+
predicate isValidForSpecific(AstNode e) { none() }
17+
18+
predicate isValidFor(AstNode e) { this.isValidForSpecific(e) }
19+
20+
/** Gets a successor type that matches this completion. */
21+
abstract SuccessorType getAMatchingSuccessorType();
22+
}
23+
24+
/**
25+
* A completion that represents normal evaluation of a statement or an
26+
* expression.
27+
*/
28+
abstract class NormalCompletion extends Completion { }
29+
30+
/** A simple (normal) completion. */
31+
class SimpleCompletion extends NormalCompletion, TSimpleCompletion {
32+
override NormalSuccessor getAMatchingSuccessorType() { any() }
33+
34+
// `SimpleCompletion` is the "default" completion type, thus it is valid for
35+
// any node where there isn't another more specific completion type.
36+
override predicate isValidFor(AstNode e) { not any(Completion c).isValidForSpecific(e) }
37+
38+
override string toString() { result = "simple" }
39+
}
40+
41+
/**
42+
* A completion that represents evaluation of an expression, whose value
43+
* determines the successor.
44+
*/
45+
abstract class ConditionalCompletion extends NormalCompletion {
46+
boolean value;
47+
48+
bindingset[value]
49+
ConditionalCompletion() { any() }
50+
51+
/** Gets the Boolean value of this conditional completion. */
52+
final boolean getValue() { result = value }
53+
54+
/** Gets the dual completion. */
55+
abstract ConditionalCompletion getDual();
56+
}
57+
58+
/**
59+
* A completion that represents evaluation of an expression
60+
* with a Boolean value.
61+
*/
62+
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
63+
BooleanCompletion() { this = TBooleanCompletion(value) }
64+
65+
override predicate isValidForSpecific(AstNode e) { e = any(IfExpr c).getCondition() }
66+
67+
/** Gets the dual Boolean completion. */
68+
override BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
69+
70+
override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
71+
72+
override string toString() { result = "boolean(" + value + ")" }
73+
}
74+
75+
/**
76+
* A completion that represents a return.
77+
*/
78+
class ReturnCompletion extends TReturnCompletion, Completion {
79+
override ReturnSuccessor getAMatchingSuccessorType() { any() }
80+
81+
override predicate isValidForSpecific(AstNode e) { e instanceof ReturnExpr }
82+
83+
override string toString() { result = "return" }
84+
}
85+
86+
/** Hold if `c` represents normal evaluation of a statement or an expression. */
87+
predicate completionIsNormal(Completion c) { c instanceof NormalCompletion }
88+
89+
/** Hold if `c` represents simple and normal evaluation of a statement or an expression. */
90+
predicate completionIsSimple(Completion c) { c instanceof SimpleCompletion }
91+
92+
/** Holds if `c` is a valid completion for `n`. */
93+
predicate completionIsValidFor(Completion c, AstNode n) { c.isValidFor(n) }

0 commit comments

Comments
 (0)