Files
firebase-ios-sdk/FirebaseAI/Tests/Unit/ObjectiveCClassEnumerationTests.swift
Andrew Heard acbf36caee fix(ai): replace Mutex to fix crash on iOS < 18 (#17024)
GeminiAPIClient's SessionDelegate and TaskDelegate stored
Synchronization.Mutex properties. Swift classes are listed in the
Objective-C class list regardless of @available, so enumerating the
classes (e.g., with objc_copyClassList, as Realm does) or looking one
up by name realized them on iOS 17 and earlier. TaskDelegate's
Mutex<ResponseState> required runtime layout, so realizing it called
the weakly linked Mutex metadata accessor, which is NULL before iOS 18,
and the app crashed. GeminiLanguageModel is only built with Swift 6.4+,
so this affected apps built with Xcode 27 even if they didn't use it.

- Replace Mutex with LockedValue, an NSLock-based class that mirrors
  the init and withLock signatures of Mutex. Add a TODO to switch back
  once the minimum deployment targets always include Mutex, and link
  the upstream Swift runtime issues (swiftlang/swift#61215 and
  swiftlang/swift#72657).
- Add a FirebaseAILogicUnit test that calls objc_copyClassList. No CI
  runner has a simulator old enough to catch this crash, so the fix
  was verified locally on a tvOS 17.5 simulator, before and after.

Fixes #17012
2026-11-09 16:12:20 -05:00

39 lines
1.6 KiB
Swift

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import ObjectiveC
import Testing
/// Tests that the Objective-C runtime can realize every class in the SDK.
///
/// The Objective-C class list includes Swift classes regardless of their `@available` annotations,
/// and `objc_copyClassList` realizes every class in it. Realizing a class crashes if one of its
/// stored properties has a type that's unavailable on the running OS version and whose layout isn't
/// known at compile time. Apps and libraries (for example, Realm) call `objc_copyClassList` at
/// launch, so this crash affects apps that don't use the class.
struct ObjectiveCClassEnumerationTests {
@Test
func enumeratingObjectiveCClassesDoesNotCrash() throws {
var classCount: UInt32 = 0
// Don't read the elements as `AnyClass`; doing so retains each class, and some private root
// classes (for example, `__NSGenericDeallocHandler`) don't implement `retain`.
let classList = try #require(objc_copyClassList(&classCount))
free(UnsafeMutableRawPointer(classList))
#expect(classCount > 0)
}
}