Merge pull request #12736 from d10c/swift/capture-flow

Swift: Closure Capture Helper APIs
This commit is contained in:
Nora Dimitrijević
2023-04-20 18:45:56 +02:00
committed by GitHub
9 changed files with 1473 additions and 63 deletions

View File

@@ -60,7 +60,6 @@ ql/lib/codeql/swift/elements/decl/SubscriptDeclConstructor.qll 3a88617b41f96827c
ql/lib/codeql/swift/elements/decl/TopLevelCodeDeclConstructor.qll 6920a4e7aec45ae2a561cef95b9082b861f81c16c259698541f317481645e194 4bd65820b93a5ec7332dd1bbf59326fc19b77e94c122ad65d41393c84e6ac581
ql/lib/codeql/swift/elements/decl/TypeAliasDecl.qll 984c5802c35e595388f7652cef1a50fb963b32342ab4f9d813b7200a0e6a37ca 630dc9cbf20603855c599a9f86037ba0d889ad3d2c2b6f9ac17508d398bff9e3
ql/lib/codeql/swift/elements/decl/TypeAliasDeclConstructor.qll ba70bb69b3a14283def254cc1859c29963838f624b3f1062a200a8df38f1edd5 96ac51d1b3156d4139e583f7f803e9eb95fe25cc61c12986e1b2972a781f9c8b
ql/lib/codeql/swift/elements/decl/ValueDecl.qll 1b7d8eeb17be4bdbabc156cb0689641ed4f9e697e334d2d14f423ed3d1a419f6 e3056cf6a883da2737cb220a89499a9e3977eb1c56b9e1d2f41a56b71a0c29f9
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExpr.qll 39b856c89b8aff769b75051fd9e319f2d064c602733eaa6fed90d8f626516306 a87738539276438cef63145461adf25309d1938cfac367f53f53d33db9b12844
ql/lib/codeql/swift/elements/expr/AbiSafeConversionExprConstructor.qll 7d70e7c47a9919efcb1ebcbf70e69cab1be30dd006297b75f6d72b25ae75502a e7a741c42401963f0c1da414b3ae779adeba091e9b8f56c9abf2a686e3a04d52
ql/lib/codeql/swift/elements/expr/AbstractClosureExpr.qll 4027b51a171387332f96cb7b78ca87a6906aec76419938157ac24a60cff16519 400790fe643585ad39f40c433eff8934bbe542d140b81341bca3b6dfc5b22861

View File

@@ -1,6 +1,8 @@
private import codeql.swift.generated.AstNode
private import codeql.swift.elements.decl.AbstractFunctionDecl
private import codeql.swift.elements.decl.Decl
private import codeql.swift.elements.expr.AbstractClosureExpr
private import codeql.swift.elements.Callable
private import codeql.swift.generated.ParentChild
private module Cached {
@@ -21,10 +23,59 @@ private module Cached {
AbstractFunctionDecl getEnclosingFunction(AstNode ast) {
result = getEnclosingFunctionStep*(getEnclosingDecl(ast))
}
private Element getEnclosingClosureStep(Element e) {
not e instanceof Callable and
result = getImmediateParent(e)
}
cached
AbstractClosureExpr getEnclosingClosure(AstNode ast) {
result = getEnclosingClosureStep*(getImmediateParent(ast))
}
}
/**
* A node in the abstract syntax tree.
*/
class AstNode extends Generated::AstNode {
/**
* Gets the nearest function definition that contains this AST node, if any.
* This includes functions, methods, (de)initializers, and accessors, but not closures.
*
* For example, in the following code, the AST node for `n + 1` has `foo` as its
* enclosing function (via `getEnclosingFunction`), whereas its enclosing callable is
* the closure `{(n : Int) in n + 1 }` (via `getEnclosingCallable`):
*
* ```swift
* func foo() {
* var f = { (n : Int) in n + 1 }
* }
* ```
*/
final AbstractFunctionDecl getEnclosingFunction() { result = Cached::getEnclosingFunction(this) }
/**
* Gets the nearest declaration that contains this AST node, if any.
*/
final Decl getEnclosingDecl() { result = Cached::getEnclosingDecl(this) }
/**
* Gets the nearest `Callable` that contains this AST node, if any.
* This includes (auto)closures, functions, methods, (de)initializers, and accessors.
*
* For example, in the following code, the AST node for `n + 1` has the closure
* `{(n : Int) in n + 1 }` as its enclosing callable.
*
* ```swift
* func foo() {
* var f = { (n : Int) in n + 1 }
* }
* ```
*/
final Callable getEnclosingCallable() {
if exists(Cached::getEnclosingClosure(this))
then result = Cached::getEnclosingClosure(this)
else result = Cached::getEnclosingFunction(this)
}
}

View File

@@ -1,5 +1,23 @@
private import codeql.swift.generated.decl.CapturedDecl
private import codeql.swift.elements.Callable
private import codeql.swift.elements.expr.DeclRefExpr
/**
* A captured variable or function parameter in the scope of a closure.
*/
class CapturedDecl extends Generated::CapturedDecl {
override string toString() { result = this.getDecl().toString() }
/**
* Gets the closure or function that captures this variable.
*/
Callable getScope() { result.getACapture() = this }
/**
* Get an access to this capture within the scope of its closure.
*/
DeclRefExpr getAnAccess() {
result.getEnclosingCallable() = this.getScope() and
result.getDecl() = this.getDecl()
}
}

View File

@@ -1,4 +1,23 @@
// generated by codegen/codegen.py, remove this comment if you wish to edit this file
private import codeql.swift.generated.decl.ValueDecl
private import codeql.swift.elements.decl.CapturedDecl
private import codeql.swift.elements.expr.DeclRefExpr
class ValueDecl extends Generated::ValueDecl { }
/**
* A declaration that introduces a value with a type.
*/
class ValueDecl extends Generated::ValueDecl {
/**
* Gets a capture of this declaration in the scope of a closure.
*/
CapturedDecl asCapturedDecl() { result.getDecl() = this }
/**
* Holds if this declaration is captured by a closure.
*/
predicate isCaptured() { exists(this.asCapturedDecl()) }
/**
* Gets an expression that references this declaration.
*/
DeclRefExpr getAnAccess() { result.getDecl() = this }
}

View File

@@ -1,13 +1,16 @@
private import codeql.swift.generated.decl.VarDecl
private import codeql.swift.elements.expr.DeclRefExpr
private import codeql.swift.elements.decl.Decl
/**
* A variable declaration.
*/
class VarDecl extends Generated::VarDecl {
override string toString() { result = this.getName() }
DeclRefExpr getAnAccess() { result.getDecl() = this }
}
/**
* A field declaration.
*/
class FieldDecl extends VarDecl {
FieldDecl() { this = any(Decl ctx).getAMember() }
}

View File

@@ -1,9 +1,23 @@
private import codeql.swift.generated.expr.DeclRefExpr
private import codeql.swift.elements.decl.CapturedDecl
/**
* An expression that references or accesses a declaration.
*/
class DeclRefExpr extends Generated::DeclRefExpr {
override string toString() {
if exists(this.getDecl().toString())
then result = this.getDecl().toString()
else result = "(unknown declaration)"
}
/**
* Gets the closure capture referenced by this expression, if any.
*/
CapturedDecl getCapturedDecl() { result.getAnAccess() = this }
/**
* Holds if this expression references a closure capture.
*/
predicate hasCapturedDecl() { exists(this.getCapturedDecl()) }
}

View File

@@ -1,5 +1,41 @@
| closures.swift:8:12:8:12 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:7:6:7:6 | x | isDirect: | yes | isEscaping: | no |
| closures.swift:9:12:9:12 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:6:7:6:7 | y | isDirect: | yes | isEscaping: | no |
| closures.swift:16:3:16:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:17:5:17:5 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:15:7:15:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:20:3:20:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:25:3:25:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:24:3:24:3 | escape | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:12:5:12:5 | escape | isDirect: | yes | isEscaping: | yes |
| closures.swift:31:11:31:11 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:29:7:29:7 | x | isDirect: | no | isEscaping: | no |
| closures.swift:32:14:32:14 | f | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:28:7:28:7 | f | isDirect: | no | isEscaping: | no |
| closures.swift:32:14:32:14 | f | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:28:7:28:7 | f | isDirect: | yes | isEscaping: | no |
| closures.swift:32:17:32:17 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:29:7:29:7 | x | isDirect: | yes | isEscaping: | no |
| closures.swift:39:20:39:20 | callback | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:36:21:36:58 | callback | isDirect: | yes | isEscaping: | yes |
| closures.swift:42:35:42:35 | wrapper(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:38:5:40:5 | wrapper(_:) | isDirect: | no | isEscaping: | yes |
| closures.swift:52:18:52:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:51:7:51:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:54:13:54:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:51:7:51:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:61:18:61:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:60:7:60:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:63:13:63:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:60:7:60:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:71:3:71:3 | g | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:68:5:68:5 | g | isDirect: | yes | isEscaping: | yes |
| closures.swift:71:14:71:14 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:70:7:70:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:73:13:73:13 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:70:7:70:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:85:7:85:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | no | isEscaping: | yes |
| closures.swift:85:7:85:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | yes | isEscaping: | yes |
| closures.swift:85:18:85:18 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:82:9:82:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:88:9:88:9 | b() | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:92:5:98:5 | b() | isDirect: | no | isEscaping: | yes |
| closures.swift:93:7:93:7 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:79:7:79:7 | y | isDirect: | yes | isEscaping: | yes |
| closures.swift:93:20:93:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:82:9:82:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:96:9:96:9 | a() | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:84:5:90:5 | a() | isDirect: | no | isEscaping: | yes |
| closures.swift:111:15:111:15 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:110:9:110:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:111:27:111:27 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:110:9:110:9 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:115:5:115:5 | incrX | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:109:8:109:8 | incrX | isDirect: | yes | isEscaping: | yes |
| closures.swift:130:25:130:25 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:133:20:133:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | no | isEscaping: | yes |
| closures.swift:133:20:133:20 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:128:7:128:7 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:133:24:133:24 | y | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:132:22:132:22 | y | isDirect: | yes | isEscaping: | yes |
| closures.swift:151:21:151:21 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:10:149:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:154:16:154:16 | g(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:3:165:3 | g(_:) | isDirect: | no | isEscaping: | yes |
| closures.swift:155:21:155:21 | next | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:154:9:154:9 | next | isDirect: | yes | isEscaping: | yes |
| closures.swift:155:34:155:34 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:10:149:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:160:21:160:21 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:10:158:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:163:16:163:16 | f(_:) | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:149:3:156:3 | f(_:) | isDirect: | no | isEscaping: | yes |
| closures.swift:164:21:164:21 | next | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:163:9:163:9 | next | isDirect: | yes | isEscaping: | yes |
| closures.swift:164:36:164:36 | x | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:158:10:158:15 | x | isDirect: | yes | isEscaping: | yes |
| closures.swift:195:3:195:3 | g | getModule: | file://:0:0:0:0 | closures | getNumberOfMembers: | 0 | getDecl: | closures.swift:68:5:68:5 | g | isDirect: | yes | isEscaping: | yes |

View File

@@ -1,26 +1,211 @@
func bar() -> String {
func hello() -> String {
return "Hello world!"
}
func foo() {
func captureList() {
let y = 123
{ [x = bar()] () in
{ [x = hello()] () in
print(x)
print(y) }()
}
var escape: (() -> ())? = nil
func baz() {
func setEscape() {
var x = 0
func quux() {
escape = {
x += 1
print(x)
}
escape = quux
}
func callEscape() {
baz()
setEscape()
escape?()
}
func logical() -> Bool {
let f: ((Int) -> Int)? = { x in x + 1 }
let x: Int? = 42
return f != nil
&& (x != nil
&& f!(x!) == 43)
}
func asyncTest() {
func withCallback(_ callback: @escaping (Int) async -> Int) {
@Sendable
func wrapper(_ x: Int) async -> Int {
return await callback(x + 1)
}
Task {
print("asyncTest():", await wrapper(40))
}
}
withCallback { x in
x + 1
}
}
func foo() -> Int {
var x = 1 // x is a non-escaping capture of f and r
let f = { y in x += y }
x += 40
let r = { x }
f(1)
return r() // 42
}
func bar() -> () -> Int {
var x = 1 // x is a non-escaping capture of f, escaping capture of r
let f = { y in x += y }
x += 40
let r = { x }
f(1)
return r // constantly 42
}
var g: ((Int) -> Void)? = nil
func baz() -> () -> Int {
var x = 1 // x is an escaping capture of g and r
g = { y in x += y } // closure escapes!
x += 40
let r = { x }
g!(1)
return r
}
func quux() -> Int {
var y = 0
func f() -> () -> Void {
var x = 5
func a() {
y = 10*y + x
x -= 1
if x > 0 {
b()
}
}
func b() {
y = 10*y + 2*x
x -= 1
if x > 0 {
a()
}
}
return a
}
let a = f()
a()
return y // 58341
}
func sharedCapture() -> Int {
let (incrX, getX) = {
var x = 0
return ({ x += 1 }, { x })
}()
let doubleIncrX = {
incrX()
incrX()
}
doubleIncrX()
doubleIncrX()
return getX() // 4
}
func sink(_ x: Int) { print("sink:", x) }
func source() -> Int { -1 }
func sharedCaptureMultipleWriters() {
var x = 123
let callSink = { sink(x) }
let makeSetter = { y in
let setter = { x = y }
return setter
}
let goodSetter = makeSetter(42)
let badSetter = makeSetter(source())
goodSetter()
callSink()
badSetter()
callSink()
}
func reentrant() -> Int {
func f(_ x: Int) -> (Int) -> Int {
if x == 0 {
return { _ in x }
}
let next = g(x - 1)
return { k in k*next(10*k) + x }
}
func g(_ x: Int) -> (Int) -> Int {
if x == 0 {
return { _ in x }
}
let next = f(x - 1)
return { k in k*next(10*k) + 2*x }
}
let h = f(5)
let y = h(10)
return y // 10004003085
}
func main() {
print("captureList():")
captureList() // Hello world! 123
print("callEscape():")
callEscape() // 1
print("logical():", logical()) // true
print("asyncTest():")
asyncTest() // 42
print("foo():", foo()) // 42
let a = bar()
let b = baz()
print("bar():", a(), a()) // 42 42
print("baz():", b(), b()) // 42 42
g!(1)
print("g!(1):", b(), b()) // 43 43
g!(1)
print("g!(1):", b(), b()) // 44 44
print("quux():", quux()) // 58341
print("sharedCapture():", sharedCapture()) // 4
print("sharedCaptureMultipleWriters():")
sharedCaptureMultipleWriters() // 42, -1
print("reentrant():", reentrant()) // 10004003085
}
main()