PC パソコン

Xcode「Exception Type: EXC_BREAKPOINT」の原因と回避方法【Swift existential cast】

XcodeでSwiftコードをビルドできるのに、as?を使った条件付きキャストを実行した瞬間、Exception Type: EXC_BREAKPOINTやSIGTRAPでプロセスが停止することがあります。Swift Issue #92887で報告されているのは、Hashable制約を持つジェネリック基底クラスと、primary associated typeを持つプロトコルを組み合わせたexistential型への動的キャストで、Swiftランタイムがクラッシュする問題です。本記事では再現条件、原因の切り分け、安全な回避策、修正版の確認方法を整理します。

表示されるエラーと代表的なログ

アプリまたはコマンドライン実行ファイルが、キャストの評価時に次のようなクラッシュを起こします。

Exception Type:  EXC_BREAKPOINT
Termination Namespace: PAC_EXCEPTION
Termination Code: 1

Thread 0 Crashed:
libswiftCore.dylib  getSuperclassForMaybeIncompleteMetadata
libswiftCore.dylib  tryCastToExtendedExistential
libswiftCore.dylib  swift_dynamicCastSlow

CIやテストランナーでは、同じ停止がSIGTRAPまたは終了コード-5として見える場合があります。IssueのクラッシュレポートにはSignal: SIGKILLも含まれますが、検索時にはEXC_BREAKPOINT、SIGTRAP、tryCastToExtendedExistentialのいずれかが手掛かりになります。

最小の再現コード

protocol Source<Element> {
    associatedtype Element
    var element: Element { get }
}

class Base<Value: Hashable> {
    let value: Value
    init(_ value: Value) {
        self.value = value
    }
}

final class Concrete<Value: Hashable>: Base<Value>, Source {
    var element: Value { value }
}

let erased: Any = Concrete(42)

if let value = erased as? any Base<Int> & Source<Int> {
    print(value.element)
} else {
    print("cast failed")
}

次のようにSwift 6モードでコンパイルして実行すると、条件付きキャストは失敗してelseへ進むのではなく、ランタイム内で停止します。

xcrun swiftc -swift-version 6 -Onone Cast.swift -o cast-repro
./cast-repro

原因

問題の中心は、any Base<Int> & Source<Int>という拡張existentialのランタイムメタデータ処理です。キャスト先には次の2要素が同時に含まれます。

  • Value: Hashableという制約を持つジェネリック基底クラスBase<Int>
  • primary associated typeを具体化したプロトコルSource<Int>

公式Issueのスタックトレースでは、動的キャストのtryCastToExtendedExistentialからスーパークラス情報を取得する経路で停止しています。また、合成existentialの静的なMemoryLayoutとジェネリック経由の照会結果が一致しない観測も報告されており、ユーザーコードの通常のキャスト失敗ではなく、Swiftランタイム側のメタデータ/レイアウト処理の不具合を示します。

重要: as?は通常、安全にnilを返す条件付きキャストです。このケースでクラッシュするのは想定された言語動作ではありません。ただし、公式Issueは未解決であり、内部原因の最終確定や修正版はまだ公表されていません。

発生条件と対象バージョン

  • Xcode 27.2(build 27B5019j)/Swift 6.4で再現
  • Xcode 26.6/Swift 6.3.3でも再現
  • Debug(-Onone)とReleaseの双方で再現
  • 値をAnyなどで型消去した後、制約付きジェネリッククラスとPATプロトコルの合成existentialへ条件付きキャストする

2026年10月4日時点でIssueはOpenです。AppleまたはSwiftプロジェクトから、修正版を含むXcode/Swiftのバージョンはまだ示されていません。

推奨する回避手順

1. 合成existentialへの一括キャストを避ける

必要な操作がプロトコルの要件だけなら、プロトコル単体へキャストします。Issueの制御ケースでは、この形は正常に動作します。

if let source = erased as? any Source<Int> {
    print(source.element)
}

2. 基底クラスの機能だけが必要なら、基底クラスへキャストする

if let base = erased as? Base<Int> {
    print(base.value)
}

Base<Int>単体へのキャストも、報告された制御ケースではクラッシュしません。まず必要な能力を最小の型に絞るのが安全です。

3. 具体型が既知なら具体型へキャストする

if let concrete = erased as? Concrete<Int> {
    print(concrete.element)
}

受け入れる具体型が限定されている設計では、問題のある合成existentialを作らずに済みます。

4. 境界では専用の型消去ラッパーを使う

複数の実装を同じコレクションやAPI境界で扱う必要がある場合は、必要な操作だけをクロージャとして保持する型消去ラッパーも選択肢です。

struct AnyIntSource {
    private let read: () -> Int

    init<S: Source>(_ source: S) where S.Element == Int {
        self.read = { source.element }
    }

    var element: Int { read() }
}

let safe = AnyIntSource(Concrete(42))
print(safe.element)

これはランタイムの合成existential castを避ける設計上の回避策です。基底クラス固有の操作も必要なら、必要な操作をラッパーのAPIとして明示してください。

代替策

公式Issueでは、Hashable制約をBaseとConcreteから外すと再現しないことも確認されています。ただし、等値比較やハッシュ化をAPI契約として必要としている場合、制約の削除は適切ではありません。型設計上不要な制約だった場合にだけ検討してください。

また、キャストを2段階に分けても、最終的に同じ合成existentialを要求すれば再び問題のコードパスへ入る可能性があります。「順番を変える」だけを恒久対策とは見なさず、キャスト先そのものを単体プロトコル、単体基底クラス、具体型、型消去ラッパーのいずれかへ変更してください。

修正できたか確認する方法

  1. 問題のキャストを含む最小再現コードを独立したファイルにする。
  2. 使用中のXcodeでxcrun swiftc -versionを実行し、Swiftバージョンを記録する。
  3. Debugと最適化ビルドの両方で実行する。
  4. 回避後、期待値42が出力され、EXC_BREAKPOINT、SIGTRAP、終了コード-5が発生しないことを確認する。
  5. 将来のXcode更新後は、元の最小再現コードでも再テストし、公式IssueのResolved状態と修正版を照合する。
xcrun swiftc -version
xcrun swiftc -swift-version 6 -Onone Cast.swift -o cast-debug
xcrun swiftc -swift-version 6 -O Cast.swift -o cast-release
./cast-debug
./cast-release

注意点

  • DerivedDataの削除、Clean Build Folder、再署名では、ランタイムの型メタデータ問題は解消しません。
  • as!への変更は危険です。条件付きキャストのクラッシュを強制キャストで解決しようとしないでください。
  • 修正版が公式に確認されるまでは、CIと本番の両方で問題の型構成を避けてください。
  • 異なるスタックトレースなら、同名のEXC_BREAKPOINTでも別原因の可能性があります。tryCastToExtendedExistentialと型構成を照合してください。

まとめ

Exception Type: EXC_BREAKPOINTがas? any Base<Int> & Source<Int>の評価で発生する場合、アプリ側の通常のキャスト失敗ではなく、制約付きジェネリック基底クラスとPATプロトコルを組み合わせたSwiftランタイムの既知問題に該当する可能性があります。現時点の推奨は、合成existentialへの動的キャストを避け、必要に応じて単体プロトコル、単体基底クラス、具体型、型消去ラッパーへ設計を変更することです。修正版は未公表のため、Swift Issue #92887の更新を確認してください。

参照した公式情報