Skip to content

Commit 9b0d84c

Browse files
authored
Merge pull request github#9268 from MathiasVP/swift-add-cfg-library
Swift: Extend AST classes and add control-flow library
2 parents 905a37c + 4ba2984 commit 9b0d84c

37 files changed

+8443
-40
lines changed

config/identical-files.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,8 @@
500500
],
501501
"CFG": [
502502
"csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll",
503-
"ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll"
503+
"ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll",
504+
"swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImplShared.qll"
504505
],
505506
"TypeTracker": [
506507
"python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll",
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/** Provides classes representing basic blocks. */
2+
3+
private import swift
4+
private import ControlFlowGraph
5+
private import internal.ControlFlowGraphImpl
6+
private import CfgNodes
7+
private import SuccessorTypes
8+
9+
/**
10+
* A basic block, that is, a maximal straight-line sequence of control flow nodes
11+
* without branches or joins.
12+
*/
13+
class BasicBlock extends TBasicBlockStart {
14+
/** Gets the scope of this basic block. */
15+
CfgScope getScope() { result = this.getAPredecessor().getScope() }
16+
17+
/** Gets an immediate successor of this basic block, if any. */
18+
BasicBlock getASuccessor() { result = this.getASuccessor(_) }
19+
20+
/** Gets an immediate successor of this basic block of a given type, if any. */
21+
BasicBlock getASuccessor(SuccessorType t) {
22+
result.getFirstNode() = this.getLastNode().getASuccessor(t)
23+
}
24+
25+
/** Gets an immediate predecessor of this basic block, if any. */
26+
BasicBlock getAPredecessor() { result.getASuccessor() = this }
27+
28+
/** Gets an immediate predecessor of this basic block of a given type, if any. */
29+
BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
30+
31+
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
32+
ControlFlowNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
33+
34+
/** Gets a control flow node in this basic block. */
35+
ControlFlowNode getANode() { result = this.getNode(_) }
36+
37+
/** Gets the first control flow node in this basic block. */
38+
ControlFlowNode getFirstNode() { this = TBasicBlockStart(result) }
39+
40+
/** Gets the last control flow node in this basic block. */
41+
ControlFlowNode getLastNode() { result = this.getNode(this.length() - 1) }
42+
43+
/** Gets the length of this basic block. */
44+
int length() { result = strictcount(this.getANode()) }
45+
46+
predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
47+
48+
predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
49+
50+
predicate dominates(BasicBlock bb) {
51+
bb = this or
52+
this.strictlyDominates(bb)
53+
}
54+
55+
predicate inDominanceFrontier(BasicBlock df) {
56+
this.dominatesPredecessor(df) and
57+
not this.strictlyDominates(df)
58+
}
59+
60+
/**
61+
* Holds if this basic block dominates a predecessor of `df`.
62+
*/
63+
private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
64+
65+
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
66+
67+
predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
68+
69+
predicate postDominates(BasicBlock bb) {
70+
this.strictlyPostDominates(bb) or
71+
this = bb
72+
}
73+
74+
/** Holds if this basic block is in a loop in the control flow graph. */
75+
predicate inLoop() { this.getASuccessor+() = this }
76+
77+
/** Gets a textual representation of this basic block. */
78+
string toString() { result = this.getFirstNode().toString() }
79+
80+
/** Gets the location of this basic block. */
81+
Location getLocation() { result = this.getFirstNode().getLocation() }
82+
}
83+
84+
cached
85+
private module Cached {
86+
/** Internal representation of basic blocks. */
87+
cached
88+
newtype TBasicBlock = TBasicBlockStart(ControlFlowNode cfn) { startsBB(cfn) }
89+
90+
/** Holds if `cfn` starts a new basic block. */
91+
private predicate startsBB(ControlFlowNode cfn) {
92+
not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
93+
or
94+
cfn.isJoin()
95+
or
96+
cfn.getAPredecessor().isBranch()
97+
}
98+
99+
/**
100+
* Holds if `succ` is a control flow successor of `pred` within
101+
* the same basic block.
102+
*/
103+
private predicate intraBBSucc(ControlFlowNode pred, ControlFlowNode succ) {
104+
succ = pred.getASuccessor() and
105+
not startsBB(succ)
106+
}
107+
108+
/**
109+
* Holds if `cfn` is the `i`th node in basic block `bb`.
110+
*
111+
* In other words, `i` is the shortest distance from a node `bb`
112+
* that starts a basic block to `cfn` along the `intraBBSucc` relation.
113+
*/
114+
cached
115+
predicate bbIndex(ControlFlowNode bbStart, ControlFlowNode cfn, int i) =
116+
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
117+
118+
/**
119+
* Holds if the first node of basic block `succ` is a control flow
120+
* successor of the last node of basic block `pred`.
121+
*/
122+
private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
123+
124+
/** Holds if `dom` is an immediate dominator of `bb`. */
125+
cached
126+
predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
127+
idominance(entryBB/1, succBB/2)(_, dom, bb)
128+
129+
/** Holds if `pred` is a basic block predecessor of `succ`. */
130+
private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
131+
132+
/** Holds if `bb` is an exit basic block that represents normal exit. */
133+
private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() }
134+
135+
/** Holds if `dom` is an immediate post-dominator of `bb`. */
136+
cached
137+
predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
138+
idominance(normalExitBB/1, predBB/2)(_, dom, bb)
139+
140+
/**
141+
* Gets the `i`th predecessor of join block `jb`, with respect to some
142+
* arbitrary order.
143+
*/
144+
cached
145+
JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) {
146+
result =
147+
rank[i + 1](JoinBlockPredecessor jbp |
148+
jbp = jb.getAPredecessor()
149+
|
150+
jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp)
151+
)
152+
}
153+
}
154+
155+
private import Cached
156+
157+
/** Holds if `bb` is an entry basic block. */
158+
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode }
159+
160+
/**
161+
* An entry basic block, that is, a basic block whose first node is
162+
* an entry node.
163+
*/
164+
class EntryBasicBlock extends BasicBlock {
165+
EntryBasicBlock() { entryBB(this) }
166+
167+
override CfgScope getScope() { this.getFirstNode() = TEntryNode(result) }
168+
}
169+
170+
/**
171+
* An annotated exit basic block, that is, a basic block whose last node is
172+
* an annotated exit node.
173+
*/
174+
class AnnotatedExitBasicBlock extends BasicBlock {
175+
private boolean normal;
176+
177+
AnnotatedExitBasicBlock() {
178+
exists(AnnotatedExitNode n |
179+
n = this.getANode() and
180+
if n.isNormal() then normal = true else normal = false
181+
)
182+
}
183+
184+
/** Holds if this block represent a normal exit. */
185+
final predicate isNormal() { normal = true }
186+
}
187+
188+
/**
189+
* An exit basic block, that is, a basic block whose last node is
190+
* an exit node.
191+
*/
192+
class ExitBasicBlock extends BasicBlock {
193+
ExitBasicBlock() { this.getLastNode() instanceof ExitNode }
194+
}
195+
196+
private module JoinBlockPredecessors {
197+
private predicate id(AstNode x, AstNode y) { x = y }
198+
199+
private predicate idOf(AstNode x, int y) = equivalenceRelation(id/2)(x, y)
200+
201+
int getId(JoinBlockPredecessor jbp) {
202+
idOf(jbp.getFirstNode().(AstCfgNode).getNode(), result)
203+
or
204+
idOf(jbp.(EntryBasicBlock).getScope(), result)
205+
}
206+
207+
string getSplitString(JoinBlockPredecessor jbp) {
208+
result = jbp.getFirstNode().(AstCfgNode).getSplitsString()
209+
or
210+
not exists(jbp.getFirstNode().(AstCfgNode).getSplitsString()) and
211+
result = ""
212+
}
213+
}
214+
215+
/** A basic block with more than one predecessor. */
216+
class JoinBlock extends BasicBlock {
217+
JoinBlock() { this.getFirstNode().isJoin() }
218+
219+
/**
220+
* Gets the `i`th predecessor of this join block, with respect to some
221+
* arbitrary order.
222+
*/
223+
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) }
224+
}
225+
226+
/** A basic block that is an immediate predecessor of a join block. */
227+
class JoinBlockPredecessor extends BasicBlock {
228+
JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
229+
}
230+
231+
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
232+
class ConditionBlock extends BasicBlock {
233+
ConditionBlock() { this.getLastNode().isCondition() }
234+
235+
/**
236+
* Holds if basic block `succ` is immediately controlled by this basic
237+
* block with conditional value `s`. That is, `succ` is an immediate
238+
* successor of this block, and `succ` can only be reached from
239+
* the callable entry point by going via the `s` edge out of this basic block.
240+
*/
241+
pragma[nomagic]
242+
predicate immediatelyControls(BasicBlock succ, SuccessorType s) {
243+
succ = this.getASuccessor(s) and
244+
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred))
245+
}
246+
247+
/**
248+
* Holds if basic block `controlled` is controlled by this basic block with
249+
* conditional value `s`. That is, `controlled` can only be reached from
250+
* the callable entry point by going via the `s` edge out of this basic block.
251+
*/
252+
predicate controls(BasicBlock controlled, BooleanSuccessor s) {
253+
exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled))
254+
}
255+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/** Provides classes representing nodes in a control flow graph. */
2+
3+
private import swift
4+
private import BasicBlocks
5+
private import ControlFlowGraph
6+
private import internal.ControlFlowGraphImpl
7+
private import internal.Splitting
8+
9+
/** An entry node for a given scope. */
10+
class EntryNode extends ControlFlowNode, TEntryNode {
11+
private CfgScope scope;
12+
13+
EntryNode() { this = TEntryNode(scope) }
14+
15+
final override EntryBasicBlock getBasicBlock() { result = ControlFlowNode.super.getBasicBlock() }
16+
17+
final override Location getLocation() { result = scope.getLocation() }
18+
19+
final override string toString() { result = "enter " + scope }
20+
}
21+
22+
/** An exit node for a given scope, annotated with the type of exit. */
23+
class AnnotatedExitNode extends ControlFlowNode, TAnnotatedExitNode {
24+
private CfgScope scope;
25+
private boolean normal;
26+
27+
AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
28+
29+
/** Holds if this node represent a normal exit. */
30+
final predicate isNormal() { normal = true }
31+
32+
final override AnnotatedExitBasicBlock getBasicBlock() {
33+
result = ControlFlowNode.super.getBasicBlock()
34+
}
35+
36+
final override Location getLocation() { result = scope.getLocation() }
37+
38+
final override string toString() {
39+
exists(string s |
40+
normal = true and s = "normal"
41+
or
42+
normal = false and s = "abnormal"
43+
|
44+
result = "exit " + scope + " (" + s + ")"
45+
)
46+
}
47+
}
48+
49+
/** An exit node for a given scope. */
50+
class ExitNode extends ControlFlowNode, TExitNode {
51+
private CfgScope scope;
52+
53+
ExitNode() { this = TExitNode(scope) }
54+
55+
final override Location getLocation() { result = scope.getLocation() }
56+
57+
final override string toString() { result = "exit " + scope }
58+
}
59+
60+
/**
61+
* A node for an AST node.
62+
*
63+
* Each AST node maps to zero or more `AstCfgNode`s: zero when the node is unreachable
64+
* (dead) code or not important for control flow, and multiple when there are different
65+
* splits for the AST node.
66+
*/
67+
class AstCfgNode extends ControlFlowNode, TElementNode {
68+
private Splits splits;
69+
private AstNode n;
70+
71+
AstCfgNode() { this = TElementNode(_, n, splits) }
72+
73+
final override AstNode getNode() { result = n }
74+
75+
override Location getLocation() { result = n.getLocation() }
76+
77+
final override string toString() {
78+
exists(string s | s = n.toString() |
79+
result = "[" + this.getSplitsString() + "] " + s
80+
or
81+
not exists(this.getSplitsString()) and result = s
82+
)
83+
}
84+
85+
/** Gets a comma-separated list of strings for each split in this node, if any. */
86+
final string getSplitsString() {
87+
result = splits.toString() and
88+
result != ""
89+
}
90+
91+
/** Gets a split for this control flow node, if any. */
92+
final Split getASplit() { result = splits.getASplit() }
93+
}
94+
95+
/** A control-flow node that wraps an AST expression. */
96+
class ExprCfgNode extends AstCfgNode {
97+
Expr e;
98+
99+
ExprCfgNode() { e = this.getNode() }
100+
101+
/** Gets the underlying expression. */
102+
Expr getExpr() { result = e }
103+
}

0 commit comments

Comments
 (0)