Skip to content

Commit f50ccbc

Browse files
committed
Merge branch 'main' into ah/ai-generable
2 parents 7f8bd29 + 04a2b57 commit f50ccbc

File tree

17 files changed

+181
-101
lines changed

17 files changed

+181
-101
lines changed

.github/workflows/zip.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
# zip from the current commit (HEAD).
1010
name: zip
1111

12+
# TODO(ncooke3): Also do Xcode 26 testing
13+
1214
permissions:
1315
actions: read
1416
contents: read
@@ -357,14 +359,13 @@ jobs:
357359
env:
358360
plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }}
359361
SDK: "FirebaseAI"
360-
# This is a workaround to use the FirebaseAIExampleZip scheme that does not have the SPM dependency.
361-
SWIFT_SUFFIX: "Zip"
362+
SWIFT_SUFFIX: " (iOS)"
362363
strategy:
363364
matrix:
364365
artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic]
365366
build-env:
366367
- os: macos-15
367-
xcode: Xcode_16.4
368+
xcode: Xcode_26.1
368369
runs-on: ${{ matrix.build-env.os }}
369370
steps:
370371
- uses: actions/checkout@v4
@@ -385,7 +386,7 @@ jobs:
385386
find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} +
386387
- uses: actions/checkout@v4
387388
- name: Setup quickstart
388-
run: SAMPLE="$SDK" TARGET="${SDK}ExampleZip" scripts/setup_quickstart_framework.sh \
389+
run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \
389390
"${HOME}"/ios_frameworks/Firebase/FirebaseAILogic/* \
390391
"${HOME}"/ios_frameworks/Firebase/FirebaseAnalytics/*
391392
- name: Install Secret GoogleService-Info.plist

Crashlytics/Crashlytics/Helpers/FIRCLSFile.m

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,13 @@ bool FIRCLSFileCloseWithOffset(FIRCLSFile* file, off_t* finalSize) {
175175
*finalSize = file->writtenLength;
176176
}
177177

178-
if (close(file->fd) != 0) {
179-
FIRCLSSDKLog("Error: Unable to close file %s\n", strerror(errno));
180-
return false;
178+
// If the FIRCLSFile struct was zero-initialized (e.g. via memset) and never opened,
179+
// fd will be 0. Closing fd 0 triggers a system-level EXC_GUARD crash.
180+
if (file->fd > STDERR_FILENO) {
181+
if (close(file->fd) != 0) {
182+
FIRCLSSDKLog("Error: Unable to close file %s\n", strerror(errno));
183+
return false;
184+
}
181185
}
182186

183187
memset(file, 0, sizeof(FIRCLSFile));

FirebaseAuth/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
# Unreleased
1+
# 12.7.0
22
- [fixed] Add a mechanism to prevent concurrent token refreshes. (#15474)
3+
- [fixed] Fix "weak never mutated" build warning introduced in Xcode 26.2.
34

45
# 12.2.0
56
- [added] Added TOTP support for macOS.

FirebaseAuth/Sources/Swift/Auth/Auth.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,30 +1938,30 @@ extension Auth: AuthInterop {
19381938
"for the new token.")
19391939
}
19401940
autoRefreshScheduled = true
1941-
weak var weakSelf = self
1942-
authDispatcher.dispatch(afterDelay: delay, queue: kAuthGlobalWorkQueue) {
1943-
guard let strongSelf = weakSelf else {
1941+
authDispatcher.dispatch(afterDelay: delay, queue: kAuthGlobalWorkQueue) { [weak self] in
1942+
guard let self else {
19441943
return
19451944
}
1946-
guard strongSelf._currentUser?.rawAccessToken() == accessToken else {
1945+
guard self._currentUser?.rawAccessToken() == accessToken else {
19471946
// Another auto refresh must have been scheduled, so keep _autoRefreshScheduled unchanged.
19481947
return
19491948
}
1950-
strongSelf.autoRefreshScheduled = false
1951-
if strongSelf.isAppInBackground {
1949+
self.autoRefreshScheduled = false
1950+
if self.isAppInBackground {
19521951
return
19531952
}
1954-
let uid = strongSelf._currentUser?.uid
1955-
strongSelf._currentUser?
1956-
.internalGetToken(forceRefresh: true, backend: strongSelf.backend) { token, error in
1957-
if strongSelf._currentUser?.uid != uid {
1953+
let uid = self._currentUser?.uid
1954+
self._currentUser?
1955+
.internalGetToken(forceRefresh: true, backend: self.backend) { [weak self] token, error in
1956+
guard let self else { return }
1957+
if self._currentUser?.uid != uid {
19581958
return
19591959
}
19601960
if error != nil {
19611961
// Kicks off exponential back off logic to retry failed attempt. Starts with one minute
19621962
// delay (60 seconds) if this is the first failed attempt.
19631963
let rescheduleDelay = retry ? min(delay * 2, 16 * 60) : 60
1964-
strongSelf.scheduleAutoTokenRefresh(withDelay: rescheduleDelay, retry: true)
1964+
self.scheduleAutoTokenRefresh(withDelay: rescheduleDelay, retry: true)
19651965
}
19661966
}
19671967
}

FirebaseCore/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Unreleased
1+
# Firebase 12.7.0
22
- [fixed] [CocoaPods] Enable module map generation for Firebase pods. This
33
resolves build failures when using static linking
44
(`use_frameworks! :linkage => :static`) in projects using frameworks like

FirebaseDatabase/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 12.7.0
2+
- [fixed] Fix `Fatal Exception: FirebaseDatabasePersistenceFailure`. (#4493)
3+
- [fixed] Concurrency crash in FView. (#15514)
4+
15
# 11.9.0
26
- [fixed] Fix connection failure issue introduced in 10.27.0 by restoring the
37
Socket Rocket implementation instead of `NSURLSessionWebSocket`. Note that

FirebaseDatabase/Sources/Core/View/FView.m

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ @interface FView ()
7070
@property(nonatomic, strong, readwrite) FQuerySpec *query;
7171
@property(nonatomic, strong) FViewProcessor *processor;
7272
@property(nonatomic, strong) FViewCache *viewCache;
73+
74+
// All accesses must be guarded by @synchronized(self.eventRegistrations)
7375
@property(nonatomic, strong) NSMutableArray *eventRegistrations;
7476
@property(nonatomic, strong) FEventGenerator *eventGenerator;
7577

@@ -165,11 +167,15 @@ - (id)initWithQuery:(FQuerySpec *)query
165167
}
166168

167169
- (BOOL)isEmpty {
168-
return self.eventRegistrations.count == 0;
170+
@synchronized(self.eventRegistrations) {
171+
return self.eventRegistrations.count == 0;
172+
}
169173
}
170174

171175
- (void)addEventRegistration:(id<FEventRegistration>)eventRegistration {
172-
[self.eventRegistrations addObject:eventRegistration];
176+
@synchronized(self.eventRegistrations) {
177+
[self.eventRegistrations addObject:eventRegistration];
178+
}
173179
}
174180

175181
/**
@@ -181,31 +187,33 @@ - (void)addEventRegistration:(id<FEventRegistration>)eventRegistration {
181187
- (NSArray *)removeEventRegistration:(id<FEventRegistration>)eventRegistration
182188
cancelError:(NSError *)cancelError {
183189
NSMutableArray *cancelEvents = [[NSMutableArray alloc] init];
184-
if (cancelError != nil) {
185-
NSAssert(eventRegistration == nil,
186-
@"A cancel should cancel all event registrations.");
187-
FPath *path = self.query.path;
188-
for (id<FEventRegistration> registration in self.eventRegistrations) {
189-
FCancelEvent *maybeEvent =
190-
[registration createCancelEventFromError:cancelError path:path];
191-
if (maybeEvent) {
192-
[cancelEvents addObject:maybeEvent];
190+
@synchronized(self.eventRegistrations) {
191+
if (cancelError != nil) {
192+
NSAssert(eventRegistration == nil,
193+
@"A cancel should cancel all event registrations.");
194+
FPath *path = self.query.path;
195+
for (id<FEventRegistration> registration in self
196+
.eventRegistrations) {
197+
FCancelEvent *maybeEvent =
198+
[registration createCancelEventFromError:cancelError
199+
path:path];
200+
if (maybeEvent) {
201+
[cancelEvents addObject:maybeEvent];
202+
}
193203
}
194204
}
195-
}
196205

197-
if (eventRegistration) {
198-
NSUInteger i = 0;
199-
while (i < self.eventRegistrations.count) {
200-
id<FEventRegistration> existing = self.eventRegistrations[i];
201-
if ([existing matches:eventRegistration]) {
202-
[self.eventRegistrations removeObjectAtIndex:i];
203-
} else {
204-
i++;
205-
}
206+
if (eventRegistration) {
207+
NSIndexSet *indexesToRemove =
208+
[self.eventRegistrations indexesOfObjectsPassingTest:^BOOL(
209+
id<FEventRegistration> existing,
210+
NSUInteger idx, BOOL *stop) {
211+
return [existing matches:eventRegistration];
212+
}];
213+
[self.eventRegistrations removeObjectsAtIndexes:indexesToRemove];
214+
} else {
215+
[self.eventRegistrations removeAllObjects];
206216
}
207-
} else {
208-
[self.eventRegistrations removeAllObjects];
209217
}
210218
return cancelEvents;
211219
}
@@ -270,7 +278,10 @@ - (NSArray *)generateEventsForChanges:(NSArray *)changes
270278
registration:(id<FEventRegistration>)registration {
271279
NSArray *registrations;
272280
if (registration == nil) {
273-
registrations = [[NSArray alloc] initWithArray:self.eventRegistrations];
281+
@synchronized(self.eventRegistrations) {
282+
registrations =
283+
[[NSArray alloc] initWithArray:self.eventRegistrations];
284+
}
274285
} else {
275286
registrations = [[NSArray alloc] initWithObjects:registration, nil];
276287
}

FirebaseDatabase/Sources/Persistence/FLevelDBStorageEngine.m

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -977,17 +977,40 @@ - (id)deserializePrimitive:(NSData *)data {
977977
}
978978

979979
+ (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup {
980+
NSFileManager *fileManager = [NSFileManager defaultManager];
980981
NSError *error;
981-
BOOL success =
982-
[[NSFileManager defaultManager] createDirectoryAtPath:path
983-
withIntermediateDirectories:YES
984-
attributes:nil
985-
error:&error];
982+
983+
// Create the directory if it doesn't exist. This call is a no-op if it
984+
// already exists.
985+
BOOL success = [fileManager createDirectoryAtPath:path
986+
withIntermediateDirectories:YES
987+
attributes:nil
988+
error:&error];
986989
if (!success) {
987990
@throw [NSException
988991
exceptionWithName:@"FailedToCreatePersistenceDir"
989992
reason:@"Failed to create persistence directory."
990-
userInfo:@{@"path" : path}];
993+
userInfo:@{
994+
@"path" : path,
995+
@"error" : error ?: [NSNull null]
996+
}];
997+
}
998+
999+
// Now, ensure the file protection attribute is set. This will apply it
1000+
// whether the directory was just created or already existed. Note, this
1001+
// attribute has no effect on simulators.
1002+
NSDictionary *attributes = @{
1003+
NSFileProtectionKey :
1004+
NSFileProtectionCompleteUntilFirstUserAuthentication
1005+
};
1006+
success = [fileManager setAttributes:attributes
1007+
ofItemAtPath:path
1008+
error:&error];
1009+
if (!success) {
1010+
FFWarn(@"I-RDB076036",
1011+
@"Failed to set file protection attribute on persistence "
1012+
@"directory: %@",
1013+
error);
9911014
}
9921015

9931016
if (markAsDoNotBackup) {
@@ -1000,9 +1023,6 @@ + (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup {
10001023
@"I-RDB076035",
10011024
@"Failed to mark firebase database folder as do not backup: %@",
10021025
error);
1003-
[NSException raise:@"Error marking as do not backup"
1004-
format:@"Failed to mark folder %@ as do not backup",
1005-
firebaseDirURL];
10061026
}
10071027
}
10081028
}

FirebaseDatabase/Tests/Integration/FIRDatabaseQueryTests.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3563,7 +3563,6 @@ - (void)testListenForChildAddedWithLimitEnsureEventsFireProperly {
35633563
WAIT_FOR(count == 3);
35643564
}
35653565

3566-
#ifdef FLAKY_TEST
35673566
- (void)testListenForChildChangedWithLimitEnsureEventsFireProperly {
35683567
FTupleFirebase* refs = [FTestHelpers getRandomNodePair];
35693568
FIRDatabaseReference* writer = refs.one;
@@ -3603,7 +3602,6 @@ - (void)testListenForChildChangedWithLimitEnsureEventsFireProperly {
36033602

36043603
WAIT_FOR(count == 3);
36053604
}
3606-
#endif
36073605

36083606
- (void)testListenForChildRemovedWithLimitEnsureEventsFireProperly {
36093607
FTupleFirebase* refs = [FTestHelpers getRandomNodePair];

FirebaseDatabase/Tests/Unit/FLevelDBStorageEngineTests.m

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525
#import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
2626
#import "FirebaseDatabase/Tests/Helpers/FTestHelpers.h"
2727

28-
@interface FLevelDBStorageEngineTests : XCTestCase
28+
@interface FLevelDBStorageEngine (Tests)
29+
+ (void)ensureDir:(NSString *)path markAsDoNotBackup:(BOOL)markAsDoNotBackup;
30+
@end
2931

32+
@interface FLevelDBStorageEngineTests : XCTestCase
3033
@end
3134

3235
@implementation FLevelDBStorageEngineTests
@@ -685,4 +688,49 @@ - (void)testRemoveTrackedQueryRemovesTrackedQueryKeys {
685688
([NSSet setWithArray:@[ @"b", @"c" ]]));
686689
}
687690

691+
- (void)testEnsureDirSetsCorrectFileProtection {
692+
NSString *testDirName =
693+
[NSString stringWithFormat:@"fdb_persistence_test_%lu", (unsigned long)arc4random()];
694+
NSString *testPath = [NSTemporaryDirectory() stringByAppendingPathComponent:testDirName];
695+
NSFileManager *fileManager = [NSFileManager defaultManager];
696+
697+
// --- Test creation ---
698+
[fileManager removeItemAtPath:testPath error:nil];
699+
[FLevelDBStorageEngine ensureDir:testPath markAsDoNotBackup:NO];
700+
701+
NSError *error = nil;
702+
NSDictionary<NSFileAttributeKey, id> *attributes = [fileManager attributesOfItemAtPath:testPath
703+
error:&error];
704+
XCTAssertNil(error, @"Failed to get attributes of directory: %@", error);
705+
706+
#if !TARGET_OS_SIMULATOR
707+
// On a physical device, file protection should be set.
708+
XCTAssertEqualObjects(attributes[NSFileProtectionKey],
709+
NSFileProtectionCompleteUntilFirstUserAuthentication);
710+
#else
711+
XCTAssertNil(attributes[NSFileProtectionKey]);
712+
#endif
713+
714+
// --- Test update on existing directory ---
715+
#if !TARGET_OS_SIMULATOR
716+
// This part of the test is only relevant on devices where file protection is supported.
717+
[fileManager removeItemAtPath:testPath error:nil];
718+
NSDictionary *initialAttributes = @{NSFileProtectionKey : NSFileProtectionNone};
719+
XCTAssertTrue([fileManager createDirectoryAtPath:testPath
720+
withIntermediateDirectories:YES
721+
attributes:initialAttributes
722+
error:&error],
723+
@"Failed to create directory for update test: %@", error);
724+
725+
[FLevelDBStorageEngine ensureDir:testPath markAsDoNotBackup:NO];
726+
727+
attributes = [fileManager attributesOfItemAtPath:testPath error:&error];
728+
XCTAssertNil(error, @"Failed to get attributes after update: %@", error);
729+
XCTAssertEqualObjects(attributes[NSFileProtectionKey],
730+
NSFileProtectionCompleteUntilFirstUserAuthentication);
731+
#endif // !TARGET_OS_SIMULATOR
732+
733+
// Clean up
734+
[fileManager removeItemAtPath:testPath error:nil];
735+
}
688736
@end

0 commit comments

Comments
 (0)