Skip to content

Commit ca722dc

Browse files
committed
Swift: add NilCoalescingTest node to CFG
Fixes an issue where a nil-coalescing operation used in a boolean context would result in no control flow out of the default operand of the nil-coalescing operator.
1 parent 2b54ad5 commit ca722dc

File tree

4 files changed

+103
-17
lines changed

4 files changed

+103
-17
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ newtype TControlFlowElement =
1212
TPropertyObserverElement(Accessor observer, AssignExpr assign) {
1313
isPropertyObserverElement(observer, assign)
1414
} or
15-
TKeyPathElement(KeyPathExpr expr)
15+
TKeyPathElement(KeyPathExpr expr) or
16+
TNilCoalescingTestElement(NilCoalescingExpr expr)
1617

1718
predicate isLValue(Expr e) { any(AssignExpr assign).getDest() = e }
1819

@@ -204,3 +205,15 @@ class ClosureElement extends ControlFlowElement, TClosureElement {
204205

205206
override string toString() { result = expr.toString() }
206207
}
208+
209+
class NilCoalescingElement extends ControlFlowElement, TNilCoalescingTestElement {
210+
NilCoalescingExpr expr;
211+
212+
NilCoalescingElement() { this = TNilCoalescingTestElement(expr) }
213+
214+
override Location getLocation() { result = expr.getLocation() }
215+
216+
NilCoalescingExpr getAst() { result = expr }
217+
218+
override string toString() { result = "emptiness test for " + expr.toString() }
219+
}

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,7 +1586,9 @@ module Exprs {
15861586
}
15871587
}
15881588

1589-
private class NilCoalescingTree extends AstControlFlowTree {
1589+
private class NilCoalescingTestTree extends LeafTree, NilCoalescingElement { }
1590+
1591+
private class NilCoalescingTree extends AstPostOrderTree {
15901592
override NilCoalescingExpr ast;
15911593

15921594
final override predicate propagatesAbnormal(ControlFlowElement child) {
@@ -1597,22 +1599,22 @@ module Exprs {
15971599
astFirst(ast.getLeftOperand().getFullyConverted(), first)
15981600
}
15991601

1600-
final override predicate last(ControlFlowElement last, Completion c) {
1601-
last.asAstNode() = ast and
1602-
exists(EmptinessCompletion ec | ec = c | not ec.isEmpty())
1603-
or
1604-
astLast(ast.getRightOperand().getFullyConverted(), last, c) and
1605-
c instanceof NormalCompletion
1606-
}
1607-
16081602
final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
16091603
astLast(ast.getLeftOperand().getFullyConverted(), pred, c) and
16101604
c instanceof NormalCompletion and
1611-
succ.asAstNode() = ast
1605+
succ.(NilCoalescingTestTree).getAst() = ast
16121606
or
1613-
pred.asAstNode() = ast and
1614-
c.(EmptinessCompletion).isEmpty() and
1607+
pred.(NilCoalescingTestTree).getAst() = ast and
1608+
exists(EmptinessCompletion ec | c = ec | ec.isEmpty()) and
16151609
astFirst(ast.getRightOperand().getFullyConverted(), succ)
1610+
or
1611+
pred.(NilCoalescingTestTree).getAst() = ast and
1612+
exists(EmptinessCompletion ec | c = ec | not ec.isEmpty()) and
1613+
succ.asAstNode() = ast
1614+
or
1615+
astLast(ast.getRightOperand().getFullyConverted(), pred, c) and
1616+
c instanceof NormalCompletion and
1617+
succ.asAstNode() = ast
16161618
}
16171619
}
16181620

swift/ql/test/library-tests/controlflow/graph/Cfg.expected

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6196,20 +6196,83 @@ cfg.swift:
61966196
#-----| -> x
61976197

61986198
# 538| x
6199-
#-----| -> ??(_:_:)
6199+
#-----| -> x
62006200

62016201
# 539| return ...
62026202
#-----| return -> exit testNilCoalescing(x:) (normal)
62036203

62046204
# 539| x
6205-
#-----| -> { ... }
6205+
#-----| -> emptiness test for ... ??(_:_:) ...
62066206

62076207
# 539| ... ??(_:_:) ...
62086208
#-----| exception -> exit testNilCoalescing(x:) (abnormal)
62096209
#-----| -> return ...
62106210

6211-
# 539| ??(_:_:)
6212-
#-----| -> x
6211+
# 539| emptiness test for ... ??(_:_:) ...
6212+
#-----| non-empty -> ... ??(_:_:) ...
6213+
#-----| empty -> { ... }
6214+
6215+
# 539| 0
6216+
#-----| -> return ...
6217+
6218+
# 539| return ...
6219+
#-----| -> ... ??(_:_:) ...
62136220

62146221
# 539| { ... }
6222+
#-----| -> 0
6223+
6224+
# 542| enter testNilCoalescing2(x:)
6225+
#-----| -> testNilCoalescing2(x:)
6226+
6227+
# 542| exit testNilCoalescing2(x:)
6228+
6229+
# 542| exit testNilCoalescing2(x:) (abnormal)
6230+
#-----| -> exit testNilCoalescing2(x:)
6231+
6232+
# 542| exit testNilCoalescing2(x:) (normal)
6233+
#-----| -> exit testNilCoalescing2(x:)
6234+
6235+
# 542| testNilCoalescing2(x:)
6236+
#-----| -> x
6237+
6238+
# 542| x
6239+
#-----| -> if ... then { ... } else { ... }
6240+
6241+
# 543| if ... then { ... } else { ... }
6242+
#-----| -> StmtCondition
6243+
6244+
# 543| x
6245+
#-----| -> emptiness test for ... ??(_:_:) ...
6246+
6247+
# 543| ... ??(_:_:) ...
6248+
#-----| exception -> exit testNilCoalescing2(x:) (abnormal)
6249+
#-----| true -> 1
6250+
#-----| false -> 0
6251+
6252+
# 543| StmtCondition
6253+
#-----| -> x
6254+
6255+
# 543| emptiness test for ... ??(_:_:) ...
6256+
#-----| non-empty -> ... ??(_:_:) ...
6257+
#-----| empty -> { ... }
6258+
6259+
# 543| false
6260+
#-----| -> return ...
6261+
6262+
# 543| return ...
62156263
#-----| -> ... ??(_:_:) ...
6264+
6265+
# 543| { ... }
6266+
#-----| -> false
6267+
6268+
# 544| return ...
6269+
#-----| return -> exit testNilCoalescing2(x:) (normal)
6270+
6271+
# 544| 1
6272+
#-----| -> return ...
6273+
6274+
# 546| return ...
6275+
#-----| return -> exit testNilCoalescing2(x:) (normal)
6276+
6277+
# 546| 0
6278+
#-----| -> return ...

swift/ql/test/library-tests/controlflow/graph/cfg.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,11 @@ func testAsyncFor () async {
538538
func testNilCoalescing(x: Int?) -> Int {
539539
return x ?? 0
540540
}
541+
542+
func testNilCoalescing2(x: Bool?) -> Int {
543+
if x ?? false {
544+
return 1
545+
} else {
546+
return 0
547+
}
548+
}

0 commit comments

Comments
 (0)