Skip to content

Commit 42ec635

Browse files
authored
Merge pull request #9349 from MathiasVP/fix-inconsistent-cfg
Swift: Fix three CFG inconsistencies
2 parents e2ddfcd + 6386daf commit 42ec635

File tree

7 files changed

+326
-259
lines changed

7 files changed

+326
-259
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

@@ -461,6 +440,34 @@ class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
461440
override string toString() { if this.isMatch() then result = "match" else result = "no-match" }
462441
}
463442

443+
class FalseOrNonMatchCompletion instanceof ConditionalCompletion {
444+
FalseOrNonMatchCompletion() {
445+
this instanceof FalseCompletion
446+
or
447+
this.(MatchingCompletion).isNonMatch()
448+
}
449+
450+
string toString() {
451+
result = this.(FalseCompletion).toString()
452+
or
453+
result = this.(MatchingCompletion).toString()
454+
}
455+
}
456+
457+
class TrueOrMatchCompletion instanceof ConditionalCompletion {
458+
TrueOrMatchCompletion() {
459+
this instanceof TrueCompletion
460+
or
461+
this.(MatchingCompletion).isMatch()
462+
}
463+
464+
string toString() {
465+
result = this.(TrueCompletion).toString()
466+
or
467+
result = this.(MatchingCompletion).toString()
468+
}
469+
}
470+
464471
class FallthroughCompletion extends Completion, TFallthroughCompletion {
465472
CaseStmt dest;
466473

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

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -215,29 +215,70 @@ module Stmts {
215215
override FailStmt ast;
216216
}
217217

218-
private class StmtConditionTree extends AstPostOrderTree {
218+
private class StmtConditionTree extends AstPreOrderTree {
219219
override StmtCondition ast;
220220

221221
final override predicate propagatesAbnormal(ControlFlowElement child) {
222-
child.asAstNode() = ast.getAnElement().getUnderlyingCondition()
222+
child.asAstNode() = ast.getAnElement().getInitializer().getFullyConverted()
223+
or
224+
child.asAstNode() = ast.getAnElement().getPattern().getFullyUnresolved()
225+
or
226+
child.asAstNode() = ast.getAnElement().getBoolean().getFullyConverted()
223227
}
224228

225-
final override predicate first(ControlFlowElement first) {
226-
astFirst(ast.getFirstElement().getUnderlyingCondition().getFullyUnresolved(), first)
229+
predicate firstElement(int i, ControlFlowElement first) {
230+
// If there is an initializer in the first element, evaluate that first
231+
astFirst(ast.getElement(i).getInitializer().getFullyConverted(), first)
232+
or
233+
// Otherwise, the first element is a boolean condition.
234+
not exists(ast.getElement(i).getInitializer()) and
235+
astFirst(ast.getElement(i).getBoolean().getFullyConverted(), first)
227236
}
228237

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

@@ -253,7 +294,7 @@ module Stmts {
253294
final override predicate last(ControlFlowElement last, Completion c) {
254295
// Condition exits with a false completion and there is no `else` branch
255296
astLast(ast.getCondition().getFullyUnresolved(), last, c) and
256-
c instanceof FalseCompletion and
297+
c instanceof FalseOrNonMatchCompletion and
257298
not exists(ast.getElse())
258299
or
259300
// Then/Else branch exits with any completion
@@ -269,10 +310,12 @@ module Stmts {
269310
astLast(ast.getCondition().getFullyUnresolved(), pred, c) and
270311
(
271312
// Flow from last element of condition to first element of then branch
272-
c instanceof TrueCompletion and astFirst(ast.getThen(), succ)
313+
c instanceof TrueOrMatchCompletion and
314+
astFirst(ast.getThen(), succ)
273315
or
274316
// Flow from last element of condition to first element of else branch
275-
c instanceof FalseCompletion and astFirst(ast.getElse(), succ)
317+
c instanceof FalseOrNonMatchCompletion and
318+
astFirst(ast.getElse(), succ)
276319
)
277320
}
278321
}
@@ -292,7 +335,7 @@ module Stmts {
292335
or
293336
// Exit when a condition is true
294337
astLast(ast.getCondition().getFullyUnresolved(), last, c) and
295-
c instanceof TrueCompletion
338+
c instanceof TrueOrMatchCompletion
296339
}
297340

298341
final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
@@ -303,7 +346,7 @@ module Stmts {
303346
or
304347
// Flow to the body when the condition is false
305348
astLast(ast.getCondition().getFullyUnresolved(), pred, c) and
306-
c instanceof FalseCompletion and
349+
c instanceof FalseOrNonMatchCompletion and
307350
astFirst(ast.getBody(), succ)
308351
}
309352
}
@@ -330,7 +373,7 @@ module Stmts {
330373
final override predicate last(ControlFlowElement last, Completion c) {
331374
// Condition exits with a false completion
332375
last(this.getCondition(), last, c) and
333-
c instanceof FalseCompletion
376+
c instanceof FalseOrNonMatchCompletion
334377
or
335378
// Body exits with a break completion
336379
exists(BreakCompletion break |
@@ -347,7 +390,7 @@ module Stmts {
347390

348391
override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
349392
last(this.getCondition(), pred, c) and
350-
c instanceof TrueCompletion and
393+
c instanceof TrueOrMatchCompletion and
351394
first(this.getBody(), succ)
352395
or
353396
last(this.getBody(), pred, c) and
@@ -441,7 +484,7 @@ module Stmts {
441484
or
442485
// Flow from last element of variable declaration ...
443486
astLast(ast.getPattern().getFullyUnresolved(), pred, c) and
444-
c instanceof SimpleCompletion and
487+
c instanceof NormalCompletion and
445488
(
446489
// ... to first element of loop body if no 'where' clause exists,
447490
astFirst(ast.getBody(), succ) and
@@ -492,9 +535,8 @@ module Stmts {
492535
astLast(ast.getExpr().getFullyConverted(), last, c) and
493536
c instanceof NormalCompletion
494537
or
495-
// A statement exits (TODO: with a `break` completion?)
496-
astLast(ast.getACase().getBody(), last, c) and
497-
c instanceof NormalCompletion
538+
// A statement exits
539+
astLast(ast.getACase().getBody(), last, c)
498540
// Note: There's no need for an exit with a non-match as
499541
// Swift's switch statements are always exhaustive.
500542
}
@@ -857,7 +899,7 @@ module Patterns {
857899
or
858900
// Or we got to the some/none check and it failed
859901
n.asAstNode() = ast and
860-
not c.(MatchingCompletion).isMatch()
902+
c.(MatchingCompletion).isNonMatch()
861903
}
862904

863905
override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
@@ -1532,6 +1574,18 @@ module Exprs {
15321574
}
15331575
}
15341576

1577+
private class OpenExistentialTree extends AstStandardPostOrderTree {
1578+
override OpenExistentialExpr ast;
1579+
1580+
override ControlFlowElement getChildElement(int i) {
1581+
i = 0 and
1582+
result.asAstNode() = ast.getExistential().getFullyConverted()
1583+
or
1584+
i = 1 and
1585+
result.asAstNode() = ast.getSubExpr().getFullyConverted()
1586+
}
1587+
}
1588+
15351589
module Conversions {
15361590
class ConversionOrIdentity = @identity_expr or @explicit_cast_expr or @implicit_conversion_expr;
15371591

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)