Skip to content

Commit 6e868c2

Browse files
committed
Rust: CFG edges for break and continue with labels
1 parent 581d0c5 commit 6e868c2

File tree

3 files changed

+87
-34
lines changed

3 files changed

+87
-34
lines changed

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

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
private import codeql.util.Option
12
private import codeql.util.Boolean
23
private import codeql.rust.controlflow.ControlFlowGraph
34
private import rust
@@ -7,9 +8,9 @@ private newtype TCompletion =
78
TSimpleCompletion() or
89
TBooleanCompletion(Boolean b) or
910
TMatchCompletion(Boolean isMatch) or
10-
TBreakCompletion() or
11-
TContinueCompletion() or
12-
TReturnCompletion()
11+
TLoopCompletion(TLoopJumpType kind, TLabelType label) or
12+
TReturnCompletion() or
13+
TDivergeCompletion() // A completion that never reaches the successor (e.g. by panicking or spinning)
1314

1415
/** A completion of a statement or an expression. */
1516
abstract class Completion extends TCompletion {
@@ -105,25 +106,44 @@ class MatchCompletion extends TMatchCompletion, ConditionalCompletion {
105106
}
106107

107108
/**
108-
* A completion that represents a break.
109+
* A completion that represents a break or a continue.
109110
*/
110-
class BreakCompletion extends TBreakCompletion, Completion {
111-
override BreakSuccessor getAMatchingSuccessorType() { any() }
111+
class LoopJumpCompletion extends TLoopCompletion, Completion {
112+
override LoopJumpSuccessor getAMatchingSuccessorType() {
113+
result = TLoopSuccessor(this.getKind(), this.getLabelType())
114+
}
112115

113-
override predicate isValidForSpecific(AstNode e) { e instanceof BreakExpr }
116+
final TLoopJumpType getKind() { this = TLoopCompletion(result, _) }
114117

115-
override string toString() { result = "break" }
116-
}
118+
final TLabelType getLabelType() { this = TLoopCompletion(_, result) }
117119

118-
/**
119-
* A completion that represents a continue.
120-
*/
121-
class ContinueCompletion extends TContinueCompletion, Completion {
122-
override ContinueSuccessor getAMatchingSuccessorType() { any() }
120+
final predicate hasLabel() { this.getLabelType() = TLabel(_) }
121+
122+
final string getLabelName() { TLabel(result) = this.getLabelType() }
123+
124+
final predicate isContinue() { this.getKind() = TContinueJump() }
125+
126+
final predicate isBreak() { this.getKind() = TBreakJump() }
123127

124-
override predicate isValidForSpecific(AstNode e) { e instanceof ContinueExpr }
128+
override predicate isValidForSpecific(AstNode e) {
129+
this.isBreak() and
130+
e instanceof BreakExpr and
131+
(
132+
not e.(BreakExpr).hasLabel() and not this.hasLabel()
133+
or
134+
e.(BreakExpr).getLabel().getName() = this.getLabelName()
135+
)
136+
or
137+
this.isContinue() and
138+
e instanceof ContinueExpr and
139+
(
140+
not e.(ContinueExpr).hasLabel() and not this.hasLabel()
141+
or
142+
e.(ContinueExpr).getLabel().getName() = this.getLabelName()
143+
)
144+
}
125145

126-
override string toString() { result = "continue" }
146+
override string toString() { result = this.getAMatchingSuccessorType().toString() }
127147
}
128148

129149
/**
@@ -145,9 +165,3 @@ predicate completionIsSimple(Completion c) { c instanceof SimpleCompletion }
145165

146166
/** Holds if `c` is a valid completion for `n`. */
147167
predicate completionIsValidFor(Completion c, AstNode n) { c.isValidFor(n) }
148-
149-
/** Holds if `c` is a completion that interacts with a loop such as `loop`, `for`, `while`. */
150-
predicate isLoopCompletion(Completion c) {
151-
c instanceof BreakCompletion or
152-
c instanceof ContinueCompletion
153-
}

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,24 +303,37 @@ class LoopExprTree extends PostOrderTree instanceof LoopExpr {
303303

304304
override predicate first(AstNode node) { first(super.getBody(), node) }
305305

306+
/** Whether this `LoopExpr` captures a completion for a `break`/`continue`. */
307+
predicate capturesLoopJumpCompletion(LoopJumpCompletion c) {
308+
not c.hasLabel()
309+
or
310+
c.getLabelName() = super.getLabel().getName()
311+
}
312+
306313
override predicate succ(AstNode pred, AstNode succ, Completion c) {
307314
// Edge back to the start for final expression and continue expressions
308315
last(super.getBody(), pred, c) and
309-
(completionIsNormal(c) or c instanceof ContinueCompletion) and
316+
(
317+
completionIsNormal(c)
318+
or
319+
c.(LoopJumpCompletion).isContinue() and this.capturesLoopJumpCompletion(c)
320+
) and
310321
this.first(succ)
311322
or
312323
// Edge for exiting the loop with a break expressions
313324
last(super.getBody(), pred, c) and
314-
c instanceof BreakCompletion and
325+
c.(LoopJumpCompletion).isBreak() and
326+
this.capturesLoopJumpCompletion(c) and
315327
succ = this
316328
}
317329

318330
override predicate last(AstNode last, Completion c) {
319331
super.last(last, c)
320332
or
333+
// Any abnormal completions that this loop does not capture should propagate
321334
last(super.getBody(), last, c) and
322335
not completionIsNormal(c) and
323-
not isLoopCompletion(c)
336+
not this.capturesLoopJumpCompletion(c)
324337
}
325338
}
326339

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

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1+
private import rust
2+
private import codeql.rust.generated.Raw
13
private import codeql.util.Boolean
4+
private import codeql.util.Option
5+
6+
newtype TLoopJumpType =
7+
TContinueJump() or
8+
TBreakJump()
9+
10+
newtype TLabelType =
11+
TLabel(string s) { any(Label l).getName() = s } or
12+
TNoLabel()
213

314
cached
415
newtype TSuccessorType =
516
TSuccessorSuccessor() or
617
TBooleanSuccessor(Boolean b) or
718
TMatchSuccessor(Boolean b) or
8-
TBreakSuccessor() or
9-
TContinueSuccessor() or
19+
TLoopSuccessor(TLoopJumpType kind, TLabelType label) or
1020
TReturnSuccessor()
1121

22+
// class TBreakSuccessor = TUnlabeledBreakSuccessor or TLabeledBreakSuccessor;
1223
/** The type of a control flow successor. */
1324
abstract private class SuccessorTypeImpl extends TSuccessorType {
1425
/** Gets a textual representation of successor type. */
@@ -51,14 +62,29 @@ final class MatchSuccessor extends ConditionalSuccessor, TMatchSuccessor {
5162
}
5263
}
5364

54-
/** A `break` control flow successor. */
55-
final class BreakSuccessor extends SuccessorTypeImpl, TBreakSuccessor {
56-
final override string toString() { result = "break" }
57-
}
65+
/**
66+
* A control flow successor of a loop control flow expression, `continue` or `break`.
67+
*/
68+
final class LoopJumpSuccessor extends SuccessorTypeImpl, TLoopSuccessor {
69+
final private TLoopJumpType getKind() { this = TLoopSuccessor(result, _) }
70+
71+
final private TLabelType getLabelType() { this = TLoopSuccessor(_, result) }
5872

59-
/** A `continue` control flow successor. */
60-
final class ContinueSuccessor extends SuccessorTypeImpl, TContinueSuccessor {
61-
final override string toString() { result = "continue" }
73+
final predicate hasLabel() { this.getLabelType() = TLabel(_) }
74+
75+
final string getLabelName() { this = TLoopSuccessor(_, TLabel(result)) }
76+
77+
final predicate isContinue() { this.getKind() = TContinueJump() }
78+
79+
final predicate isBreak() { this.getKind() = TBreakJump() }
80+
81+
final override string toString() {
82+
exists(string kind, string label |
83+
(if this.isContinue() then kind = "continue" else kind = "break") and
84+
(if this.hasLabel() then label = "(" + this.getLabelName() + ")" else label = "") and
85+
result = kind + label
86+
)
87+
}
6288
}
6389

6490
/**

0 commit comments

Comments
 (0)