Skip to content

Commit 73c1899

Browse files
committed
Swift: Fix CFG inconsistencies with StmtConditions.
1 parent e3ef258 commit 73c1899

File tree

7 files changed

+305
-258
lines changed

7 files changed

+305
-258
lines changed

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

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private predicate inBooleanContext(ControlFlowElement n) {
119119
}
120120

121121
private predicate astInBooleanContext(AstNode n) {
122-
n = any(ConditionElement condElem).getFullyUnresolved()
122+
n = any(ConditionElement condElem).getBoolean().getFullyUnresolved()
123123
or
124124
n = any(StmtCondition stmtCond).getFullyUnresolved()
125125
or
@@ -160,9 +160,7 @@ private predicate astInBooleanContext(AstNode n) {
160160
* Holds if a normal completion of `ast` must be a matching completion. Thats is,
161161
* whether `ast` determines a match in a `switch` or `catch` statement.
162162
*/
163-
private predicate mustHaveMatchingCompletion(AstNode ast) {
164-
switchMatching(_, _, ast) or catchMatching(_, _, ast)
165-
}
163+
private predicate mustHaveMatchingCompletion(AstNode ast) { ast instanceof Pattern }
166164

167165
/**
168166
* Holds if `ast` must have a matching completion, and `e` is the fully converted
@@ -180,30 +178,11 @@ private predicate mustHaveMatchingCompletion(Expr e, AstNode ast) {
180178
* case `c`, belonging to `switch` statement `switch`.
181179
*/
182180
private predicate switchMatching(SwitchStmt switch, CaseStmt c, AstNode ast) {
183-
switchMatchingCaseLabelItem(switch, c, ast)
184-
or
185-
switchMatchingPattern(switch, c, ast)
186-
}
187-
188-
/**
189-
* Holds if `cli` a top-level pattern of case `c`, belonging
190-
* to `switch` statement `switch`.
191-
*/
192-
private predicate switchMatchingCaseLabelItem(SwitchStmt switch, CaseStmt c, CaseLabelItem cli) {
193181
switch.getACase() = c and
194-
c.getALabel() = cli
195-
}
196-
197-
/**
198-
* Holds if `pattern` is a sub-pattern of the pattern in case
199-
* statement `c` of the `switch` statement `switch`.
200-
*/
201-
private predicate switchMatchingPattern(SwitchStmt s, CaseStmt c, Pattern pattern) {
202-
s.getACase() = c and
203-
exists(CaseLabelItem cli | switchMatching(s, c, cli) |
204-
cli.getPattern() = pattern
182+
(
183+
c.getALabel() = ast
205184
or
206-
isSubPattern+(cli.getPattern(), pattern)
185+
isSubPattern+(c.getALabel().getPattern(), ast)
207186
)
208187
}
209188

@@ -455,6 +434,34 @@ class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
455434
override string toString() { if this.isMatch() then result = "match" else result = "no-match" }
456435
}
457436

437+
class FalseOrNonMatchCompletion instanceof ConditionalCompletion {
438+
FalseOrNonMatchCompletion() {
439+
this instanceof FalseCompletion
440+
or
441+
this.(MatchingCompletion).isNonMatch()
442+
}
443+
444+
string toString() {
445+
result = this.(FalseCompletion).toString()
446+
or
447+
result = this.(MatchingCompletion).toString()
448+
}
449+
}
450+
451+
class TrueOrMatchCompletion instanceof ConditionalCompletion {
452+
TrueOrMatchCompletion() {
453+
this instanceof TrueCompletion
454+
or
455+
this.(MatchingCompletion).isMatch()
456+
}
457+
458+
string toString() {
459+
result = this.(TrueCompletion).toString()
460+
or
461+
result = this.(MatchingCompletion).toString()
462+
}
463+
}
464+
458465
class FallthroughCompletion extends Completion, TFallthroughCompletion {
459466
CaseStmt dest;
460467

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

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -207,29 +207,70 @@ module Stmts {
207207
override FailStmt ast;
208208
}
209209

210-
private class StmtConditionTree extends AstPostOrderTree {
210+
private class StmtConditionTree extends AstPreOrderTree {
211211
override StmtCondition ast;
212212

213213
final override predicate propagatesAbnormal(ControlFlowElement child) {
214-
child.asAstNode() = ast.getAnElement().getUnderlyingCondition()
214+
child.asAstNode() = ast.getAnElement().getInitializer().getFullyConverted()
215+
or
216+
child.asAstNode() = ast.getAnElement().getPattern().getFullyUnresolved()
217+
or
218+
child.asAstNode() = ast.getAnElement().getBoolean().getFullyConverted()
215219
}
216220

217-
final override predicate first(ControlFlowElement first) {
218-
astFirst(ast.getFirstElement().getUnderlyingCondition().getFullyUnresolved(), first)
221+
predicate firstElement(int i, ControlFlowElement first) {
222+
// If there is an initializer in the first element, evaluate that first
223+
astFirst(ast.getElement(i).getInitializer().getFullyConverted(), first)
224+
or
225+
// Otherwise, the first element is a boolean condition.
226+
not exists(ast.getElement(i).getInitializer()) and
227+
astFirst(ast.getElement(i).getBoolean().getFullyConverted(), first)
219228
}
220229

221-
final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
222-
// Left-to-right evaluation of elements
223-
exists(int i |
224-
astLast(ast.getElement(i).getUnderlyingCondition().getFullyUnresolved(), pred, c) and
225-
c instanceof NormalCompletion and
226-
astFirst(ast.getElement(i + 1).getUnderlyingCondition().getFullyUnresolved(), succ)
227-
)
230+
predicate succElement(int i, ControlFlowElement pred, ControlFlowElement succ, Completion c) {
231+
// Evaluate the pattern after the initializer
232+
astLast(ast.getElement(i).getInitializer().getFullyConverted(), pred, c) and
233+
c instanceof NormalCompletion and
234+
astFirst(ast.getElement(i).getPattern().getFullyUnresolved(), succ)
228235
or
229-
// Post-order: flow from thrown expression to the throw statement.
230-
astLast(ast.getLastElement().getUnderlyingCondition().getFullyUnresolved(), pred, c) and
236+
(
237+
// After evaluating the pattern
238+
astLast(ast.getElement(i).getPattern().getFullyUnresolved(), pred, c)
239+
or
240+
// ... or the boolean ...
241+
astLast(ast.getElement(i).getBoolean().getFullyConverted(), pred, c)
242+
) and
243+
// We evaluate the next element
231244
c instanceof NormalCompletion and
232-
succ.asAstNode() = ast
245+
this.firstElement(i + 1, succ)
246+
}
247+
248+
final override predicate last(ControlFlowElement last, Completion c) {
249+
// Stop if a boolean check failed
250+
astLast(ast.getAnElement().getBoolean().getFullyConverted(), last, c) and
251+
c instanceof FalseCompletion
252+
or
253+
// Stop is a pattern match failed
254+
astLast(ast.getAnElement().getPattern().getFullyUnresolved(), last, c) and
255+
not c.(MatchingCompletion).isMatch()
256+
or
257+
// Stop if we sucesfully evaluated all the conditionals
258+
(
259+
astLast(ast.getLastElement().getBoolean().getFullyConverted(), last, c)
260+
or
261+
astLast(ast.getLastElement().getPattern().getFullyUnresolved(), last, c)
262+
) and
263+
c instanceof NormalCompletion
264+
}
265+
266+
final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
267+
// Pre-order: Flow from this ast node to the first condition
268+
pred.asAstNode() = ast and
269+
c instanceof SimpleCompletion and
270+
this.firstElement(0, succ)
271+
or
272+
// Left-to-right evaluation of elements
273+
succElement(_, pred, succ, c)
233274
}
234275
}
235276

@@ -245,7 +286,7 @@ module Stmts {
245286
final override predicate last(ControlFlowElement last, Completion c) {
246287
// Condition exits with a false completion and there is no `else` branch
247288
astLast(ast.getCondition().getFullyUnresolved(), last, c) and
248-
c instanceof FalseCompletion and
289+
c instanceof FalseOrNonMatchCompletion and
249290
not exists(ast.getElse())
250291
or
251292
// Then/Else branch exits with any completion
@@ -261,10 +302,12 @@ module Stmts {
261302
astLast(ast.getCondition().getFullyUnresolved(), pred, c) and
262303
(
263304
// Flow from last element of condition to first element of then branch
264-
c instanceof TrueCompletion and astFirst(ast.getThen(), succ)
305+
c instanceof TrueOrMatchCompletion and
306+
astFirst(ast.getThen(), succ)
265307
or
266308
// Flow from last element of condition to first element of else branch
267-
c instanceof FalseCompletion and astFirst(ast.getElse(), succ)
309+
c instanceof FalseOrNonMatchCompletion and
310+
astFirst(ast.getElse(), succ)
268311
)
269312
}
270313
}
@@ -284,7 +327,7 @@ module Stmts {
284327
or
285328
// Exit when a condition is true
286329
astLast(ast.getCondition().getFullyUnresolved(), last, c) and
287-
c instanceof TrueCompletion
330+
c instanceof TrueOrMatchCompletion
288331
}
289332

290333
final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
@@ -295,7 +338,7 @@ module Stmts {
295338
or
296339
// Flow to the body when the condition is false
297340
astLast(ast.getCondition().getFullyUnresolved(), pred, c) and
298-
c instanceof FalseCompletion and
341+
c instanceof FalseOrNonMatchCompletion and
299342
astFirst(ast.getBody(), succ)
300343
}
301344
}
@@ -322,7 +365,7 @@ module Stmts {
322365
final override predicate last(ControlFlowElement last, Completion c) {
323366
// Condition exits with a false completion
324367
last(this.getCondition(), last, c) and
325-
c instanceof FalseCompletion
368+
c instanceof FalseOrNonMatchCompletion
326369
or
327370
// Body exits with a break completion
328371
exists(BreakCompletion break |
@@ -339,7 +382,7 @@ module Stmts {
339382

340383
override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
341384
last(this.getCondition(), pred, c) and
342-
c instanceof TrueCompletion and
385+
c instanceof TrueOrMatchCompletion and
343386
first(this.getBody(), succ)
344387
or
345388
last(this.getBody(), pred, c) and
@@ -484,9 +527,8 @@ module Stmts {
484527
astLast(ast.getExpr().getFullyConverted(), last, c) and
485528
c instanceof NormalCompletion
486529
or
487-
// A statement exits (TODO: with a `break` completion?)
488-
astLast(ast.getACase().getBody(), last, c) and
489-
c instanceof NormalCompletion
530+
// A statement exits
531+
astLast(ast.getACase().getBody(), last, c)
490532
// Note: There's no need for an exit with a non-match as
491533
// Swift's switch statements are always exhaustive.
492534
}
@@ -849,7 +891,7 @@ module Patterns {
849891
or
850892
// Or we got to the some/none check and it failed
851893
n.asAstNode() = ast and
852-
not c.(MatchingCompletion).isMatch()
894+
c.(MatchingCompletion).isNonMatch()
853895
}
854896

855897
override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {

swift/ql/lib/codeql/swift/controlflow/internal/Scope.qll

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,16 @@ private module Cached {
194194
result = guard.getCondition()
195195
)
196196
or
197-
result = ast.(StmtCondition).getElement(index).getUnderlyingCondition()
197+
exists(StmtCondition stmtCond | stmtCond = ast |
198+
index = 0 and
199+
result = stmtCond.getElement(index).getPattern()
200+
or
201+
index = 1 and
202+
result = stmtCond.getElement(index).getInitializer()
203+
or
204+
index = 2 and
205+
result = stmtCond.getElement(index).getBoolean()
206+
)
198207
or
199208
exists(IfStmt ifStmt | ifStmt = ast |
200209
index = 0 and

swift/ql/lib/codeql/swift/elements/stmt/ConditionElement.qll

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@ private import codeql.swift.generated.stmt.ConditionElement
22
private import codeql.swift.elements.AstNode
33

44
class ConditionElement extends ConditionElementBase {
5-
AstNode getUnderlyingCondition() {
6-
result = this.getBoolean()
5+
override string toString() {
6+
result = this.getBoolean().toString()
77
or
8-
result = this.getInitializer()
9-
or
10-
result = this.getPattern()
8+
result = this.getPattern().toString() + " = ... "
119
}
12-
13-
override string toString() { result = this.getUnderlyingCondition().toString() }
1410
}
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
| main.swift:3:8:3:13 | ... call to == ... | main.swift:3:8:3:13 | ... call to == ... |
22
| main.swift:10:17:10:24 | ... call to < ... | main.swift:10:18:10:22 | ... call to < ... |
33
| main.swift:39:9:39:14 | ... call to != ... | main.swift:39:9:39:14 | ... call to != ... |
4-
| main.swift:65:4:65:19 | let ... | main.swift:65:9:65:15 | let ... |
5-
| main.swift:65:4:65:19 | let ... | main.swift:65:19:65:19 | x |
6-
| main.swift:65:4:65:19 | x | main.swift:65:9:65:15 | let ... |
7-
| main.swift:65:4:65:19 | x | main.swift:65:19:65:19 | x |
8-
| main.swift:67:4:67:20 | .some(...) | main.swift:67:9:67:16 | .some(...) |
9-
| main.swift:67:4:67:20 | .some(...) | main.swift:67:20:67:20 | x |
10-
| main.swift:67:4:67:20 | x | main.swift:67:9:67:16 | .some(...) |
11-
| main.swift:67:4:67:20 | x | main.swift:67:20:67:20 | x |
4+
| main.swift:65:4:65:19 | let ... = ... | main.swift:65:9:65:15 | let ... |
5+
| main.swift:65:4:65:19 | let ... = ... | main.swift:65:19:65:19 | x |
6+
| main.swift:67:4:67:20 | .some(...) = ... | main.swift:67:9:67:16 | .some(...) |
7+
| main.swift:67:4:67:20 | .some(...) = ... | main.swift:67:20:67:20 | x |

0 commit comments

Comments
 (0)