Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-12268] Closure value in mirror crashes #54696

Open
stephencelis opened this issue Feb 25, 2020 · 3 comments
Open

[SR-12268] Closure value in mirror crashes #54696

stephencelis opened this issue Feb 25, 2020 · 3 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@stephencelis
Copy link
Contributor

Previous ID SR-12268
Radar rdar://problem/59791962
Original Reporter @stephencelis
Type Bug
Environment

Swift 5.1

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: 05f52733774e73a6ed86c94ce0aed672

Issue Description:

Mirrors that contain closures can crash in unexpected ways. For example, run the following code:

struct Baz {
  let id: Int
  let name: String
}


enum Foo {
  case bar(() -> Baz)
}

let foo = Foo.bar { Baz(id: 42, name: "Blob") }
let bar = Mirror(reflecting: foo).children.first!.value as! () -> Baz
let baz = bar()
print(baz.name)

And you'll get a bad access fatal error when invoking bar despite the fact that it was cast just fine.

@hborla
Copy link
Member

hborla commented Feb 26, 2020

@swift-ci create

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@tbkka
Copy link
Contributor

tbkka commented Jul 6, 2022

Here's a simplified reproducer:

struct Baz {
  let closure: () -> Int
}

let baz = Baz(closure: { 7 })
let mirror = Mirror(reflecting: baz)
let children = mirror.children
let firstChild = children.first!
let childValue: Any = firstChild.value
let closure = childValue as! () -> Int
let closureResult = closure()
expectEqual(closureResult, 7)

This crashes in various ways: I've seen it crash in first (apparently with a malformed collection object?) and I've seen it run to completion with closureResult having a random value.

@tbkka
Copy link
Contributor

tbkka commented Jul 9, 2022

Note: #56720 is another variation of this.

tbkka added a commit to tbkka/swift that referenced this issue Jul 9, 2022
The Swift compiler uses various calling conventions for closures depending
on context.  This context disappears when you try to use Mirror to reflect
a closure-valued property, which can make the closure un-callable.

There doesn't seem to be any good solution to this, unless we
dictate that a closure's ABI is determined by it's type, which
we've so far been unwilling to guarantee.

So instead, this PR erases closure values during reflection,
making it impossible to directly reflect closures.
Note that the field still appears in the reflection list,
but the value is replaced with `()` (the empty tuple, aka Void).

```
struct A { var prop: () -> Int }
let a = A { return 7 }
let m = Mirror(reflecting: a)
let c = m.children.first!.value
let p = c as? () -> Int
// Before this PR, the cast above would succeed,
// but the call below could crash.
p()
```

Note however, that it _does_ work to put a closure in an `Any` or
other container, reflect that container, and then access the closure.
(In this case, the compiler has full visibility into all direct
accesses to the closure.)

```
struct B { var prop: Any }
let b = B(prop: { return 7 } as Any)
let m = Mirror(reflecting: b)
let c = m.children.first!.value
let p = c as? () -> Int // This always worked
p() // This always succeeded
```

Resolves rdar://59791962, SR-12268, SR-14362, apple#54696
tbkka added a commit to tbkka/swift that referenced this issue Feb 1, 2023
The Swift compiler uses various calling conventions for closures depending
on context.  This context disappears when you try to use Mirror to reflect
a closure-valued property, which can make the closure un-callable.

There doesn't seem to be any good solution to this, unless we
dictate that a closure's ABI is determined by it's type, which
we've so far been unwilling to guarantee.

So instead, this PR erases closure values during reflection,
making it impossible to directly reflect closures.
Note that the field still appears in the reflection list,
but the value is replaced with `()` (the empty tuple, aka Void).

```
struct A { var prop: () -> Int }
let a = A { return 7 }
let m = Mirror(reflecting: a)
let c = m.children.first!.value
let p = c as? () -> Int
// Before this PR, the cast above would succeed,
// but the call below could crash.
p()
```

Note however, that it _does_ work to put a closure in an `Any` or
other container, reflect that container, and then access the closure.
(In this case, the compiler has full visibility into all direct
accesses to the closure.)

```
struct B { var prop: Any }
let b = B(prop: { return 7 } as Any)
let m = Mirror(reflecting: b)
let c = m.children.first!.value
let p = c as? () -> Int // This always worked
p() // This always succeeded
```

Resolves rdar://59791962, SR-12268, SR-14362, apple#54696
tbkka added a commit to tbkka/swift that referenced this issue Aug 17, 2023
The Swift compiler uses various calling conventions for closures depending
on context.  This context disappears when you try to use Mirror to reflect
a closure-valued property, which can make the closure un-callable.

There doesn't seem to be any good solution to this, unless we
dictate that a closure's ABI is determined by it's type, which
we've so far been unwilling to guarantee.

So instead, this PR erases closure values during reflection,
making it impossible to directly reflect closures.
Note that the field still appears in the reflection list,
but the value is replaced with `()` (the empty tuple, aka Void).

```
struct A { var prop: () -> Int }
let a = A { return 7 }
let m = Mirror(reflecting: a)
let c = m.children.first!.value
let p = c as? () -> Int
// Before this PR, the cast above would succeed,
// but the call below could crash.
p()
```

Note however, that it _does_ work to put a closure in an `Any` or
other container, reflect that container, and then access the closure.
(In this case, the compiler has full visibility into all direct
accesses to the closure.)

```
struct B { var prop: Any }
let b = B(prop: { return 7 } as Any)
let m = Mirror(reflecting: b)
let c = m.children.first!.value
let p = c as? () -> Int // This always worked
p() // This always succeeded
```

Resolves rdar://59791962, SR-12268, SR-14362, apple#54696
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

3 participants