-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
Previous ID | SR-9176 |
Radar | rdar://problem/45962437 |
Original Reporter | pacheco (JIRA User) |
Type | Bug |
Attachment: Download
Environment
Xcode 10.1 (10B61)
Swift 4.0, 4.2
macOS 10.13.6, 10.14
Additional Detail from JIRA
Votes | 0 |
Component/s | Compiler |
Labels | Bug, 4.2Regression, RunTimeCrash |
Assignee | None |
Priority | Medium |
md5: e3edd86a852ba61276c0ea3eb81b3582
Issue Description:
Sorry if my description is not the most accurate, but it's not an easy one to describe 😛
When passing an enum
's case with associated value T
as a closure to another function (i.e. to "wrap" a T
and create a new enum
instance), if the type T
is an associatedtype
on a protocol (specialized in the generic of the function's parent type), an over-release is made, causing a EXC_BAD_ACCESS
runtime crash.
I've only observed this issue since Xcode 10 (also present in 10.1), in both Swift 4 and Swift 4.2 modes. On Xcode 9 the same code worked without any issues, and since the code is valid Swift I am led to believe that this is most likely a compiler regression.
When this issue started occurring I analyzed the app with the Zombies Instrument and discovered that an additional release was being generated when the enum
's case is used as a parameter.
I've managed to create a reduced example which demonstrates the issue:
enum Result<T, E: Error> {
case success(T)
case failure(E)
func mapError<E2: Error>(_ f: (E) -> E2) -> Result<T, E2> {
switch self {
case .success(let value): return .success(value)
case .failure(let error): return .failure(f(error))
}
}
}
protocol Store {
associatedtype E: Error
func fetch(_ completion: (Result<Void, E>) -> Void)
}
// specializing using a value type doesn't crash
enum EnumError: Error { case error }
struct StructError: Error {}
// only specializing using a reference type crashes
class ClassError: Error {}
final class MyStore: Store {
typealias E = ClassError
func fetch(_ completion: (Result<Void, ClassError>) -> Void) {
completion(.failure(ClassError()))
}
}
enum ServiceError: Error {
case fetch(Error)
}
struct MyService<S: Store> {
let store: S
func fetch(_ completion: (Result<Void, ServiceError>) -> Void) {
store.fetch { result in
completion(result.mapError(ServiceError.fetch))
}
}
func explicitFetch(_ completion: (Result<Void, ServiceError>) -> Void) {
store.fetch { result in
completion(result.mapError { ServiceError.fetch($0) })
}
}
}
// test
let store = MyStore()
let service = MyService(store: store)
// doesn't crash
// store.fetch { print($0) }
// crashes
service.fetch { print($0) }
// doesn't crash
// service.explicitFetch { print($0) }
I've atached a demo app demonstrating the issue, but it's also manifested on a playground (even though it may require a small change so that the result
lives a bit longer).