Skip to content

Commit a935bde

Browse files
committed
Rust: CFG for match expressions
1 parent 04aa7b4 commit a935bde

File tree

3 files changed

+90
-9
lines changed

3 files changed

+90
-9
lines changed

rust/ql/lib/codeql/rust/controlflow/internal/Completion.qll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ private import SuccessorType
66
private newtype TCompletion =
77
TSimpleCompletion() or
88
TBooleanCompletion(Boolean b) or
9+
TMatchCompletion(Boolean isMatch) or
910
TBreakCompletion() or
1011
TContinueCompletion() or
1112
TReturnCompletion()
@@ -53,6 +54,10 @@ abstract class ConditionalCompletion extends NormalCompletion {
5354
/** Gets the Boolean value of this conditional completion. */
5455
final boolean getValue() { result = value }
5556

57+
final predicate succeeded() { value = true }
58+
59+
final predicate failed() { value = false }
60+
5661
/** Gets the dual completion. */
5762
abstract ConditionalCompletion getDual();
5863
}
@@ -71,6 +76,8 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
7176
expr.getOp() = ["&&", "||"] and
7277
e = [expr.getLhs(), expr.getRhs()]
7378
)
79+
or
80+
any(MatchArm arm).getGuard() = e
7481
}
7582

7683
/** Gets the dual Boolean completion. */
@@ -81,6 +88,22 @@ class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
8188
override string toString() { result = "boolean(" + value + ")" }
8289
}
8390

91+
/**
92+
* A completion that represents the result of a pattern match.
93+
*/
94+
class MatchCompletion extends TMatchCompletion, ConditionalCompletion {
95+
MatchCompletion() { this = TMatchCompletion(value) }
96+
97+
override predicate isValidForSpecific(AstNode e) { e = any(MatchArm arm).getPat() }
98+
99+
override MatchSuccessor getAMatchingSuccessorType() { result.getValue() = value }
100+
101+
/** Gets the dual match completion. */
102+
override MatchCompletion getDual() { result = TMatchCompletion(value.booleanNot()) }
103+
104+
override string toString() { result = "match(" + value + ")" }
105+
}
106+
84107
/**
85108
* A completion that represents a break.
86109
*/

rust/ql/lib/codeql/rust/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,13 @@ class LogicalOrBinaryOpExprTree extends PreOrderTree instanceof BinaryExpr {
9494
// Edge from the last node in the lhs to the first node in the rhs
9595
last(super.getLhs(), pred, c) and
9696
first(super.getRhs(), succ) and
97-
c.(BooleanCompletion).getValue() = false
97+
c.(BooleanCompletion).failed()
9898
}
9999

100100
override predicate last(AstNode node, Completion c) {
101101
// Lhs. as the last node
102102
last(super.getLhs(), node, c) and
103-
c.(BooleanCompletion).getValue() = true
103+
c.(BooleanCompletion).succeeded()
104104
or
105105
// Rhs. as the last node
106106
last(super.getRhs(), node, c) // and
@@ -124,13 +124,13 @@ class LogicalAndBinaryOpExprTree extends PreOrderTree instanceof BinaryExpr {
124124
// Edge from the last node in the lhs to the first node in the rhs
125125
last(super.getLhs(), pred, c) and
126126
first(super.getRhs(), succ) and
127-
c.(BooleanCompletion).getValue() = true
127+
c.(BooleanCompletion).succeeded()
128128
}
129129

130130
override predicate last(AstNode node, Completion c) {
131131
// Lhs. as the last node
132132
last(super.getLhs(), node, c) and
133-
c.(BooleanCompletion).getValue() = false
133+
c.(BooleanCompletion).failed()
134134
or
135135
// Rhs. as the last node
136136
last(super.getRhs(), node, c)
@@ -205,11 +205,11 @@ class IfExprTree extends PostOrderTree instanceof IfExpr {
205205
// Edges from the condition to the branches
206206
last(super.getCondition(), pred, c) and
207207
(
208-
first(super.getThen(), succ) and c.(BooleanCompletion).getValue() = true
208+
first(super.getThen(), succ) and c.(BooleanCompletion).succeeded()
209209
or
210-
first(super.getElse(), succ) and c.(BooleanCompletion).getValue() = false
210+
first(super.getElse(), succ) and c.(BooleanCompletion).failed()
211211
or
212-
not super.hasElse() and succ = this and c.(BooleanCompletion).getValue() = false
212+
not super.hasElse() and succ = this and c.(BooleanCompletion).failed()
213213
)
214214
or
215215
// An edge from the then branch to the last node
@@ -272,6 +272,52 @@ class LoopExprTree extends PostOrderTree instanceof LoopExpr {
272272
}
273273
}
274274

275+
class MatchArmTree extends ControlFlowTree instanceof MatchArm {
276+
override predicate propagatesAbnormal(AstNode child) { child = super.getExpr() }
277+
278+
override predicate first(AstNode node) { node = super.getPat() }
279+
280+
override predicate succ(AstNode pred, AstNode succ, Completion c) {
281+
// Edge from pattern to guard/arm if match succeeds.
282+
pred = super.getPat() and
283+
c.(MatchCompletion).succeeded() and
284+
(if super.hasGuard() then first(super.getGuard(), succ) else first(super.getExpr(), succ))
285+
or
286+
// Edge from guard to arm if the guard succeeds.
287+
last(super.getGuard(), pred, c) and
288+
first(super.getExpr(), succ) and
289+
c.(BooleanCompletion).succeeded()
290+
}
291+
292+
override predicate last(AstNode node, Completion c) {
293+
node = super.getPat() and c.(MatchCompletion).failed()
294+
or
295+
last(super.getGuard(), node, c) and c.(BooleanCompletion).failed()
296+
or
297+
last(super.getExpr(), node, c)
298+
}
299+
}
300+
301+
class MatchExprTree extends PostOrderTree instanceof MatchExpr {
302+
override predicate propagatesAbnormal(AstNode child) { child = super.getABranch().getExpr() }
303+
304+
override predicate first(AstNode node) { first(super.getExpr(), node) }
305+
306+
override predicate succ(AstNode pred, AstNode succ, Completion c) {
307+
// Edge from the scrutinee to the first arm.
308+
last(super.getExpr(), pred, c) and succ = super.getBranch(0).getPat()
309+
or
310+
// Edge from a failed match/guard in one arm to the beginning of the next arm.
311+
exists(int i |
312+
last(super.getBranch(i), pred, c) and
313+
first(super.getBranch(i + 1), succ) and
314+
c.(ConditionalCompletion).failed()
315+
)
316+
or
317+
exists(int i | last(super.getBranch(i), pred, c) and succ = this and completionIsSimple(c))
318+
}
319+
}
320+
275321
class MethodCallExprTree extends StandardPostOrderTree instanceof MethodCallExpr {
276322
override ControlFlowTree getChildNode(int i) {
277323
result = super.getReceiver() and

rust/ql/lib/codeql/rust/controlflow/internal/SuccessorType.qll

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ cached
44
newtype TSuccessorType =
55
TSuccessorSuccessor() or
66
TBooleanSuccessor(Boolean b) or
7+
TMatchSuccessor(Boolean b) or
78
TBreakSuccessor() or
89
TContinueSuccessor() or
910
TReturnSuccessor()
@@ -30,13 +31,24 @@ abstract private class ConditionalSuccessor extends SuccessorTypeImpl {
3031

3132
/** Gets the Boolean value of this successor. */
3233
final boolean getValue() { result = value }
33-
34-
override string toString() { result = this.getValue().toString() }
3534
}
3635

3736
/** A boolean control flow successor for a boolean conditon. */
3837
final class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor {
3938
BooleanSuccessor() { this = TBooleanSuccessor(value) }
39+
40+
override string toString() { result = this.getValue().toString() }
41+
}
42+
43+
/**
44+
* A control flow successor of a pattern match.
45+
*/
46+
final class MatchSuccessor extends ConditionalSuccessor, TMatchSuccessor {
47+
MatchSuccessor() { this = TMatchSuccessor(value) }
48+
49+
override string toString() {
50+
if this.getValue() = true then result = "match" else result = "no-match"
51+
}
4052
}
4153

4254
/** A `break` control flow successor. */

0 commit comments

Comments
 (0)