swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.19k stars 10.32k forks source link

ABI incompatibility between Swift 5 & 6 with inherited protocols #75155

Closed KeithBauerANZ closed 2 weeks ago

KeithBauerANZ commented 1 month ago

Description

Where a library is built with Swift 5, with ABI resilience, and then called from Swift 6, inherited protocols cause a crash at runtime.

Rebuilding the framework with the Swift 6 compiler resolves the problem, but that shouldn't be necessary!

Reproduction

Build this as a dynamic library with ABI resilience and Swift 5,

public class Database {}

public protocol Reader: AnyObject, Sendable {
    func read<T>(_ closure: (Database) throws -> T) throws -> T
}

public protocol Writer: Reader {
    func write<T>(_ closure: (Database) throws -> T) throws -> T
}

public final class ReaderWriter: Writer {

    public init() {}

    public func write<T>(_ closure: (Database) throws -> T) throws -> T {
        return try closure(Database())
    }

    public func read<T>(_ closure: (Database) throws -> T) throws -> T {
        return try closure(Database())
    }

}

Then call it from code compile with Swift 6 (Swift 5 language mode):

                let db: any Writer = ReaderWriter()
                let result = try db.read { _ in
                    print("reading")
                    return [3: "hi"]
                }
                print("result: \(result)")

You'll crash in the call to read, because it's on the Reader protocol. Calls to write on the child Writer protocol will work fine:

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x8)
  * frame #0: 0x0000000104df3bb8 ABIBugTestFramework`dispatch thunk of ABIBugTestFramework.Reader.read<τ_0_0>((ABIBugTestFramework.Database) throws -> τ_1_0) throws -> τ_1_0 + 8
    frame #1: 0x0000000105372464 GRDBABIBug2.debug.dylib`closure #2 in ContentView.body.getter at ContentView.swift:33:37

(lldb) disassemble
ABIBugTestFramework`dispatch thunk of ABIBugTestFramework.Reader.read<τ_0_0>((ABIBugTestFramework.Database) throws -> τ_1_0) throws -> τ_1_0:
    0x104df3bb0 <+0>:  stp    x29, x30, [sp, #-0x10]!
    0x104df3bb4 <+4>:  mov    x29, sp
->  0x104df3bb8 <+8>:  ldr    x9, [x4, #0x8]
    0x104df3bbc <+12>: blr    x9
    0x104df3bc0 <+16>: ldp    x29, x30, [sp], #0x10
    0x104df3bc4 <+20>: ret    

Both projects to reproduce in case the above isn't sufficient: Archive.zip

Expected behavior

No crash!

Environment

Swift 5 (Xcode 15.4) swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4) Target: arm64-apple-macosx14.0

Swift 6 (Xcode 16.0b3) swift-driver version: 1.111.2 Apple Swift version 6.0 (swiftlang-6.0.0.5.15 clang-1600.0.22.6) Target: arm64-apple-macosx14.0

Additional information

If there's any workaround for this, I'd love to hear it.

KeithBauerANZ commented 1 month ago

The crash appears to occur when Swift 6 built code is responsible for creating the type metadata (even though it gets it from the type metadata accessor in the framework?!). For example, adding this function to the framework:

public func createReaderWriter() -> ReaderWriter {
    ReaderWriter()
}

(and calling it instead of ReaderWriter() from Swift 6) will still crash, but adding this function to the framework:

public func createReaderWriter() -> any Writer {
    ReaderWriter()
}

(And removing all references to ReaderWriter from the Swift 6-compiled code) will no longer crash.

KeithBauerANZ commented 1 month ago

@hborla not sure why "concurrency"? AFAICT, nothing here is related to concurrency?

hborla commented 1 month ago

Ah sorry, I'm triaging fairly quickly to make progress through the large backlog of untraiged issues. I saw Sendable and "ABI difference between Swift 5 and Swift 6" and assumed it was because of Sendable stripping, but I agree that's not the problem here.

slavapestov commented 3 weeks ago

Minimal repro:

// lib.swift
public protocol Reader: Sendable {
  func read()
}

public protocol Writer: Reader {}

public struct ReaderWriter: Writer {
  public init() {}
  public func read() {}
}
// use.swift
import lib

let db: any Writer = ReaderWriter()
db.read()
slavapestov commented 3 weeks ago

Looks like an unexpected regression from https://github.com/swiftlang/swift/commit/b333fb1683ef20b6dc965b78a743feccaea9fedd. Odd

DougGregor commented 2 weeks ago

Fix is at https://github.com/swiftlang/swift/pull/75769

KeithBauerANZ commented 2 weeks ago

Haha it was Concurrency after all 😅